feat: add rules crate with standard-mode Battlesnake rules#85
feat: add rules crate with standard-mode Battlesnake rules#85byte-the-bot wants to merge 5 commits intomainfrom
Conversation
Port the complete standard-mode rules from BattlesnakeOfficial/rules (Go) to a new standalone Rust crate. Implements all rule steps: snake movement, health reduction, hazard damage, food consumption, mid-game food spawning, elimination (out-of-bounds, out-of-health, self-collision, body collision, head-to-head), game-over detection, and initial board setup (fixed spawn positions, initial food placement). Includes 43 tests ported from the Go test suite covering all rule steps, board setup, edge cases (stacked hazards, eating on last move, head-to-head on food, small board detection), and food spawning probability. The crate is self-contained with no dependency on arena server types, and includes an optional wire-types feature for conversion to/from battlesnake-game-types wire representation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
coreyja
left a comment
There was a problem hiding this comment.
I do want to plug this into the server and start using it
We can also drop all the stuff about Battlesnake game types. We don't need to keep compatibility with that since we are replacing it. Clean break
|
Done — both items addressed in d46f068: Clean break from
Integrated rules crate into the server:
|
Remove the wire-types feature and all battlesnake-game-types compatibility from the rules crate (delete convert.rs, remove optional dependency). Integrate the rules crate as the game engine for the server: - engine/mod.rs: Use rules::BoardState and rules::standard::* pipeline instead of reimplementing game rules with wire_representation types - wire.rs: Convert from rules types instead of battlesnake-game-types - snake_client.rs: Use rules::Direction instead of Move - engine/frame.rs: Use rules types for frame generation - game_runner.rs: Use new engine types and proper elimination tracking Key behavioral improvement: The rules crate correctly implements the official Battlesnake rule that feeding happens before elimination, so a snake with health=1 reaching food survives (matching the Go reference). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
d46f068 to
9490fb3
Compare
The Dockerfile was only copying server/src but not rules/src, causing the build to fail with unresolved import errors since the server now depends on the local rules crate. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
coreyja
left a comment
There was a problem hiding this comment.
Lets refactor the tests to be more Rust-y plz
rules/src/tests/mod.rs
Outdated
| mod board_tests; | ||
| mod standard_tests; |
There was a problem hiding this comment.
I don't like how these tests are organized...
Can we make this more Rust idiomatic? Can we have the unit tests in the files where they are most relevant. Or these should be integration tests if we don't want them inline with the modules
There was a problem hiding this comment.
Done! Moved all tests inline with their modules:
- Board setup tests →
board.rs - Standard rule pipeline tests →
standard.rs - Food spawning tests →
food.rs - Point/distance tests →
types.rs
Shared test helpers (make_snake, make_board) live in types::test_helpers so all modules can use them without duplication. The separate tests/ directory is removed. All 43 tests still pass.
Move tests from the separate `rules/src/tests/` directory into idiomatic `#[cfg(test)] mod tests` blocks within each source file: - Board setup tests -> board.rs - Standard rule pipeline tests -> standard.rs - Food spawning tests -> food.rs - Point/distance tests -> types.rs Shared test helpers (make_snake, make_board) live in types::test_helpers, accessible to all modules' test blocks. All 43 tests pass, clippy and fmt clean. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Review Brief
What changed and why
A new
rulesworkspace crate implements the complete Battlesnake standard-mode rules engine, ported from the official Go implementation (BattlesnakeOfficial/rules). It adds the two capabilities currently missing fromserver/src/engine/mod.rs: mid-game food spawning and hazard damage. This crate is the intended production replacement; the server integration (swapping outserver/src/engine/mod.rscallers) is a follow-up task and not in this PR.Architecture decisions and trade-offs
BoardState/Snake/Pointdefined in the crate; wire types lack elimination fields and carry irrelevant metadata. Conversion tobattlesnake-game-typesis behind an optionalwire-typesfeature flag.move_snakes,reduce_snake_health,damage_hazards,feed_snakes,eliminate_snakescalled in sequence; avoids Go's trait/plugin abstraction which only pays off for multiple game modes.Vec<Point>for snake body — O(n) insert/pop matches Go's slice semantics and simplifiesbody[1..]collision indexing;VecDequewould be faster but adds conversion friction. Fine for standard board sizes (≤ 121 cells).rand::Rngtrait parameter — lets tests useStdRng::seed_from_u64(42)for determinism; nothread_rng()inside the rules crate.hazard_damage_per_turndefaults to 14 — matches Go CLI default; existingserver/src/engine/mod.rsuses 15, a documented discrepancy. The arena can override viaStandardSettings.Risk assessment
server/code — existing engine is untouched. Adding"rules"to workspace members is the only workspace-level change."rules"fromCargo.tomlmembers and delete the crate directory.Spot-check suggestions
rules/src/standard.rs:damage_hazards— Verify it iterates ALL entries inboard.hazards(duplicates included for stacking), checks food per-entry, clamps health to[0, SNAKE_MAX_HEALTH]after EACH subtraction, eliminates withEliminationCause::Hazard(notOutOfHealth), and does NOT break the inner loop after elimination.rules/src/standard.rs:eliminate_snakes— Confirm Phase 1 (health/bounds) applies immediately in natural order; Phase 2 outer loop is also natural order but inner collision-attribution loops usesnake_indices_by_length(length-descending); all Phase 2 results are collected before any are applied (mutual elimination correctness).rules/src/board.rs:place_food_fixed— Verify small-board check is area-based (width * height < 121), the "away from center" filter uses strict axis ordering (not distance), and center food is always attempted in Phase 2.rules/src/food.rs:maybe_spawn_food— Check spawn formula:(100 - rng.gen_range(0..100)) < food_spawn_chance. At chance=100 this is 99% per roll (fails when RNG returns 0); test uses iteration count to handle this edge case robustly.rules/src/convert.rs:BoardState::from_wire_game— Note thatEliminationCausealways initializes toNotEliminatedon conversion from wire types, since wire types have no elimination concept. Callers converting mid-game wire state should be aware eliminated snakes are invisible to the rules crate.What the agent verified
cargo test -p rules— 43 tests passingcargo clippy -p rules --all-targets -- -D warnings— cleancargo fmt -p rules -- --check— cleancargo check --workspace— full workspace compilesSummary
rulesworkspace crate implementing all standard-mode Battlesnake rule steps ported from BattlesnakeOfficial/rules (Go)wire-typesfeature for conversion to/frombattlesnake-game-typeswire representationTest plan
cargo test -p rules— 43 tests passingcargo clippy -p rules --all-targets -- -D warnings— cleancargo fmt -p rules -- --check— cleancargo check --workspace— workspace compilesTask: BS-eb0ddcf28dd9484f
🤖 Generated with Claude Code