Skip to content

IASC-00/decipher

Repository files navigation

Decipher

A browser puzzle game that teaches programming. Four rounds — Python, JavaScript, HTML/CSS, and finding a bug in code that looks correct — plus accounts, a leaderboard, achievements, and a head-to-head duel mode.

Live: https://decipher.iswain.dev Stack: Python · Flask · Gunicorn · PostgreSQL in production, SQLite locally

No install, no account ceremony, no toolchain: the first puzzle is one click from the link. That constraint drove most of the design decisions below, because the usual reason people quit learning to program is setup, not difficulty.

Run it locally

python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/python seed.py          # populates puzzles into a fresh SQLite db
.venv/bin/flask --app app run     # http://127.0.0.1:5000

Nothing else is required. With no DATABASE_URL set it uses SQLite and creates the database on first run.

To run against Postgres instead, set DATABASE_URL to a connection string and start it the same way — no code or config changes.

Tests

.venv/bin/python -m pytest tests/ -v

Covers the health endpoint, the pages that must render for a logged-out visitor, and that /play refuses an unauthenticated request.

Design decisions worth reading

One database layer, two databases. db_exec in app.py normalises the differences between SQLite and Postgres — parameter placeholders, INSERT OR IGNORE versus ON CONFLICT DO NOTHING, auto-increment syntax, datetime handling — so application code never branches on which database it is talking to. This started as a fix for a deploy that kept breaking on Postgres-only syntax, and it is the reason deploys stopped breaking. Local development stays zero-config on SQLite while production runs Postgres.

The seeder is idempotent and self-healing. seed.py checks what content already exists and inserts only what is missing, and it runs on cold start. A fresh deploy comes up fully populated with no manual migration step, and a database that loses rows repairs itself on the next boot. Adding puzzles is a matter of extending the list and raising a threshold rather than writing a migration.

Rate limiting at the edge of the app. Answer submission is the endpoint worth abusing, so Flask-Limiter caps it rather than relying on the host.

Layout

app.py           routes, auth, scoring, the db_exec compatibility layer
seed.py          puzzle content + the idempotent seeder
schema.sql       6 tables: puzzles, sessions, answers, high_scores, player, achievements
templates/       Jinja templates — play, duel, leaderboard, achievements, auth
static/          CSS, JS, images
tests/           pytest

Deployment

Runs in a container on a server I administer, behind a reverse proxy that terminates TLS. It previously ran on a hosted free tier and went offline when the service was reclaimed, which is why it now runs on infrastructure I control.


Built by Ian Swain — iswain.dev

About

Browser puzzle game that teaches programming — Flask, runs on SQLite or Postgres through one compatibility layer. Live at decipher.iswain.dev

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages