See `requirements.md` for what is built; requirement ids (R1.1, …) are referenced below. Builds on `game-clock`
(the clock), `game-history` (saved games, live previews) and `quarkus-backend` (the Java backend and its engine).
## Decisions
| Decision | Reason |
|---|---|
| The bot runs in the backend, in Java | The rules are already in Java (`app.playmancala.engine`) and kept equal to the TypeScript engine by `EngineParityTest`. A bot game is then an ordinary server game: the clock, saving, spectators, previews and restarts work without changes in the clients (R4). A bot on the device would only add offline play, at the cost of a second bot in TypeScript (out of scope). |
| A compact board, `Position` (`int[14]` and whose turn it is), in the engine package | Searching means playing millions of moves; `MancalaGame` builds `Pit` objects, lists and animation steps for each, which is far too slow. `Position` plays a move into an existing array without allocating. It is in `engine`, not `bot`, so other code can use it too, e.g. checking a move before running it (see `TODO.md`, Security). It is tested against `MancalaGame` with the recorded parity games and random games. |
| `MancalaGame` is not rebuilt on `Position` now | `MancalaGame` also produces the game steps clients animate, the history and the JSON the Node backend wrote, and it must stay equal to the TypeScript engine. Rebuilding it is a separate refactor once `Position` has proven itself (out of scope). |
| Negamax with alpha-beta pruning, limited by depth and a node budget, not by time | Mangala is small (6 moves at most per turn), so alpha-beta is strong enough: at depth 12 the bot plays close to perfectly. Limits that do not depend on time make a move depend only on the position, the strength and the seed, so a game can be reproduced for debugging; a slower machine only takes longer. |
| Strength (0–100) sets three things: search depth, how much the bot prefers the best move (softmax temperature) and how often it plays a random move | Depth alone gives few, coarse steps and a predictable bot. With softmax, a weaker bot more often plays a move that is nearly as good, which looks like a human mistake; the random moves make Easy beatable by beginners (R2.3). Interpolating a table gives smooth steps between levels. |
| Each level is a range of strengths; the strength is drawn per game and varies by ±5 per move | R2.2. The ranges have gaps between them so neighbouring levels stay clearly apart (R2.4). |
| Randomness comes from a per-game seed and the move number | The bot's choice of a move is reproducible from the saved game, and a restart does not change how it plays (R4.6). The seed and strength are kept with the live game and the saved game. |
| Searching runs on its own small thread pool; the result goes back to the game loop, which checks that the game is still waiting for that move | The game loop must never wait (R3.5, `AGENTS.md`). The pool has a fixed number of threads, so many bot games share the CPU instead of starving the loop and the WebSocket threads. A result is dropped if the game ended or moved on meanwhile. |
| The bot's waiting time is computed on the server: the previous move's animation (300 ms per step, as `PitAnimator`) plus a random 1–3 seconds | R3.2. Clients do not need to know about bots for this. Searching happens during the wait, so the time it takes is not added to it. |
| Bot ids are `bot:<level>`, e.g. `bot:medium` | One stable id per level reads well in saved games and lets later statistics group games by level. It is public and cannot belong to a person: Keycloak ids start with `kc:` and legacy ids must be UUIDs (`RtmtServer`). Because one id plays many games at once, `GameStore` does not map it to a game. Clients recognise bots by the id, so `PlayerSummary` keeps its shape. |
| The offer to play the bot (R1.1) is a timer in the client | The server does not need to know how long someone has waited. Matchmaking stays as it is; choosing a level sends a new message that also ends the search on the server. |
| Bot games are ordinary games everywhere: clock, saving, spectators, live previews, popular games | R4. Fewer special cases. The bot's moves go through the same code as a player's moves (R3.1). |
of the last move plus 300 ms; before the first move it is 1.5 s, so the game page has opened. `thinkMs` is random: Easy 0.8–1.8 s, Medium 1.0–2.2 s, Hard 1.2–2.6 s, Master