feat(snake): add adaptive adversarial search
- Share duel search contexts and transpositions across candidate moves. - Add aspiration windows, principal variation ordering, and body caches. - Model simultaneous multiplayer responses with a compact beam rollout. - Adapt search depth and response breadth to the remaining deadline. - Add a deterministic arena benchmark with optional JSON reporting. - Expose search metrics, document benchmarking, and bump Prism to 1.2.0.
This commit is contained in:
@@ -3,9 +3,10 @@ from time import perf_counter
|
||||
|
||||
from snakes import SnakeBuilder, get_snake_version
|
||||
from snakes.ApexBattleSnake import ApexBattleSnake
|
||||
from snakes.PrismBattleSnake_GPT_5_6_Sol import PrismBattleSnake_GPT_5_6_Sol
|
||||
from snakes.bitboard import BitBoard
|
||||
from snakes.bitboard_duel_search import BitboardDuelSearch
|
||||
from snakes.compact_survival_search import CompactSurvivalSearch
|
||||
from snakes.PrismBattleSnake_GPT_5_6_Sol import PrismBattleSnake_GPT_5_6_Sol
|
||||
|
||||
class TestBitBoard(unittest.TestCase):
|
||||
|
||||
@@ -51,8 +52,8 @@ class TestPrismBattleSnake_GPT_5_6_Sol(unittest.TestCase):
|
||||
snake = PrismBattleSnake_GPT_5_6_Sol()
|
||||
|
||||
self.assertEqual(snake.name, "PrismBattleSnake")
|
||||
self.assertEqual(snake.version, "1.1.0")
|
||||
self.assertEqual(get_snake_version("PrismBattleSnake_GPT_5_6_Sol"), "1.1.0")
|
||||
self.assertEqual(snake.version, "1.2.0")
|
||||
self.assertEqual(get_snake_version("PrismBattleSnake_GPT_5_6_Sol"), "1.2.0")
|
||||
self.assertIsInstance(SnakeBuilder.build("PrismBattleSnake_GPT_5_6_Sol"), PrismBattleSnake_GPT_5_6_Sol)
|
||||
|
||||
def test_bitboard_primitives_match_apex(self):
|
||||
@@ -156,6 +157,55 @@ class TestPrismBattleSnake_GPT_5_6_Sol(unittest.TestCase):
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
def test_bitboard_duel_search_reuses_transpositions(self):
|
||||
board = BitBoard(5, 5)
|
||||
search = BitboardDuelSearch(
|
||||
|
||||
Reference in New Issue
Block a user