A relaxing mahjong solitaire matching game built with the
zigrok/MonoGame fork and the
zigrok/Forma UI toolkit.
The game uses a 2D presentation with 2.5D tiles, supports portrait and landscape layouts, and is designed for mouse, touch, keyboard and controller from the start. Desktop is the first target; native mobile is a later Forma workstream.
Status: desktop v1 is playable. The 12-level journey, hint/undo/shuffle,
mouse/touch/keyboard/gamepad input, save/resume and headless gameplay suite are
implemented. See docs/plan.md for the live checklist and the
post-v1 backlog.
make build
make run
make watch LEVEL=1 # watch the solver play a level in a real windowMouse: click or drag matching tiles. Keyboard: arrows/Tab to move, Enter to select, H hint, S shuffle, U undo, Esc pause. Gamepad: D-pad/left stick, A select, B back, X hint, Y shuffle, LB undo, Start pause. Touch uses the same pointer path as the mouse.
Initial dependency baseline: MonoGame 260e930, Forma 8827393, XamlX
0337e9b. The pinned Forma core and Forma.Xaml.Build compile cleanly against
the pinned MonoGame DesktopGL project.
- .NET SDK 10.0.100 or newer (see
global.json) - Git 2.40+
- Desktop OpenGL-capable machine for the default
DesktopGLbuild - Optional: GitHub CLI (
gh) for the submodule PR workflow - Optional: native MonoGame backend toolchains (Metal/Vulkan/DX12) only when building those backends
The MonoGame and Forma dependencies are Git submodules. Do not use
--recurse-submodules: MonoGame contains many native backend submodules that are
only needed for specific builds.
git clone https://github.com/zigrok/RokMahjong.git
cd RokMahjong
# Required: the game framework and the UI toolkit.
git submodule update --init third_party/MonoGame third_party/Forma
# Required by Forma's compiled FHTML/XAML pipeline.
git -C third_party/Forma submodule update --init external/XamlXOptional runtime SVG icons (only if the bitmap icon atlas is not enough):
git -C third_party/Forma submodule update --init external/ThorVGNative MonoGame backends initialize their own submodules on demand. For example,
the native Vulkan/Metal path needs SDL3, FAudio and SPIRV-Cross; see
third_party/MonoGame/README.md and BACKEND-DEPENDENCIES.md before building
those targets. The default DesktopGL build does not need them.
To inspect the exact dependency revisions:
git submodule status --recursive| Path | Purpose |
|---|---|
src/RokMahjong.Core |
Pure C# rules, layouts, deals, history, scoring |
src/RokMahjong.Game |
MonoGame + Forma client, board and screens |
src/RokMahjong.Desktop |
Desktop executable host |
tests/RokMahjong.Core.Tests |
Rules, layouts, solvability, 10k-seed replay |
tests/RokMahjong.Game.Tests |
Real input-router gameplay + Forma headless UI |
tools/RokMahjong.Playtest |
Headless level solver with JSON output |
tools/RokMahjong.TileArtGen |
SkiaSharp generator for reference art and the tile atlas |
tools/tile-pipeline |
roktiles CLI for AI face generation, history and selection |
tools/gallery.sh |
Windowed screenshot smoke check |
tools/plan-progress.sh |
Checklist progress reporter |
assets/art/ |
Tile atlas, body-less references and selected face art |
assets/fonts/ |
OFL CJK subset used only by the tile generator |
docs/ |
Plan, architecture, testing strategy, goal prompt and screenshots |
AGENTS.md |
Project rules and definition of done for coding agents |
third_party/ |
Pinned MonoGame and Forma submodules |
make submodules # initialize MonoGame, Forma and XamlX
make build # build the solution
make run # launch the desktop game
make test # core + game tests
make test-all # whole solution
make test-tiles # tile pipeline Python tests
make playtest LEVEL=1 DEVICE=mouse
make watch LEVEL=1 # auto-solve a level in a real window
make tiles # regenerate reference art and the tile atlas
make tilegen ARGS="status" # tile generation CLI (ComfyUI / OpenRouter)
make gallery # capture and validate screenshots
make check-submodules # verify pins are reachabledocs/plan.md is a live checklist (- [ ] open, - [~] in progress,
- [x] done).
make progress # overall progress
make progress-desktop # M0-M5 desktop progressThe desktop goal prompt for the installed /goal plugin is
docs/goal-desktop-v1.md. Paste the whole block into
the composer and send it; it already starts with /goal. Optionally run
/goal budget 120 first for a larger turn budget. The goal is complete only
when the plan’s M0–M5 phases and the Desktop v1 acceptance checklist are
fully checked and the reply includes the build, test, headless gameplay and
gallery evidence.
The test strategy is layered:
- Core and solvability: pure C# tests prove that every generated deal and shuffle carries a verified removal order that replays through the real game API. The smoke suite includes a 10,000-seed sweep.
- Headless UI and gameplay:
RokMahjong.Game.Testsdrives the realGameInputRouterandBoardControllerwith a scripted input source for all 12 levels and all four input devices, plus a FormaHeadlessInputUI flow test. The native MonoGamedesktopheadlessbackend is a post-v1 upgrade. - Visual and soak: the desktop host captures deterministic screenshots and
make galleryvalidates that every screen rendered real pixels.
make test-all
make playtest LEVEL=12 DEVICE=gamepad
make galleryRokMahjong.Playtest prints a JSON result with the level, device, status,
moves and score.
Full details: docs/testing.md.
The 42 tile faces are AI-generated and chosen through a local-first pipeline.
The generator emits body-less reference art for every face, and
tools/tile-pipeline sends it to ComfyUI (Qwen-Image-Edit 2511 by default) or
an optional OpenRouter image model. Every generation is kept with its prompt,
seed and parameters; selecting one updates the face art the game ships with.
The vector 2.5D body, bevel and shadow are always drawn by
tools/RokMahjong.TileArtGen, so tiles stay physically consistent.
make tilegen ARGS="doctor" # check ComfyUI + references
make tilegen ARGS="generate --face C1 --style porcelain" # generate one face
make tilegen ARGS="history --face C1" # list past generations
make tilegen ARGS="select <generation-id>" # apply + rebuild atlas
make tilegen ARGS="reset --face C1" # back to vector art
make tiles # rebuild atlas onlyHistory lives in artifacts/tile-pipeline/ (gitignored); only the selected
faces and their provenance manifest are committed. See
tools/tile-pipeline/README.md.
Changes to either dependency are developed on a feature branch inside the submodule, validated by the upstream repository's CI, merged through a pull request, and then pinned in this repository with a separate submodule-pointer commit.
See CONTRIBUTING.md for the full workflow and helper
scripts.
docs/plan.md— product, architecture, milestones, risks and live checklistdocs/architecture.md— Mermaid architecture diagrams and invariantsdocs/testing.md— test layers, solvability, headless gameplay, replays, CI gatesAGENTS.md— project rules and definition of done for coding agentsCONTRIBUTING.md— submodule branches, PRs, CI gatesdocs/goal-desktop-v1.md— prompt for the installed/goalplugin



