Sanmill is a free, powerful N men's morris game with a Flutter GUI and a native Rust AI engine. It is distributed under the GNU Affero General Public License version 3 (AGPL v3), ensuring that it remains free software. Users can modify and redistribute the software, provided they adhere to the AGPL terms.
Nine men's morris is a strategy board game for two players dating back to the Roman Empire. The game is also known as nine-man morris, mill, mills, the mill game, merels, merrills, merelles, marelles, morelles, and ninepenny marl in English.
This distribution of Sanmill consists of the following files:
Readme.md: The file you are currently reading.Copying.txt: A text file containing the GNU Affero General Public License version 3.crates: The Rust/TGF workspace containing the rules engine, search code, FRB API crate, and headless CLI.src/ui/flutter_app: The Flutter frontend.scripts: Build, test, and repository maintenance scripts.
Sanmill's supported frontend is the Flutter app. Debugging and headless
engine work use the Rust CLI in crates/tgf-cli.
bash scripts/build_console_engine.shWhen reporting an issue or a bug, please provide information about the version and compiler used to create your executable. You can obtain this information by running:
target/release/tgf compilerTo build the Flutter app, run ./flutter-init.sh, and then use Android Studio or Visual Studio Code to open src/ui/flutter_app.
The script automatically provisions Flutter 3.44.6 in .tools/flutter when the required SDK
isn't already available on your system, ensuring the repository always uses the expected version.
We use compile-time environment configs to enable specific parts of the code:
testto prepare the app for Monkey and Appium testing. (References to external sites will be disabled.)dev_modeto show the developer mode without needing to enable it first.catcherto control the use of Catcher. (Enabled by default; disable if necessary.)
All environment configs can be combined and take boolean values, like:
flutter run --dart-define catcher=false dev_mode=trueLaunch configurations for Android Studio or Visual Studio Code are available. Select the needed one in the Run and Debug or Run/Debug Configurations tab.
Sanmill is being migrated toward TGF (TabletopGameFramework), a Rust + FRB
framework shared by Mill and future tabletop games. The Rust workspace lives
under crates/:
cargo test --workspace
cargo clippy --workspace --all-targets --all-features -- -D warningsMill rules and search are fully implemented in Rust (crates/tgf-mill,
crates/tgf-search). The AI uses PVS with aspiration windows, null-move
pruning, MTD(f), MCTS, and iterative deepening. The perfect database is kept
as a vendored C++ library behind the Rust perfect-db wrapper.
The Flutter bridge is generated by flutter_rust_bridge (FRB). ./flutter-init.sh
will build the Rust workspace and run FRB code generation when cargo and
flutter_rust_bridge_codegen are available. You can also run code generation
manually:
cd src/ui/flutter_app
flutter_rust_bridge_codegen generateRust performance smoke results use the same TOML schema as
tests/perf_baseline.toml:
python scripts/record_rust_perf_baseline.py --output target/tgf_perf_result.toml
python scripts/check_perf_baseline.py --baseline tests/perf_baseline.toml --result target/tgf_perf_result.tomlFor fixed-depth search optimization A/B measurements, including node-count
parity requirements, see docs/FRAMEWORK_API.md.
For the framework API contract, see docs/FRAMEWORK_API.md.
Sanmill's improvements have been a community effort. You can contribute in several ways:
- Sanmill Wiki: Contains explanations of techniques used in Sanmill, with background information.
- GitHub Repository: The latest source can always be found here.
- Discussions: Join discussions about Sanmill.
Sanmill is distributed under the GNU Affero General Public License version 3 (AGPL v3). This allows you to use, modify, and distribute the software, provided you include the full source code or a pointer to where the source can be found. Any changes to the source code must also be made available under the AGPL.
For full details, see the AGPL v3 in the Copying.txt file.
Note on App Store Distribution: As an additional permission under section 7 of the AGPL v3, you are allowed to distribute the software through app stores, even if they have restrictive terms that are incompatible with the AGPL. However, the source code must also be available under the AGPL, either through the app store or another channel without those restrictive terms.
All unofficial builds and forks of the app must be clearly labeled as unofficial (e.g., "Sanmill UNOFFICIAL") or use a different name altogether. They must use a different application ID to avoid conflicts with official releases.
Sanmill collects non-sensitive crash information to help improve the software. The information collected may include:
- Device type and operating system version
- The actions leading up to the crash
- The crash error message
Users can review the crash report contents before sending. No personally identifiable information (PII) is collected, and all data is anonymized to ensure user privacy. Users can choose not to send crash reports if they prefer.
This data is used solely for improving the quality and stability of Sanmill and is not shared with any third parties.
Sanmill is free software, and we emphasize the importance of free software as a matter of freedom. We encourage the use of AGPL v3 or later as a license for contributions and discourage the use of non-free licenses.
AI Game Analysis is an optional, 18+ feature that is disabled by default and
restricted to fixed game-analysis tasks. It is not a person, may be inaccurate,
and is not professional advice. Sanmill does not provide a hosted inference
service. Desktop users may use a loopback-only Ollama installation with the
mandatory gpt-oss-safeguard:20b classifier, or users may choose an HTTPS proxy
whose operator and privacy policy are shown before consent.
Each analysis request contains only the selected task, app language, game variant and rules, FEN position, side and phase, piece counts, and bounded move notations. It excludes player names, comments, device identifiers, clipboard contents, and arbitrary chat text. Proxy credentials use platform secure storage; browser credentials are session-only. Changing the endpoint, model, operator, privacy URL, protocol, or consent notice requires fresh consent.
AI-answer reports are submitted in-app. Reports contain metadata only unless the user checks an unchecked option and reviews or redacts the answer. The EU D1 relay stores raw reports for 30 days, keeps de-identified category totals for 12 months, never stores raw IP addresses, and issues a deletion token.
Official releases must keep SANMILL_ENABLE_AI_ANALYSIS=false unless all of
the following are complete: deploy both reference Workers; create the report
D1 database with wrangler d1 create sanmill-ai-reports-eu --jurisdiction=eu;
configure Worker secrets and disable body logs; complete provider DPA/transfer
safeguards; obtain counsel review of the EU AI Act/GDPR notice and Article 27
representative assessment; update Apple and Google privacy/data-safety and AI
content declarations; and verify in-app reporting and deletion. After approval,
build with both --dart-define=SANMILL_ENABLE_AI_ANALYSIS=true and an HTTPS
--dart-define=SANMILL_AI_REPORT_RELAY_URL=... value. The compile-time gate
fails closed when either value is missing.
For the inference proxy, set OPENAI_API_KEY as a Worker secret and optionally
set ACCESS_TOKEN; do not place either in wrangler.jsonc. For the report
relay, replace the D1 database ID, apply migrations/0001_reports.sql, and set
RATE_LIMIT_SECRET as a Worker secret. Run pnpm run typecheck and
pnpm test in each service directory before deployment. Cloudflare's
--jurisdiction=eu constraint, rather than a Western Europe location hint, is
required because only the jurisdiction setting guarantees that D1 runs and
stores data within the EU.

