Code for the online, client-side versions of past strategy games at the Dürer Math Competition.
The deployed version is here: https://a-gondolkodas-orome.github.io/durer-jatekok/.
Feel free to commit directly to the default (master) branch. If in doubt, send a pull request instead.
When you push to the default (master) branch, the tests are run, and if they are successful, the project is deployed to the live website within a few minutes.
To keep track of who works on which game, use this table.
TL;DR;
- Create a react component for the game under
src/components/games. - Add the game component to the router in
src/components/app/app.js. - Add the game metadata to
src/components/games/gameList.js.
For more information, see Section How to Develop
There is a (fairly minimal) devcontainer setup if you prefer that. Alternatively, here are the installation instructions:
- install Node.js on your computer globally (or use nvm)
- in the project directory terminal run
npm ci
The commands
npm run devnpm run test # lint and tests (as Github Actions)Simple formatting errors such as trailing spaces can be automatically fixed with
npm run lint:fix(some problems only appear in prod build, not while testing, for example using a variable without declaring it)
npm run buildRecommended VS Code extensions:
This project uses the React frontend "framework", the official tutorial is a good starting point.
The common parts of all games (showing rules, alternating turns, buttons for choosing a role, restart game) are extracted
to a strategyGameFactory which is highly recommended (but not a must). The below documentation is about creating a new game
with this factory (so that you can focus on game logic and designing the board interactions.)
It is recommended to copy and modify an existing, similar game.
The code
const moves = {
addNumber: (board, { ctx, events }, number) => {
const nextBoard = board + number;
events.endTurn();
if (nextBoard >= 20) {
events.endGame({ winnerIndex: 1 - ctx.currentPlayer })
}
return { nextBoard }
}
};
const BoardClient = ({ board, ctx, moves }) => {
const clickNumber = (number) => {
if (!ctx.isClientMoveAllowed) return;
moves.addNumber(board, number);
};
return <>
<button onClick={() => clickNumber(1)}>1</button>
<button onClick={() => clickNumber(2)}>2</button>
</>
};
const botStrategy = ({ board, moves }) => {
const optimalStep = board % 3 === 0 ? 1 : (3 - board % 3)
moves.addNumber(board, optimalStep);
};
// React component added to router in app.js
export const PlusOneTwo = strategyGameFactory({
presentation: {
rule: <>0-ról +1/+2 20-ig</>,
// a function returning a string, receives optional { board, ctx }
getPlayerStepDescription: () => 'Válaszd ki, hogy hánnyal növelsz.'
},
BoardClient,
gameplay: { moves },
variants: [{ botStrategy, generateStartBoard: () => 0 }]
});The details
Concept: board holds the state necessary to know the game state, specific to
each game, that the next player needs to know. It can be also convenient to
store temporary state during a turn with multiple moves. Common state, managed
by the framework is stored in ctx (such as currentPlayer).
See generateStartBoard() inside each variant.
Conceptually a move is a unit that captures a change in the board initiated by
a player. Moves help ensure that the game is played according to rules by all
players.
Technically a move is a function whose first param is board, second param is { ctx, events } and may receive any number of additional params. The second param
is provided by the framework, additional params will be provided by the client
based on player interaction or by the bot strategy. Each move must return an
object with nextBoard.
A move may result in ending the turn of the current player or ending the game or allow further moves within the same turn.
You must always pass board as a first param to all moves (meaning you must
pass the updated board to subsequent moves in case of multiple moves within a
turn).
BoardClient: a React component which renders the board and calls appropriate
move functions triggered by user interaction.
Props passed by framework:
board(result of last move),ctx, (i.e. to know whose turn it is)events(i.e. tosetTurnState) andmoves
Additional state variables may be created within the BoardClient component
that is relevant only during a turn, not between turns, such as reacting to
hover events.
Given board and ctx what move(s) should the bot make?
To emulate thinking time for bot in case of multiple moves within a turn, subsequent moves must be wrapped in setTimeout, this is not (yet) handled by the framework.
board is updated after every move
ctx is an object and will contain the following (extendable):
isHumanVsHumanGame: boolean (true when two humans play; false when the user plays agains the computer)isClientMoveAllowed: boolean, use it to disable interactions while the other player's (or computer's) turn is in progresscurrentPlayer: 0/1 (whose turn it is — use this for game logic in both modes)chosenRoleIndex: null/0/1 (the role the human chose; only meaningful invsComputermode)turnState: use for multi-stage turns or other state that needs to be remembered during a turn, i.e. to expose it from BoardClient to getPlayerStepDescription
events is and objects that will contain the following (extendable):
endTurn: a functionendGame: a function with optional winnerIndex specified, if not, last player to move is the winnersetTurnState: a function to setturnState
- do not allow the player interacting with the game while the other player's
(or computer's) turn is in progress, use
ctx.isClientMoveAllowed - are the starting positions representative of the game complexity?
- can the player win with a not-winning strategy?
- never modify react state (e.g. the board) in place
- the game should work both in
vsComputerandvsHumanmode - is it easy to guess the winning strategy from watching the bot play?
- is the game (mostly) mobile-friendly?
- is the game usable only with keyboard (without a mouse)?
- is it clear what the player should do next?
- can the player undo their last interaction in turns with multiple moves?
- check for console errors/warnings as well, i.e. missing keys on react components
- pretend the bot is thinking in turns with multiple moves (for one move it is handled by framework)
The site supports Hungarian (default) and English. See TicTacToe
for a complete example. English translations are added on a game-per-game bases,
it is fine to add new games with Hungarian only.
The t() helper from translate.js resolves a value to the active language.
The value can be a plain string if there are no translations available, or a
{hu, en } object. For longer strings, consider extracting the english versions
to <game-name>-en.js to keep the main files more compact.
Check the Dürer Archive for existing translations.
Example internationalization of existing games: Pairs of numbers, 4 piles: spread ahead, Add N, take 2N
Details
- Node.js for the development server and building the application
- React frontend framework (official tutorial is a good starting point)
- [optional] Tailwindcss for styling with utility classes
- [optional] vitest for unit testing
- github actions for CI/CD.
- github pages as hosting
- goatcounter as usage tracker (Ildi has access)
Copyright (c) 2020-present A Gondolkodás Öröme Alapítvány.
The promblems originate from the Dürer Math Competition and remain the intellectual property of their respective authors.
This project is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0). You may share and adapt this material for non-commercial purposes, provided you give appropriate credit and distribute your contributions under the same license.