Skip to main content

Tic-Tac-Toe Gauntlet

A terminal tic-tac-toe game that gets harder the more you win. Beat the random Rookie to unlock the rule-based Contender, then the unbeatable Minimax Champion. Every game and move is streamed into a local DuckDB database.

Built with Python + uv + Pydantic + Textual + DuckDB.

Quickstart

No clone, no database setup — the database is created on first run.

uvx ttt-gauntlet play        # try it without installing anything

Or install it:

pip install ttt-gauntlet
ttt-gauntlet play            # play the gauntlet (interactive TUI)
ttt-gauntlet stats           # see your record

(uvx ships with uv; pipx run ttt-gauntlet play works the same way.)

How it plays

  • You are X and always move first.
  • Win to advance to the next tier. Lose or draw to replay the tier.
  • Progress is remembered across sessions — ttt-gauntlet play resumes just past your best win. Force a tier with ttt-gauntlet play --level 3.
  • A live stats panel sits to the right of the board — record, current/best win streak, average moves per win, win rate per tier, and a trend of your recent results — refreshed after every game.

Controls: click a square (or navigate with the keyboard), N to continue after a game, Q to quit.

The difficulty ladder

Level Tier Engine
1 Rookie Random legal move
2 Contender Heuristic: win → block → center → corner → edge
3 Champion Minimax — optimal, cannot be beaten

Architecture

The rules engine and the AI are pure and UI-agnostic, so the same logic backs the game, the opponents, and the tests.

src/ttt/
  models.py        # Pydantic models (Board, GameEvent, …)
  engine/game.py   # pure tic-tac-toe rules (single source of truth)
  opponents/       # Opponent abstraction + random / heuristic / minimax + ladder
  data/db.py       # DuckDB connection + GameRepository
  tui/app.py       # Textual UI (thin: renders + dispatches only)
  cli.py           # Typer commands: play / stats

Data model

game_events is an append-only event stream — the source of truth for everything that happens (game_started / move_made / game_ended). The games and moves tables are projections written alongside it to keep common queries simple.

The database lives at $XDG_DATA_HOME/ttt/ttt.duckdb (or ~/.local/share/ttt/ttt.duckdb). Override with TTT_DB_PATH.

Roadmap

Planned features are written up as one-file specs — these are the units of work fed to the OpenHands PR automation.

Development

Requires uv.

git clone https://github.com/cmacdonald0514/tictactoe.git
cd tictactoe
uv sync                     # install dependencies
uv run ttt-gauntlet play    # run from the working tree
uv run pytest          # unit tests (engine, opponents, persistence)
uv run ruff check      # lint
uv run ruff format     # format

Releasing

Bump version in pyproject.toml and merge to master. The release workflow compares that version against the previous commit and publishes to PyPI via Trusted Publishing when it changes; merges that leave the version alone publish nothing.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ttt_gauntlet-0.2.0.tar.gz (35.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ttt_gauntlet-0.2.0-py3-none-any.whl (18.1 kB view details)

Uploaded Python 3

File details

Details for the file ttt_gauntlet-0.2.0.tar.gz.

File metadata

  • Download URL: ttt_gauntlet-0.2.0.tar.gz
  • Upload date:
  • Size: 35.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for ttt_gauntlet-0.2.0.tar.gz
Algorithm Hash digest
SHA256 a870efedc84d6a83f631306a5ae82f3e32f72c0385ef7389b0ec9886a591b5a5
MD5 d4e8df555acf558b5a22b4032bf49490
BLAKE2b-256 d9db4eced8590a981cc66c99430dc4d6260f897c3392a0621bc78d358f00985c

See more details on using hashes here.

Provenance

The following attestation bundles were made for ttt_gauntlet-0.2.0.tar.gz:

Publisher: release.yml on cmacdonald0514/tictactoe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ttt_gauntlet-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: ttt_gauntlet-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 18.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for ttt_gauntlet-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 84086aa4f2c78b3776ba7352078955616b792769523efed5cf2ea4ec37a4dfec
MD5 b2e34d01a6704e5a7c3df3eac2f4c584
BLAKE2b-256 008411e92230ec6e2a9d9bccba566ec339a50a38d5ea30fad96e7c52a7149c0e

See more details on using hashes here.

Provenance

The following attestation bundles were made for ttt_gauntlet-0.2.0-py3-none-any.whl:

Publisher: release.yml on cmacdonald0514/tictactoe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page