Files
snake-python/tests/snakes/test_PrismBattleSnake_GPT_5_6_Sol.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

258 lines
9.0 KiB
Python

import unittest
from time import perf_counter
from snakes import SnakeBuilder, get_snake_version
from snakes.engine.bitboard import BitBoard
from snakes.engine.duel_search import BitboardDuelSearch
from snakes.engine.survival_search import CompactSurvivalSearch
from snakes.strategies.apex import ApexBattleSnake
from snakes.strategies.prism import PrismBattleSnake_GPT_5_6_Sol
class TestBitBoard(unittest.TestCase):
def test_flood_fill_respects_walls(self):
board = BitBoard(3, 3)
blocked = board.set_to_bits({(1, 0), (1, 1), (1, 2)})
self.assertEqual(board.flood_count(board.idx(0, 1), blocked), 3)
self.assertEqual(board.path_distance(board.idx(0, 1), board.idx(2, 1), blocked), None)
def test_territory_counts_ties_for_neither_side(self):
board = BitBoard(5, 1)
self.assertEqual(board.territory(board.idx(0, 0), [board.idx(4, 0)], 0), 0)
def test_territory_propagates_through_contested_cells(self):
board = BitBoard(5, 3)
blocked = board.set_to_bits({(0, 1), (1, 1), (3, 1), (4, 1)})
self.assertEqual(board.territory(board.idx(0, 0), [board.idx(4, 0)], blocked), 0)
def test_territory_ignores_enemy_only_disconnected_space_like_apex(self):
board = BitBoard(5, 1)
blocked = board.set_to_bits({(2, 0)})
self.assertEqual(board.territory(board.idx(0, 0), [board.idx(4, 0)], blocked), 2)
def test_nearest_food_returns_shortest_distance(self):
board = BitBoard(5, 5)
food = board.set_to_bits({(4, 4), (2, 1)})
self.assertEqual(board.nearest_food(board.idx(0, 0), food, 0), (3, board.idx(2, 1)))
def test_nearest_food_uses_apex_direction_order_for_ties(self):
board = BitBoard(3, 3)
food = board.set_to_bits({(1, 2), (0, 1), (2, 1), (1, 0)})
self.assertEqual(board.nearest_food(board.idx(1, 1), food, 0), (1, board.idx(1, 2)))
class TestPrismBattleSnake_GPT_5_6_Sol(unittest.TestCase):
def test_api_name_and_version_are_exposed(self):
snake = PrismBattleSnake_GPT_5_6_Sol()
self.assertEqual(snake.name, "PrismBattleSnake")
self.assertEqual(snake.version, "1.3.0")
self.assertEqual(get_snake_version("PrismBattleSnake_GPT_5_6_Sol"), "1.3.0")
self.assertGreaterEqual(snake._planning_depth, 4)
self.assertIsInstance(SnakeBuilder.build("PrismBattleSnake_GPT_5_6_Sol"), PrismBattleSnake_GPT_5_6_Sol)
def test_bitboard_primitives_match_apex(self):
apex = ApexBattleSnake()
prism = PrismBattleSnake_GPT_5_6_Sol()
blocked = {(1, 0), (1, 1), (3, 2), (3, 3)}
self.assertEqual(
prism._flood_fill_count((0, 0), blocked, 5, 5),
apex._flood_fill_count((0, 0), blocked, 5, 5),
)
self.assertEqual(
prism._distance_map((0, 0), blocked, 5, 5),
apex._distance_map((0, 0), blocked, 5, 5),
)
self.assertEqual(
prism._path_distance((0, 0), (4, 4), blocked, 5, 5),
apex._path_distance((0, 0), (4, 4), blocked, 5, 5),
)
def test_mutated_blocked_set_does_not_return_stale_result(self):
snake = PrismBattleSnake_GPT_5_6_Sol()
blocked: set[tuple[int, int]] = set()
open_count = snake._flood_fill_count((1, 1), blocked, 3, 3)
blocked.update({(0, 1), (1, 0), (2, 1), (1, 2)})
trapped_count = snake._flood_fill_count((1, 1), blocked, 3, 3)
self.assertEqual(open_count, 9)
self.assertEqual(trapped_count, 1)
def test_bitboard_duel_search_values_length_advantage(self):
snake = PrismBattleSnake_GPT_5_6_Sol()
my_body = [{"x": 1, "y": 0}, {"x": 0, "y": 0}, {"x": 0, "y": 1}]
enemy_body = [{"x": 1, "y": 2}, {"x": 0, "y": 2}]
value = snake._minimax_sim(
my_body=my_body, enemy_body=enemy_body,
food_set=set(), hazard_set=set(),
my_health=100, enemy_health=100,
hazard_damage=15, hazard_count={},
width=3, height=3, depth=2,
alpha=-1e9, beta=1e9, deadline=None,
)
self.assertGreater(value, 0)
def test_candidate_search_resolves_enemy_reply_on_the_same_turn(self):
board = BitBoard(3, 3)
search = BitboardDuelSearch(
board=board, food=set(), hazards=set(), hazard_count={},
hazard_damage=15, deadline=None,
)
my_body = [{"x": 0, "y": 1}, {"x": 0, "y": 0}]
enemy_body = [{"x": 2, "y": 1}, {"x": 2, "y": 0}]
value, depth = search.search_candidate(
my_body=my_body,
enemy_body=enemy_body,
my_target=(1, 1),
my_health=100,
enemy_health=100,
max_depth=1,
previous_hazards=set(),
)
self.assertEqual(value, -500.0)
self.assertEqual(depth, 1)
def test_candidate_search_does_not_advance_our_snake_twice_at_root(self):
board = BitBoard(4, 1)
search = BitboardDuelSearch(
board=board, food=set(), hazards=set(), hazard_count={},
hazard_damage=15, deadline=None,
)
my_body = [{"x": 0, "y": 0}]
enemy_body = [{"x": 3, "y": 0}]
value, depth = search.search_candidate(
my_body=my_body,
enemy_body=enemy_body,
my_target=(1, 0),
my_health=100,
enemy_health=100,
max_depth=1,
previous_hazards=set(),
)
self.assertEqual(value, 0.0)
self.assertEqual(depth, 1)
def test_duel_search_keeps_tail_blocked_when_its_snake_eats(self):
board = BitBoard(3, 3)
search = BitboardDuelSearch(
board=board, food={(0, 1)}, hazards=set(), hazard_count={},
hazard_damage=15, deadline=None,
)
body = (board.idx(0, 0), board.idx(1, 0), board.idx(1, 1))
advanced = search._advance_body(body, board.idx(0, 1), ate=True)
self.assertIn(board.idx(1, 1), advanced)
def test_candidate_duel_searches_share_the_same_context(self):
snake = PrismBattleSnake_GPT_5_6_Sol()
kwargs = {
"my_body": [{"x": 0, "y": 1}, {"x": 0, "y": 0}],
"enemy_body": [{"x": 3, "y": 1}, {"x": 3, "y": 0}],
"food_set": set(), "hazard_set": set(),
"my_health": 100, "enemy_health": 100,
"hazard_damage": 15, "hazard_count": {},
"width": 4, "height": 3, "max_depth": 2,
"alpha": -1e9, "beta": 1e9, "deadline": perf_counter() + 1.0,
}
snake._minimax_candidate_id(my_target=(1, 1), **kwargs)
first_context = snake._duel_search_context
snake._minimax_candidate_id(my_target=(0, 2), **kwargs)
self.assertIs(snake._duel_search_context, first_context)
self.assertGreater(first_context.nodes, 0)
self.assertGreater(first_context.completed_depth, 0)
def test_duel_evaluation_prioritizes_reachable_food_when_starving(self):
board = BitBoard(5, 3)
search = BitboardDuelSearch(
board=board, food={(2, 1)}, hazards=set(), hazard_count={},
hazard_damage=15, deadline=None,
)
my_body = [{"x": 0, "y": 1}, {"x": 0, "y": 0}]
enemy_body = [{"x": 4, "y": 1}, {"x": 4, "y": 0}]
hungry = search.search_depth(my_body, enemy_body, 15, 100, 0, set())
healthy = search.search_depth(my_body, enemy_body, 100, 100, 0, set())
self.assertLess(hungry, healthy)
def test_compact_rollout_models_lethal_enemy_head_response(self):
board = BitBoard(3, 3)
search = CompactSurvivalSearch(
board=board, food=set(), is_constrictor=False,
deadline=perf_counter() + 1.0, branch=2,
)
mine = [{"x": 0, "y": 1}, {"x": 0, "y": 0}]
enemies = [{
"body": [{"x": 2, "y": 1}, {"x": 2, "y": 0}, {"x": 1, "y": 0}],
}]
value = search.search_selected(mine, enemies, (1, 1), depth=1)
self.assertEqual(value, search.DEATH)
def test_compact_rollout_reuses_transpositions(self):
board = BitBoard(5, 5)
search = CompactSurvivalSearch(
board=board, food=set(), is_constrictor=False,
deadline=perf_counter() + 1.0, branch=2,
)
mine = [{"x": 1, "y": 1}, {"x": 1, "y": 0}]
enemies = [{"body": [{"x": 3, "y": 3}, {"x": 3, "y": 4}]}]
search.search_selected(mine, enemies, (2, 1), depth=3)
hits_before = search.cache_hits
search.search_selected(mine, enemies, (2, 1), depth=3)
self.assertGreater(search.cache_hits, hits_before)
self.assertGreaterEqual(search.completed_depth, 3)
def test_bitboard_duel_search_reuses_transpositions(self):
board = BitBoard(5, 5)
search = BitboardDuelSearch(
board=board, food=set(), hazards=set(), hazard_count={},
hazard_damage=15, deadline=perf_counter() + 1.0,
)
my_body = [{"x": 1, "y": 1}, {"x": 1, "y": 0}, {"x": 0, "y": 0}]
enemy_body = [{"x": 3, "y": 3}, {"x": 3, "y": 4}, {"x": 4, "y": 4}]
search.search_depth(my_body, enemy_body, 100, 100, 3, set())
hits_before = search.cache_hits
search.search_depth(my_body, enemy_body, 100, 100, 3, set())
self.assertGreater(search.cache_hits, hits_before)
def test_bitboard_duel_search_respects_deadline(self):
board = BitBoard(11, 11)
search = BitboardDuelSearch(
board=board, food=set(), hazards=set(), hazard_count={},
hazard_damage=15, deadline=perf_counter() - 0.001,
)
my_body = [{"x": 2, "y": 2}, {"x": 2, "y": 1}, {"x": 2, "y": 0}]
enemy_body = [{"x": 8, "y": 8}, {"x": 8, "y": 9}, {"x": 8, "y": 10}]
started = perf_counter()
_, depth = search.search(my_body, enemy_body, 100, 100, 6, set())
self.assertEqual(depth, 0)
self.assertLess(perf_counter() - started, 0.05)
if __name__ == "__main__":
unittest.main()