Visualiser for Solitaire game graphs
  • C++ 82.8%
  • CMake 9.7%
  • JavaScript 5.3%
  • HTML 0.8%
  • CSS 0.7%
  • Other 0.7%
Find a file
benmandrew 5e601b710c
build(docker): run images unprivileged and fix latent build defects
Both images ran as root. The graph image now creates a rose system
account and chowns /app/shared before the VOLUME declaration, so an
anonymous volume inherits that ownership; the web image hands
/usr/src/app to the node account the base image already ships.

Three latent defects surfaced alongside. The builder stage had no
WORKDIR, so sources landed in / and cmake's output came out at /build
by accident of that default, which the runtime COPY then depended on.
Adding a WORKDIR later would have moved the binary and broken that
COPY silently, so it is now pinned to /src and referenced explicitly.

set -e does not cover a failure on the left of a pipe, so a failed
fetch of the LLVM signing key wrote an empty keyring and surfaced
later as an opaque apt error. The builder stage now runs under
bash -o pipefail.

The web entrypoint was npx live-server, but live-server was never a
declared dependency, so npx fetched it from the registry on every
container start. It is installed once at build time now, pinned to
1.2.2, and invoked directly. A global install rather than an entry in
web/package.json: live-server is unmaintained and would have taken
that lockfile from 175 packages to 360.
2026-07-26 22:23:33 +01:00
.github/workflows build: add Nix flake devShell and wire CI to use it 2026-07-01 20:39:34 +01:00
doc Round edges of README screenshot 2025-12-01 20:06:07 +00:00
docker build(docker): run images unprivileged and fix latent build defects 2026-07-26 22:23:33 +01:00
include perf(graph): replace hash-ordered std::set with unordered_set 2026-06-13 18:48:18 +01:00
src fix: correct foundation push in waste/tableau_to_foundation 2026-06-13 23:06:12 +01:00
tests build: scope strict warnings to our own targets 2026-06-13 23:13:12 +01:00
web fix(web): stack graph/sidebar layout responsively on mobile 2026-07-03 00:30:30 +01:00
.clang-format Initial 2025-10-13 01:12:43 +01:00
.dockerignore Improve .dockerignore 2025-12-06 12:55:59 +00:00
.gitignore chore: gitignore build-prof/ profiling build directory 2026-06-13 19:35:25 +01:00
CMakeLists.txt build: scope strict warnings to our own targets 2026-06-13 23:13:12 +01:00
CMakePresets.json build: add CMake presets and multi-compiler CI matrix 2026-06-13 23:04:17 +01:00
CPPLINT.cfg Remove unnecessary lint exceptions 2025-11-30 21:41:34 +00:00
docker-compose.yml docs: document ALGORITHM option and wire it into Docker 2026-06-13 20:16:24 +01:00
flake.lock build: add Nix flake devShell and wire CI to use it 2026-07-01 20:39:34 +01:00
flake.nix build: add Nix flake devShell and wire CI to use it 2026-07-01 20:39:34 +01:00
LICENCE Licence 2025-10-18 13:21:31 +01:00
PLAN.md feat(search): add A* and best-first search with state heuristics 2026-06-13 16:50:12 +01:00
PROFILING.md docs: add PROFILING.md guide for finding hot codepaths 2026-06-13 18:49:19 +01:00
README.md build: add Nix flake devShell and wire CI to use it 2026-07-01 20:39:34 +01:00

Rose

Visualiser for Solitaire game graphs. You can interact with it online here.

Screenshot

Run with Docker

Set the timeout to whatever you wish; the performance of the webpage is proportional to the graph size, so if it's too slow, drop this lower to get a smaller graph.

BFS_TIMEOUT_S=0.1 docker compose up

Access the web app on http://localhost:8080.

By default, the graph will be explored by a breadth-first search (BFS) until the timeout. Alternatively, by setting WITH_DFS=true as below, it will first explore down a single path, depth-first, using a simple heuristic for the best move in each state. Once no more moves can be made, the BFS is done from every node in the path. Using this heuristic makes it possible to find the winning state, which will be labelled if found.

WITH_DFS=true BFS_TIMEOUT_S=0.1 docker compose up

Search algorithm

Set ALGORITHM to choose how the graph is explored (within the same depth/timeout budget):

  • bfs (default) — breadth-first search; explores every state level by level.
  • bestfirst — greedy best-first search ordered by a state heuristic (foundation progress, face-down cards, blocked cards, and free columns), biasing exploration toward promising states.
  • astar — A* search ordered by depth plus an admissible foundation-distance heuristic, reaching winning states along shorter paths first.
ALGORITHM=astar BFS_TIMEOUT_S=0.1 docker compose up

WITH_DFS=true takes precedence over ALGORITHM. All three algorithms build the complete state graph for visualisation; they differ only in which states they expand first when the budget is limited.

Build locally

A flake.nix provides a dev shell with every tool the build needs (cmake, clang, cppcheck, cpplint, node/npm, fmt), pinned via flake.lock for reproducibility.

$ nix develop

Run any of the commands below inside that shell.

Without Nix

Depends on:

  • cmake
  • a C++23 compiler (gcc or clang)
  • npm
  • cpplint and cppcheck (for the lint target)
  • clang-format (for the format target)

Build

$ mkdir build
$ cd build
$ cmake ..
$ cmake --build .

Or with CMake presets (also used by CI):

$ cmake --preset release
$ cmake --build --preset release

Run Tests

cmake --build . --target tests

Lint

cmake --build . --target lint

Format

cmake --build . --target format