Files
wordsearch/docs/superpowers/specs/2026-05-06-game-logic-design.md
Mathew Sir Guest the bestandClaude Opus 4.6 85045f2a5f Add game logic design spec
Defines data models (Category, CategoryWord, Game, Word), board
factory, REST API endpoints, and dual-input frontend design for the
word search game.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-06 22:28:24 -06:00

249 lines
9.0 KiB
Markdown

# Word Search Game Logic Design
## Context
The project has a Tornado web server with two stub pages (main, game) and a dark teal Bootstrap theme. No game logic exists yet. This spec defines the data layer (SQLAlchemy + Alembic + SQLite), board generation, REST API, and frontend interaction needed to make the word search game playable.
The design prioritizes server-side game logic for testability and cheat resistance, while keeping the frontend interactive enough for both text and click-based input.
## Data Models
All models use SQLAlchemy ORM with a SQLite database file (e.g. `wordsearch.db`).
### Category
| Column | Type | Notes |
|--------|-------------|--------------------|
| id | Integer PK | Auto-increment |
| name | String(50) | Unique, e.g. "animals" |
### CategoryWord
| Column | Type | Notes |
|-------------|-------------|------------------------|
| id | Integer PK | Auto-increment |
| category_id | Integer FK | References Category.id |
| word | String(20) | Uppercase, e.g. "TIGER" |
Unique constraint on `(category_id, word)`.
### Game
| Column | Type | Notes |
|--------------|--------------|------------------------------------|
| id | String(36) PK | UUID as string |
| category_id | Integer FK | References Category.id |
| board_size | Integer | Grid dimension (N x N), default 10 |
| grid | JSON | 2D array of single uppercase chars |
| status | String(20) | "in_progress" or "completed" |
| started_at | DateTime | Set on creation (UTC) |
| completed_at | DateTime | Nullable, set when all words found |
### Word
| Column | Type | Notes |
|-----------|--------------|----------------------------------------|
| id | Integer PK | Auto-increment |
| game_id | String(36) FK | References Game.id |
| text | String(20) | The placed word, uppercase |
| start_row | Integer | 0-indexed row of first letter |
| start_col | Integer | 0-indexed column of first letter |
| direction | String(2) | One of: E, W, N, S, NE, NW, SE, SW |
| found | Boolean | Default false |
| found_at | DateTime | Nullable, set when word is matched |
## Direction Encoding
Directions map to (row_delta, col_delta):
- E = (0, +1), W = (0, -1)
- S = (+1, 0), N = (-1, 0)
- SE = (+1, +1), SW = (+1, -1)
- NE = (-1, +1), NW = (-1, -1)
## Board Factory
Module: `game/factory.py`
Class `BoardFactory`:
1. **Input**: `category_id`, `board_size` (default 10), `word_count` (default 8)
2. **Query**: Fetch all `CategoryWord` rows for the category. Filter to words that fit within `board_size`. Randomly select `word_count` words.
3. **Placement loop**: For each word, try random (row, col, direction) positions. A position is valid if:
- The word fits within grid bounds
- Each cell is either empty or already contains the same letter (overlap is allowed)
- Max placement attempts per word: 100. If exceeded, skip the word.
4. **Fill**: Remaining empty cells get random uppercase letters (A-Z).
5. **Output**: Returns a `Game` object and list of `Word` objects (not yet committed to DB).
## Database Setup
- **Engine**: SQLAlchemy with `sqlite:///wordsearch.db` (file in project root)
- **Session**: A session factory created at app startup, passed to handlers via `Application.settings`
- **Alembic**: Configured with `alembic init`, migrations in `alembic/versions/`
- **Seed data**: An initial migration or seed script populates `Category` and `CategoryWord` tables with at least 3 categories (~20-30 words each):
- Animals (TIGER, DOLPHIN, EAGLE, etc.)
- Colors (CRIMSON, VIOLET, AMBER, etc.)
- Food (PIZZA, SUSHI, MANGO, etc.)
## API Endpoints
All API handlers inherit from a base that sets `Content-Type: application/json`.
### GET /api/categories
Returns all available categories.
**Response:**
```json
[
{"id": 1, "name": "animals"},
{"id": 2, "name": "colors"},
{"id": 3, "name": "food"}
]
```
### POST /api/game/new
**Request body:**
```json
{"category_id": 1, "board_size": 12}
```
`board_size` is optional (default 10, clamped to 8-20).
**Behavior**: Creates a new game via `BoardFactory`, persists to DB, returns game state.
**Response** (same format as GET /api/game/<id>):
```json
{
"id": "uuid",
"board_size": 12,
"category": "animals",
"grid": [["A","B",...], ...],
"words": [
{"text": "TIGER", "found": false},
...
],
"started_at": "2026-05-06T12:00:00Z",
"status": "in_progress"
}
```
Note: word positions (start_row, start_col, direction) are NOT sent to the client. The client only knows the word text and whether it's been found.
### GET /api/game/{id}
Returns the current game state in the same format as above. Includes `completed_at` if finished.
### POST /api/game/{id}/guess
**Request body** (text guess):
```json
{"word": "TIGER"}
```
**Request body** (click guess):
```json
{"cells": [{"row": 0, "col": 3}, {"row": 0, "col": 4}, ...]}
```
**Validation (text guess)**:
1. Uppercase the input
2. Check if the word exists in this game's `Word` list
3. Check if it hasn't already been found
4. If valid: mark `found=true`, set `found_at`
**Validation (click guess)**:
1. Extract the letter at each (row, col) from the grid
2. Concatenate to form a word string
3. Check if that word exists and the cells match the stored placement coordinates
4. If valid: mark found
**Response**:
```json
{
"correct": true,
"word": "TIGER",
"cells": [{"row": 0, "col": 3}, {"row": 0, "col": 4}, ...],
"words": [...updated word list...],
"status": "in_progress",
"completed_at": null
}
```
When `correct` is true, `cells` contains the word's actual grid positions so the frontend can highlight them. When the last word is found, `status` changes to `"completed"` and `completed_at` is set.
## Frontend (game.html)
### Game Setup
On the `/game` page, before a game starts:
- A dropdown to pick a category (populated from a `GET /api/categories` endpoint)
- A board size selector (range input or dropdown, 8-20, default 10)
- A "New Game" button
### Game Board
After starting a game:
- The grid renders as an HTML `<table>` with one letter per `<td>`, styled with the teal theme
- A word list sidebar shows all target words, with found words crossed off and highlighted
- A timer displays elapsed seconds since `started_at`
### Text Input
- An input field below the grid with a "Submit" button
- On submit: `POST /api/game/{id}/guess` with `{word: input_value}`
- If correct: highlight the word's cells on the grid in accent color, cross off the word in the list
- If incorrect: brief shake animation or "not found" flash message
### Click Selection
- Clicking a cell starts a selection, subsequent clicks extend it
- Selected cells highlight in a temporary color
- A "Submit Selection" button sends `POST /api/game/{id}/guess` with the cell coordinates
- Clear selection on successful match or via a "Clear" button
- Constraint: clicks must form a straight line (horizontal, vertical, or diagonal). Frontend validates this before submitting.
### Game Complete
When all words are found:
- Stop the timer
- Show a completion banner with elapsed time
- Offer a "Play Again" button that returns to game setup
## Project Structure (new/modified files)
```
ai_demo_backend/
├── app.py # Add db session setup, new API routes
├── db.py # SQLAlchemy engine + session factory
├── models/
│ ├── __init__.py
│ ├── category.py # Category, CategoryWord
│ └── game.py # Game, Word
├── game/
│ ├── __init__.py
│ └── factory.py # BoardFactory
├── handlers/
│ ├── api.py # API handlers (new game, guess, categories)
│ ├── game.py # (existing, updated)
│ └── main.py # (existing, unchanged)
├── templates/
│ ├── game.html # (existing, rewritten with game UI)
│ └── ...
├── static/
│ ├── css/theme.css # (existing, extended with grid styles)
│ └── js/game.js # Frontend game logic
├── alembic/ # Alembic migrations directory
├── alembic.ini # Alembic config
└── seed.py # Optional: seed script for categories
```
## Future Scaling Considerations
- The `Game` model already supports multiple concurrent games via UUID. The current UI only shows one game at a time, but the API is stateless per game ID.
- To support multiple users: add a `player_id` or session token to `Game`.
- To support leaderboards: query `Game` rows with `status=completed`, ordered by `completed_at - started_at`.
- Categories and words in the DB means they can be managed via an admin interface or API later.