Release: Quarkus backend, Keycloak login, game history, clock, bot, tutorial and rematch #3

Merged
jhalitaksoy merged 88 commits from develop into main 2026-10-17 03:23:38 +03:00
3 changed files with 244 additions and 0 deletions
Showing only changes of commit 9522224bf9 - Show all commits

View File

@ -0,0 +1,145 @@
# Tutorial: design
See `requirements.md`; requirement ids (R1.1, …) are referenced below. Builds on `web-redesign` (board, pages,
themes) and `game-bot` (the Easy bot for the practice game).
## Decisions
| Decision | Reason |
|---|---|
| Rules, lessons and hints are defined once in `packages/core` | The web and mobile apps show the same rules (R5.1) and the texts stay in one place. |
| The "after" board of every example and the outcome of every lesson move come from running the engine (`mancala.js`), never written by hand | The texts and pictures cannot drift from what the game does (R1.3). A rule change shows up in the tutorial at once, and a test fails if a lesson no longer works. |
| Lessons run entirely in the client on the TypeScript engine | No server, no login, no waiting (R2.5). The engine is already in every client. |
| A lesson's task is an outcome ("end in your store"), not a pit | Players who find another move with the same effect are right too; the check is the engine's game steps, the same ones the move list uses. |
| The practice game is an ordinary Easy bot game with hints on; hints are a client feature of any bot game | No new server state (R3.1). Hints only show the immediate effect of a move, which the board's preview mostly shows already, so they give little away; still, they are limited to bot games (R3.5). |
| Hints look one move ahead only | They explain the rules, not strategy. Looking further would need the bot's search on the client or a new server message (out of scope). |
| Progress, "not now" and the hints setting are kept in the device's storage | Enough for learning the rules (R2.6, R3.4, R4.1); accounts are not needed for any of it. |
| Mobile gets the rules screen only for now | The mobile app still has the old board and game screen; lessons and hints there would be built twice (R5.2). |
| The end-of-game lesson teaches the current rule | `TODO.md` (Game rules) asks whether the remaining stones should always go to the player whose side is empty. The tutorial follows the engine; because its boards come from the engine, a rule change only needs the texts updated. |
## The rules as taught (R1.3)
Checked against `packages/mancala.js` (`Board.move`, the four `GR…` rules):
1. **Board and goal**: two rows of 6 pits with 4 stones each, a store on each side. Stones in your store are yours;
at the end, more stones in your store wins (48 stones: 25 always wins). A draw is possible.
2. **Sowing**: take all stones from one of your pits; put the first back into the same pit and one into each pit
after it. A single stone moves to the next pit. Your own store gets a stone on the way, the opponent's is
skipped.
3. **Extra turn**: if the last stone lands in your store, you play again.
4. **Even capture**: if the last stone lands in an opponent's pit and makes its count even, you take all stones in
it.
5. **Empty pit capture**: if the last stone lands in an empty pit of yours and the opponent's pit opposite has
stones, you take both.
6. **End of the game**: the game ends when one side's pits are empty; the player whose side is empty gets the
stones left on the other side. Then the stores are counted.
7. **Clock**: 5 minutes per player for the whole game; the clock of the player to move runs; when it reaches zero,
that player loses.
## Shared code (`packages/core/src/tutorial/`)
```ts
type RuleId = "board" | "sowing" | "extraTurn" | "evenCapture" | "emptyPitCapture" | "endOfGame" | "clock";
interface RuleExample {
board: number[]; // 14 stone counts, player 1 (bottom) to move
pit: number; // board index played
}
type LessonTask = "anyMove" | "extraTurn" | "evenCapture" | "emptyPitCapture" | "endGame";
interface Lesson {
board: number[]; // player 1 (bottom) to move
task: LessonTask;
taskKey: string; // e.g. "LessonTaskExtraTurn": "End your move in your store"
opponentReply?: number; // board index the opponent plays after a fulfilled move, if the lesson needs one
}
interface Rule {
id: RuleId;
titleKey: string;
textKeys: string[]; // at most three sentences (R1.2)
example?: RuleExample;
lesson?: Lesson; // every rule except the clock (R2.1)
}
export const RULES: Rule[];
// The immediate effect of playing a pit, by running the engine on a copy of the board.
interface MoveOutcome {
path: number[]; // where the stones went (getSowingPath)
extraTurn: boolean;
capture?: "even" | "emptyPit";
captured: number; // stones taken from the opponent's side
storeGain: number;
endsGame: boolean;
board: number[]; // after the move
}
export function getMoveOutcome(board: number[], player1ToMove: boolean, pit: number): MoveOutcome;
export function fulfilsTask(task: LessonTask, outcome: MoveOutcome): boolean;
// "Why did that happen" for a wrong lesson move and for the practice game (R2.3, R3.3).
export function explainMove(outcome: MoveOutcome, mover: "you" | "opponent" | "bot", t): string;
```
`getMoveOutcome` builds the position with `createGameFromBoard` (core history helpers), plays the pit and reads the
game steps, as `describeMove` does for the move list. `describeMove` gains `capture` kind and `captured`, so the
move list, hints and explanations share one reading of the steps.
Lesson positions (player 1, bottom, to move; pits 0–5, store 6), checked with the rules above. Task 2 tests that
each has a move that fulfils its task and, where there is more than one legal move, one that does not:
| Lesson | Board | Task | Moves that fulfil it |
|---|---|---|---|
| Board and goal | start position | `anyMove` | all |
| Sowing | `[4,4,1,4,4,4, 1, 4,4,4,4,4,4, 2]` | `anyMove`; the text points at pit 3's single stone, which moves to pit 4 | all |
| Extra turn | start position | `extraTurn` | pit 4 |
| Even capture | `[4,4,4,4,4,4, 0, 4,3,4,4,4,4, 1]` | `evenCapture` | pit 6: its last stone makes the opponent's 3 a 4 |
| Empty pit capture | `[2,0,4,4,4,4, 3, 4,4,4,4,5,4, 2]` | `emptyPitCapture` | pit 1: its last stone lands in the empty pit 2, opposite 5 stones |
| End of the game | `[0,0,0,0,0,1, 20, 2,3,1,0,2,1, 18]` | `endGame` | pit 6 (the only move): your side is empty and the 9 stones opposite are yours, 30–18 |
Texts: `Rule<Id>Title`, `Rule<Id>Text1..3`, `LessonTask…`, `MoveExplain…`, `HowToPlay`, `Learn`, `Hints`,
`NewToMangala`, `StartTutorial`, `NotNow`, `PracticeGame`, in `en.ts` and `tr.ts`.
Storage helpers next to the rules: `getCompletedLessons` / `completeLesson`, `isTutorialBannerHidden` /
`hideTutorialBanner`, `getHintsEnabled` / `setHintsEnabled`, `markGamePlayed`.
## Web app
Routes:
- `/rules`: `RulesPage` with `RulesView`.
- `/learn`: the lesson list with progress, then the practice game button.
- `/learn/:ruleId`: `LessonPage`.
Components:
- `RulesView`: the rules in order; each with its texts, a `RuleBoard` and "Try it" linking to the lesson (R1.5).
- `RuleBoard`: two `MiniBoard`s, before and after the example move, the played pit and the landing pit marked.
- `RulesPanel`: `RulesView` in a side panel (full screen below 600 px) over the game page; opened from a "How to
play" button in the game page's header; the game keeps running behind it (R1.4).
- `AppHeader`: a help icon button "How to play" on every page, going to `/rules` (or opening `RulesPanel` on the game
page).
- `LessonPage`: the rule's text and task above a `BoardView` on a local game (`createGameFromBoard`), animated with
`PitAnimator` like a live game. On a move: `getMoveOutcome`, animate, then praise and "Next lesson" or
`explainMove` and "Try again", which resets the board. A scripted `opponentReply` is played after a short pause.
- `TutorialBanner` on the home page for newcomers (R4).
- Hints (R3): in `GamePage`, when the opponent is a bot and hints are on, `BoardView` gets a `hints` map
(pit → "Extra turn" / "Captures 5"), drawn as a chip on the pit like the move preview; below the board a line with
`explainMove` of the last move. A "Hints" switch in the game page's toolbar, only in bot games.
- Practice game: the lesson list's last button sets hints on and goes to `/loby?bot=easy`.
## Mobile app
`RulesScreen`: the rules' texts and a simple stone-count drawing of each example (before/after), reachable from
`HelpScreen` and from the game screen's header (R5.1).
## Testing
- Core: every lesson position fulfils its task with some move and (except `anyMove`) fails it with another;
`getMoveOutcome` against the engine on random positions; every rule example produces the effect its rule is about
(the even capture example really captures, etc.); `explainMove` for each kind of move in both languages; every
text key exists in `en` and `tr`.
- Web: build and the temporary type check.
- End to end in Chrome: a newcomer sees the banner and "Not now" hides it; all six lessons, each first with a wrong
move (explanation, reset) and then a right one; the list shows them done; the rules page and the rules panel during
a game (the clock goes on); a practice game shows hints and explanations, and a game between two people shows
none.
- Mobile: type check and bundle; the rules screen on a device when one is available.

