Skip to content

feat: add rules crate with standard-mode Battlesnake rules#85

Open
byte-the-bot wants to merge 5 commits intomainfrom
implement/BS-eb0ddcf28dd9484f
Open

feat: add rules crate with standard-mode Battlesnake rules#85
byte-the-bot wants to merge 5 commits intomainfrom
implement/BS-eb0ddcf28dd9484f

Conversation

@byte-the-bot
Copy link
Copy Markdown
Collaborator

@byte-the-bot byte-the-bot commented Apr 10, 2026

Review Brief

What changed and why

A new rules workspace crate implements the complete Battlesnake standard-mode rules engine, ported from the official Go implementation (BattlesnakeOfficial/rules). It adds the two capabilities currently missing from server/src/engine/mod.rs: mid-game food spawning and hazard damage. This crate is the intended production replacement; the server integration (swapping out server/src/engine/mod.rs callers) is a follow-up task and not in this PR.

Architecture decisions and trade-offs

  • Own types instead of wire typesBoardState/Snake/Point defined in the crate; wire types lack elimination fields and carry irrelevant metadata. Conversion to battlesnake-game-types is behind an optional wire-types feature flag.
  • Flat pipeline functionsmove_snakes, reduce_snake_health, damage_hazards, feed_snakes, eliminate_snakes called 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 simplifies body[1..] collision indexing; VecDeque would be faster but adds conversion friction. Fine for standard board sizes (≤ 121 cells).
  • rand::Rng trait parameter — lets tests use StdRng::seed_from_u64(42) for determinism; no thread_rng() inside the rules crate.
  • hazard_damage_per_turn defaults to 14 — matches Go CLI default; existing server/src/engine/mod.rs uses 15, a documented discrepancy. The arena can override via StandardSettings.

Risk assessment

  • Blast radius: New standalone crate only. No changes to server/ code — existing engine is untouched. Adding "rules" to workspace members is the only workspace-level change.
  • Confidence level: High — 43 Go tests ported, all passing; clippy and fmt clean.
  • Rollback safety: Clean — remove "rules" from Cargo.toml members and delete the crate directory.

Spot-check suggestions

  • rules/src/standard.rs:damage_hazards — Verify it iterates ALL entries in board.hazards (duplicates included for stacking), checks food per-entry, clamps health to [0, SNAKE_MAX_HEALTH] after EACH subtraction, eliminates with EliminationCause::Hazard (not OutOfHealth), 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 use snake_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 that EliminationCause always initializes to NotEliminated on 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 passing
  • cargo clippy -p rules --all-targets -- -D warnings — clean
  • cargo fmt -p rules -- --check — clean
  • cargo check --workspace — full workspace compiles

Summary

  • New rules workspace crate implementing all standard-mode Battlesnake rule steps ported from BattlesnakeOfficial/rules (Go)
  • Covers: snake movement, health reduction, hazard damage (with stacking), 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)
  • Optional wire-types feature for conversion to/from battlesnake-game-types wire representation
  • 43 tests ported from the Go test suite, all passing

Test plan

  • cargo test -p rules — 43 tests passing
  • cargo clippy -p rules --all-targets -- -D warnings — clean
  • cargo fmt -p rules -- --check — clean
  • cargo check --workspace — workspace compiles

Task: BS-eb0ddcf28dd9484f

🤖 Generated with Claude Code

byte-the-bot and others added 2 commits April 10, 2026 13:13
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>
Copy link
Copy Markdown
Member

@coreyja coreyja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@byte-the-bot
Copy link
Copy Markdown
Collaborator Author

Done — both items addressed in d46f068:

Clean break from battlesnake-game-types:

  • Removed the wire-types feature and battlesnake-game-types dependency from the rules crate
  • Deleted rules/src/convert.rs entirely

Integrated rules crate into the server:

  • Replaced all battlesnake-game-types usage with the local rules crate
  • Engine now wraps rules::BoardState in an EngineGame struct with game metadata
  • engine/mod.rs, frame.rs, wire.rs, snake_client.rs, and game_runner.rs all rewritten to use rules types
  • Net reduction: ~1,100 lines removed across the codebase
  • All 252 tests pass, including updated proptests that now correctly match Go rules semantics (e.g., health=1 snakes can eat food and survive since feed_snakes runs before eliminate_snakes)

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>
@byte-the-bot byte-the-bot force-pushed the implement/BS-eb0ddcf28dd9484f branch from d46f068 to 9490fb3 Compare April 11, 2026 08:40
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>
Copy link
Copy Markdown
Member

@coreyja coreyja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets refactor the tests to be more Rust-y plz

Comment on lines +1 to +2
mod board_tests;
mod standard_tests;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants