# Game Grid Persistence ## Problem Game grids are currently generated on the fly at every page load in `HomepageController`. There is no persistence — refreshing the page gives a new grid. Players cannot resume a game. ## Goal Persist game grids so players can resume their current game. Display a "Start a game" button when no game is in progress. Allow players to abandon a game. ## Scope - Grid generation and persistence only - No win/loss logic (will be added later) - No saving of player input (filled letters) ## Data Model ### Entity: `Game` | Field | Type | Description | |-------------|-------------------------|------------------------------------------------| | `id` | int (auto) | Primary key | | `user` | ManyToOne → User, nullable | Null for anonymous players | | `mainActor` | ManyToOne → Actor | The actor to guess | | `status` | string | `in_progress` or `abandoned` (extensible to `won` later) | | `startedAt` | DateTimeImmutable | When the game started | | `endedAt` | DateTimeImmutable, nullable | When the game ended (abandon or win) | ### Entity: `GameRow` | Field | Type | Description | |------------|--------------------|------------------------------------------------| | `id` | int (auto) | Primary key | | `game` | ManyToOne → Game | Parent game | | `actor` | ManyToOne → Actor | The actor for this row | | `position` | int | Index of the target letter in the actor's name | | `rowOrder` | int | Order of this row in the grid | `GameRow` is a separate entity (not JSON) to allow attaching hints/clues per row in a future iteration. ## Service: `GameGridGenerator` Extracted from the current `HomepageController::index` logic: 1. Select a random main actor via `ActorRepository::findOneRandom()` 2. For each letter (a-z) in the main actor's name, find a random actor containing that letter 3. Create a `Game` entity with `status = in_progress` and `startedAt = now` 4. Create `GameRow` entities with the actor, letter position, and row order 5. Persist and flush 6. Return the `Game` entity Also provides a method to compute grid display data (width, middle) from a `Game` and its rows, for passing to the React component. ## Routes ### `GET /` — Homepage (modified) Current behavior replaced: 1. If user is connected → query `GameRepository` for a `Game` with `status = in_progress` for this user 2. If anonymous → get `current_game_id` from the Symfony session, then look up the `Game` in DB 3. If a game is found and still `in_progress` → render the grid with an "Abandon" button above it 4. If no game is found → render a "Start a game" button ### `POST /game/start` — Start a new game (new) 1. Verify no game is already in progress for this player (connected or session) 2. Call `GameGridGenerator` to create the game 3. If user is connected → the game is linked to the user 4. If anonymous → store `$game->getId()` in session key `current_game_id` 5. Redirect to `/` ### `POST /game/{id}/abandon` — Abandon a game (new) 1. Verify the game belongs to the current player (user match or session match) 2. Set `status = abandoned` and `endedAt = now` 3. If anonymous → remove `current_game_id` from session 4. Redirect to `/` Both POST routes use CSRF protection. ## Anonymous Session Handling - Session key: `current_game_id` (stores the `Game` ID) - Set on game creation, removed on abandon - On homepage load: if the stored game ID no longer exists or is not `in_progress`, clean up the session key - No migration of anonymous games on login — the anonymous game is lost when the user logs in. Acceptable since we don't save letter input. ## Frontend ### Template changes (`homepage/index.html.twig`) Three states: 1. **No game in progress**: centered "Start a game" button (Twig form, POST to `/game/start`) 2. **Game in progress**: action bar above the grid with an "Abandon" button (Twig form, POST to `/game/{id}/abandon` with CSRF token), then the React `GameGrid` component below 3. **After abandon**: redirect to `/`, which shows state 1 ### React components No changes to `GameGrid`, `GameRow`, `LetterInput`, or `ActorPopover`. They receive the same props (`grid`, `width`, `middle`) — the data source changes from on-the-fly generation to database lookup. ## Future Extensibility - **Win logic**: add `won` to the status enum, set `endedAt` on win - **Hints/clues per row**: add a relation on `GameRow` (e.g., `GameRowHint` entity) - **Game history/stats**: query `Game` entities by user with status filters