From dfcdbae85bbe3ad21f2d182071731e8223794dd1 Mon Sep 17 00:00:00 2001 From: Daniel Dolezal Date: Fri, 3 Apr 2026 21:39:08 +0200 Subject: [PATCH] add better enemy constrictor projection --- snakes/BestBattleSnake.py | 40 ++++++++++++++++++++ tests/test_BestBattleSnake.py | 70 +++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/snakes/BestBattleSnake.py b/snakes/BestBattleSnake.py index cf26310..549f072 100644 --- a/snakes/BestBattleSnake.py +++ b/snakes/BestBattleSnake.py @@ -583,6 +583,12 @@ class BestBattleSnake(TemplateSnake): width=width, height=height, ) + enemy_best_space, enemy_total_options = self._enemy_constrictor_projection( + other_snakes=other_snakes, + blocked=blocked, + width=width, + height=height, + ) enemy_len = enemy_attack_map.get(point) is_losing_head_to_head = enemy_len is not None and enemy_len >= my_len @@ -596,6 +602,13 @@ class BestBattleSnake(TemplateSnake): score += liberties * 24.0 score += next_options * 16.0 score += territory * 0.65 + score += (reachable_space - enemy_best_space) * 3.2 + score += (max(0, 8 - enemy_total_options)) * 18.0 + + if enemy_total_options <= 2: + score += 110.0 + if enemy_best_space > int(reachable_space * 1.2): + score -= 320.0 if is_dead_end: score -= 2600.0 @@ -992,6 +1005,33 @@ class BestBattleSnake(TemplateSnake): ) return enemy_space, enemy_options + def _enemy_constrictor_projection(self, other_snakes:list[SnakeState], blocked:set[Point], width:int, height: int) -> tuple[int, int]: + """Estimate enemy best-space and total options after our candidate move.""" + best_enemy_space = 0 + total_enemy_options = 0 + + for enemy in other_snakes: + enemy_head = (enemy["head"]["x"], enemy["head"]["y"]) + enemy_best_for_snake = 0 + + for neighbor in self._neighbors(enemy_head): + if not self._in_bounds(neighbor, width, height): + continue + if neighbor in blocked: + continue + + total_enemy_options += 1 + enemy_blocked = set(blocked) + enemy_blocked.add(neighbor) + enemy_space = self._flood_fill_count( + neighbor, enemy_blocked, width, height + ) + enemy_best_for_snake = max(enemy_best_for_snake, enemy_space) + + best_enemy_space = max(best_enemy_space, enemy_best_for_snake) + + return best_enemy_space, total_enemy_options + def _neighbors(self, point:Point) -> Iterator[Point]: """Yield orthogonal neighbor coordinates for a point.""" for dx, dy in self.DIRECTIONS.values(): diff --git a/tests/test_BestBattleSnake.py b/tests/test_BestBattleSnake.py index f7c78c9..478afd0 100644 --- a/tests/test_BestBattleSnake.py +++ b/tests/test_BestBattleSnake.py @@ -681,5 +681,75 @@ class TestBestBattleSnake(unittest.TestCase): move = make_board(game_state).snake_neat_make_a_move() self.assertEqual(move, "right") + def test_constrictor_prefers_choke_when_safe(self): + game_state = { + "game": { + "id": "test-constrictor-choke", + "ruleset": {"name": "constrictor", "version": "v1.0.0"}, + "source": "custom", + "map": "standard", + }, + "turn": 25, + "board": { + "height": 7, + "width": 7, + "food": [], + "hazards": [], + "snakes": [ + { + "id": "me", + "name": "me", + "health": 100, + "length": 7, + "head": {"x": 2, "y": 3}, + "body": [ + {"x": 2, "y": 3}, + {"x": 2, "y": 2}, + {"x": 1, "y": 2}, + {"x": 1, "y": 3}, + {"x": 1, "y": 4}, + {"x": 2, "y": 4}, + {"x": 2, "y": 5}, + ], + }, + { + "id": "enemy", + "name": "enemy", + "health": 100, + "length": 7, + "head": {"x": 4, "y": 3}, + "body": [ + {"x": 4, "y": 3}, + {"x": 5, "y": 3}, + {"x": 5, "y": 2}, + {"x": 4, "y": 2}, + {"x": 4, "y": 1}, + {"x": 5, "y": 1}, + {"x": 6, "y": 1}, + ], + }, + ], + }, + "you": { + "id": "me", + "name": "me", + "health": 100, + "length": 7, + "head": {"x": 2, "y": 3}, + "body": [ + {"x": 2, "y": 3}, + {"x": 2, "y": 2}, + {"x": 1, "y": 2}, + {"x": 1, "y": 3}, + {"x": 1, "y": 4}, + {"x": 2, "y": 4}, + {"x": 2, "y": 5}, + ], + }, + } + + move = make_board(game_state).snake_neat_make_a_move() + self.assertEqual(move, "right") + if __name__ == "__main__": unittest.main()