Files
snake-python/tests/snakes/test_SupremeBattleSnake.py
T
daniel156161 3a9af3f54d feat(snake): modularize engine and add tournament tools
- Split active strategies, reusable engine code, core classes, and legacy snakes.
- Replace implicit snake imports with explicit module registrations.
- Extract Prism duel, spatial, and survival behavior into focused mixins.
- Improve duel scoring with food races, pressure, caches, and depth metrics.
- Add deterministic arena scenarios and paired seeded engine tournaments.
- Expand benchmark telemetry and bump Prism to version 1.3.0.
- Update documentation and tests for the new package layout and tooling.
2026-08-01 20:25:07 +02:00

372 lines
14 KiB
Python

"""Tests for SupremeBattleSnake.
Validates that the bitboard-accelerated snake produces correct results
and that the bitboard engine itself is sound.
"""
import unittest
from snakes.legacy.SupremeBattleSnake_ClaudeOpus4_6 import SupremeBattleSnake_ClaudeOpus4_6 as SupremeBattleSnake
from snakes.engine.bitboard import BitBoard
from server.GameBoard import GameBoard
# ── Helpers ───────────────────────────────────────────────────────────────────
def make_board(game_state: dict) -> GameBoard:
snake = SupremeBattleSnake()
board = GameBoard(
game_id=game_state["game"]["id"],
width=game_state["board"]["width"],
height=game_state["board"]["height"],
ruleset=game_state["game"]["ruleset"],
source=game_state["game"].get("source", "custom"),
map=game_state["game"].get("map", "standard"),
snake_class=snake,
)
board.read_game_data(game_state)
return board
def move(game_state: dict) -> str:
return make_board(game_state).snake_neat_make_a_move()
def gs(
my_body: list[tuple],
other_bodies: list[list[tuple]] | None = None,
foods: list[tuple] | None = None,
hazards: list[tuple] | None = None,
my_health: int = 90,
my_id: str = "me",
enemy_health: int = 90,
game_type: str = "standard",
game_map: str = "standard",
hazard_damage: int = 14,
width: int = 11,
height: int = 11,
turn: int = 20,
game_id: str = "test-game",
) -> dict:
other_bodies = other_bodies or []
foods = foods or []
hazards = hazards or []
def body_dicts(coords):
return [{"x": x, "y": y} for x, y in coords]
my_snake = {
"id": my_id, "name": "SupremeBattleSnake", "health": my_health,
"body": body_dicts(my_body),
"head": {"x": my_body[0][0], "y": my_body[0][1]},
"length": len(my_body),
"latency": "50", "shout": "",
}
snakes = [my_snake]
for i, body in enumerate(other_bodies):
snakes.append({
"id": f"enemy-{i}", "name": f"Enemy{i}", "health": enemy_health,
"body": body_dicts(body),
"head": {"x": body[0][0], "y": body[0][1]},
"length": len(body),
"latency": "60", "shout": "",
})
ruleset = {
"name": game_type, "version": "v1.0.0",
"settings": {"hazardDamagePerTurn": hazard_damage},
}
return {
"game": {"id": game_id, "ruleset": ruleset, "source": "custom", "map": game_map},
"turn": turn,
"board": {
"height": height, "width": width,
"food": body_dicts(foods),
"hazards": body_dicts(hazards),
"snakes": snakes,
},
"you": my_snake,
}
# ── BitBoard unit tests ──────────────────────────────────────────────────────
class TestBitBoard(unittest.TestCase):
def test_flood_fill_open_board(self):
bb = BitBoard(5, 5)
count = bb.flood_count(bb.idx(2, 2), 0)
self.assertEqual(count, 25)
def test_flood_fill_blocked_center(self):
bb = BitBoard(5, 5)
# Block all 4 neighbours of (2,2)
blocked = (
bb.pt_bit(1, 2) | bb.pt_bit(3, 2)
| bb.pt_bit(2, 1) | bb.pt_bit(2, 3)
)
count = bb.flood_count(bb.idx(2, 2), blocked)
self.assertEqual(count, 1) # only the start cell
def test_flood_fill_row_wall(self):
bb = BitBoard(5, 5)
# Block entire row y=2, except (2,2) itself
blocked = 0
for x in range(5):
if x != 2:
blocked |= bb.pt_bit(x, 2)
# Start at (2,3) — should reach everything above the wall
count_above = bb.flood_count(bb.idx(2, 3), blocked)
self.assertGreater(count_above, 1)
self.assertLess(count_above, 25)
def test_territory_center_vs_corner(self):
bb = BitBoard(11, 11)
score = bb.territory(bb.idx(5, 5), [bb.idx(0, 0)], 0)
self.assertGreater(score, 0)
def test_territory_symmetric(self):
bb = BitBoard(11, 11)
score = bb.territory(bb.idx(0, 0), [bb.idx(10, 10)], 0)
self.assertEqual(score, 0) # symmetric → tied
def test_partition_sizes_no_cut(self):
bb = BitBoard(5, 5)
sizes = bb.partition_sizes(bb.idx(2, 2), 0)
# Open board — removing center doesn't split it (all neighbours connected)
self.assertEqual(sizes, [])
def test_partition_sizes_bridge(self):
bb = BitBoard(3, 3)
# Block corners so (1,1) becomes a bridge:
# . X .
# X . X
# . X .
blocked = (
bb.pt_bit(0, 0) | bb.pt_bit(2, 0)
| bb.pt_bit(0, 2) | bb.pt_bit(2, 2)
)
sizes = bb.partition_sizes(bb.idx(1, 1), blocked)
# Removing (1,1) from the cross → 4 isolated cells
self.assertEqual(len(sizes), 4)
self.assertTrue(all(s == 1 for s in sizes))
def test_distance_map_correctness(self):
bb = BitBoard(5, 5)
dmap = bb.distance_map(bb.idx(0, 0), 0)
self.assertEqual(dmap[bb.idx(0, 0)], 0)
self.assertEqual(dmap[bb.idx(1, 0)], 1)
self.assertEqual(dmap[bb.idx(4, 4)], 8)
def test_path_distance_blocked(self):
bb = BitBoard(5, 5)
# Block a wall separating left from right
blocked = 0
for y in range(5):
blocked |= bb.pt_bit(2, y)
result = bb.path_distance(bb.idx(0, 0), bb.idx(4, 4), blocked)
self.assertIsNone(result)
def test_path_distance_unblocked(self):
bb = BitBoard(5, 5)
result = bb.path_distance(bb.idx(0, 0), bb.idx(4, 4), 0)
self.assertEqual(result, 8)
def test_nearest_food_finds_closest(self):
bb = BitBoard(11, 11)
food = bb.pt_bit(5, 6) | bb.pt_bit(0, 0)
dist, idx = bb.nearest_food(bb.idx(5, 5), food, 0)
self.assertEqual(dist, 1)
self.assertEqual(idx, bb.idx(5, 6))
def test_nearest_food_none(self):
bb = BitBoard(5, 5)
dist, idx = bb.nearest_food(bb.idx(2, 2), 0, 0)
self.assertIsNone(dist)
def test_open_neighbor_count_center(self):
bb = BitBoard(5, 5)
self.assertEqual(bb.open_neighbor_count(bb.idx(2, 2), 0), 4)
def test_open_neighbor_count_corner(self):
bb = BitBoard(5, 5)
self.assertEqual(bb.open_neighbor_count(bb.idx(0, 0), 0), 2)
def test_set_to_bits_roundtrip(self):
bb = BitBoard(11, 11)
pts = {(3, 7), (0, 0), (10, 10), (5, 5)}
bits = bb.set_to_bits(pts)
for x, y in pts:
self.assertTrue(bits & bb.pt_bit(x, y))
self.assertEqual(bits.bit_count(), len(pts))
def test_no_row_wraparound(self):
"""Right-column expansion must not wrap to the next row's left column."""
bb = BitBoard(5, 5)
start = bb.idx(4, 0) # rightmost column, bottom row
# Block everything except start and (0,1) — if wrapping happened, (0,1) would be adjacent
blocked = bb.board_mask & ~(1 << start) & ~bb.pt_bit(0, 1)
reachable = bb.flood_fill(start, blocked)
self.assertEqual(reachable.bit_count(), 1) # only start itself
# ── Snake safety tests ────────────────────────────────────────────────────────
class TestSupremeWallAndBodyAvoidance(unittest.TestCase):
def test_avoids_left_wall(self):
result = move(gs(my_body=[(0, 5), (1, 5), (2, 5)],
other_bodies=[[(9, 9), (9, 8), (9, 7)]]))
self.assertNotEqual(result, "left")
def test_avoids_bottom_wall(self):
result = move(gs(my_body=[(5, 0), (5, 1), (5, 2)],
other_bodies=[[(9, 9), (9, 8), (9, 7)]]))
self.assertNotEqual(result, "down")
def test_avoids_own_body(self):
result = move(gs(my_body=[(5, 5), (6, 5), (7, 5), (8, 5)],
other_bodies=[[(1, 1), (1, 2), (1, 3)]],
foods=[(5, 9)]))
self.assertNotEqual(result, "right")
def test_avoids_enemy_body(self):
result = move(gs(my_body=[(5, 5), (5, 4), (5, 3)],
other_bodies=[[(6, 5), (7, 5), (8, 5), (9, 5), (9, 6), (9, 7)]]))
self.assertNotEqual(result, "right")
def test_only_one_safe_move_taken(self):
result = move(gs(my_body=[(1, 1), (1, 2), (2, 2), (2, 1)],
other_bodies=[], foods=[(5, 5)], width=7, height=7))
self.assertEqual(result, "right")
def test_no_safe_moves_returns_valid_direction(self):
result = move(gs(my_body=[(0, 0), (0, 1), (1, 1), (1, 0)], other_bodies=[]))
self.assertIn(result, ("up", "down", "left", "right"))
# ── Duel mode ─────────────────────────────────────────────────────────────────
class TestSupremeDuelMode(unittest.TestCase):
def test_avoids_h2h_with_equal_length(self):
result = move(gs(my_body=[(5, 5), (5, 4), (5, 3)],
other_bodies=[[(7, 5), (7, 4), (7, 3)]],
foods=[(0, 0)]))
self.assertNotEqual(result, "right")
def test_head_hunts_smaller_enemy(self):
result = move(gs(
my_body=[(5, 5), (5, 4), (5, 3), (5, 2), (5, 1), (4, 1), (4, 2)],
other_bodies=[[(7, 5), (7, 4), (7, 3)]],
foods=[(0, 0)]))
self.assertEqual(result, "right")
def test_chases_food_when_low_health(self):
result = move(gs(my_body=[(5, 5), (5, 4), (5, 3)],
other_bodies=[[(9, 9), (9, 8), (9, 7)]],
foods=[(5, 6)], my_health=10))
self.assertEqual(result, "up")
# ── Constrictor mode ──────────────────────────────────────────────────────────
class TestSupremeConstrictorMode(unittest.TestCase):
def test_returns_valid_move(self):
result = move(gs(my_body=[(5, 5), (5, 4), (5, 3)],
other_bodies=[[(3, 3), (3, 4), (3, 5)]],
game_type="constrictor"))
self.assertIn(result, ("up", "down", "left", "right"))
# ── Multi-snake mode ─────────────────────────────────────────────────────────
class TestSupremeMultiSnakeMode(unittest.TestCase):
def test_returns_valid_move(self):
result = move(gs(my_body=[(5, 5), (5, 4), (5, 3)],
other_bodies=[[(2, 2), (2, 3), (2, 4)],
[(8, 8), (8, 7), (8, 6)]],
foods=[(3, 3), (7, 7)]))
self.assertIn(result, ("up", "down", "left", "right"))
# ── Hazard tests ──────────────────────────────────────────────────────────────
class TestSupremeHazard(unittest.TestCase):
def test_hazard_penalizes_score(self):
snake = SupremeBattleSnake()
snake._bb = BitBoard(11, 11)
snake._bb_w = 11
snake._bb_h = 11
snake._bits_cache = {}
snake._bits_cache_turn = 0
snake.game_board = make_board(gs(
my_body=[(5, 5), (5, 4), (5, 3)],
hazards=[(6, 5)], hazard_damage=14))
snake.previous_hazards = {(6, 5)}
snake._enemy_dmaps = []
snake._enemy_heads = []
snake._base_blocked = set()
score_right, _ = snake._score_move(
move="right", pos={"x": 6, "y": 5},
my_body=[{"x": 5, "y": 5}, {"x": 5, "y": 4}, {"x": 5, "y": 3}],
my_len=3, my_health=90,
other_snakes=[], food_set=set(),
hazard_set={(6, 5)}, hazard_damage=14, hazard_count={(6, 5): 1},
previous_hazard_set={(6, 5)},
is_constrictor=False, enemy_attack_map={},
enemy_can_grow={}, total_occupancy=0.05,
width=11, height=11, deadline=None)
score_up, _ = snake._score_move(
move="up", pos={"x": 5, "y": 6},
my_body=[{"x": 5, "y": 5}, {"x": 5, "y": 4}, {"x": 5, "y": 3}],
my_len=3, my_health=90,
other_snakes=[], food_set=set(),
hazard_set={(6, 5)}, hazard_damage=14, hazard_count={(6, 5): 1},
previous_hazard_set={(6, 5)},
is_constrictor=False, enemy_attack_map={},
enemy_can_grow={}, total_occupancy=0.05,
width=11, height=11, deadline=None)
self.assertGreater(score_up, score_right)
# ── Version ───────────────────────────────────────────────────────────────────
class TestSupremeVersion(unittest.TestCase):
def test_version(self):
self.assertEqual(SupremeBattleSnake.VERSION, "1.0.0")
def test_class_name_contains_claude(self):
snake = SupremeBattleSnake()
self.assertIn("Claude", snake.__class__.__name__)
def test_builder(self):
from snakes import SnakeBuilder
snake = SnakeBuilder.build("SupremeBattleSnake_ClaudeOpus4_6")
self.assertIsInstance(snake, SupremeBattleSnake)
# ── Parity: Supreme makes same decisions as Apex on key scenarios ────────────
class TestParityWithApex(unittest.TestCase):
"""Ensure the bitboard optimisations don't change strategic behaviour."""
def test_trapped_corner(self):
"""Both snakes should survive a forced single-exit scenario."""
from snakes.strategies.apex import ApexBattleSnake
state = gs(my_body=[(1, 1), (1, 2), (2, 2), (2, 1)],
other_bodies=[], foods=[(5, 5)], width=7, height=7)
apex_snake = ApexBattleSnake()
apex_board = GameBoard(game_id="parity", width=7, height=7,
ruleset=state["game"]["ruleset"],
source="custom", map="standard",
snake_class=apex_snake)
apex_board.read_game_data(state)
apex_move = apex_board.snake_neat_make_a_move()
supreme_move = move(state)
# Both must find the only safe exit
self.assertEqual(apex_move, "right")
self.assertEqual(supreme_move, "right")
if __name__ == "__main__":
unittest.main()