add timeout budget when exeaded use quick save move before timeout

This commit is contained in:
2026-04-03 21:17:08 +02:00
parent f124ce6f96
commit 8f6bc3cfdd
2 changed files with 43 additions and 3 deletions
+6 -1
View File
@@ -16,12 +16,13 @@ class GameBoard:
self.ruleset = ruleset self.ruleset = ruleset
self.map = map self.map = map
self.url = self._get_game_url(True if ruleset["version"] == "cli" else False) self.url = self._get_game_url(True if ruleset["version"] == "cli" else False)
self.timeout = 500
# Setter Functions # Setter Functions
def _set_snakes(self, snakes:list[dict]): def _set_snakes(self, snakes:list[dict]):
self.other_snakes = [ x for x in snakes if x["id"] != self.my_snake["id"] ] self.other_snakes = [ x for x in snakes if x["id"] != self.my_snake["id"] ]
def _set_my_snake(self, my_snake:str): def _set_my_snake(self, my_snake:dict):
self.my_snake = my_snake self.my_snake = my_snake
def _set_food(self, food:list[dict]): def _set_food(self, food:list[dict]):
@@ -67,6 +68,9 @@ class GameBoard:
def get_ruleset(self): def get_ruleset(self):
return self.ruleset return self.ruleset
def get_timeout(self):
return self.timeout
def get_my_snake_head(self): def get_my_snake_head(self):
return self.my_snake["head"] return self.my_snake["head"]
@@ -97,6 +101,7 @@ class GameBoard:
self._set_snakes(game_data['board']['snakes']) self._set_snakes(game_data['board']['snakes'])
self._set_turn(game_data["turn"]) self._set_turn(game_data["turn"])
self.timeout = int(game_data.get('game', {}).get('timeout', 500))
async def start_game(self, game_data:dict): async def start_game(self, game_data:dict):
self.init_snakes = len(game_data['board']['snakes']) self.init_snakes = len(game_data['board']['snakes'])
+37 -2
View File
@@ -2,6 +2,7 @@ from collections.abc import Iterator
from collections import deque from collections import deque
from typing import Any, cast from typing import Any, cast
import random, os import random, os
from time import perf_counter
from snakes.TemplateSnake import TemplateSnake from snakes.TemplateSnake import TemplateSnake
@@ -76,6 +77,8 @@ class BestBattleSnake(TemplateSnake):
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()
timeout_ms = (game_data.get_timeout() if hasattr(game_data, "get_timeout") else 500)
deadline = perf_counter() + (max(50, int(timeout_ms) - 120) / 1000.0)
game_id = getattr(game_data, "id", None) game_id = getattr(game_data, "id", None)
turn = game_data.get_turn() turn = game_data.get_turn()
@@ -161,6 +164,7 @@ class BestBattleSnake(TemplateSnake):
enemy_can_grow_cache=enemy_can_grow_cache, enemy_can_grow_cache=enemy_can_grow_cache,
width=width, width=width,
height=height, height=height,
deadline=deadline,
) )
self.recent_heads.append(current_head_point) self.recent_heads.append(current_head_point)
self.last_move = best_move self.last_move = best_move
@@ -183,6 +187,7 @@ class BestBattleSnake(TemplateSnake):
hazard_damage=hazard_damage, hazard_damage=hazard_damage,
width=width, width=width,
height=height, height=height,
deadline=deadline,
) )
self.recent_heads.append(current_head_point) self.recent_heads.append(current_head_point)
self.last_move = best_move self.last_move = best_move
@@ -193,6 +198,8 @@ class BestBattleSnake(TemplateSnake):
scores:dict[str, float] = {} scores:dict[str, float] = {}
move_safety:dict[str, dict[str, Any]] = {} move_safety:dict[str, dict[str, Any]] = {}
for move, pos in safe_moves.items(): for move, pos in safe_moves.items():
if self._time_exceeded(deadline):
break
point = (pos["x"], pos["y"]) point = (pos["x"], pos["y"])
ate_food = point in food_set ate_food = point in food_set
@@ -307,6 +314,18 @@ class BestBattleSnake(TemplateSnake):
scores[move] = round(score, 5) scores[move] = round(score, 5)
if not scores:
quick_move = (
self.last_move
if self.last_move in safe_moves
else random.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.previous_hazards = set(hazard_set)
return quick_move
survivable_moves = [ survivable_moves = [
move for move, data in move_safety.items() if data["is_survivable"] move for move, data in move_safety.items() if data["is_survivable"]
] ]
@@ -341,7 +360,7 @@ class BestBattleSnake(TemplateSnake):
self.previous_hazards = set(hazard_set) self.previous_hazards = set(hazard_set)
return best_move return best_move
def _choose_duel_move(self, safe_moves:MoveMap, my_body:list[Coord], my_len:int, my_health:int, food_set:set[Point], hazard_set:set[Point], other_snakes:list[SnakeState],enemy_attack_map:AttackMap, enemy_can_grow_cache:dict[Any, bool], previous_hazard_set:set[Point], hazard_damage:int, width:int, height:int) -> tuple[str, dict[str, float]]: def _choose_duel_move(self, safe_moves:MoveMap, my_body:list[Coord], my_len:int, my_health:int, food_set:set[Point], hazard_set:set[Point], other_snakes:list[SnakeState], enemy_attack_map:AttackMap, enemy_can_grow_cache:dict[Any, bool], previous_hazard_set:set[Point], hazard_damage:int, width:int, height:int, deadline:float|None=None) -> tuple[str, dict[str, float]]:
"""Score and select a move for one-vs-one games.""" """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]
@@ -354,6 +373,8 @@ class BestBattleSnake(TemplateSnake):
move_safety:dict[str, dict[str, Any]] = {} move_safety:dict[str, dict[str, Any]] = {}
for move, pos in safe_moves.items(): for move, pos in safe_moves.items():
if self._time_exceeded(deadline):
break
point = (pos["x"], pos["y"]) point = (pos["x"], pos["y"])
ate_food = point in food_set ate_food = point in food_set
@@ -509,18 +530,23 @@ class BestBattleSnake(TemplateSnake):
else: else:
considered_moves = list(scores.keys()) considered_moves = list(scores.keys())
if not scores:
return random.choice(list(safe_moves.keys())), {}
best_score = max(scores[move] for move in considered_moves) best_score = max(scores[move] for move in considered_moves)
top_moves = [ top_moves = [
move for move in considered_moves if best_score - scores[move] <= 1.5 move for move in considered_moves if best_score - scores[move] <= 1.5
] ]
return random.choice(top_moves), scores return random.choice(top_moves), scores
def _choose_constrictor_move(self, safe_moves:MoveMap, my_body:list[Coord], my_len:int, other_snakes:list[SnakeState], food_set:set[Point], enemy_attack_map:AttackMap, enemy_heads:list[Point], enemy_can_grow_cache:dict[Any, bool], width:int, height:int) -> tuple[str, dict[str, float]]: def _choose_constrictor_move(self, safe_moves:MoveMap, my_body:list[Coord], my_len:int, other_snakes:list[SnakeState], food_set:set[Point], enemy_attack_map:AttackMap, enemy_heads:list[Point], enemy_can_grow_cache:dict[Any, bool], width:int, height:int, deadline:float|None=None) -> tuple[str, dict[str, float]]:
"""Score and select a move for constrictor games.""" """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]] = {}
for move, pos in safe_moves.items(): for move, pos in safe_moves.items():
if self._time_exceeded(deadline):
break
point = (pos["x"], pos["y"]) point = (pos["x"], pos["y"])
future_body = self._future_body( future_body = self._future_body(
my_body, pos, ate_food=False, is_constrictor=True my_body, pos, ate_food=False, is_constrictor=True
@@ -604,6 +630,9 @@ class BestBattleSnake(TemplateSnake):
else: else:
considered_moves = list(scores.keys()) considered_moves = list(scores.keys())
if not scores:
return random.choice(list(safe_moves.keys())), {}
best_score = max(scores[move] for move in considered_moves) best_score = max(scores[move] for move in considered_moves)
top_moves = [ top_moves = [
move for move in considered_moves if best_score - scores[move] <= 2.0 move for move in considered_moves if best_score - scores[move] <= 2.0
@@ -779,6 +808,12 @@ class BestBattleSnake(TemplateSnake):
return False return False
return point in previous_hazard_set return point in previous_hazard_set
def _time_exceeded(self, deadline:float|None) -> bool:
"""Return True when the move-calculation time budget is exhausted."""
if deadline is None:
return False
return perf_counter() >= deadline
def _nearest_food_distance(self, start:Point, food_set:set[Point], blocked:set[Point], width:int, height:int) -> int|None: def _nearest_food_distance(self, start:Point, food_set:set[Point], blocked:set[Point], width:int, height:int) -> int|None:
"""Compute shortest reachable distance to any food using BFS.""" """Compute shortest reachable distance to any food using BFS."""
if not food_set: if not food_set: