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 (Rule in C++, RuleSettings in 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)
- 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_RULES constant (to match new RULES[] length)
-
If new parameters needed, added new fields to Rule struct?
// e.g., if it was 11, now it should be 12 constexpr auto N_RULES = 12;
- Flutter UI Models
File: lib/rule_settings/models/rule_settings.dart
-
Added new rule variant value to RuleSet enum
-
Created new RuleSettings subclass (e.g., YourNewVariantRuleSettings )
-
All fields in new subclass match 1-to-1 with C++ Rule struct
-
Updated ruleSetDescriptions map with description
-
Updated ruleSetProperties map 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(/* ... */); }
- Flutter UI Selection Interface
File: lib/rule_settings/widgets/modals/rule_set_modal.dart
-
Added RadioListTile or similar UI component for selecting new rule
-
UI item correctly references new RuleSet enum value
-
If rule is experimental, marked as "Experimental" in rule_settings_page.dart ?
- 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_*.arb files - At least copied English version
-
Ran flutter gen-l10n to generate localization code
Key strings:
-
Rule name
-
Rule description
-
Any new UI labels or hints
Conditional Validation Checklist (Based on Mechanic Type)
- 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: 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 , _removePiece related logic
Important: User-visible game logic must be symmetrically implemented in both C++ and Dart. Move generation (movegen) is C++ only.
- 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?
- Mill Formation
File: src/mills.cpp
- If rule has non-standard mill formation, updated this file?
- Engine Options (If Added New Rule Fields)
File: lib/game_page/services/engine/engine.dart
-
Must modify: If Rule struct 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 Rule struct gained new fields
-
Added corresponding UCI option definitions
-
Added on_change handlers to apply values to global rule object
Example:
// ucioption.cpp {"YourNewOption", "false", "bool", {}, on_your_new_option}
void on_your_new_option(Option &o) { rule.yourNewField = o.get<bool>(); }
- 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
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)
- 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
- 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 test to ensure all tests pass
Run tests:
cd src make build all make test
- 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
- 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
- Code Formatting
Format all code
./format.sh
-
C++ code formatted (clang-format)
-
Dart code formatted (dart format)
- Localization Generation
cd src/ui/flutter_app flutter gen-l10n
- Generated localization code without errors
- 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
- Backward Compatibility
-
All existing rule variants remain unchanged
-
All old tests still pass
-
New features don't affect existing game logic (via conditional guards)
- Performance Parity
-
When new feature is off, no performance regression
-
Hot paths remain efficient (early exits)
-
Benchmarks show acceptable performance
- Code Symmetry
-
C++ and Dart user-visible logic fully symmetric
-
Same inputs produce same outputs
-
Tests verify symmetry
- 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)
- 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_RULES incremented
-
Flutter RuleSet enum and RuleSettings subclass 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 Rule fields 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.h and src/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 's setRuleOptions()
-
Run Flutter tests, ensure they pass
Phase 4: Localization (30 minutes)
-
Update intl_en.arb and intl_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_RULES in src/rule.h matches RULES[] length
C++ and Flutter fields don't match
-
Symptom: UI-set rule parameters don't take effect
-
Fix: Ensure RuleSettings fields match Rule struct 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.dart mirrors position.cpp logic
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.