### ### ### #### ###
# # # # # # # #
## ##### ### ### ###
# # # # # # #
### # # # #### # #
A Minesweeper clone for the terminal, built specifically to be played by touch on an Android tablet running Termux — no mouse, no keyboard required, just taps.
Written in Go with Bubble Tea, rendered entirely in ANSI box-drawing and 256-color, with a hybrid tap-and-button control scheme designed around one hard constraint: a terminal character cell is too small to reliably hit with a finger.
Terminal apps assume a keyboard. Touchscreens send taps, which a terminal emulator can forward as mouse events (xterm mouse reporting) — but a single character cell on a tablet screen is a few millimeters wide, far smaller than a fingertip. Saper's UI is built around that constraint from the ground up:
- Tapping a board cell only moves the selection cursor. It never fires an action by itself, so a slightly-off tap can't accidentally detonate a mine.
- Every action goes through a large, explicitly labeled, colored button (
ОТКРЫТЬ/ФЛАГ/CHORD), not a tiny glyph. - Keyboard input (arrows/hjkl, enter, f, c, q) works everywhere too, as a fallback and for testing on a desktop terminal.
- Classic rules: safe first click, flood-fill reveal, flagging, and chording (opening all neighbors of a satisfied number at once).
- Three presets (Beginner / Intermediate / Expert) plus a custom board size screen with +/- steppers.
- Save & resume: progress is written to disk after every move; closing the app mid-game and reopening it offers to continue exactly where you left off, with the timer picking back up rather than jumping forward.
- Statistics screen: games played, win %, best time, average time, and current/best win streak, per difficulty.
- Settings screen: toggle sound (terminal bell) and vibration (via
termux-vibrate, when reachable) independently, plus a high-contrast color theme. - Animated title screen: a big block-letter "SAPER" banner with a rainbow color wave and a blinking prompt, in the spirit of old console game intros.
- Every screen is a single pure function from state to
(lines, tap-targets), so what you see and what you can tap are always in sync by construction — see Architecture below.
| Keyboard | Touch / mouse | |
|---|---|---|
| Move selection | Arrow keys / h j k l |
Tap a cell |
| Reveal | Enter / Space |
Tap the ОТКРЫТЬ button |
| Flag | f |
Tap the ФЛАГ button |
| Chord | c |
Tap the CHORD button |
| Back to menu | q / n |
Tap the МЕНЮ button |
| Quit | Ctrl+C |
— |
- Go 1.25 or newer (the version declared in
go.mod). - A terminal emulator with mouse-reporting support if you want touch/mouse input (Termux on Android works out of the box; most desktop terminals do too).
git clone https://github.com/pashki975/saper.git
cd saper
./build.sh # runs `go vet` + `go test`, then builds ./saperor manually:
go build -o saper ./cmd/saper./saper./clean.sh # removes the ./saper binary and Go's build artifactsgo test ./...saper/
├── build.sh # vet + test + build → ./saper
├── clean.sh # removes build output
├── LICENSE
├── go.mod / go.sum
│
├── cmd/saper/
│ └── main.go # entry point: wires internal/ui into a Bubble Tea program
│
└── internal/
├── game/ # pure game engine — no I/O, no UI dependency
│ ├── board.go # board generation, reveal/flood-fill, flag, chord, win/loss
│ ├── difficulty.go # the three presets + custom-difficulty validation
│ └── board_test.go
│
├── store/ # persistence — everything under ~/.config/minesweeper-tui/
│ ├── store.go # records.json: per-difficulty stats (win%, avg time, streaks)
│ ├── session.go # session.json: the save/resume snapshot of an in-progress game
│ ├── settings.go # settings.json: sound/vibration/theme toggles
│ └── *_test.go
│
├── feedback/ # best-effort haptic/audio cues (never a hard dependency)
│ └── feedback.go # terminal bell + termux-vibrate, both gated by settings
│
└── ui/ # the Bubble Tea model — one file per screen
├── model.go # Model struct, Init/Update/View, mouse hit-testing, game actions
├── splash.go # animated title screen
├── menu.go # difficulty picker + resume/stats/settings entries
├── custom.go # custom board size screen
├── game.go # the board itself: grid, HUD, action buttons
├── stats.go # statistics screen
├── settings.go # settings screen
├── style.go # the color palette (default + high-contrast theme)
└── widgets.go # shared rendering helpers (borders, tables, button rows)
internal/gameis a dependency-free engine:Boardholds all state as exported fields (so it can be JSON-persisted verbatim for save/resume), and every rule — safe first click, iterative flood fill, chording, win detection — is unit tested in isolation from any UI concern.internal/uiis a single Bubble TeaModelshared across screens (splash, menu, custom, game, stats, settings). Each screen implements a*Render() ([]string, []buttonRect)function that is the single source of truth for both what gets drawn (View()) and what can be tapped (layoutButtons()) — the two can never disagree, because they're the same computation.internal/storepersists three independent JSON documents (records, session, settings) under the user's XDG config directory, with no shared schema coupling — each survives the others changing shape.internal/feedbackwraps sound/vibration behind settings-driven toggles and fails silently if the mechanism isn't available (e.g.termux-vibrateunreachable from inside aproot-distrocontainer) — feedback is cosmetic, never load-bearing.
Saper was built and tested inside Termux — including running a full Linux distribution via proot-distro (e.g. Ubuntu) inside Termux, which works transparently since it's just a regular Go binary underneath. Enable mouse reporting in your terminal if taps aren't registering; Termux does this automatically for apps that request it (which Saper does via Bubble Tea's WithMouseCellMotion).
Player data (best times, statistics, an in-progress game, and settings) lives entirely under ~/.config/minesweeper-tui/ as three small JSON files — nothing is written anywhere else.