simple quick draw style god card selector for the game santorini https://santorini.studio-umzu.de
  • JavaScript 77.3%
  • CSS 14.6%
  • HTML 3.9%
  • Python 3.8%
  • Dockerfile 0.4%
Find a file
2026-09-13 11:08:47 +02:00
static working random 2026-09-13 11:08:47 +02:00
app.py working random 2026-09-13 11:08:47 +02:00
docker-compose.yml working random 2026-09-13 11:08:47 +02:00
Dockerfile working random 2026-09-13 11:08:47 +02:00
README.md working random 2026-09-13 11:08:47 +02:00
requirements.txt Initial commit 2026-09-10 20:48:47 +02:00
server.py Initial commit 2026-09-10 20:48:47 +02:00

Santorini · Oracle of the Gods

A tiny web toy for the board game Santorini: draw a sign, and an in-browser AI reads your doodle and deals you a god card.

Canvas → 400×400 bitmap → DoodleNet (tiny CNN, runs in-browser) → label
       → Santorini god mapping → god card revealed

No account, no backend classification, nothing uploaded anywhere — the recognition model runs entirely on the visitor's device.

How it works

  • Drawing — a p5.js canvas, driven by native Pointer Events so mouse, trackpad, touchscreen, and stylus all draw identically.
  • Recognition — ml5.js's DoodleNet, a small CNN pretrained on Google's Quick, Draw! dataset (~345 doodle categories). Classification happens client-side; the Python backend never sees the drawing.
  • Mapping — static/godcards.js maps a recognized label (e.g. hammer, snake, eye) to a Santorini god and a one-line power description. Anything the oracle doesn't recognize falls back to a generic Mortal card. It covers the full base-game roster of 32 gods, keyed across 51 doodle labels so recognition has decent coverage (e.g. both owl and book point to Athena). Power text is paraphrased for a small card — check the printed rulebook for exact wording.
  • Portraits (optional) — see Custom card art below. Every card works out of the box with just an emoji glyph.
  • Backend — a minimal Flask app whose only job is serving static/index.html and its assets. There is no database, no API, no user data.

Project layout

santorini/
├── app.py               Flask app — serves static/ from the site root
├── server.py            Production entrypoint (waitress)
├── requirements.txt
├── Dockerfile
├── docker-compose.yml   OMV / reverse-proxy deployment
└── static/
    ├── index.html       Page shell
    ├── style.css        Dark Aegean-night theme, card reveal styling
    ├── sketch.js         Drawing, classification, and card-reveal logic
    ├── godcards.js       Doodle-label → Santorini god mapping
    └── images/           Optional god portrait PNGs (see below)

Everything the app serves lives in one static/ folder — there's no separate templates/ directory to keep in sync.

Running locally (no Docker)

pip install -r requirements.txt
python server.py            # http://localhost:8080

Set DEBUG=true to run Flask's dev server with auto-reload instead of the production waitress server:

DEBUG=true python server.py

Running with Docker

docker build -t santorini-oracle .
docker run -p 8080:8080 santorini-oracle

Running with Docker Compose (OMV deployment)

docker-compose.yml is set up for a reverse-proxy setup where Caddy sits in front of the container on a shared external Docker network (proxy-network), rather than publishing the port directly to the host.

Expected host path:

/srv/dev-disk-by-uuid-a920d9c0-dfc1-4a58-ae4d-92cf88ff04a5/docker-app/santorini

The compose file bind-mounts that same directory into the container's /app, so editing anything under static/ (HTML, CSS, JS, or a portrait PNG) takes effect on browser refresh — no rebuild needed. Editing app.py or server.py needs a container restart, since Python only reads those at process start.

docker compose up -d --build

Matching Caddy site block:

santorini.studio-umzu.de {
    reverse_proxy santorini:8080
}

(santorini resolves via Docker's internal DNS because the container is named santorini and joined to the same proxy-network Caddy uses — no host port needs to be published.)

Custom card art

By default every god card shows an emoji glyph (🔨, 🐍, 👁️, …). To use real artwork instead, drop a PNG into static/images/ named after the god:

static/images/hephaestus.png
static/images/athena.png
static/images/mortal.png       ← the no-match fallback card

Naming rule: lowercase the god's name, replace anything that isn't a letter or number with a hyphen, add .png. No code changes and no per-card configuration needed — the app looks for the matching file automatically, and quietly falls back to the emoji glyph if the file is missing.

Adding or editing gods

All mapping lives in static/godcards.js as a plain object keyed by the doodle label ml5/DoodleNet returns (lowercase, matching Quick, Draw!'s category names — e.g. "hammer", "wine glass", "sailboat"). Each entry looks like:

hammer: {
  name: "Hephaestus",
  glyph: "🔨",
  tagline: "The Smith",
  power: "After moving, you may build one additional time on the same space.",
},

Add homebrew: true if the power is a fan invention rather than an approximation of an official Santorini god's ability — it adds a small "homebrew power" tag to the revealed card. (The current roster is the full base-game set, so none are flagged this way today — this is for if you extend it with expansion or custom gods later.)

Notes & limitations

  • DoodleNet's vocabulary is fixed to Quick, Draw!'s ~345 categories, so recognition works best with quick, simple, single-subject doodles — the same style the original Quick, Draw! game encourages. Thick single strokes drawn quickly tend to classify better than careful, detailed drawings.
  • Nothing is persisted anywhere: no accounts, no database, no server-side logging of drawings. Refreshing the page clears everything.
  • The god-power text is intentionally simplified/paraphrased for a quick browser toy and shouldn't be treated as an accurate rules reference for actual gameplay.