Constrictor game mode (BS-cb89aee1)#128
Open
byte-the-bot wants to merge 1 commit into
Open
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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_turnmirrorsconstrictorRulesetStages: game-over check → move → starvation → hazard damage → feed → eliminate → remove_food (RemoveFoodConstrictor) → grow_snakes (GrowSnakesConstrictor) → turn increment.grow_snakesdoes not skip eliminated snakes — a snake that dies this turn still ends it grown and at health 100 (asserted by the portedconstrictorMoveAndCollideMADcase).ruleset.Execute(boardState, nil)); the standard stages no-op viaIsInitialization, leaving RemoveFood + GrowSnakes.modify_initial_boardreproduces 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).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.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_gamegains aGameType::Constrictorarm and applies the init modifications.run_game_with_random_moves(execute_turn) andapply_turn(the livegame_runnerpath) — is refactored frommatch &game.meta.royaletomatch game.meta.ruleset_name.as_str()with"royale"/"constrictor"/_arms.maybe_spawn_food(called outsideexecute_turn) is skipped for constrictor games.ruleset.name = "constrictor"and the settings flow fromGameMeta; a wire test locks in the shape.Test coverage
constrictorMoveAndCollideMADwith exact bodies, health 100, elimination causes/by/turn, food cleared;standardCaseErrNoMoveFound;standardCaseErrZeroLengthSnake.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.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.arena): constrictorcreate_initial_gamesettings + 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 --checkclean,cargo clippy --workspace --all-targetszero warnings,cargo test -p rules90 passed,cargo test -p arena --bin arena419 passed.Merge note
This PR trivially conflicts with the parallel Snail Mode PR in the
server/src/engine/mod.rsdispatch matches (create_initial_game,run_game_with_random_moves,apply_turn) — both add an arm to the samematch game.meta.ruleset_name.as_str()blocks, so the resolution is a mechanical union of arms.🤖 Generated with Claude Code