Files
wordsearch/docs/superpowers/specs/2026-05-06-game-logic-design.md
T
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

9.0 KiB

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:

[
  {"id": 1, "name": "animals"},
  {"id": 2, "name": "colors"},
  {"id": 3, "name": "food"}
]

POST /api/game/new

Request body:

{"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": "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):

{"word": "TIGER"}

Request body (click guess):

{"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:

{
  "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.