commit 85045f2a5fff57efc137496670c00db7889d6462 Author: Mathew Sir Guest the best Date: Wed May 6 22:28:24 2026 -0600 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 diff --git a/docs/superpowers/specs/2026-05-06-game-logic-design.md b/docs/superpowers/specs/2026-05-06-game-logic-design.md new file mode 100644 index 0000000..26e7b90 --- /dev/null +++ b/docs/superpowers/specs/2026-05-06-game-logic-design.md @@ -0,0 +1,248 @@ +# 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/): +```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 `` with one letter per `
`, 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.