7.8 KiB
Play Minesweeper Online for Free
Play it here: mnswpr.com. This is the classic game Minesweeper built with vanilla web technologies (i.e., no framework dependency).
How to Play
The goal is to reveal every safe cell without detonating a mine. Your first click is always safe.
- Left click — reveal a cell
- Right click — flag / unflag a suspected mine
- Left + right click together (chording) — reveal the neighbors of a satisfied number
- Touch — tap to reveal, long-press to flag
Ways to Use
The web is a wonderful, free, and open platform to create and distribute value. You can use mnswpr in different ways:
Using it as a library takes only a few lines — mount it onto any element by id:
import '@ayo-run/mnswpr/mnswpr.css'
import mnswpr from '@ayo-run/mnswpr'
const game = new mnswpr('app')
game.initialize()
Tooling
The project has gone through years of existence. It started from 2019 when tooling was massively different. I have modernized it since and have witnessed how much easier and faster it is to build now - even without web frameworks or LLMs!
As of now the tooling I use are:
- Vite for bundling and development server
- Eslint for JS linting & CSS linting
- ESLint Stylistic for JS formatting
- Husky for git hooks
- PNPM for dependency & workspace management
- and a bunch of automation using scripts and Continuous Integration actions
Because a big part of this project's purpose is to track how the software development industry evolves — and because it has come a long way in modernizing along the way — I now also use it as a playground for coding agents. It's a small, framework-free, well-scoped codebase, which makes it a great sandbox to see how AI agents read, reason about, and change real code. To help them get their bearings quickly, the repo ships an AGENTS.md describing the architecture and conventions.
Development
Technology Stack: HTML, JS, and CSS; Google Firebase for leader board store; Netlify for hosting
To start development, you need node. I highly recommend pnpm to be used as well. Once you know you have this, you can do the following:
- Install dependencies:
pnpm i - Start the dev server:
pnpm -F mnswpr run dev
The rest of the everyday commands:
pnpm test # run the Vitest suite (workspace-wide)
pnpm lint # ESLint (JS + CSS)
pnpm lint:fix # ESLint with autofix
pnpm -F mnswpr run build # build the website
pnpm build:lib # build the publishable library
Leaderboard (local Firestore emulator)
The leader board is backed by Google Firestore. For local development the app talks to the Firestore emulator by default — fully local, no cloud, no deploy. The flag VITE_FIRESTORE_EMULATOR=1 is already set in app/.env.development.
dev is the whole loop in one command — it wraps Vite in firebase emulators:exec, so the Firestore emulator (on :8080, + UI) comes up, gets seeded with sample scores automatically, and the app dev server starts against it; everything shuts down when you stop it. (Each dev starts a fresh in-memory emulator, so the auto-seed writes exactly one clean set every time — no accumulation.) firebase-tools is a pinned devDependency of this app (installed by pnpm install, invoked as the firebase binary), so the only extra prerequisite is Java — the Firestore emulator is a Java program (java -jar cloud-firestore-emulator-*.jar), and firebase-tools does not bundle a JRE.
Usually automatic. pnpm install runs a root postinstall (scripts/ensure-java.mjs) that installs a user-local Temurin JRE 21 into ~/.local (no sudo) when java isn't already on your PATH. It's idempotent and never fails the install, and it skips when CI or SKIP_JRE_SETUP=1 is set, or on unsupported platforms.
If that skipped and you need Java (or prefer a system-wide install), do it manually — install a JRE (Java 11+; 21 recommended):
# Debian / Ubuntu / Pop!_OS
sudo apt update && sudo apt install -y openjdk-21-jre-headless
# Fedora
sudo dnf install -y java-21-openjdk-headless
# macOS (Homebrew)
brew install openjdk@21
java -version # verify: should print "openjdk 21.x" (or 11+)
No sudo? Install a JRE into your home directory instead (no root needed):
curl -fsSL -o /tmp/jre21.tgz "https://api.adoptium.net/v3/binary/latest/21/ga/linux/x64/jre/hotspot/normal/eclipse"
mkdir -p ~/.local/lib && tar xzf /tmp/jre21.tgz -C ~/.local/lib
ln -sf ~/.local/lib/jdk-21*-jre/bin/java ~/.local/bin/java # ~/.local/bin is already on PATH
java -version
Without Java, dev and db:start fail with Could not spawn 'java -version'. Install it to a permanent location — a JRE unpacked under /tmp disappears when the OS cleans temp files. Then just:
pnpm -F mnswpr run dev # emulator (:8080 + UI) + auto-seed + app dev server — one command
That's the everyday loop. The other DB scripts are for when you want to run pieces separately:
pnpm -F mnswpr run db:start # emulator only (stays up across app restarts); pair with dev:no-db
pnpm -F mnswpr run db:seed # seed a separately-running emulator (what dev does for you)
pnpm -F mnswpr run db:stop # kill a stray/orphaned emulator holding :8080
To skip the emulator entirely — for quick UI-only work, or if you don't have a JDK — run pnpm -F mnswpr run dev:no-db (plain Vite) and set VITE_FIRESTORE_EMULATOR= (empty) in a local, gitignored app/.env.local; the app then uses the cloud mw-test namespace instead.
See docs/firebase-leaderboards.md for the full data model, security rules, environments, and deployment.
Contributing
Contributions are welcome! See AGENTS.md for the architecture, conventions, and release workflow before opening a pull request.
You just want to play?
👉 The live site is here: mnswpr.com
Background
One day, while working in my home office, I heard loud and fast mouse clicks coming from our bedroom. It's my wife, playing her favorite game (Minesweeper) on a crappy website full of advertisements.
I can't allow this, it's a security issue. 🤣
But it is also an opportunity.
I wanted to give her the same game, with a similar leader board she can dominate. And this is also a chance for me to dig deeper into vanilla JS.
Can I make a page with complex interactions (more on this later) without any library dependency?
What I have learned:
- JS is awesome ✨
- We don't always need JS frameworks (or TS) ✨
- Even subtle UI changes can improve user gameplay experience ✨
- There's more ways to break your app than you are initially aware of ✨
- Competition motivates users to use your app more ✨
- Hash in bundled filenames helps avoid issues with browser caching (when shipping versions fast) ✨
License
Just keep building.
A project by Ayo
