Skip to content

Constrictor game mode (BS-cb89aee1)#128

Open
byte-the-bot wants to merge 1 commit into
mainfrom
byte/constrictor-mode
Open

Constrictor game mode (BS-cb89aee1)#128
byte-the-bot wants to merge 1 commit into
mainfrom
byte/constrictor-mode

Conversation

@byte-the-bot

Copy link
Copy Markdown
Collaborator

What

Implements the Constrictor game mode in the Rust engine. Constrictor is the standard ruleset plus two extra stages that run after elimination every turn:

  • RemoveFood — all food is cleared from the board.
  • GrowSnakes — every snake is pinned at health 100 and its tail segment is duplicated (grows by one), unless the tail is already stacked (tail == sub-tail).

No food ever spawns, snakes never starve, and hazards are inert. Games end via collisions and space exhaustion as ever-growing bodies fill the board.

Mapping to the Go rules

Ported from BattlesnakeOfficial/rules (constrictor.go / constrictor_test.go, verified against the live repo):

  • rules/src/constrictor.rs::execute_turn mirrors constrictorRulesetStages: game-over check → move → starvation → hazard damage → feed → eliminate → remove_food (RemoveFoodConstrictor) → grow_snakes (GrowSnakesConstrictor) → turn increment.
  • Matching Go exactly, grow_snakes does not skip eliminated snakes — a snake that dies this turn still ends it grown and at health 100 (asserted by the ported constrictorMoveAndCollideMAD case).
  • Init semantics: the Go engine runs the full pipeline once with no moves at game start (ruleset.Execute(boardState, nil)); the standard stages no-op via IsInitialization, leaving RemoveFood + GrowSnakes. modify_initial_board reproduces that: the initial board has its food stripped and snakes at health 100; the stacked 3-segment starting bodies don't grow yet (tail == sub-tail).
  • One documented deviation: Go would panic indexing body[len-2] on a 1-segment snake; the Rust port grows it instead (a lone tail is definitionally not stacked). Unreachable in real games — snakes start at 3 segments.
  • Settings: foodSpawnChance: 0, minimumFood: 0, hazardDamagePerTurn: 15. The Go repo defines no constrictor-specific settings (the CLI applies its global 15/1/14 defaults to every mode); 0/0 correctly advertises "no food ever spawns" on the wire, and hazard damage is inert either way.

Engine wiring

  • create_initial_game gains a GameType::Constrictor arm and applies the init modifications.
  • Per-turn dispatch in both paths — run_game_with_random_moves (execute_turn) and apply_turn (the live game_runner path) — is refactored from match &game.meta.royale to match game.meta.ruleset_name.as_str() with "royale" / "constrictor" / _ arms.
  • maybe_spawn_food (called outside execute_turn) is skipped for constrictor games.
  • Wire protocol needed no structural change: ruleset.name = "constrictor" and the settings flow from GameMeta; a wire test locks in the shape.

Test coverage

  • Ported Go cases: constrictorMoveAndCollideMAD with exact bodies, health 100, elimination causes/by/turn, food cleared; standardCaseErrNoMoveFound; standardCaseErrZeroLengthSnake.
  • Unit tests (rules): food removed each turn and at init, growth + health pinning, no growth on stacked tails, eliminated snakes still grown/health-set, eating doesn't double-grow, starting-stack unwind timing, early-exit no-op, never-starve.
  • Property tests (rules, default proptest config): food always cleared, health pinned at 100 for all snakes, tail stacked after every turn, growth exactly determined by feeding/pre-move tail stacking, turn increment, game-over early exit is a no-op. Properties are elimination-agnostic, so they don't trip over the two-phase elimination ordering.
  • Engine tests (arena): constrictor create_initial_game settings + modified initial board, live-loop (apply_turn) frames never contain food and snakes grow, eliminated snakes still grown in the live path, full random-move constrictor games terminate, wire serialization.

Results: cargo fmt --check clean, cargo clippy --workspace --all-targets zero warnings, cargo test -p rules 90 passed, cargo test -p arena --bin arena 419 passed.

Merge note

This PR trivially conflicts with the parallel Snail Mode PR in the server/src/engine/mod.rs dispatch matches (create_initial_game, run_game_with_random_moves, apply_turn) — both add an arm to the same match game.meta.ruleset_name.as_str() blocks, so the resolution is a mechanical union of arms.

🤖 Generated with Claude Code

Port the constrictor ruleset from the canonical Go implementation
(BattlesnakeOfficial/rules, constrictor.go): the standard pipeline plus
two post-elimination stages each turn -- RemoveFood (clear ALL food) and
GrowSnakes (pin every snake at health 100 and duplicate its tail unless
the tail is already stacked). Matching Go, GrowSnakes does not skip
eliminated snakes, so snakes that die in a turn still end it grown and
at max health. No food ever spawns; games end via collisions and space
exhaustion.

- New rules/src/constrictor.rs with execute_turn, the individual stage
  functions (remove_food, grow_snakes) for the engine's apply_turn path,
  and modify_initial_board mirroring Go's initialization pass (the
  pipeline runs once with no moves at game start, stripping food and
  pinning health; stacked starting bodies do not grow yet).
- Engine wiring: create_initial_game gets a GameType::Constrictor arm
  (ruleset "constrictor", foodSpawnChance 0 / minimumFood 0 /
  hazardDamagePerTurn 15, royale: None) and applies the initial board
  modifications. Per-turn dispatch in run_game_with_random_moves and
  apply_turn (the live game_runner path) now matches on
  meta.ruleset_name so parallel game-mode PRs union their arms. Food
  spawning is skipped outright for constrictor games.
- Wire protocol needs no structural change: ruleset.name and settings
  flow from GameMeta; a test locks in the constrictor wire shape.
- Tests: ported Go constrictor_test.go cases (move-and-collide with
  exact bodies/health/causes, missing-move and zero-length errors),
  unit tests for food removal, growth, stacked-tail skip, eliminated-
  snake growth, init semantics, and never-starving, plus property tests
  (food always cleared, health pinned, tail stacked after every turn,
  growth exactly determined, early-exit no-op).

Co-Authored-By: Claude Fable 5 <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.

1 participant