add python doc strings

This commit is contained in:
2026-04-03 20:21:12 +02:00
parent eace1872d7
commit d7bd89eae9
+37
View File
@@ -33,6 +33,7 @@ class BestBattleSnake(TemplateSnake):
self.duel_style = self._get_duel_style() self.duel_style = self._get_duel_style()
def _get_duel_style(self): def _get_duel_style(self):
"""Resolve duel tuning style from `BATTLE_SNAKE_DUEL_STYLE` or `DUEL_STYLE`."""
value = os.getenv("BATTLE_SNAKE_DUEL_STYLE") value = os.getenv("BATTLE_SNAKE_DUEL_STYLE")
if value is None: if value is None:
value = os.getenv("DUEL_STYLE", "balanced") value = os.getenv("DUEL_STYLE", "balanced")
@@ -43,6 +44,7 @@ class BestBattleSnake(TemplateSnake):
return style return style
def _duel_weights(self, style): def _duel_weights(self, style):
"""Return score multipliers for the selected duel style preset."""
if style == "safe": if style == "safe":
return { return {
"head_pressure": 0.65, "head_pressure": 0.65,
@@ -62,6 +64,10 @@ class BestBattleSnake(TemplateSnake):
} }
def choose_move(self, game_data): 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.game_board = game_data
self.calculations = [] self.calculations = []
self.duel_style = self._get_duel_style() self.duel_style = self._get_duel_style()
@@ -331,6 +337,7 @@ class BestBattleSnake(TemplateSnake):
return best_move 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): 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) duel_weights = self._duel_weights(self.duel_style)
enemy = other_snakes[0] enemy = other_snakes[0]
enemy_head = (enemy["head"]["x"], enemy["head"]["y"]) enemy_head = (enemy["head"]["x"], enemy["head"]["y"])
@@ -501,6 +508,7 @@ class BestBattleSnake(TemplateSnake):
return random.choice(top_moves), scores 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): 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] = {} scores: dict[str, float] = {}
move_safety: dict[str, dict[str, Any]] = {} move_safety: dict[str, dict[str, Any]] = {}
@@ -595,6 +603,7 @@ class BestBattleSnake(TemplateSnake):
return random.choice(top_moves), scores return random.choice(top_moves), scores
def _legal_moves(self, my_head, my_body, other_snakes, food_set, is_constrictor, width, height): 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) occupied = self._occupied_cells(my_body, other_snakes)
own_tail = (my_body[-1]["x"], my_body[-1]["y"]) own_tail = (my_body[-1]["x"], my_body[-1]["y"])
own_tail_stacked = self._is_tail_stacked(my_body) own_tail_stacked = self._is_tail_stacked(my_body)
@@ -622,12 +631,14 @@ class BestBattleSnake(TemplateSnake):
return safe_moves return safe_moves
def _occupied_cells(self, my_body, other_snakes): 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} occupied = {(segment["x"], segment["y"]) for segment in my_body}
for snake in other_snakes: for snake in other_snakes:
occupied |= {(segment["x"], segment["y"]) for segment in snake["body"]} occupied |= {(segment["x"], segment["y"]) for segment in snake["body"]}
return occupied return occupied
def _simulation_blocked(self, future_body, other_snakes, food_set, is_constrictor, enemy_can_grow_cache=None): 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} blocked = {(segment["x"], segment["y"]) for segment in future_body}
if not is_constrictor and not self._is_tail_stacked(future_body): if not is_constrictor and not self._is_tail_stacked(future_body):
@@ -659,6 +670,7 @@ class BestBattleSnake(TemplateSnake):
return blocked return blocked
def _build_enemy_attack_map(self, my_snake, other_snakes, food_set, is_constrictor, width, height, enemy_can_grow_cache=None): 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) occupied = self._occupied_cells(my_snake["body"], other_snakes)
my_body_points = {(segment["x"], segment["y"]) for segment in my_snake["body"]} my_body_points = {(segment["x"], segment["y"]) for segment in my_snake["body"]}
attack_map = {} attack_map = {}
@@ -701,6 +713,7 @@ class BestBattleSnake(TemplateSnake):
return attack_map return attack_map
def _future_body(self, current_body, next_head, ate_food, is_constrictor): 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 = [next_head]
next_body.extend(current_body) next_body.extend(current_body)
@@ -711,6 +724,7 @@ class BestBattleSnake(TemplateSnake):
return next_body return next_body
def _can_step_on_own_tail(self, point, own_tail, own_tail_is_stacked, ate_food, is_constrictor): 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: if is_constrictor:
return False return False
if ate_food: if ate_food:
@@ -720,11 +734,13 @@ class BestBattleSnake(TemplateSnake):
return point == own_tail return point == own_tail
def _is_tail_stacked(self, body): def _is_tail_stacked(self, body):
"""Check whether tail overlaps the previous body segment."""
if len(body) < 2: if len(body) < 2:
return False return False
return body[-1]["x"] == body[-2]["x"] and body[-1]["y"] == body[-2]["y"] return body[-1]["x"] == body[-2]["x"] and body[-1]["y"] == body[-2]["y"]
def _enemy_can_grow_this_turn(self, snake, food_set): def _enemy_can_grow_this_turn(self, snake, food_set):
"""Return True if an enemy can eat food in one move."""
head = snake["head"] head = snake["head"]
for dx, dy in self.DIRECTIONS.values(): for dx, dy in self.DIRECTIONS.values():
if (head["x"] + dx, head["y"] + dy) in food_set: if (head["x"] + dx, head["y"] + dy) in food_set:
@@ -732,6 +748,10 @@ class BestBattleSnake(TemplateSnake):
return False return False
def _hazard_damage_per_turn(self, game_data): def _hazard_damage_per_turn(self, game_data):
"""Read royale hazard damage from ruleset settings.
Docs: https://docs.battlesnake.com/maps/royale
"""
ruleset = {} ruleset = {}
if hasattr(game_data, "get_ruleset"): if hasattr(game_data, "get_ruleset"):
ruleset = game_data.get_ruleset() or {} ruleset = game_data.get_ruleset() or {}
@@ -741,6 +761,10 @@ class BestBattleSnake(TemplateSnake):
return int(settings.get("hazardDamagePerTurn", 15)) return int(settings.get("hazardDamagePerTurn", 15))
def _hazard_is_active(self, point, ate_food, hazard_set, previous_hazard_set): 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: if point not in hazard_set:
return False return False
if ate_food: if ate_food:
@@ -748,6 +772,7 @@ class BestBattleSnake(TemplateSnake):
return point in previous_hazard_set return point in previous_hazard_set
def _nearest_food_distance(self, start, food_set, blocked, width, height): def _nearest_food_distance(self, start, food_set, blocked, width, height):
"""Compute shortest reachable distance to any food using BFS."""
if not food_set: if not food_set:
return None return None
queue = deque([(start, 0)]) queue = deque([(start, 0)])
@@ -771,6 +796,7 @@ class BestBattleSnake(TemplateSnake):
return None return None
def _path_distance(self, start, goal, blocked, width, height): def _path_distance(self, start, goal, blocked, width, height):
"""Compute shortest path distance between two cells."""
queue = deque([(start, 0)]) queue = deque([(start, 0)])
seen = {start} seen = {start}
@@ -792,6 +818,7 @@ class BestBattleSnake(TemplateSnake):
return None return None
def _flood_fill_count(self, start, blocked, width, height): def _flood_fill_count(self, start, blocked, width, height):
"""Count reachable cells from `start` using flood fill."""
queue = deque([start]) queue = deque([start])
seen = {start} seen = {start}
@@ -810,6 +837,7 @@ class BestBattleSnake(TemplateSnake):
return len(seen) return len(seen)
def _open_neighbor_count(self, start, blocked, width, height): def _open_neighbor_count(self, start, blocked, width, height):
"""Count walkable orthogonal neighbors around `start`."""
count = 0 count = 0
for neighbor in self._neighbors(start): for neighbor in self._neighbors(start):
if not self._in_bounds(neighbor, width, height): if not self._in_bounds(neighbor, width, height):
@@ -820,6 +848,7 @@ class BestBattleSnake(TemplateSnake):
return count return count
def _next_turn_option_count(self, future_body, blocked, width, height): def _next_turn_option_count(self, future_body, blocked, width, height):
"""Estimate options available after the next simulated turn."""
if not future_body: if not future_body:
return 0 return 0
@@ -835,6 +864,7 @@ class BestBattleSnake(TemplateSnake):
return count return count
def _revisit_penalty(self, point): def _revisit_penalty(self, point):
"""Return penalty for revisiting recent head positions."""
if not self.recent_heads: if not self.recent_heads:
return 0.0 return 0.0
penalty = 0.0 penalty = 0.0
@@ -845,6 +875,7 @@ class BestBattleSnake(TemplateSnake):
return penalty return penalty
def _territory_control_score(self, my_start, enemy_starts, blocked, width, height): def _territory_control_score(self, my_start, enemy_starts, blocked, width, height):
"""Estimate territorial advantage versus enemy start positions."""
if not enemy_starts: if not enemy_starts:
return 0 return 0
@@ -880,6 +911,7 @@ class BestBattleSnake(TemplateSnake):
return score return score
def _distance_map(self, start, blocked, width, height): def _distance_map(self, start, blocked, width, height):
"""Build a BFS distance map from the given start cell."""
queue = deque([(start, 0)]) queue = deque([(start, 0)])
distances = {start: 0} distances = {start: 0}
@@ -898,6 +930,7 @@ class BestBattleSnake(TemplateSnake):
return distances return distances
def _enemy_confinement_metrics(self, enemy_head, blocked, width, height): def _enemy_confinement_metrics(self, enemy_head, blocked, width, height):
"""Return enemy reachable space and immediate exit count."""
enemy_blocked = set(blocked) enemy_blocked = set(blocked)
enemy_blocked.discard(enemy_head) enemy_blocked.discard(enemy_head)
enemy_space = self._flood_fill_count(enemy_head, enemy_blocked, width, height) enemy_space = self._flood_fill_count(enemy_head, enemy_blocked, width, height)
@@ -907,16 +940,20 @@ class BestBattleSnake(TemplateSnake):
return enemy_space, enemy_options return enemy_space, enemy_options
def _neighbors(self, point): def _neighbors(self, point):
"""Yield orthogonal neighbor coordinates for a point."""
for dx, dy in self.DIRECTIONS.values(): for dx, dy in self.DIRECTIONS.values():
yield (point[0] + dx, point[1] + dy) yield (point[0] + dx, point[1] + dy)
def _manhattan(self, a, b): def _manhattan(self, a, b):
"""Return Manhattan distance between two points."""
return abs(a[0] - b[0]) + abs(a[1] - b[1]) return abs(a[0] - b[0]) + abs(a[1] - b[1])
def _in_bounds(self, point, width, height): 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 return 0 <= point[0] < width and 0 <= point[1] < height
def _fallback_move(self, head, width, 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(): for move, (dx, dy) in self.DIRECTIONS.items():
point = (head["x"] + dx, head["y"] + dy) point = (head["x"] + dx, head["y"] + dy)
if self._in_bounds(point, width, height): if self._in_bounds(point, width, height):