diff --git a/AGENTS.md b/AGENTS.md index 033a853..bcd1915 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,8 +23,8 @@ Online Mancala (the Turkish *Mangala* rules) with a web app, an Android/iOS app Specs that exist: `keycloak-auth` (login, guests, tokens), `game-history` (saved games, live games, replay), `game-clock` (5 minutes per player, ending games on time), `web-redesign` (the web app's look: themes, board, -pages), `quarkus-backend` (the backend's move from Node to Java and Quarkus) and `game-bot` (playing against a bot -at four levels). +pages), `quarkus-backend` (the backend's move from Node to Java and Quarkus), `game-bot` (playing against a bot +at four levels) and `tutorial` (rules page, lessons, hints in bot games). ## Running locally @@ -114,6 +114,9 @@ which helps to check what the server holds. (`theme/themes.ts`) are passed to core's `ThemeManager`, which the mobile app uses with its own default themes. - Styles that need container queries (`@container`, `cqw`), such as the board's, are plain `.css` files imported by the component; styled-jsx 5.0 does not handle them. `BoardView` fills its container and stands upright below 600 px. +- The rules, lessons and move explanations live in `packages/core/src/tutorial`; their boards after a move come from + the engine (`getMoveOutcome`). After a rule change, run core's tests: they fail if a lesson no longer works, and + the rule texts in `en.ts`/`tr.ts` may need updating. - When a script drives the app in Chrome, wait for the header (`.hdr`) and a few seconds after the first load before navigating; navigating during the guest login leaves an invalid token. Open the lobby with the "New Game" button, not by URL, or the matchmaking message goes out before the WebSocket is open. diff --git a/TODO.md b/TODO.md index 7ab5374..04de548 100644 --- a/TODO.md +++ b/TODO.md @@ -65,6 +65,16 @@ See `docs/specs/game-bot`. - [ ] Tune the levels from real games (`games.bot` holds each game's level, strength and seed) - [ ] Try the bot on a mobile device; only the type check and the bundle were run +## Tutorial + +See `docs/specs/tutorial`. + +- [ ] Lessons and hints in the mobile app, once it has the new design (it has the rules screen) +- [ ] Hints that look ahead, e.g. a warning when a move lets the opponent capture (the bot's search could do it) +- [ ] Strategy lessons beyond the rules +- [ ] Tutorial progress per account instead of per device +- [ ] If the end-of-game rule changes (Game rules below), update `RuleEndOfGameText2` in `en.ts` and `tr.ts` + ## Web redesign See `docs/specs/web-redesign`. diff --git a/docs/specs/tutorial/design.md b/docs/specs/tutorial/design.md index 2bf3edb..c075457 100644 --- a/docs/specs/tutorial/design.md +++ b/docs/specs/tutorial/design.md @@ -144,3 +144,10 @@ Components: 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. + +Checked end to end in Chrome on 2026-10-16: a newcomer's banner, "Not now" hiding it, and the banner gone after the +lessons; all six lessons from the banner, the four with a wrong move first (explained, then reset) and each done; +the list showing all six done and the last lesson offering the practice game; the rules page and, during a game, +the rules panel with the clock running behind it (5:00 → 4:57); a practice game with hint chips that match the board +and an explanation of each move, hints turned off with the switch; a game between two people with hints turned on +on both devices showing no switch and no hints; the rules page and the lesson list at phone width. diff --git a/docs/specs/tutorial/requirements.md b/docs/specs/tutorial/requirements.md index eab42ad..db35ea0 100644 --- a/docs/specs/tutorial/requirements.md +++ b/docs/specs/tutorial/requirements.md @@ -1,6 +1,6 @@ # Tutorial: requirements -Status: approved, being implemented on branch `feature/tutorial` (based on `develop`). +Status: implemented on branch `feature/tutorial` (based on `develop`). Scope: `packages/core`, web app; the mobile app gets the rules only (see R5). ## Goals diff --git a/docs/specs/tutorial/tasks.md b/docs/specs/tutorial/tasks.md index e47ec30..9a5f06a 100644 --- a/docs/specs/tutorial/tasks.md +++ b/docs/specs/tutorial/tasks.md @@ -12,5 +12,5 @@ Work through the tasks in order; each ends with its tests passing and a commit. - [x] 5. **Newcomers and practice game**: `TutorialBanner`, `markGamePlayed`, the practice game button. - [x] 6. **Hints**: hint chips on `BoardView`, the explanation line and the "Hints" switch in bot games on `GamePage`. - [x] 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 +- [x] 8. **End to end and docs**: the Chrome tests from `design.md`; `AGENTS.md` (specs list), `TODO.md` (out of scope items). diff --git a/packages/core/src/tutorial/MoveOutcome.ts b/packages/core/src/tutorial/MoveOutcome.ts index ea4abb8..d965e92 100644 --- a/packages/core/src/tutorial/MoveOutcome.ts +++ b/packages/core/src/tutorial/MoveOutcome.ts @@ -94,7 +94,8 @@ export function explainMove(outcome: MoveOutcome, mover: Mover, t: Translate): s } else if (outcome.capture === "emptyPit") { sentences.push(t(`MoveExplainEmptyPit${who}`, { count: outcome.captured })); } else if (outcome.landing === "ownStore") { - sentences.push(t(`MoveExplainExtraTurn${who}`)); + // A move that ends the game gives no turn to play. + if (!outcome.endsGame) sentences.push(t(`MoveExplainExtraTurn${who}`)); } else if (outcome.landing === "opponentPit") { sentences.push(t(`MoveExplainOdd${who}`, { count: outcome.stonesBeforeLanding + 1 })); } else if (outcome.stonesBeforeLanding === 0) { diff --git a/packages/core/tests/tutorial/Tutorial.test.ts b/packages/core/tests/tutorial/Tutorial.test.ts index 3b5db88..5ab0fb3 100644 --- a/packages/core/tests/tutorial/Tutorial.test.ts +++ b/packages/core/tests/tutorial/Tutorial.test.ts @@ -145,7 +145,7 @@ describe("explainMove", () => { expect(explainMove(outcome([4, 4, 4, 4, 4, 4, 0, 4, 3, 4, 4, 4, 4, 1], 5), "you", translator(en))) .toBe("Your last stone made an opponent's pit even: you took 4 stones."); expect(explainMove(outcome([0, 0, 0, 0, 0, 1, 20, 2, 3, 1, 0, 2, 1, 18], 5), "you", translator(en))) - .toBe("Your last stone landed in your store: you play again. One side is empty, so the game is over."); + .toBe("One side is empty, so the game is over."); }); it("explains the bot's moves from the player's side", () => {