Release: Quarkus backend, Keycloak login, game history, clock, bot, tutorial and rematch #3
145
docs/specs/tutorial/design.md
Normal file
145
docs/specs/tutorial/design.md
Normal 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.
|
||||||
83
docs/specs/tutorial/requirements.md
Normal file
83
docs/specs/tutorial/requirements.md
Normal 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.
|
||||||
16
docs/specs/tutorial/tasks.md
Normal file
16
docs/specs/tutorial/tasks.md
Normal 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).
|
||||||
Loading…
Reference in New Issue
Block a user