update snakes with new code
Build and Push Docker Container / build-and-push (push) Successful in 1m21s
Build and Push Docker Container / build-and-push (push) Successful in 1m21s
This commit is contained in:
+106
-35
@@ -2,7 +2,7 @@ from collections.abc import Iterator
|
||||
from collections import deque
|
||||
from typing import Any, cast
|
||||
from time import perf_counter
|
||||
import random, os
|
||||
import os
|
||||
|
||||
from server.dataset.RLBootstrapDataset import RLBootstrapDataset
|
||||
|
||||
@@ -10,7 +10,7 @@ from snakes.TemplateSnake import TemplateSnake
|
||||
from server.GameBoard import GameBoard
|
||||
|
||||
class BestBattleSnake(TemplateSnake):
|
||||
VERSION = "2.6.1"
|
||||
VERSION = "2.7.0"
|
||||
Point = tuple[int, int]
|
||||
Coord = dict[str, int]
|
||||
SnakeState = dict[str, Any]
|
||||
@@ -39,6 +39,7 @@ class BestBattleSnake(TemplateSnake):
|
||||
self.last_move = None
|
||||
self.last_game_id = None
|
||||
self.previous_hazards = set()
|
||||
self.hazard_stack_counts:dict[tuple[int, int], int] = {}
|
||||
self.duel_style = self._get_duel_style()
|
||||
self.timeout_buffer_ms = self._get_timeout_buffer_ms()
|
||||
self.rl_bootstrap = RLBootstrapDataset()
|
||||
@@ -124,15 +125,18 @@ class BestBattleSnake(TemplateSnake):
|
||||
width = game_data.get_width()
|
||||
height = game_data.get_height()
|
||||
board_area = max(1, width * height)
|
||||
occupancy_ratio = my_len / board_area
|
||||
preserve_space_mode = occupancy_ratio >= 0.34 and my_health > 35
|
||||
foods = game_data.get_food()
|
||||
hazards = game_data.get_hazard()
|
||||
other_snakes = game_data.get_other_snakes()
|
||||
is_constrictor = game_data.get_type() == "constrictor"
|
||||
|
||||
total_body_cells = len(my_body) + sum(len(snake["body"]) for snake in other_snakes)
|
||||
occupancy_ratio = total_body_cells / board_area
|
||||
preserve_space_mode = occupancy_ratio >= 0.34 and my_health > 35
|
||||
|
||||
food_set = {(food["x"], food["y"]) for food in foods}
|
||||
hazard_set = {(hazard["x"], hazard["y"]) for hazard in hazards}
|
||||
self.hazard_stack_counts = self._hazard_stack_count_map(hazards)
|
||||
previous_hazard_set = set(self.previous_hazards)
|
||||
hazard_damage = self._hazard_damage_per_turn(game_data)
|
||||
enemy_heads = [
|
||||
@@ -159,13 +163,11 @@ class BestBattleSnake(TemplateSnake):
|
||||
fallback = self._fallback_move(my_head, width, height)
|
||||
self.recent_heads.append(current_head_point)
|
||||
self.last_move = fallback
|
||||
self.add_to_history(
|
||||
{
|
||||
"turn": turn,
|
||||
"move": fallback,
|
||||
"reason": "no_safe_moves",
|
||||
}
|
||||
)
|
||||
self.add_to_history({
|
||||
"turn": turn,
|
||||
"move": fallback,
|
||||
"reason": "no_safe_moves",
|
||||
})
|
||||
self.rl_bootstrap.record_sample(game_data, fallback, safe_moves, "no_safe_moves")
|
||||
self.previous_hazards = set(hazard_set)
|
||||
return fallback
|
||||
@@ -302,9 +304,16 @@ class BestBattleSnake(TemplateSnake):
|
||||
else:
|
||||
score += 70.0
|
||||
|
||||
health_after_move = 100 if ate_food else my_health - 1
|
||||
hazard_active = self._hazard_is_active(point, ate_food, hazard_set, previous_hazard_set)
|
||||
if hazard_active:
|
||||
health_after_move -= self._hazard_cell_damage(point, hazard_damage)
|
||||
|
||||
hunger_urgency = max(0.0, (60.0 - my_health) / 60.0)
|
||||
if nearest_food_dist is not None:
|
||||
score += (28.0 + 70.0 * hunger_urgency) / (nearest_food_dist + 1)
|
||||
if my_health < 40 and nearest_food_dist >= health_after_move:
|
||||
score -= 800.0 + (40 - my_health) * 20.0
|
||||
elif my_health < 30:
|
||||
score -= 150.0
|
||||
|
||||
@@ -323,7 +332,7 @@ class BestBattleSnake(TemplateSnake):
|
||||
score -= 40.0
|
||||
|
||||
if point in hazard_set:
|
||||
hazard_scale = max(0.5, hazard_damage / 14.0)
|
||||
hazard_scale = max(0.5, self._hazard_cell_damage(point, hazard_damage) / 14.0)
|
||||
if not ate_food:
|
||||
score -= (70.0 if my_health > 35 else 250.0) * hazard_scale
|
||||
|
||||
@@ -338,10 +347,6 @@ class BestBattleSnake(TemplateSnake):
|
||||
):
|
||||
score -= 20.0
|
||||
|
||||
health_after_move = 100 if ate_food else my_health - 1
|
||||
hazard_active = self._hazard_is_active(point, ate_food, hazard_set, previous_hazard_set)
|
||||
if hazard_active:
|
||||
health_after_move -= hazard_damage
|
||||
if health_after_move <= 0:
|
||||
score -= 10000.0
|
||||
|
||||
@@ -364,11 +369,15 @@ class BestBattleSnake(TemplateSnake):
|
||||
quick_move = (
|
||||
self.last_move
|
||||
if self.last_move in safe_moves
|
||||
else random.choice(list(safe_moves.keys()))
|
||||
else self._deterministic_move_choice(list(safe_moves.keys()))
|
||||
)
|
||||
self.recent_heads.append(current_head_point)
|
||||
self.last_move = quick_move
|
||||
self.add_to_history({"turn": turn, "move": quick_move, "reason": "timeout_budget"})
|
||||
self.add_to_history({
|
||||
"turn": turn,
|
||||
"move": quick_move,
|
||||
"reason": "timeout_budget"
|
||||
})
|
||||
self.rl_bootstrap.record_sample(game_data, quick_move, safe_moves, "timeout_budget")
|
||||
self.previous_hazards = set(hazard_set)
|
||||
return quick_move
|
||||
@@ -412,7 +421,11 @@ class BestBattleSnake(TemplateSnake):
|
||||
)
|
||||
self.recent_heads.append(current_head_point)
|
||||
self.last_move = best_move
|
||||
self.add_to_history({"turn": turn, "move": best_move, "scores": scores})
|
||||
self.add_to_history({
|
||||
"turn": turn,
|
||||
"move": best_move,
|
||||
"scores": scores
|
||||
})
|
||||
self.rl_bootstrap.record_sample(game_data, best_move, safe_moves, "multi", scores)
|
||||
self.previous_hazards = set(hazard_set)
|
||||
return best_move
|
||||
@@ -528,11 +541,18 @@ class BestBattleSnake(TemplateSnake):
|
||||
if direct_head_distance == 1:
|
||||
score -= 180.0 * duel_weights["distance_safety"]
|
||||
|
||||
health_after_move = 100 if ate_food else my_health - 1
|
||||
hazard_active = self._hazard_is_active(point, ate_food, hazard_set, previous_hazard_set)
|
||||
if hazard_active:
|
||||
health_after_move -= self._hazard_cell_damage(point, hazard_damage)
|
||||
|
||||
hunger_urgency = max(0.0, (65.0 - my_health) / 65.0)
|
||||
if nearest_food_dist is not None:
|
||||
score += (
|
||||
(25.0 + 90.0 * hunger_urgency) * duel_weights["food_bias"]
|
||||
) / (nearest_food_dist + 1)
|
||||
if my_health < 40 and nearest_food_dist >= health_after_move:
|
||||
score -= 800.0 + (40 - my_health) * 20.0
|
||||
|
||||
if ate_food:
|
||||
if likely_dead_end:
|
||||
@@ -546,7 +566,7 @@ class BestBattleSnake(TemplateSnake):
|
||||
score -= 50.0
|
||||
|
||||
if point in hazard_set:
|
||||
hazard_scale = max(0.5, hazard_damage / 14.0)
|
||||
hazard_scale = max(0.5, self._hazard_cell_damage(point, hazard_damage) / 14.0)
|
||||
if not ate_food:
|
||||
score -= (70.0 if my_health > 35 else 250.0) * hazard_scale
|
||||
|
||||
@@ -561,10 +581,6 @@ class BestBattleSnake(TemplateSnake):
|
||||
):
|
||||
score -= 20.0
|
||||
|
||||
health_after_move = 100 if ate_food else my_health - 1
|
||||
hazard_active = self._hazard_is_active(point, ate_food, hazard_set, previous_hazard_set)
|
||||
if hazard_active:
|
||||
health_after_move -= hazard_damage
|
||||
if health_after_move <= 0:
|
||||
score -= 10000.0
|
||||
|
||||
@@ -604,7 +620,7 @@ class BestBattleSnake(TemplateSnake):
|
||||
considered_moves = list(scores.keys())
|
||||
|
||||
if not scores:
|
||||
return random.choice(list(safe_moves.keys())), {}
|
||||
return self._deterministic_move_choice(list(safe_moves.keys())), {}
|
||||
|
||||
best_move = self._pick_best_with_future_planning(
|
||||
considered_moves=considered_moves,
|
||||
@@ -744,7 +760,7 @@ class BestBattleSnake(TemplateSnake):
|
||||
considered_moves = list(scores.keys())
|
||||
|
||||
if not scores:
|
||||
return random.choice(list(safe_moves.keys())), {}
|
||||
return self._deterministic_move_choice(list(safe_moves.keys())), {}
|
||||
|
||||
best_move = self._pick_best_with_future_planning(
|
||||
considered_moves=considered_moves,
|
||||
@@ -767,6 +783,15 @@ class BestBattleSnake(TemplateSnake):
|
||||
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)
|
||||
vacatable_enemy_tails: set[tuple[int, int]] = set()
|
||||
if not is_constrictor:
|
||||
for snake in other_snakes:
|
||||
if self._is_tail_stacked(snake["body"]):
|
||||
continue
|
||||
if self._enemy_can_grow_this_turn(snake, food_set):
|
||||
continue
|
||||
tail = snake["body"][-1]
|
||||
vacatable_enemy_tails.add((tail["x"], tail["y"]))
|
||||
|
||||
safe_moves = {}
|
||||
for move, (dx, dy) in self.DIRECTIONS.items():
|
||||
@@ -783,7 +808,12 @@ class BestBattleSnake(TemplateSnake):
|
||||
is_constrictor=is_constrictor,
|
||||
)
|
||||
|
||||
if point in occupied and not can_step_on_tail:
|
||||
can_step_on_enemy_tail = point in vacatable_enemy_tails
|
||||
if (
|
||||
point in occupied
|
||||
and not can_step_on_tail
|
||||
and not can_step_on_enemy_tail
|
||||
):
|
||||
continue
|
||||
|
||||
safe_moves[move] = {"x": point[0], "y": point[1]}
|
||||
@@ -859,9 +889,15 @@ class BestBattleSnake(TemplateSnake):
|
||||
and not enemy_tail_stacked
|
||||
and not enemy_can_grow
|
||||
)
|
||||
can_contest_my_tail = (not is_constrictor and point == my_tail and not my_tail_stacked)
|
||||
can_contest_my_tail = (
|
||||
not is_constrictor and point == my_tail and not my_tail_stacked
|
||||
)
|
||||
|
||||
if point in occupied and not can_step_on_enemy_tail and not can_contest_my_tail:
|
||||
if (
|
||||
point in occupied
|
||||
and not can_step_on_enemy_tail
|
||||
and not can_contest_my_tail
|
||||
):
|
||||
continue
|
||||
|
||||
# Ignore impossible overlap into our occupied body, but keep our vacatable tail
|
||||
@@ -924,6 +960,19 @@ class BestBattleSnake(TemplateSnake):
|
||||
settings = ruleset.get("settings", {}) if isinstance(ruleset, dict) else {}
|
||||
return int(settings.get("hazardDamagePerTurn", 15))
|
||||
|
||||
def _hazard_stack_count_map(self, hazards:list[Coord]) -> dict[Point, int]:
|
||||
counts: dict[tuple[int, int], int] = {}
|
||||
for hazard in hazards:
|
||||
point = (hazard["x"], hazard["y"])
|
||||
counts[point] = counts.get(point, 0) + 1
|
||||
return counts
|
||||
|
||||
def _hazard_cell_damage(self, point:Point, hazard_damage:int) -> int:
|
||||
stack = self.hazard_stack_counts.get(point, 0)
|
||||
if stack <= 0:
|
||||
return 0
|
||||
return hazard_damage * stack
|
||||
|
||||
def _hazard_is_active(self, point:Point, ate_food:bool, hazard_set:set[Point], previous_hazard_set:set[Point]) -> bool:
|
||||
"""Apply royale hazard grace and food-exception behavior.
|
||||
|
||||
@@ -952,8 +1001,11 @@ class BestBattleSnake(TemplateSnake):
|
||||
if len(top_moves) <= 1:
|
||||
return top_moves[0]
|
||||
|
||||
if self._time_exceeded(deadline) or self._remaining_ms(deadline) < self.future_planning_min_time_ms:
|
||||
return random.choice(top_moves)
|
||||
if (
|
||||
self._time_exceeded(deadline)
|
||||
or self._remaining_ms(deadline) < self.future_planning_min_time_ms
|
||||
):
|
||||
return self._deterministic_move_choice(top_moves, scores)
|
||||
|
||||
candidate_limit = max(1, self.future_planning_branch)
|
||||
candidate_moves = sorted(top_moves, key=lambda move: scores[move], reverse=True)[:candidate_limit]
|
||||
@@ -978,7 +1030,7 @@ class BestBattleSnake(TemplateSnake):
|
||||
lookahead_bonus[move] = bonus
|
||||
|
||||
if not lookahead_bonus:
|
||||
return random.choice(top_moves)
|
||||
return self._deterministic_move_choice(top_moves, scores)
|
||||
|
||||
for move, bonus in lookahead_bonus.items():
|
||||
scores[move] += bonus
|
||||
@@ -989,7 +1041,27 @@ class BestBattleSnake(TemplateSnake):
|
||||
for move in top_moves
|
||||
if refined_best - scores[move] <= max(0.5, tie_window / 2)
|
||||
]
|
||||
return random.choice(refined_top)
|
||||
return self._deterministic_move_choice(refined_top, scores)
|
||||
|
||||
def _move_preference_key(self, move:str) -> tuple[int, int, int]:
|
||||
same_as_last = 1 if self.last_move == move else 0
|
||||
is_reverse = (
|
||||
1
|
||||
if self.last_move is not None and self.OPPOSITE.get(self.last_move) == move
|
||||
else 0
|
||||
)
|
||||
direction_priority = {"up": 3, "left": 2, "right": 1, "down": 0}
|
||||
return same_as_last, -is_reverse, direction_priority.get(move, -1)
|
||||
|
||||
def _deterministic_move_choice(self, moves:list[str], scores:dict[str, float]|None=None) -> str:
|
||||
if not moves:
|
||||
return "up"
|
||||
if scores is None:
|
||||
return max(moves, key=self._move_preference_key)
|
||||
return max(
|
||||
moves,
|
||||
key=lambda move: (scores.get(move, -1e9), self._move_preference_key(move)),
|
||||
)
|
||||
|
||||
def _future_rollout_bonus_for_move(self, move:str, safe_moves:MoveMap, my_body:list[Coord], other_snakes:list[SnakeState], food_set:set[Point], is_constrictor:bool, width:int, height:int, enemy_can_grow_cache:dict[Any, bool]|None, depth:int, branch_limit:int, deadline:float|None) -> float:
|
||||
pos = safe_moves.get(move)
|
||||
@@ -1017,8 +1089,7 @@ class BestBattleSnake(TemplateSnake):
|
||||
)
|
||||
return raw_score * 0.06
|
||||
|
||||
def _future_survival_tree_score(self, my_body:list[Coord], other_snakes:list[SnakeState], food_set:set[Point], is_constrictor:bool, width:int, height:int, enemy_can_grow_cache:dict[Any, bool]|None, depth:int, branch_limit:int, deadline:float|None,
|
||||
) -> float:
|
||||
def _future_survival_tree_score(self, my_body:list[Coord], other_snakes:list[SnakeState], food_set:set[Point], is_constrictor:bool, width:int, height:int, enemy_can_grow_cache:dict[Any, bool]|None, depth:int, branch_limit:int, deadline:float|None) -> float:
|
||||
if depth <= 0 or self._time_exceeded(deadline):
|
||||
return 0.0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user