4.9 KiB
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:
- Select a random main actor via
ActorRepository::findOneRandom() - For each letter (a-z) in the main actor's name, find a random actor containing that letter
- Create a
Gameentity withstatus = in_progressandstartedAt = now - Create
GameRowentities with the actor, letter position, and row order - Persist and flush
- Return the
Gameentity
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:
- If user is connected → query
GameRepositoryfor aGamewithstatus = in_progressfor this user - If anonymous → get
current_game_idfrom the Symfony session, then look up theGamein DB - If a game is found and still
in_progress→ render the grid with an "Abandon" button above it - If no game is found → render a "Start a game" button
POST /game/start — Start a new game (new)
- Verify no game is already in progress for this player (connected or session)
- Call
GameGridGeneratorto create the game - If user is connected → the game is linked to the user
- If anonymous → store
$game->getId()in session keycurrent_game_id - Redirect to
/
POST /game/{id}/abandon — Abandon a game (new)
- Verify the game belongs to the current player (user match or session match)
- Set
status = abandonedandendedAt = now - If anonymous → remove
current_game_idfrom session - Redirect to
/
Both POST routes use CSRF protection.
Anonymous Session Handling
- Session key:
current_game_id(stores theGameID) - 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:
- No game in progress: centered "Start a game" button (Twig form, POST to
/game/start) - Game in progress: action bar above the grid with an "Abandon" button (Twig form, POST to
/game/{id}/abandonwith CSRF token), then the ReactGameGridcomponent below - 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
wonto the status enum, setendedAton win - Hints/clues per row: add a relation on
GameRow(e.g.,GameRowHintentity) - Game history/stats: query
Gameentities by user with status filters