add python doc strings
This commit is contained in:
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user