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.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'])
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user