docs: lock scope to lô tô hội chợ tân tân variant

- pdr: explicit in-scope (9x9, 5/row + 5/col, ascending cols, single-row
  Kinh, "Chờ N", continue-after-win) vs out-of-scope (3x9 european
  bingo 90, two-line / full-house tiers, custom number ranges)
- update codebase-summary, system-architecture, development-roadmap to
  reflect 11x9 master board, draw-order overlay, exact 5/col picker
- add researcher report comparing tân tân vs european bingo 90
This commit is contained in:
tiennm99 committed 2026-04-26 23:40:07 +07:00
1 parent 189ae3d4e5
commit de6bdf7eec
5 files changed
+277 -26

No files matched your search

+10 -8
View File
@@ -7,7 +7,7 @@
|------|---------|
| `src/routes/+layout.svelte` | Root HTML layout. Sets Vietnamese lang, imports Geist font, applies global flex layout. |
| `src/routes/+page.svelte` | Player page (`/`). Instructions toggle, PlayerBoard component, indigo gradient branding. |
| `src/routes/master/+page.svelte` | Host page (`/master`). Controls (new game, draw number), 9×10 master board, host's player card. |
| `src/routes/master/+page.svelte` | Host page (`/master`). Controls (new game, draw number), 11×9 last-digit-aligned master board with draw-order overlay, host's player card. |
### Shared Components
| File | Purpose |
@@ -17,7 +17,7 @@
### Game Logic
| File | Purpose |
|------|---------|
| `src/lib/game-logic.js` | Stateless utilities: generateGrid (weighted column selection), saveGrid, loadGrid, saveCrossedState, loadCrossedState, isRowComplete, getWaitingNumber. |
| `src/lib/game-logic.js` | Stateless utilities: generateGrid (constraint-aware picker — exact 5 per row & per col, ascending-sorted columns), saveGrid, loadGrid, saveCrossedState, loadCrossedState, isRowComplete, getWaitingNumber. |
### Styling
| File | Purpose |
@@ -60,7 +60,7 @@ RootLayout
│ └── PlayerBoard (storagePrefix="loto")
└── MasterPage (/master)
├── Controls (new game, draw)
├── Master board (9×10)
├── Master board (11×9, last-digit aligned, draw-order overlay)
└── PlayerBoard (storagePrefix="loto_master_card")
```
@@ -68,10 +68,12 @@ RootLayout
| Function | Location | Effect |
|----------|----------|--------|
| `generateGrid()` | game-logic.js:74 | Creates 9×9 with weighted column selection (5 nums/row). |
| `isRowComplete()` | game-logic.js:200 | Boolean: all non-zero cells in row crossed? |
| `getWaitingNumber()` | game-logic.js:218 | Returns the single uncrossed number in row, or null. |
| `handleCellClick()` | PlayerBoard.svelte:127 | Toggle crossed[row][col]. |
| `saveGrid()` / `loadGrid()` | game-logic.js:149–167 | localStorage with prefix-based keys. |
| `pickFilledCols()` | game-logic.js | Per-row column selection that guarantees exact 5 per col (forces any col whose remaining quota equals rowsLeft, random-fills the rest). |
| `generateGrid()` | game-logic.js | Builds 9×9; ascending-sorted numbers per column. |
| `isRowComplete()` | game-logic.js | Boolean: all non-zero cells in row crossed? |
| `getWaitingNumber()` | game-logic.js | Returns the single uncrossed number in row, or null. |
| `buildBoard()` | master/+page.svelte | Builds 11×9 master grid; row = ones-digit, col = tens-digit; col 0 holds 1–9, col 8 holds 80–90. |
| `handleCellClick()` | PlayerBoard.svelte | Toggle crossed[row][col]. |
| `saveGrid()` / `loadGrid()` | game-logic.js | localStorage with prefix-based keys. |
Last reviewed: 2026-04-26
+12 -7
View File
@@ -4,13 +4,15 @@ This document tracks **future work only**. Completed features live in git commit
## Currently Implemented Features
The app is fully functional for core gameplay:
- 9×9 player card generation with weighted number distribution
- Cell marking (toggle crossed state)
- Bingo detection and celebration popup
The app is fully functional for core gameplay (Lô tô hội chợ Tân Tân variant):
- 9×9 player card with **exactly 5 per row and 5 per column**, columns
ascending top-to-bottom
- Cell marking (toggle crossed state); player can keep playing after Kinh
- Bingo detection and "Kinh!" celebration popup
- "Chờ X" waiting notifications
- Host number drawing from 1–90 deck
- 9×10 master board tracking called numbers
- 11×9 last-digit-aligned master board with draw-order overlay for fast
Kinh verification
- Host's own player card (isolated instance)
- localStorage persistence
- Dark mode
@@ -79,10 +81,13 @@ Track games won, time per bingo, etc. Requires server-side persistence. Out of s
- Screen reader support
### Custom Number Range
Host selects range (e.g., 1–75 for American bingo) instead of hardcoded 1–90. Requires config UI and refactor of game logic constants.
Host selects range (e.g., 1–75 for American bingo) instead of hardcoded 1–90. Requires config UI and refactor of game logic constants. **Out of scope** per Lô tô hội chợ Tân Tân focus.
### Different Grid Sizes
Support 8×8 or 10×10 grids. Major refactor (NUM_ROWS, NUM_COLS constants, weighted selection algorithm).
Support 8×8 or 10×10 grids, or 3×9 European Bingo 90 tickets. **Out of scope** — variant locked to 9×9 Tân Tân format.
### Two-line / Full-house Win Tiers
European Bingo 90 patterns. **Out of scope** — Tân Tân uses single-line "Kinh!" only.
---
+46 -8
View File
@@ -2,15 +2,50 @@
## What is Lô Tô?
Lô tô is a traditional Vietnamese bingo game. The app replicates the game digitally for players to generate their own 9×9 number cards and mark cells as a host calls numbers from 1–90. First player to complete an entire row wins and shouts "Kinh!" (the game's victory cheer).
This app replicates **Lô tô hội chợ** (Vietnamese fairground / carnival lô tô),
specifically the **Tân Tân** style most familiar from southern Vietnam class
reunions and Tết gatherings. Inspiration: TN1 class reunions (2014–2017)
where players ran build of physical bingo cards.
The inspiration comes from TN1 class reunions (2014–2017) where players often ran build of physical bingo cards.
It is **not** the British Bingo 90 / Italian Tombola 3×9 / 15-number ticket.
That format is intentionally out of scope.
## Game Variant & Scope
### In scope (Lô tô hội chợ Tân Tân)
- **Card**: 9 rows × 9 columns. Exactly **5 numbers per row** AND exactly
**5 numbers per column** (45 numbers per card; 36 blanks).
- **Column ranges**: col 0 = 1–9, col 1 = 10–19, …, col 7 = 70–79,
col 8 = 80–90.
- **Within a column**: numbers placed top-to-bottom in **ascending** order.
- **Number pool**: 1–90.
- **Win condition**: 1 row complete = **"Kinh!"**. After winning, the player
may keep playing further rows (game does not end).
- **Waiting state**: when a row needs only 1 number, app announces **"Chờ N"**.
- **Host (quản trò)** draws numbers from a shuffled 1–90 deck, tracks them on
a master board, and may also play their own card.
### Out of scope
- 3×9 / 15-number Bingo 90 / Tombola tickets (European format).
- Two-line and full-house win tiers.
- Custom number ranges (1–75 American bingo, etc.).
- Stake / pot / payout logic.
- Multiplayer real-time sync.
## Core Mechanics
- **Players**: Generate a randomized 9×9 card with 45 numbers (5 per row, weighted distribution across columns 1–90). Click cells to mark them as numbers are called.
- **Host**: Draws numbers randomly from a shuffled 1–90 deck, displays the current number on a large board, and tracks which numbers have been called.
- **Bingo**: When a row is complete, the player's card triggers a celebration popup showing "Kinh!" with confetti emojis. Before bingo, toast notifications prompt "Chờ X" (waiting for X) when only one number remains in a row.
- **Players**: Generate a randomized 9×9 card. The generator guarantees 5 per
row and 5 per column (constraint-aware picker, not loose weighted random).
Numbers within each column ascend top-to-bottom. Click cells to mark them.
- **Host**: Draws numbers randomly from a shuffled 1–90 deck, shows the
current number, and tracks all called numbers on an **11×9 master board
aligned by ones-digit** (col 0 = 1–9, col 8 = 80–90; 1 / 11 / 21 / … / 81
share row 1, etc.). Each called cell shows its 1-based **draw order** as a
small superscript so the host can quickly verify a "Kinh!" claim by reading
the order across the winning row.
- **Bingo**: When a row is complete, a celebration popup shows "Kinh!" with
confetti. Player keeps playing afterward — no game-end gate.
- **Waiting**: "Chờ N" toast when a row is one number away.
## Tech Stack
@@ -37,11 +72,14 @@ State is entirely client-side. Each page/card instance uses a unique localStorag
## Key Acceptance Criteria
- [x] Player can generate a new 9×9 card with valid number distribution.
- [x] Player card is 9×9 with **exactly 5 per row and 5 per column**.
- [x] Numbers within each column are **ascending top-to-bottom**.
- [x] Player can click cells to toggle crossed state.
- [x] Bingo popup triggers when row is complete, shows row number and "Kinh!" message.
- [x] Player may keep marking after a Kinh — no game-end lock.
- [x] Toast notifications show "Chờ X" before bingo (one number remaining).
- [x] Host can draw numbers and see them on the 9×10 master board.
- [x] Host can draw numbers and see them on the **11×9 last-digit-aligned master board**.
- [x] Master board shows **draw order** on each called cell for Kinh verification.
- [x] Host has their own player card (isolated by localStorage prefix).
- [x] Offline persistence via localStorage (grid and crossed state).
- [x] Dark mode support (Tailwind dark classes).
@@ -65,4 +103,4 @@ State is entirely client-side. Each page/card instance uses a unique localStorag
- Multiplayer sync (real-time via WebSocket)
- i18n beyond Vietnamese
Last reviewed: 2026-04-26
Last reviewed: 2026-04-26 (scope locked: Lô tô hội chợ Tân Tân)
+6 -3
View File
@@ -13,8 +13,9 @@ Entry
│
└─ /master (Host Page)
├─ Load loto_master (called/remaining)
├─ Display 9×10 master board (numbers 1–90)
├─ Draw button shows next called number
├─ Display 11×9 master board (numbers 1–90, aligned by ones-digit;
│ col 0 = 1–9, col 8 = 80–90; each called cell shows draw order)
├─ Draw button shows next called number (highlighted in red on board)
├─ Display host's own card (loto_master_card prefix)
└─ New Game button resets called/remaining
```
@@ -27,7 +28,9 @@ grid: number[][] // 9×9 numbers (0 = empty)
crossed: boolean[][] // 9×9 marked state
```
Each row has exactly 5 non-zero numbers (distributed across columns via weighted random).
Each row has exactly 5 non-zero numbers AND each column has exactly 5
(constraint-aware picker — no slack). Numbers within a column are sorted
ascending top-to-bottom (lô tô hội chợ Tân Tân convention).
### Host State (`storagePrefix="loto_master"`)
```