View File

@ -0,0 +1,83 @@
# Tutorial: requirements
Status: approved, being implemented on branch `feature/tutorial` (based on `develop`).
Scope: `packages/core`, web app; the mobile app gets the rules only (see R5).
## Goals
Someone who has never played Mangala can learn it in a few minutes, and anyone can look a rule up later, also in
the middle of a game. Three parts, from reading to playing:
1. **Rules**: one page with every rule, a short text and a small board for each. Always reachable.
2. **Lessons**: one short interactive step per rule; the player makes the move the rule is about on a prepared
board and sees what happens. Runs in the browser, without the server.
3. **Practice game**: a real game against the Easy bot with hints: which moves give an extra turn or capture, and
after every move a sentence about what happened.
## Terms
- **Store**: the large pit on each side (in the texts "hazine"); stones in it count for the player.
- **Lesson**: one rule taught on a prepared position with a task, e.g. "End your move in your store".
- **Hint**: in the practice game, the effect a move would have right away (extra turn, capture, stones to store).
Hints never look further ahead than the move itself.
- **Newcomer**: someone on a device where no game has been played and the tutorial has not been opened or
dismissed.
## Requirements
### R1. Rules
- R1.1 A rules page ("How to play" / "Nasıl oynanır") lists, in order: the board and the goal, sowing, the extra
turn, capturing an even pit, capturing from an empty pit, the end of the game and who wins, and the clock.
- R1.2 Each rule has at most three sentences and a small board that shows it (before and after the move).
- R1.3 The texts describe exactly what the game does (`packages/mancala.js` and the backend's port), including the
details beginners miss: the first stone goes back into the pit it was taken from, a single stone moves to the next
pit, the opponent's store is skipped.
- R1.4 The rules can be reached from the header on every page, and from the game page without leaving the game
(a panel over the game; the game and its clock go on).
- R1.5 Each rule links to its lesson.
### R2. Lessons
- R2.1 There is a lesson for each rule in R1.1 except the clock: six in all, in the order of R1.1.
- R2.2 A lesson shows the rule's text, a board with a prepared position and a task. The player plays on the real
board (same look, animation and move preview as a game).
- R2.3 A move that fulfils the task is praised and the next lesson is offered. Any other move is animated, then
explained ("This move ended in the opponent's pit, so the turn passed") and the position is set back so they can
try again.
- R2.4 Where a lesson needs the opponent to move, the opponent's move is scripted and shown like the bot's.
- R2.5 Lessons work without a connection to the server and need no login.
- R2.6 Completed lessons are remembered on the device and marked in the list. Lessons can be taken in any order
and repeated.
- R2.7 After the last lesson, the player is offered the practice game.
### R3. Practice game
- R3.1 The practice game is a game against the Easy bot with hints turned on, started from the tutorial. It is
otherwise a normal bot game (clock, saving, history).
- R3.2 While it is the player's turn, every pit whose move gives an extra turn or captures is marked, with what it
does ("Extra turn", "Captures 5").
- R3.3 After every move, of either side, one sentence says what happened, built from the move: e.g. "The bot's
last stone landed in its store: it plays again" or "You captured 6 stones from an even pit".
- R3.4 Hints can be turned on and off in every game against the bot; the choice is remembered on the device.
- R3.5 Hints are only available in games against the bot. Games between people never show them, also not to
spectators.
### R4. Newcomers
- R4.1 A newcomer sees a banner on the home page: "New to Mangala? Learn it in 3 minutes" with "Start" and
"Not now". "Not now" hides it for good on that device.
- R4.2 The banner does not show once the tutorial has been opened or a game has been played on the device.
- R4.3 Nothing is forced: a newcomer can start a game without the tutorial.
### R5. Mobile app
- R5.1 The mobile app shows the same rules (same texts, from `packages/core`) on a rules screen reachable from the
help screen and from the game screen's header.
- R5.2 Lessons and the practice game's hints on mobile are out of scope until the mobile app gets the new design
(`TODO.md`, Web redesign).
### R6. Languages
- R6.1 All texts exist in English and Turkish. Turkish uses the usual Mangala words (kuyu, hazine, taş).
## Out of scope (TODO)
- Lessons and hints in the mobile app (R5.2).
- Hints that look ahead (warning about a move that gives the opponent a capture); the bot's search could provide
them later.
- Strategy lessons beyond the rules.
- Keeping tutorial progress per account instead of per device.

View File

@ -0,0 +1,16 @@
# Tutorial: tasks
Work through the tasks in order; each ends with its tests passing and a commit. Tick a task when it is committed.
- [x] 1. **Spec**: requirements, design and tasks.
- [ ] 2. **Core**: `RULES` with examples and lessons, `getMoveOutcome`, `fulfilsTask`, `explainMove`, `captured` and
the capture kind in `describeMove`, the storage helpers, texts in English and Turkish; unit tests.
- [ ] 3. **Rules on the web**: `RuleBoard`, `RulesView`, `/rules`, the help button in `AppHeader`, `RulesPanel` on the
game page.
- [ ] 4. **Lessons**: `/learn` with progress, `LessonPage` with the local game, animation, praise, explanation and
reset, scripted opponent replies.
- [ ] 5. **Newcomers and practice game**: `TutorialBanner`, `markGamePlayed`, the practice game button.
- [ ] 6. **Hints**: hint chips on `BoardView`, the explanation line and the "Hints" switch in bot games on `GamePage`.
- [ ] 7. **Mobile rules**: `RulesScreen`, links from the help and game screens; type check and bundle.
- [ ] 8. **End to end and docs**: the Chrome tests from `design.md`; `AGENTS.md` (specs list), `TODO.md` (out of scope
items).