@onnie81/murdoku-spor
v1.26.0
Published
Kludoku — online murder-mystery logic puzzle (sudoku-style placement deduction). Deploy artifact for games.onniehex.dev.
Readme
Kludoku
A complete online murder-mystery placement-deduction game — the pencil-and-paper placement-deduction genre (Cluedo × Sudoku), fully procedural and multiplayer-social.
A victim and a cast of suspects must be placed on an illustrated crime scene: each row and each column holds exactly one person, furniture blocks squares, rooms and testimonies constrain everyone — and whoever ends up alone with the victim is the killer.
Features
- Procedural cases — every board is generated: room layout, furniture, ground truth, and a minimal clue set proven to have a unique solution that is solvable without blind guessing (propagation + single-assumption logic).
- 5 difficulties — Beginner 5×5 → Expert 9×9, graded by the number of assumption rounds a logical solver needs, not just clue count.
- Daily challenges — same five seeded cases for the whole world (UTC), with global and friends leaderboards, streaks, and a case archive.
- Full play kit — tap or drag placement, legal-move highlighting with automatic graying of non-allowed squares, X marks, pencil marks (small numbers or mini faces), clue spotlighting + strike-through, undo/redo, hints, pause, mistake tracking, three assist levels.
- Accounts, sessions, friends — instant guest play (per-device session), optional username+password accounts that adopt the guest history, friend requests, and daily leaderboards. SQLite storage, one file.
- History tracker — per-user record: solved/played, streaks, best and average times per difficulty, full case log; autosave + resume on any device.
- Responsive + touch — phone, tablet, desktop; pointer-based drag & drop, long-press to mark X, keyboard shortcuts (1-9, N, X, U, R, E).
- Multilingual — English and Spanish shipped; clues are structured data rendered through per-language templates, so adding a language is one file.
- Furniture that matters — blockers, seats you can be seated on (armchairs, two-square sofas), beds you can be lying in, and wall-mounted windows/paintings/mirrors that live on the walls themselves (windows count from both sides). Clues use all of it: "was seated on the sofa", "was not lying in any bed", "was beside a window".
- Original art & sound — 12 hand-drawn SVG suspect portraits, colorful furniture sprites, 8 scene themes; synthesized noir soundtrack and sound effects (WebAudio, zero assets, all original).
- Phone-first play screen — fullscreen mode, an always-visible lateral suspect overlay that turns translucent while you place, floating controls, and a clue bottom-sheet.
Quick start (Docker)
docker compose up -d --build
# open http://localhost:8080Data (users, games, puzzles) lives in the murdoku-data volume as a single
SQLite file. Back it up with:
docker run --rm -v murdoku-data:/data -v "$PWD:/out" debian \
cp /data/murdoku.db /out/murdoku-backup.dbHTTPS / domain
The stack ships a Caddy service that terminates TLS and reverse-proxies the
app. On docker compose up -d it automatically obtains and renews a Let's
Encrypt certificate for the domain — no manual certs. Requirements:
- ports 80 and 443 open to the internet (Caddy needs 80/443 for the ACME challenge),
- DNS A records, all DNS-only (not proxied, or the challenge can't reach
Caddy):
kludoku.comandwww.kludoku.com→ the server's public IP — the canonical home. Legacygames.onniehex.dev(and optionallyonniehex.dev) point at the same IP and 301-redirect to kludoku.com.
MURDOKU_DOMAIN still names the legacy redirect host. Port 5000 stays mapped
straight to the app for direct testing / health checks. The app sets
trust proxy, so Secure cookies and client IPs work correctly behind Caddy.
Cloud delivery (npm)
Published as @onnie81/murdoku-spor. First-time server setup:
curl -fsSL https://registry.npmjs.org/@onnie81/murdoku-spor/-/murdoku-spor-<version>.tgz \
| tar xz && sudo bash package/scripts/npm-server-bootstrap.shThis installs Docker/git if missing, unpacks a real git repo (from the bundled
history) into /opt/murdoku, builds, and installs a systemd timer that polls
npm every 60s and rebuilds on each new published version.
Configuration
Operator settings live in /opt/murdoku/.env on the server (created by hand,
preserved across deploys; docker compose reads it automatically). Every
feature below is off until its variables are set — the app runs fine without any.
| Env | Default | Meaning |
|---|---|---|
| PORT | 8080 | app HTTP port (inside the container) |
| DATA_DIR | /data (container) / ./data (bare) | SQLite location |
| MURDOKU_DOMAIN | games.onniehex.dev | domain Caddy requests a cert for |
| PUBLIC_URL | https://kludoku.com | canonical origin (OAuth redirects, reset links) |
| ADMIN_TOKEN | (unset = admin off) | bearer token for /admin + /api/admin/* |
| SMTP_URL | (unset = mail off) | e.g. smtps://user:[email protected]:465 — enables password-reset mail |
| MAIL_FROM | Kludoku <no-reply@…> | From header for outgoing mail |
| ADSENSE_CLIENT | (unset = ads off) | AdSense publisher id ca-pub-… (see MONETIZATION.md) |
| AUTH_GOOGLE_CLIENT_ID / AUTH_GOOGLE_CLIENT_SECRET | (unset) | "Sign in with Google" |
| AUTH_FACEBOOK_CLIENT_ID / AUTH_FACEBOOK_CLIENT_SECRET | (unset) | "Sign in with Facebook" |
| AUTH_APPLE_CLIENT_ID / AUTH_APPLE_TEAM_ID / AUTH_APPLE_KEY_ID / AUTH_APPLE_PRIVATE_KEY | (unset) | "Sign in with Apple" (private key PEM, \n-escaped) |
OAuth redirect URIs to register with each provider:
https://kludoku.com/api/auth/oauth/google/callback (Google),
…/facebook/callback (Facebook), …/apple/callback (Apple — also requires the
domain to be verified in the Apple developer console).
Monitoring
With ADMIN_TOKEN set, GET /admin serves a small authenticated dashboard
(users, DAU/WAU, wins by difficulty, telemetry top events, stuck rate, DB sizes)
and GET /api/admin/metrics returns the same as JSON:
curl -H "Authorization: Bearer $ADMIN_TOKEN" https://kludoku.com/api/admin/metricsPOST /api/admin/premium {username, premium} toggles an account's ad-free flag.
/admin is noindex and disallowed in robots.txt.
Development
npm install && npm --prefix client install
npm run build # vite build -> server/public
npm run dev # serve on :8080
npm test # engine + API integration tests
node tests/e2e.mjs # Playwright visual/interaction smoke (needs chromium)Dev loop with hot reload: npm run dev in one terminal,
npm --prefix client run dev in another (Vite on :5173 proxies /api).
Architecture
shared/ puzzle engine (isomorphic, seeded & deterministic)
rng.js xmur3 + mulberry32 seeded RNG
model.js cast, themes, difficulty bands, clue-type sets
clues.js scene context + bitmask tables, predicates, clue-pool gen
bits.js 96-bit bitset primitives
solver.js CSP: propagation (singles, arc revision, hidden singles,
room counts), uniqueness counting, "what-if" logical solver
generator.js rooms → furniture → ground truth (victim alone with exactly
one person) → clue selection/minimization → difficulty grading
server/ Express + better-sqlite3
db.js schema + queries (users, sessions, puzzles, games, friendships)
auth.js cookie sessions, bcrypt accounts, guest→account merge
puzzles.js daily seeding (UTC), free-play pregeneration pool
api.js REST: auth, settings, daily, games, history, stats,
friends, leaderboards
client/ React + Vite single-page app
game.js per-game store: placement, legality, notes, X, undo/redo,
autosave, accusation flow
components/ Board (SVG scene), Tray, CluePanel, Play/Home/Daily/
History/Friends/Settings views
i18n/ en/es dictionaries + structured clue renderer
art/ original SVG portraits, furniture, logoThe daily seed is daily:<UTC date>; generation is fully deterministic, and
generated dailies are persisted, so every player gets the identical case.
Honesty model: the solution ships to the client to power instant assists (wrong-placement flags, accusation feedback). Finishing is validated server-side against the stored solution, but a determined user could read the solution from devtools — fine for a friendly game, worth knowing for prizes.
Adding a language
Copy client/src/i18n/en.js to e.g. fr.js, translate the values (UI strings,
roles, rooms, objects, scenes, clue templates), and register it in
client/src/i18n/index.js (DICTS, LANGS). Rooms/objects carry n (label)
and p (in-sentence with article) forms so grammar stays natural.
Originality
Game mechanics follow the published "Murdoku" puzzle genre by Manuel Garand (one person per row/column, map evidence, the murderer alone with the victim). All code, characters, scenes, artwork, and text in this project are original. The game ships under the original name "Kludoku"; "Murdoku" as a name/brand belongs to its owners and is referenced only to credit the genre. For a public commercial deployment, consider your own title.
