Tile Stakes is a browser-based Mahjong hand betting game built as an interview-friendly Angular assessment. The player predicts whether the next three-tile hand will be higher or lower while honor tile values shift after every result.
The project targets Node.js 24 LTS.
nvm use
npm install
npm startOpen http://localhost:4200.
npm test # Run Vitest once
npm run build # Create a production bundle- Angular 22 with strict TypeScript and standalone components
- Angular signals in one game store
- Pure TypeScript game engine
- Vitest for engine and persistence tests
- Browser
localStoragefor the top-five leaderboard
src/app/engine contains tile configuration, deck operations, rules, and session orchestration. These files have no Angular, storage, or UI imports and always return new state.
src/app/store/game-store.service.ts is the single bridge from the engine to Angular signals. Components render state and forward user intent to the store; they do not calculate rules.
src/app/store/leaderboard-repo.ts contains leaderboard rules and localStorage persistence. The game store is the only caller, so another persistence adapter can replace it later without touching the engine or components.
Each component under src/app/components has one visible responsibility: landing, leaderboard, table, controls, history, tiles, game over, or exit.
- A hand always contains exactly three tiles.
- The deck contains the standard 136 tiles: four copies of each Dragon, Wind, and numbered tile.
- Number tile values equal their rank.
- Each honor kind starts at 5 and changes independently.
- Honor deltas apply once per honor tile copy across both compared hands: the previous current hand and the newly drawn hand.
- Ties count as losses.
- A win adds the next hand total; a loss subtracts it.
- A draw-pile run-out means fewer than three tiles remain.
- The first two run-outs add a fresh 136-tile deck to all available tiles; the third ends the game without resolving the pending bet.
- If an honor reaches 0 or 10, remaining honor deltas stop, the resolved round is recorded, and the session ends.
- A leaderboard score must be strictly greater than fifth place when five entries already exist.
The player starts with a visible three-tile hand and total. Each round, they bet whether the next three-tile hand will be higher or lower than the current total.
After the bet resolves, the next hand becomes the new current hand. Number tiles keep their face value, while Dragon and Wind values shift over time based on wins and losses. The run ends when an honor value reaches 0 or 10, or when the draw pile runs out for the third time.
H: Bet HigherL: Bet LowerEsc: Exit to the landing page
Shortcuts are ignored while a text field has focus. Controls use semantic HTML, visible focus indicators, non-color status labels, an announced result region, WCAG 2.2 AA contrast, and reduced-motion behavior.
The suite covers the 136-tile composition, four physical copies per tile kind, unique physical IDs, complete draws, both permitted reshuffles, scoring, tie handling, per-copy honor updates across both compared hands, boundary termination, session transitions, third run-out behavior, persistence, and name normalization. A seeded simulation runs 1,000 games and verifies tile accounting, complete hands, bounded honor values, and eventual game-over.
- Treat ties as a push
- Make hand size configurable
- Add selectable scoring modes
- Replace localStorage with a Node.js and MongoDB leaderboard API
- Add alternate tile sets or difficulty modes
- Persist an active session across refreshes
AI assistance was used to accelerate scaffolding, UI iteration, and test drafting. The game rules, edge-case handling, architecture boundaries, and final code review were manually directed and reviewed.
Mahjong tile SVGs are sourced from the Black set in FluffyStuff/riichi-mahjong-tiles. The project serves the selected files from public/tiles/black through static URL paths such as /tiles/black/Man1.svg.
Source and public-domain details are recorded in public/tiles/ATTRIBUTION.md.