# 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.