3a9af3f54d
- 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.
90 lines
3.5 KiB
Python
90 lines
3.5 KiB
Python
"""Reusable compact duel-search integration for Apex-style snakes."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from snakes.engine.duel_search import BitboardDuelSearch
|
|
|
|
class BitboardDuelMixin:
|
|
def _new_duel_search(
|
|
self, food_set: set, hazard_set: set, hazard_count: dict,
|
|
hazard_damage: int, width: int, height: int, deadline: float | None,
|
|
) -> BitboardDuelSearch:
|
|
if self._duel_search_context is None:
|
|
self._duel_search_context = BitboardDuelSearch(
|
|
board=self._get_bb(width, height),
|
|
food=food_set,
|
|
hazards=hazard_set,
|
|
hazard_count=hazard_count,
|
|
hazard_damage=hazard_damage,
|
|
deadline=deadline,
|
|
)
|
|
return self._duel_search_context
|
|
|
|
def _minimax_candidate_id(
|
|
self, my_body: list, enemy_body: list, my_target: tuple[int, int],
|
|
food_set: set, hazard_set: set,
|
|
my_health: int, enemy_health: int, hazard_damage: int, hazard_count: dict,
|
|
width: int, height: int, max_depth: int, alpha: float, beta: float,
|
|
deadline: float | None, previous_hazard_set: set | None = None,
|
|
) -> tuple[float, int]:
|
|
"""Resolve our selected move and every enemy reply simultaneously."""
|
|
search = self._new_duel_search(
|
|
food_set, hazard_set, hazard_count, hazard_damage,
|
|
width, height, deadline,
|
|
)
|
|
adaptive_depth = max_depth
|
|
remaining = self._remaining_ms(deadline)
|
|
if remaining > 250:
|
|
adaptive_depth = min(7, max_depth + 1)
|
|
elif remaining < 120:
|
|
adaptive_depth = min(max_depth, 2)
|
|
return search.search_candidate(
|
|
my_body=my_body,
|
|
enemy_body=enemy_body,
|
|
my_target=my_target,
|
|
my_health=my_health,
|
|
enemy_health=enemy_health,
|
|
max_depth=adaptive_depth,
|
|
previous_hazards=previous_hazard_set if previous_hazard_set is not None else hazard_set,
|
|
)
|
|
|
|
def _minimax_sim_id(
|
|
self, my_body: list, enemy_body: list, food_set: set, hazard_set: set,
|
|
my_health: int, enemy_health: int, hazard_damage: int, hazard_count: dict,
|
|
width: int, height: int, max_depth: int, alpha: float, beta: float,
|
|
deadline: float | None, previous_hazard_set: set | None = None,
|
|
) -> tuple[float, int]:
|
|
"""Run iterative deepening with one reusable compact search context."""
|
|
search = self._new_duel_search(
|
|
food_set, hazard_set, hazard_count, hazard_damage,
|
|
width, height, deadline,
|
|
)
|
|
return search.search(
|
|
my_body=my_body,
|
|
enemy_body=enemy_body,
|
|
my_health=my_health,
|
|
enemy_health=enemy_health,
|
|
max_depth=max_depth,
|
|
previous_hazards=previous_hazard_set if previous_hazard_set is not None else hazard_set,
|
|
)
|
|
|
|
def _minimax_sim(
|
|
self, my_body: list, enemy_body: list, food_set: set, hazard_set: set,
|
|
my_health: int, enemy_health: int, hazard_damage: int, hazard_count: dict,
|
|
width: int, height: int, depth: int, alpha: float, beta: float,
|
|
deadline: float | None, previous_hazard_set: set | None = None,
|
|
) -> float:
|
|
"""Compatibility entry point for tests and callers requesting one depth."""
|
|
search = self._new_duel_search(
|
|
food_set, hazard_set, hazard_count, hazard_damage,
|
|
width, height, deadline,
|
|
)
|
|
return search.search_depth(
|
|
my_body=my_body,
|
|
enemy_body=enemy_body,
|
|
my_health=my_health,
|
|
enemy_health=enemy_health,
|
|
depth=depth,
|
|
previous_hazards=previous_hazard_set if previous_hazard_set is not None else hazard_set,
|
|
)
|