fix spec review issues: bingo scoring, data model, edge cases
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -57,7 +57,10 @@ Replaces the current `RoomHeader` component.
|
||||
```
|
||||
|
||||
- **Left:** "I❤️ESC" as the app title (text, not emoji — the heart is the Unicode character ❤️)
|
||||
- **Right:** Room code (monospace, bold) + connection status dot (green/yellow/red)
|
||||
- **Right:** Room code (monospace, bold) + connection status dot
|
||||
- Green = `connected`
|
||||
- Yellow = `connecting`
|
||||
- Red = `disconnected`
|
||||
- `position: sticky; top: 0; z-index: 50`
|
||||
- `padding-top: env(safe-area-inset-top)` for iPhone notch/Dynamic Island
|
||||
- Act badge removed — the game content makes the current phase obvious
|
||||
@@ -135,26 +138,46 @@ Shows the player's current bingo card. Same `BingoCard` component as today, with
|
||||
When a player completes a bingo line:
|
||||
|
||||
1. **Server detects completion** — the server already detects bingo lines and awards 10 bonus points. Extended to mark the card as `completed`.
|
||||
2. **Card stored** — the completed card moves to a `completedBingoCards` array in game state (per player). Contains: `playerId`, `displayName`, `card` (the full card data), `completedAt` (timestamp).
|
||||
2. **Card stored** — the completed card moves to a `completedBingoCards` array in game state (per player). Contains: `playerId`, `displayName`, `card` (the full card data with marked squares), `completedAt` (ISO 8601 timestamp).
|
||||
3. **Player sees** — a celebration message ("Bingo!") + "Draw New Card" button.
|
||||
4. **Redraw** — new WS message `request_new_bingo_card`. Server generates a fresh card with different tropes. The old card is already in `completedBingoCards`.
|
||||
4. **Redraw** — new WS message `request_new_bingo_card`. Server generates a fresh card with tropes not present on the just-completed card (best effort — if the trope pool is exhausted, duplicates are allowed). The old card is already in `completedBingoCards`.
|
||||
5. **New card** — `myBingoCard` in game state updates to the fresh card. Player can continue playing.
|
||||
6. **Redraw is only available during `live-event`** — same act gate as tapping squares.
|
||||
|
||||
### Scoring Across Multiple Cards
|
||||
|
||||
Bingo points accumulate across all cards (completed + active). `getBingoScore()` sums:
|
||||
- 2 points per tapped square across all completed cards and the active card
|
||||
- 10 bonus points per completed bingo line (each completed card contributed at least one line; the active card may also have lines)
|
||||
|
||||
The `announcedBingo` set is changed to track `playerId:cardIndex` instead of just `playerId`, so multiple bingo announcements per player are possible.
|
||||
|
||||
### Data Changes
|
||||
|
||||
Shared types additions:
|
||||
```
|
||||
completedBingoCards: { playerId: string, displayName: string, card: BingoCard, completedAt: string }[]
|
||||
New Zod schema in `game-types.ts`:
|
||||
```typescript
|
||||
export const completedBingoCardSchema = z.object({
|
||||
playerId: z.string(),
|
||||
displayName: z.string(),
|
||||
card: bingoCardSchema,
|
||||
completedAt: z.string(),
|
||||
})
|
||||
```
|
||||
|
||||
New WS client message:
|
||||
Added to `gameStateSchema`:
|
||||
```typescript
|
||||
completedBingoCards: z.array(completedBingoCardSchema)
|
||||
```
|
||||
{ type: "request_new_bingo_card" }
|
||||
|
||||
New WS client message in `ws-messages.ts` (added to `clientMessage` discriminated union):
|
||||
```typescript
|
||||
export const requestNewBingoCardMessage = z.object({ type: z.literal("request_new_bingo_card") })
|
||||
```
|
||||
|
||||
Game state changes:
|
||||
- `myBingoCard` continues to represent the current active card
|
||||
- `completedBingoCards` is a new array on `GameState` (visible to all — host needs it for verification)
|
||||
- Display view receives `completedBingoCards` for bingo announcement purposes only (no card detail needed on projector)
|
||||
|
||||
---
|
||||
|
||||
@@ -162,7 +185,7 @@ Game state changes:
|
||||
|
||||
Replaces both the current `Leaderboard` and `PlayerList` components as separate UI elements.
|
||||
|
||||
- **Lobby** (no scores yet): shows player list with names only — a "who's here" view
|
||||
- **Lobby** (no scores yet): shows player list with names only — a "who's here" view. The `Leaderboard` component renders a simplified layout: just rank numbers and names, no score columns.
|
||||
- **All other acts**: full leaderboard table (rank, name, P/J/B/Q breakdown, total points)
|
||||
- **Scoring explanation**: "How scoring works" box at the bottom (same as current)
|
||||
|
||||
@@ -195,7 +218,7 @@ Vertical stack of cards, only visible to the host. This tab contains all host-ex
|
||||
|
||||
### Bottom
|
||||
|
||||
7. **End Party** button — destructive (red), always available except when already ended
|
||||
7. **End Party** button — destructive (red), always available except when already ended. This is the same action as advancing from scoring to ended, but available from any act as a shortcut. The act-specific "End Party" label in Act Controls (scoring → ended) is removed to avoid duplication.
|
||||
|
||||
---
|
||||
|
||||
@@ -253,12 +276,31 @@ Unchanged. Full-screen passive view, no bottom nav. Continues to show:
|
||||
|
||||
### Game State Changes
|
||||
|
||||
- Add `completedBingoCards` to game state schema
|
||||
- Include in `getGameStateForPlayer` and `getGameStateForDisplay`
|
||||
- Add `completedBingoCards` to game state schema (Zod schema + TypeScript type)
|
||||
- Include in `getGameStateForPlayer` (full card data for host verification) and `getGameStateForDisplay` (for announcements only)
|
||||
- Update `getBingoScore()` to sum points across all completed cards + active card
|
||||
- Change `announcedBingo` from `Set<string>` (playerId) to track `playerId:cardIndex` pairs, allowing multiple bingo announcements per player
|
||||
|
||||
### WS Handler Changes
|
||||
|
||||
- Add `request_new_bingo_card` case to handler, gated to `live-event` act
|
||||
- Add the message to the `clientMessage` discriminated union in `ws-messages.ts`
|
||||
|
||||
### Route Transition Safety
|
||||
|
||||
- WebSocket connection and Zustand store are managed in the layout route, not child routes. Navigating between tabs (child routes) does not trigger WS reconnection or store reset.
|
||||
|
||||
---
|
||||
|
||||
## 11. Migration / URL Compatibility
|
||||
|
||||
- Old URLs (`/play/ABCD`, `/host/ABCD`) should redirect to `/play/ABCD/game` and `/host/ABCD/game` respectively
|
||||
- TanStack Router can handle this via the layout route's default redirect
|
||||
- Implemented via TanStack Router index routes that redirect (e.g., a `/play/$roomCode/` index route with `beforeLoad` that throws `redirect({ to: '/play/$roomCode/game' })`)
|
||||
|
||||
---
|
||||
|
||||
## 12. Label Naming
|
||||
|
||||
- Player bottom nav labels: "Game", "Bingo", "Leaderboard" (3 tabs, enough space for full labels)
|
||||
- Host bottom nav labels: "Game", "Bingo", "Board", "Host" (4 tabs, "Board" is shortened from "Leaderboard" for space)
|
||||
- Route paths use short names: `/game`, `/bingo`, `/board`, `/host`
|
||||
|
||||
Reference in New Issue
Block a user