New Rule Completeness Validator
Purpose
When adding a new game rule or rule variant to Sanmill, you need to modify multiple files (typically 70-80 files, including ~60 localization files). This skill provides a completeness checklist to ensure no necessary code changes are missed.
Reference: docs/guides/ADDING_NEW_GAME_RULES.md
Use Cases
- •Adding a new game rule variant
- •Adding new game mechanics to existing rules (e.g., new capture rules)
- •Modifying rule structure or parameters
- •Reviewing rule-related pull requests
Architecture Philosophy
Sanmill is configuration-based, not inheritance-based. Rule variants are expressed as data (
Rulein C++,RuleSettingsin Flutter) and toggled at runtime. Any new mechanics must be gated by booleans/params so existing variants remain untouched and fast.
Quick Overview
- •Estimated time: Simple parameter rule 2-4 hours; new mechanics 6-8 hours
- •Complexity: ⭐⭐⭐ Medium-High
- •Touched files: ~70-80 total (incl. ~60 ARB localization files)
Core Validation Checklist (Required Modifications)
1. C++ Engine Rule Definition
File: src/rule.cpp
- • Added new rule definition to the end of
RULES[]array - • Set all required fields:
- •
name- Rule name (max 32 chars) - •
description- Rule description (max 512 chars) - •
pieceCount,flyPieceCount,piecesAtLeastCount - •
hasDiagonalLines- Whether diagonal lines exist - • Mill formation action and phase flags
- • Capture rule configs (custodian, intervention, leap)
- • Flying, draw rules (
mayFly,nMoveRule, etc.)
- •
Example structure:
{
"Your Rule Name",
"Short description",
9, 3, 3, // pieceCount, flyPieceCount, piecesAtLeastCount
false, // hasDiagonalLines
MillFormationActionInPlacingPhase::removeOpponentsPieceFromBoard,
/*mayMoveInPlacingPhase*/ false,
/*isDefenderMoveFirst*/ false,
/*mayRemoveMultiple*/ false,
// ... other fields
kDefaultCaptureRuleConfig, // custodian
kDefaultCaptureRuleConfig, // intervention
kDefaultCaptureRuleConfig, // leap
/*mayFly*/ true,
/*nMoveRule*/ 100,
/*endgameNMoveRule*/ 100,
/*threefoldRepetitionRule*/ true
}
File: src/rule.h
- • Incremented
N_RULESconstant (to match new RULES[] length) - • If new parameters needed, added new fields to
Rulestruct?
// e.g., if it was 11, now it should be 12 constexpr auto N_RULES = 12;
2. Flutter UI Models
File: lib/rule_settings/models/rule_settings.dart
- • Added new rule variant value to
RuleSetenum - • Created new
RuleSettingssubclass (e.g.,YourNewVariantRuleSettings) - • All fields in new subclass match 1-to-1 with C++
Rulestruct - • Updated
ruleSetDescriptionsmap with description - • Updated
ruleSetPropertiesmap with default property instance
Example:
enum RuleSet {
// ... existing variants
yourNewVariant, // new
}
class YourNewVariantRuleSettings extends RuleSettings {
const YourNewVariantRuleSettings({
// All params must match C++ Rule fields
}) : super(/* ... */);
}
3. Flutter UI Selection Interface
File: lib/rule_settings/widgets/modals/rule_set_modal.dart
- • Added
RadioListTileor similar UI component for selecting new rule - • UI item correctly references new
RuleSetenum value - • If rule is experimental, marked as "Experimental" in
rule_settings_page.dart?
4. Localization (Internationalization)
Files: lib/l10n/intl_*.arb (~60 files)
- •
intl_en.arb- Added English translations - •
intl_zh_CN.arb- Added Simplified Chinese translations - • All other
intl_*.arbfiles - At least copied English version - • Ran
flutter gen-l10nto generate localization code
Key strings:
- •Rule name
- •Rule description
- •Any new UI labels or hints
Conditional Validation Checklist (Based on Mechanic Type)
5. Game Logic Modifications (If Gameplay Changed)
File: src/position.cpp (C++ side)
- • Must modify: If new rule changes placement, movement, or capture logic
- • Used conditional guards for performance:
cpp
if (!rule.yourFeature) return fastPath(); // new logic
- • Handled all edge cases
File: lib/game_page/services/engine/position.dart (Flutter side)
- • Must mirror logic from C++
position.cpp - • C++ and Dart game logic remain fully symmetric
- • Implemented
_putPiece,_movePiece,_removePiecerelated logic
Important: User-visible game logic must be symmetrically implemented in both C++ and Dart. Move generation (movegen) is C++ only.
6. Move Generation (C++ Only)
File: src/movegen.cpp
- • If custom movement patterns exist (e.g., special move rules), updated move generation logic?
- • Added conditional guards to avoid affecting other rules?
7. Mill Formation
File: src/mills.cpp
- • If rule has non-standard mill formation, updated this file?
8. Engine Options (If Added New Rule Fields)
File: lib/game_page/services/engine/engine.dart
- • Must modify: If
Rulestruct gained new fields - • Added logic in
setRuleOptions()method to send new parameters - • Parameter names match UCI option names
File: src/ucioption.cpp
- • Must modify: If
Rulestruct gained new fields - • Added corresponding UCI option definitions
- • Added
on_changehandlers to apply values to globalruleobject
Example:
// ucioption.cpp
{"YourNewOption", "false", "bool", {}, on_your_new_option}
void on_your_new_option(Option &o) {
rule.yourNewField = o.get<bool>();
}
9. FEN Format Extension (Only If Dynamic State Persistence Needed)
When to extend FEN: Only when you must persist dynamic state that cannot be recomputed from the board.
Need to extend if:
- • Need to track capture targets (custodian/intervention)
- • Need to persist temporary user choice (e.g.,
preferredRemoveTarget) - • Need to store intermediate multi-step state across moves/undo/search
- • Need history-dependent constraints (e.g., "same piece can't move twice")
Do NOT extend for:
- •✗ Static rule params (piece counts,
hasDiagonalLines, etc.) - •✗ Anything derivable from board (piece counts, mills)
- •✗ Global flags (
mayFly,mayRemoveMultiple)
Files: src/position.h, src/position.cpp
- • If FEN extension needed:
- • Added new fields in
position.h - • Added export logic in
fen()method - • Added parsing logic in
set()method - • Followed backward compatibility rules: new fields at end, missing fields have safe defaults
- • Added new fields in
File: lib/game_page/services/engine/position.dart
- • Mirrored C++ FEN fields
- • Implemented
_getFen()export logic - • Implemented
setFen()parsing logic
Files: tests/test_position.cpp, Flutter integration tests
- • Added FEN round-trip tests (export then reimport, state should match)
10. Evaluation and Search (Rare)
File: src/evaluate.cpp
- • If rule variant requires special evaluation logic, updated?
File: src/search.cpp
- • If rule variant requires special search logic, updated?
Testing Validation Checklist
11. C++ Tests
Files: tests/test_*.cpp
- • Rule loading and bounds tests (
set_rule(i)correctly loads new rule) - • Position/logic unit tests (including new mechanics)
- • If FEN extended, added FEN round-trip tests (
tests/test_position.cpp) - • Ran
make testto ensure all tests pass
Run tests:
cd src make build all make test
12. Flutter Tests
Widget tests
- • New rule displays correctly in modal
- • Selecting new rule correctly saves and restores
Engine mirror tests
- • Test scenarios cover
_putPiece,_movePiece,_removePiece - • C++ and Dart logic produce same results
Integration tests
- • Select new rule → start new game → verify behavior visible
Run tests:
cd src/ui/flutter_app flutter test
13. Performance Benchmarks
- • Ran benchmarks (if project has
make benchmark) - • Ensured no performance regression when new feature is off
- • Verified conditional guards enable early exits on hot paths
Build, Format, and Generate
14. Code Formatting
# Format all code ./format.sh
- • C++ code formatted (clang-format)
- • Dart code formatted (dart format)
15. Localization Generation
cd src/ui/flutter_app flutter gen-l10n
- • Generated localization code without errors
16. Full Build
# C++ build cd src make build all # Flutter build (pick a platform to test) cd ../src/ui/flutter_app flutter build apk # Android # or flutter build ios # iOS # or flutter build windows # Windows
- • All platforms build successfully
Quality & Safety Assurance Checklist
17. Backward Compatibility
- • All existing rule variants remain unchanged
- • All old tests still pass
- • New features don't affect existing game logic (via conditional guards)
18. Performance Parity
- • When new feature is off, no performance regression
- • Hot paths remain efficient (early exits)
- • Benchmarks show acceptable performance
19. Code Symmetry
- • C++ and Dart user-visible logic fully symmetric
- • Same inputs produce same outputs
- • Tests verify symmetry
20. Documentation and Comments
- • Clear docs and comments near each new flag
- • Complex logic has explanatory comments
- • Updated
docs/guides/ADDING_NEW_GAME_RULES.md(if needed)
21. Localization Completeness
- • At minimum, includes English (EN) and Simplified Chinese (ZH_CN)
- • Ideally, all ARB files updated
- • All translation strings meaningful with no placeholders
Complete QA Checklist (Summary)
Before submitting PR, confirm all of the following:
- •
RULES[]has new entry,N_RULESincremented - • Flutter
RuleSetenum andRuleSettingssubclass match C++ - • UI selection interface added, strings localized
- • Game logic uses conditional guards and early exits
- • C++ ↔ Dart position logic symmetric
- • FEN extended if necessary; round-trip tests added
- • If new
Rulefields added, engine options updated - • All tests pass (C++ and Flutter)
- • Benchmarks show no performance drop when feature off
- • Code formatted, builds succeed
- • Documentation and comments clear and complete
Validation Workflow
Phase 1: Planning (1 hour)
- •Determine specific requirements for rule variant
- •Determine if simple parameter adjustment or new mechanics needed
- •List files that need modification
Phase 2: C++ Implementation (2-3 hours)
- •Modify
src/rule.handsrc/rule.cpp - •If new mechanics needed, modify
src/position.cpp,src/movegen.cpp, etc. - •If new fields added, update
src/ucioption.cpp - •Run C++ tests, ensure they pass
Phase 3: Flutter Implementation (2-3 hours)
- •Modify
rule_settings.dart(enum + subclass + mappings) - •Modify
rule_set_modal.dart(UI selection) - •If game logic modified, mirror to
position.dart - •Update
engine.dart'ssetRuleOptions() - •Run Flutter tests, ensure they pass
Phase 4: Localization (30 minutes)
- •Update
intl_en.arbandintl_zh_CN.arb - •Batch update other ARB files (can use English initially)
- •Run
flutter gen-l10n
Phase 5: Testing & Validation (1-2 hours)
- •Run all C++ tests
- •Run all Flutter tests
- •Manually test new rule behavior in UI
- •Check performance benchmarks
- •Verify backward compatibility
Phase 6: Code Review & Documentation (30 minutes)
- •Run code formatting
- •Review all changes
- •Update docs and comments
- •Complete QA checklist
Common Pitfalls and Notes
❌ Common Mistakes
- •
Forgot to increment
N_RULES- •Symptom: Runtime crash or rule loading failure
- •Fix: Ensure
N_RULESinsrc/rule.hmatchesRULES[]length
- •
C++ and Flutter fields don't match
- •Symptom: UI-set rule parameters don't take effect
- •Fix: Ensure
RuleSettingsfields matchRulestruct 1-to-1
- •
Missing UCI options
- •Symptom: New Rule field values always default
- •Fix: Add corresponding options and handlers in
ucioption.cpp
- •
Didn't mirror game logic to Dart
- •Symptom: UI preview inconsistent with actual game
- •Fix: Ensure
position.dartmirrorsposition.cpplogic
- •
Incomplete localization
- •Symptom: Some languages show placeholders or English
- •Fix: At minimum complete English and Chinese, others can use English placeholder
- •
Performance regression
- •Symptom: Game slower even when not using new feature
- •Fix: Use conditional guards, ensure early exits
- •
Over-extended FEN
- •Symptom: FEN strings too long, hard to debug
- •Fix: Only extend FEN when must persist dynamic state
✓ Best Practices
- •Incremental development: Implement C++ first, test, then do Flutter
- •Frequent testing: Run relevant tests after each file modification
- •Use conditional guards: Ensure new code doesn't affect existing rules
- •Maintain symmetry: C++ and Dart logic should be readable side-by-side
- •Documentation first: Write comments and docs before code
- •Reference existing rules: Look at other rules in
RULES[]as templates
Reference Files and Resources
Core Documentation
- •
docs/guides/ADDING_NEW_GAME_RULES.md- Official guide for adding rules (must read)
C++ Files
- •
src/rule.h- Rule struct definition - •
src/rule.cpp- RULES[] array - •
src/position.h/.cpp- Game position and logic - •
src/movegen.cpp- Move generation - •
src/ucioption.cpp- UCI options - •
src/mills.cpp- Mill formation logic - •
include/config.h- Default config (rarely modified)
Flutter Files
- •
lib/rule_settings/models/rule_settings.dart- Rule models - •
lib/rule_settings/widgets/modals/rule_set_modal.dart- UI selection - •
lib/game_page/services/engine/position.dart- Position mirror - •
lib/game_page/services/engine/engine.dart- Engine communication - •
lib/l10n/intl_*.arb- Localization files
Test Files
- •
tests/test_*.cpp- C++ unit tests - •
test/- Flutter unit and widget tests - •
integration_test/- Flutter integration tests
Output Format
Validation results should be reported clearly:
✓ [Complete] Core File Modifications ✓ src/rule.cpp - RULES[] added ✓ src/rule.h - N_RULES incremented ✓ rule_settings.dart - RuleSet enum added ... ⚠ [Warning] Conditional File Modifications ✓ src/position.cpp - Logic updated ✗ position.dart - Logic not mirrored (needs fix) ... ✓ [Complete] Test Validation ✓ C++ tests all passed (25/25) ✓ Flutter tests all passed (42/42) ... ✗ [Failed] Localization ✓ intl_en.arb - Updated ✓ intl_zh_CN.arb - Updated ✗ Other ARB files - Not updated (needs completion) ... 📊 Completion: 75% (15/20 checks passed) 💡 Recommendation: Priority fix position.dart mirror and localization
Summary
Adding a new game rule is a systematic effort requiring careful coordination between the C++ engine and Flutter UI. Using this checklist ensures:
- •✓ No necessary file modifications are missed
- •✓ C++ and Flutter stay synchronized
- •✓ Backward compatibility is not broken
- •✓ Performance is not affected
- •✓ Test coverage is adequate
- •✓ Localization is complete
Remember: When in doubt, refer to docs/guides/ADDING_NEW_GAME_RULES.md and existing rule implementations.