diff --git a/snakes/UltimateBattleSnake.py b/snakes/UltimateBattleSnake.py index 7491164..d2fdc70 100644 --- a/snakes/UltimateBattleSnake.py +++ b/snakes/UltimateBattleSnake.py @@ -10,7 +10,7 @@ from server.dataset.RLBootstrapDataset import RLBootstrapDataset class UltimateBattleSnake(TemplateSnake): """ - UltimateBattleSnake v4.0.0 + UltimateBattleSnake v4.2.0 All improvements over BestBattleSnake: v3: #1+#9 Simultaneous minimax (both snakes move at once) with hazard/health tracking @@ -39,9 +39,12 @@ class UltimateBattleSnake(TemplateSnake): v4.1 B1 _simulation_blocked now correctly keeps enemy tail blocked when enemy_can_grow=True v4.1 B2 _build_enemy_attack_map: can_en_tail=False when enemy is about to eat (won't vacate) v4.1 B3 _compute_base_blocked: same enemy_can_grow fix for Voronoi dmap accuracy + v4.2 B4 _build_enemy_attack_map: enemy_can_grow now actually passed from choose_move and tree + v4.2 B5 _choose_duel_move: removed double food bias (score_move already adds it; duel now only adjusts delta) + v4.2 B6 _minimax_sim: occupancy now respects _is_tail_stacked (stacked tail not vacated) """ - VERSION = "4.1.0" + VERSION = "4.2.0" Point = tuple[int, int] Coord = dict[str, int] SnakeState = dict[str, Any] @@ -178,6 +181,7 @@ class UltimateBattleSnake(TemplateSnake): enemy_attack_map = self._build_enemy_attack_map( my_snake=my_snake, other_snakes=other_snakes, food_set=food_set, is_constrictor=is_constrictor, width=width, height=height, + enemy_can_grow=enemy_can_grow, ) safe_moves = self._legal_moves( @@ -252,7 +256,6 @@ class UltimateBattleSnake(TemplateSnake): ) -> tuple[str, dict[str, float]]: scores: dict[str, float] = {} safety: dict[str, dict] = {} - enemy_heads = self._enemy_heads for move, pos in safe_moves.items(): if self._time_exceeded(deadline): @@ -382,11 +385,14 @@ class UltimateBattleSnake(TemplateSnake): if dist == 1: sc -= 180.0 * dw["distance_safety"] - # Override food bias with duel style weight + # Apply duel-style food multiplier on top of the base food bias already added by _score_move. + # _score_move contributes (30+80*hunger)/(nearest_food+1); here we scale that contribution + # by (dw["food_bias"] - 1) so the net effect is the full duel-weighted amount. nearest_food = info.get("nearest_food") - if nearest_food is not None: + if nearest_food is not None and dw["food_bias"] != 1.0: hunger = max(0.0, (65.0 - my_health) / 65.0) - sc += ((25.0 + 90.0 * hunger) * dw["food_bias"]) / (nearest_food + 1) + base_food_contribution = (30.0 + 80.0 * hunger) / (nearest_food + 1) + sc += base_food_contribution * (dw["food_bias"] - 1.0) sc += self._territory_fast(point, blocked, width, height, deadline) * 0.55 scores[move] = round(sc, 5) @@ -475,7 +481,6 @@ class UltimateBattleSnake(TemplateSnake): ) -> tuple[str, dict[str, float]]: scores: dict[str, float] = {} safety: dict[str, dict] = {} - enemy_heads = self._enemy_heads for move, pos in safe_moves.items(): if self._time_exceeded(deadline): @@ -821,9 +826,14 @@ class UltimateBattleSnake(TemplateSnake): my_h = my_body[0] en_h = enemy_body[0] - # Occupied: bodies excluding tails (tails vacate this turn) - my_occ = {(s["x"], s["y"]) for s in my_body[:-1]} - en_occ = {(s["x"], s["y"]) for s in enemy_body[:-1]} + # Occupied: bodies excluding tails that will vacate this turn. + # A tail only vacates if the snake is NOT stacked (i.e. didn't eat food last turn). + my_occ = {(s["x"], s["y"]) for s in my_body} + if not self._is_tail_stacked(my_body): + my_occ.discard((my_body[-1]["x"], my_body[-1]["y"])) + en_occ = {(s["x"], s["y"]) for s in enemy_body} + if not self._is_tail_stacked(enemy_body): + en_occ.discard((enemy_body[-1]["x"], enemy_body[-1]["y"])) all_occ = my_occ | en_occ my_moves = [] @@ -1019,7 +1029,7 @@ class UltimateBattleSnake(TemplateSnake): # Build attack map for safe option count future_snake = {"head": my_body[0], "body": my_body, "length": len(my_body), "id": "__future__"} - atk = self._build_enemy_attack_map(future_snake, other_snakes, food_set, is_constrictor, width, height) + atk = self._build_enemy_attack_map(future_snake, other_snakes, food_set, is_constrictor, width, height, enemy_can_grow) en_safe = self._safe_next_options(my_body, len(my_body), blocked, atk, food_set, is_constrictor, width, height) # Zero safe options = will be forced into a losing head-to-head next turn diff --git a/snakes/__init__.py b/snakes/__init__.py index 1686652..20c4301 100644 --- a/snakes/__init__.py +++ b/snakes/__init__.py @@ -8,7 +8,7 @@ SNAKE_REGISTRY = { "BetterMasterSnake": "1.3.0", "BestBattleSnake": "2.6.0", "TrainedBattleSnake": "0.1.0", - "UltimateBattleSnake": "4.1.0", + "UltimateBattleSnake": "4.2.0", } def build_snake(selected_snake: str):