Skip to content

Repository files navigation

Nosku - Offline Sudoku PWA

A chalkboard-styled Sudoku game that installs as a Progressive Web App and runs 100% offline after the first load. Ships with 200 preloaded puzzles (50 each of Easy / Medium / Hard / Diabolical), with an optional in-app way to pull more from the public sudoku-exchange-puzzle-bank when you do have a connection.

Disclaimer: This project is 100% "vibe coded": every line was written by an AI coding assistant from conversational prompts, with no professional code review behind it. It works, and it's been tested for basic correctness, but don't expect the kind of scrutiny a hand-reviewed production codebase would get. Bug reports and PRs welcome.

What you won't find here

  • No monetization - no upfront cost, no subscription, no in-app purchases
  • Offline - an internet connection is only needed to install the app and to download more puzzles; everything else works with it off
  • No unnecessary permissions - the only thing it ever touches is local storage, and only to save your progress and hold puzzles you download
  • No ads
  • No notifications
  • No popups
  • No distraction
  • No FOMO, no dark patterns - no streak-shaming, no fake urgency, no manufactured reasons to come back
  • No telemetry - nothing is tracked, collected, or sent anywhere

Features

  • Fully offline PWA: a service worker precaches the entire app shell (HTML/CSS/JS/icons/puzzle data) on first visit. No CDN, no web fonts, no external runtime dependencies. Installable to the home screen / as a desktop app.
  • 200 preloaded puzzles, 50 per difficulty, each solved and verified ahead of time (see gen_puzzles.py).
  • Download more puzzles from GitHub's puzzle bank, straight from the Library screen, while online; puzzles are de-duplicated against what you already have and solved locally in the browser before being saved.
  • Notes/pencil-marks, undo, hints, mistake tracking (3-strike), a timer, autosave/resume per puzzle, and simple stats (solved count, streak, best time); all stored locally in IndexedDB. Nothing leaves the device except the optional GitHub puzzle download.
  • Ships as a small static site behind nginx in Docker.

Running with Docker

docker compose up -d --build

Then open http://localhost:8282. To use a different port, edit the ports mapping in docker-compose.yml.

Without compose:

docker build -t nosku .
docker run -d -p 8282:8282 --name nosku nosku

Running without Docker

It's a static site, and any static file server works:

cd public
python3 -m http.server 8282

The service worker requires either http://localhost or https:// to register (browsers block SW registration on plain http://<lan-ip>), so for real offline testing on another device, put it behind HTTPS or use localhost port-forwarding.

Installing as an app

Open the site in Chrome/Edge/Safari and use the "Download app" button in Settings, or the browser's own "Install app" / "Add to Home Screen" option. Once installed, the whole game (board, all 200 puzzles, stats) works with airplane mode on, as a real app in your app drawer/home screen, not just a bookmark.

This requires HTTPS. Browsers only offer a true install (and only run the service worker that makes offline play possible) over a secure context: https:// or http://localhost. Plain http:// on a LAN IP or hostname (e.g. http://192.168.1.50:8282 or http://mele:8282) will never show a real install prompt; "Add to Home Screen" in that case just creates a plain browser-shortcut icon that still needs the site online to open, with no offline support. If the "Download app" button says it can't offer a one-tap install, this is almost always why.

A few practical ways to get HTTPS:

  • GitHub Pages (easiest, and free): this repo includes a .github/workflows/pages.yml workflow that publishes public/ straight to GitHub Pages on every push to main. In the repo's Settings → Pages, set Source to "GitHub Actions" (not "Deploy from a branch", since that option can't point at the public/ subfolder). Push once, and the app is live at https://<username>.github.io/<repo>/ with a real, trusted certificate, no server of your own required at all. This is a separate deployment from the Docker one; run either, or both.
  • Tailscale Serve: if you're already using Tailscale, tailscale serve --bg 8282 gives you a real https://<device>.<tailnet>.ts.net URL with a trusted certificate, reachable from your phone with zero extra config.
  • A reverse proxy with a domain: point a domain (even a free DDNS one) at your server and put Caddy or nginx-proxy-manager in front of this container; both provision Let's Encrypt certificates automatically.
  • Quick local testing only: on the phone you're testing with, you can add http://<your-ip>:8282 to Chrome's chrome://flags/#unsafely-treat-insecure-origin-as-secure flag. This tricks that one browser into treating the origin as secure, which is fine for trying things out, but it's a per-device hack, not a real fix, and won't help anyone else who opens the link.

Downloading more puzzles

From the Library screen, pick a difficulty tab and tap "Download 25 more from GitHub". This fetches a random slice of that difficulty's file directly from raw.githubusercontent.com/grantm/sudoku-exchange-puzzle-bank (using HTTP range requests so it never pulls the whole 10–35MB file), solves each candidate puzzle locally, skips anything you already have, and saves the rest to IndexedDB. This is the one feature that needs a live connection; everything else keeps working offline whether or not you ever use it.

Regenerating the bundled puzzles

public/data/puzzles.json was generated from the same puzzle bank with gen_puzzles.py: it samples 50 random puzzles per difficulty from easy.txt / medium.txt / hard.txt / diabolical.txt, solves each with a backtracking solver, and writes out { puzzle, solution, rating, difficulty, id } records. Re-run it against a fresh clone of the puzzle bank to reshuffle the bundled set.

Project layout

sudoku-pwa/
├── Dockerfile
├── docker-compose.yml
├── nginx.conf
├── gen_puzzles.py            # (re)generates public/data/puzzles.json
└── public/                   # everything served by nginx
    ├── index.html
    ├── manifest.json
    ├── service-worker.js
    ├── css/style.css
    ├── js/
    │   ├── sudoku-engine.js  # validation + backtracking solver
    │   ├── db.js             # IndexedDB wrapper
    │   └── app.js            # UI + game logic
    ├── data/puzzles.json     # 200 bundled puzzles
    └── icons/                # generated PWA icon set

Working on this project? See DEVNOTES.md first, it covers several non-obvious decisions (especially around the board/number-pad sizing) that are easy to accidentally undo.

Credits

The 200 bundled puzzles, and the optional in-app "download more" puzzles, come from the sudoku-exchange-puzzle-bank dataset by grantm, generated for the Sudoku Exchange website. The puzzles themselves were generated with QQWing Sudoku by Stephen Ostermiller, and graded for difficulty with Sukaku Explainer. Huge thanks to all three projects; this app wouldn't have a puzzle library without them.

License

This project is licensed under the MIT License.

The bundled puzzle data derives from the sudoku-exchange-puzzle-bank dataset (see Credits above), which is dedicated to the public domain by its author.

About

A free, open-source, offline-first Sudoku PWA. No ads, no tracking, no accounts - just 200+ puzzles and your phone.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages