add timeout budget when exeaded use quick save move before timeout
This commit is contained in:
+6
-1
@@ -16,12 +16,13 @@ class GameBoard:
|
||||
self.ruleset = ruleset
|
||||
self.map = map
|
||||
self.url = self._get_game_url(True if ruleset["version"] == "cli" else False)
|
||||
self.timeout = 500
|
||||
|
||||
# Setter Functions
|
||||
def _set_snakes(self, snakes:list[dict]):
|
||||
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
|
||||
|
||||
def _set_food(self, food:list[dict]):
|
||||
@@ -67,6 +68,9 @@ class GameBoard:
|
||||
def get_ruleset(self):
|
||||
return self.ruleset
|
||||
|
||||
def get_timeout(self):
|
||||
return self.timeout
|
||||
|
||||
def get_my_snake_head(self):
|
||||
return self.my_snake["head"]
|
||||
|
||||
@@ -97,6 +101,7 @@ class GameBoard:
|
||||
self._set_snakes(game_data['board']['snakes'])
|
||||
|
||||
self._set_turn(game_data["turn"])
|
||||
self.timeout = int(game_data.get('game', {}).get('timeout', 500))
|
||||
|
||||
async def start_game(self, game_data:dict):
|
||||
self.init_snakes = len(game_data['board']['snakes'])
|
||||
|
||||
@@ -2,6 +2,7 @@ from collections.abc import Iterator
|
||||
from collections import deque
|
||||
from typing import Any, cast
|
||||
import random, os
|
||||
from time import perf_counter
|
||||
|
||||
from snakes.TemplateSnake import TemplateSnake
|
||||
|
||||
@@ -76,6 +77,8 @@ class BestBattleSnake(TemplateSnake):
|
||||
self.game_board = game_data
|
||||
self.calculations = []
|
||||
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)
|
||||
turn = game_data.get_turn()
|
||||
@@ -161,6 +164,7 @@ class BestBattleSnake(TemplateSnake):
|
||||
enemy_can_grow_cache=enemy_can_grow_cache,
|
||||
width=width,
|
||||
height=height,
|
||||
deadline=deadline,
|
||||
)
|
||||
self.recent_heads.append(current_head_point)
|
||||
self.last_move = best_move
|
||||
@@ -183,6 +187,7 @@ class BestBattleSnake(TemplateSnake):
|
||||
hazard_damage=hazard_damage,
|
||||
width=width,
|
||||
height=height,
|
||||
deadline=deadline,
|
||||
)
|
||||
self.recent_heads.append(current_head_point)
|
||||
self.last_move = best_move
|
||||
@@ -193,6 +198,8 @@ class BestBattleSnake(TemplateSnake):
|
||||
scores:dict[str, float] = {}
|
||||
move_safety:dict[str, dict[str, Any]] = {}
|
||||
for move, pos in safe_moves.items():
|
||||
if self._time_exceeded(deadline):
|
||||
break
|
||||
point = (pos["x"], pos["y"])
|
||||
ate_food = point in food_set
|
||||
|
||||
@@ -307,6 +314,18 @@ class BestBattleSnake(TemplateSnake):
|
||||
|
||||
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 = [
|
||||
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)
|
||||
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."""
|
||||
duel_weights = self._duel_weights(self.duel_style)
|
||||
enemy = other_snakes[0]
|
||||
@@ -354,6 +373,8 @@ class BestBattleSnake(TemplateSnake):
|
||||
move_safety:dict[str, dict[str, Any]] = {}
|
||||
|
||||
for move, pos in safe_moves.items():
|
||||
if self._time_exceeded(deadline):
|
||||
break
|
||||
point = (pos["x"], pos["y"])
|
||||
ate_food = point in food_set
|
||||
|
||||
@@ -509,18 +530,23 @@ class BestBattleSnake(TemplateSnake):
|
||||
else:
|
||||
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)
|
||||
top_moves = [
|
||||
move for move in considered_moves if best_score - scores[move] <= 1.5
|
||||
]
|
||||
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."""
|
||||
scores:dict[str, float] = {}
|
||||
move_safety:dict[str, dict[str, Any]] = {}
|
||||
|
||||
for move, pos in safe_moves.items():
|
||||
if self._time_exceeded(deadline):
|
||||
break
|
||||
point = (pos["x"], pos["y"])
|
||||
future_body = self._future_body(
|
||||
my_body, pos, ate_food=False, is_constrictor=True
|
||||
@@ -604,6 +630,9 @@ class BestBattleSnake(TemplateSnake):
|
||||
else:
|
||||
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)
|
||||
top_moves = [
|
||||
move for move in considered_moves if best_score - scores[move] <= 2.0
|
||||
@@ -779,6 +808,12 @@ class BestBattleSnake(TemplateSnake):
|
||||
return False
|
||||
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:
|
||||
"""Compute shortest reachable distance to any food using BFS."""
|
||||
if not food_set:
|
||||
|
||||
Reference in New Issue
Block a user