fix: preserve gameplay data and correct duel evaluation

- Preserve snake customizations across database migrations and merges.
- Lazily load optional storage backends for SQLite maintenance scripts.
- Match Apex territory and nearest-food tie-breaking semantics.
- Resolve duel occupancy after simultaneous movement and food growth.
- Recompute simulated head-to-head danger after body growth.
- Add regression coverage and declare the aiofiles dependency.
This commit is contained in:
2026-08-01 18:16:04 +02:00
parent 4f022d3d01
commit c646392b84
14 changed files with 323 additions and 107 deletions
+9 -10
View File
@@ -129,8 +129,8 @@ class BitboardDuelSearch:
if alpha >= beta:
return cached_value, True
my_moves = self._legal_targets(state.my_body, state.enemy_body)
enemy_moves = self._legal_targets(state.enemy_body, state.my_body)
my_moves = self._candidate_targets(state.my_body)
enemy_moves = self._candidate_targets(state.enemy_body)
if not my_moves:
return self.LOSS - depth, True
if not enemy_moves:
@@ -219,14 +219,13 @@ class BitboardDuelSearch:
)
return child, None
def _legal_targets(self, body: Body, other_body: Body) -> list[int]:
occupied = self._body_bits(body) | self._body_bits(other_body)
if not self._tail_stacked(body):
occupied &= ~(1 << body[-1])
if not self._tail_stacked(other_body):
occupied &= ~(1 << other_body[-1])
legal = self.board.neighbors_of(body[0]) & ~occupied & self.board.board_mask
return list(self._iter_bits(legal))
def _candidate_targets(self, body: Body) -> list[int]:
"""Return in-bounds targets; `_advance` resolves simultaneous collisions.
Delaying occupancy checks until both targets and food growth are known is
essential: whether either tail vacates depends on that snake eating.
"""
return list(self._iter_bits(self.board.neighbors_of(body[0])))
def _ordered_moves(self, moves: list[int], state: DuelState, depth: int, mine: bool) -> list[int]:
body = state.my_body if mine else state.enemy_body