sugarcraft/candy-mines

Minesweeper TUI — port of maxpaulus43/go-sweep on the SugarCraft stack.

Maintainers

Package info

github.com/sugarcraft/candy-mines

Homepage

Documentation

Type:project

pkg:composer/sugarcraft/candy-mines

Transparency log

Statistics

Installs: 0

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

dev-master 2026-07-12 22:06 UTC

This package is auto-updated.

Last update: 2026-07-13 00:24:33 UTC


README

candy-mines

CandyMines

CI codecov Packagist Version License PHP

demo

Minesweeper on the SugarCraft stack — port of maxpaulus43/go-sweep. Customisable board, recursive flood-fill, win / lose detection, vim-style movement.

Difficulty presets are available via Game::withDifficulty(), passing a Difficulty case — Difficulty::EASY (9×9, 10 mines), Difficulty::MEDIUM (16×16, 40 mines), Difficulty::EXPERT (30×16, 99 mines).

Run it

composer install
./bin/candy-mines                            # default 10×10, 12 mines
./bin/candy-mines [width] [height] [mines]   # custom board (validated: 2–50 per side, 1..w×h−9 mines)
./bin/candy-mines --easy | --medium | --expert

Custom board arguments are validated through CustomDifficulty::fromInput(): each side must be 2–50 and the mine count must leave a safe 3×3 first-click area (1 ≤ mines ≤ width × height − 9). Out-of-range values exit with an error rather than starting a broken game.

Stats persistence is opt-in. Set CANDY_MINES_STATS=/path/to/stats.json and each completed game's outcome (games, wins, best time per preset) is recorded there via DifficultyStats, surviving across sessions. Without the env var the game stays entirely in-memory.

Keys

Key Action
↑/↓/←/→ or hjkl Move cursor
Space / Enter Reveal cell
f Toggle flag
c / middle-click Chord — reveal safe neighbors
r Restart with new mines
q / Esc Quit

Architecture

Five pure-state classes plus the runtime Model, renderer, UI helper, and persistence:

File Role
Cell Value object — mine / revealed / flagged / adjacent count
Board The grid + every transition (reveal, flag, flood-fill, chord). Win detection is O(1) via revealedCount counter. Serialises to versioned JSON for mid-game save/load.
Game (Model) Cursor + key routing + restart + win/lose gate + sub-second timer (microtime(true)). Records the result into Stats on the win/lose transition; persists via DifficultyStats when an opt-in stats path is configured.
Stats Immutable difficulty stats — games, wins, best time per preset
DifficultyStats Atomic JSON persistence wrapper (tmp+rename, Homestead pattern)
Ui/CustomDifficulty Validated custom board dimensions — rows (2–50), cols (2–50), mines (1 to rows×cols−9). Throws i18n-aware InvalidArgumentException on constraint violation.
Renderer Pure view function. CandySprinkles Style + Border::rounded()

The first reveal is always safe — mines are placed only after click 1, with the clicked cell's 3×3 neighbourhood excluded so the player gets a non-trivial flood-fill on every game.

Chord click (c / middle-click): when a revealed number has exactly the right number of flagged neighbours, chord-clicking it safely reveals all remaining neighbours. This mirrors the standard left+right simultaneous press in classic minesweeper.

O(1) win detection: Board::isWon() compares revealedCount (the number of non-mine cells revealed) against width × height − mineCount. Win detection is constant-time regardless of board size — no full-grid scan on every move.

Save / restore mid-game: Board::serialize() produces a versioned JSON payload ({v:1, w, h, m, p, e, r, c}) covering every cell and board state. Board::unserialize() reconstructs an identical Board instance so sessions can be suspended and resumed.

Demos

Gameplay

play

Flagging

flagging

Shared foundations

The minefield renderer builds output via candy-buffer — each cell is zone-tagged via Mark::zone("cell:$row:$col", $glyph) so Scanner::hit($x, $y) maps directly to a cell address. Renderer::resolveClick() bridges mouse coordinates to row/col. Snapshot tests via candy-testing pin canonical game states (first-click safe area, cascade reveal, win overlay).

Test

composer install
vendor/bin/phpunit