diff --git a/snakes/BestBattleSnake.py b/snakes/BestBattleSnake.py index c2aa7cb..22629fb 100644 --- a/snakes/BestBattleSnake.py +++ b/snakes/BestBattleSnake.py @@ -33,6 +33,7 @@ class BestBattleSnake(TemplateSnake): self.duel_style = self._get_duel_style() def _get_duel_style(self): + """Resolve duel tuning style from `BATTLE_SNAKE_DUEL_STYLE` or `DUEL_STYLE`.""" value = os.getenv("BATTLE_SNAKE_DUEL_STYLE") if value is None: value = os.getenv("DUEL_STYLE", "balanced") @@ -43,6 +44,7 @@ class BestBattleSnake(TemplateSnake): return style def _duel_weights(self, style): + """Return score multipliers for the selected duel style preset.""" if style == "safe": return { "head_pressure": 0.65, @@ -62,6 +64,10 @@ class BestBattleSnake(TemplateSnake): } def choose_move(self, game_data): + """Pick the next move from a Battlesnake move request. + + Docs: https://docs.battlesnake.com/api/example-move + """ self.game_board = game_data self.calculations = [] self.duel_style = self._get_duel_style() @@ -331,6 +337,7 @@ class BestBattleSnake(TemplateSnake): return best_move def _choose_duel_move(self, safe_moves, my_body, my_len, my_health, food_set, hazard_set, other_snakes, enemy_attack_map, enemy_can_grow_cache, previous_hazard_set, hazard_damage, width, height): + """Score and select a move for one-vs-one games.""" duel_weights = self._duel_weights(self.duel_style) enemy = other_snakes[0] enemy_head = (enemy["head"]["x"], enemy["head"]["y"]) @@ -501,6 +508,7 @@ class BestBattleSnake(TemplateSnake): return random.choice(top_moves), scores def _choose_constrictor_move(self, safe_moves, my_body, my_len, other_snakes, food_set, enemy_attack_map, enemy_heads, enemy_can_grow_cache, width, height): + """Score and select a move for constrictor games.""" scores: dict[str, float] = {} move_safety: dict[str, dict[str, Any]] = {} @@ -595,6 +603,7 @@ class BestBattleSnake(TemplateSnake): return random.choice(top_moves), scores def _legal_moves(self, my_head, my_body, other_snakes, food_set, is_constrictor, width, height): + """Return legal immediate moves after body, wall, and tail checks.""" occupied = self._occupied_cells(my_body, other_snakes) own_tail = (my_body[-1]["x"], my_body[-1]["y"]) own_tail_stacked = self._is_tail_stacked(my_body) @@ -622,12 +631,14 @@ class BestBattleSnake(TemplateSnake): return safe_moves def _occupied_cells(self, my_body, other_snakes): + """Build a set of occupied coordinates for all snake bodies.""" occupied = {(segment["x"], segment["y"]) for segment in my_body} for snake in other_snakes: occupied |= {(segment["x"], segment["y"]) for segment in snake["body"]} return occupied def _simulation_blocked(self, future_body, other_snakes, food_set, is_constrictor, enemy_can_grow_cache=None): + """Build blocked cells for evaluating the board one turn ahead.""" blocked = {(segment["x"], segment["y"]) for segment in future_body} if not is_constrictor and not self._is_tail_stacked(future_body): @@ -659,6 +670,7 @@ class BestBattleSnake(TemplateSnake): return blocked def _build_enemy_attack_map(self, my_snake, other_snakes, food_set, is_constrictor, width, height, enemy_can_grow_cache=None): + """Map cells enemies can contest next turn to their effective length.""" occupied = self._occupied_cells(my_snake["body"], other_snakes) my_body_points = {(segment["x"], segment["y"]) for segment in my_snake["body"]} attack_map = {} @@ -701,6 +713,7 @@ class BestBattleSnake(TemplateSnake): return attack_map def _future_body(self, current_body, next_head, ate_food, is_constrictor): + """Simulate future body segments after a candidate move.""" next_body = [next_head] next_body.extend(current_body) @@ -711,6 +724,7 @@ class BestBattleSnake(TemplateSnake): return next_body def _can_step_on_own_tail(self, point, own_tail, own_tail_is_stacked, ate_food, is_constrictor): + """Return whether stepping onto our tail is allowed this turn.""" if is_constrictor: return False if ate_food: @@ -720,11 +734,13 @@ class BestBattleSnake(TemplateSnake): return point == own_tail def _is_tail_stacked(self, body): + """Check whether tail overlaps the previous body segment.""" if len(body) < 2: return False return body[-1]["x"] == body[-2]["x"] and body[-1]["y"] == body[-2]["y"] def _enemy_can_grow_this_turn(self, snake, food_set): + """Return True if an enemy can eat food in one move.""" head = snake["head"] for dx, dy in self.DIRECTIONS.values(): if (head["x"] + dx, head["y"] + dy) in food_set: @@ -732,6 +748,10 @@ class BestBattleSnake(TemplateSnake): return False def _hazard_damage_per_turn(self, game_data): + """Read royale hazard damage from ruleset settings. + + Docs: https://docs.battlesnake.com/maps/royale + """ ruleset = {} if hasattr(game_data, "get_ruleset"): ruleset = game_data.get_ruleset() or {} @@ -741,6 +761,10 @@ class BestBattleSnake(TemplateSnake): return int(settings.get("hazardDamagePerTurn", 15)) def _hazard_is_active(self, point, ate_food, hazard_set, previous_hazard_set): + """Apply royale hazard grace and food-exception behavior. + + Docs: https://docs.battlesnake.com/maps/royale + """ if point not in hazard_set: return False if ate_food: @@ -748,6 +772,7 @@ class BestBattleSnake(TemplateSnake): return point in previous_hazard_set def _nearest_food_distance(self, start, food_set, blocked, width, height): + """Compute shortest reachable distance to any food using BFS.""" if not food_set: return None queue = deque([(start, 0)]) @@ -771,6 +796,7 @@ class BestBattleSnake(TemplateSnake): return None def _path_distance(self, start, goal, blocked, width, height): + """Compute shortest path distance between two cells.""" queue = deque([(start, 0)]) seen = {start} @@ -792,6 +818,7 @@ class BestBattleSnake(TemplateSnake): return None def _flood_fill_count(self, start, blocked, width, height): + """Count reachable cells from `start` using flood fill.""" queue = deque([start]) seen = {start} @@ -810,6 +837,7 @@ class BestBattleSnake(TemplateSnake): return len(seen) def _open_neighbor_count(self, start, blocked, width, height): + """Count walkable orthogonal neighbors around `start`.""" count = 0 for neighbor in self._neighbors(start): if not self._in_bounds(neighbor, width, height): @@ -820,6 +848,7 @@ class BestBattleSnake(TemplateSnake): return count def _next_turn_option_count(self, future_body, blocked, width, height): + """Estimate options available after the next simulated turn.""" if not future_body: return 0 @@ -835,6 +864,7 @@ class BestBattleSnake(TemplateSnake): return count def _revisit_penalty(self, point): + """Return penalty for revisiting recent head positions.""" if not self.recent_heads: return 0.0 penalty = 0.0 @@ -845,6 +875,7 @@ class BestBattleSnake(TemplateSnake): return penalty def _territory_control_score(self, my_start, enemy_starts, blocked, width, height): + """Estimate territorial advantage versus enemy start positions.""" if not enemy_starts: return 0 @@ -880,6 +911,7 @@ class BestBattleSnake(TemplateSnake): return score def _distance_map(self, start, blocked, width, height): + """Build a BFS distance map from the given start cell.""" queue = deque([(start, 0)]) distances = {start: 0} @@ -898,6 +930,7 @@ class BestBattleSnake(TemplateSnake): return distances def _enemy_confinement_metrics(self, enemy_head, blocked, width, height): + """Return enemy reachable space and immediate exit count.""" enemy_blocked = set(blocked) enemy_blocked.discard(enemy_head) enemy_space = self._flood_fill_count(enemy_head, enemy_blocked, width, height) @@ -907,16 +940,20 @@ class BestBattleSnake(TemplateSnake): return enemy_space, enemy_options def _neighbors(self, point): + """Yield orthogonal neighbor coordinates for a point.""" for dx, dy in self.DIRECTIONS.values(): yield (point[0] + dx, point[1] + dy) def _manhattan(self, a, b): + """Return Manhattan distance between two points.""" return abs(a[0] - b[0]) + abs(a[1] - b[1]) def _in_bounds(self, point, width, height): + """Return True when a point is inside board boundaries.""" return 0 <= point[0] < width and 0 <= point[1] < height def _fallback_move(self, head, width, height): + """Pick the first in-bounds move as emergency fallback.""" for move, (dx, dy) in self.DIRECTIONS.items(): point = (head["x"] + dx, head["y"] + dy) if self._in_bounds(point, width, height):