92 lines
2.8 KiB
Python
92 lines
2.8 KiB
Python
from pathlib import Path
|
|
from typing import Any
|
|
import random, json, os
|
|
|
|
from server.TrainBattleSnakeAI import MOVES, extract_feature_values
|
|
from snakes.TemplateSnake import TemplateSnake
|
|
|
|
class TrainedBattleSnake(TemplateSnake):
|
|
VERSION = "0.1.0"
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.name = "TrainedBattleSnake"
|
|
self.version = self.VERSION
|
|
self._model_path:Path|None=None
|
|
self._model_data:dict[str, Any]|None=None
|
|
|
|
def choose_move(self, game_data) -> str:
|
|
self.game_board = game_data
|
|
self.calculations = []
|
|
|
|
safe_positions = self.find_safe_positions(add_to_calculations=True)
|
|
if not safe_positions:
|
|
self.add_to_history({"turn": game_data.get_turn(), "reason": "no_safe_moves"})
|
|
return "up"
|
|
|
|
model = self._load_model()
|
|
if not model:
|
|
move = random.choice(list(safe_positions.keys()))
|
|
self.add_to_history({
|
|
"turn": game_data.get_turn(),
|
|
"move": move,
|
|
"reason": "model_missing",
|
|
"safe_moves": list(safe_positions.keys()),
|
|
})
|
|
return move
|
|
|
|
row = {
|
|
"turn": game_data.get_turn(),
|
|
"game_board": game_data.get_game_board_as_dict(),
|
|
}
|
|
scores = self._predict_scores(model, row)
|
|
|
|
best_safe_move = max(safe_positions.keys(), key=lambda move: scores.get(move, float("-inf")))
|
|
self.add_to_history({
|
|
"turn": game_data.get_turn(),
|
|
"move": best_safe_move,
|
|
"safe_moves": list(safe_positions.keys()),
|
|
"scores": {move: round(scores.get(move, 0.0), 5) for move in MOVES},
|
|
})
|
|
return best_safe_move
|
|
|
|
def _load_model(self) -> dict[str, Any] | None:
|
|
env_path = os.getenv("TRAINED_SNAKE_MODEL", "models/battlesnake_softmax_v2.json")
|
|
path = Path(env_path)
|
|
|
|
if self._model_path == path and self._model_data is not None:
|
|
return self._model_data
|
|
|
|
if not path.exists() or not path.is_file():
|
|
self._model_path = path
|
|
self._model_data = None
|
|
return None
|
|
|
|
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
model = payload.get("model")
|
|
if not isinstance(model, dict):
|
|
self._model_path = path
|
|
self._model_data = None
|
|
return None
|
|
|
|
self._model_path = path
|
|
self._model_data = model
|
|
return model
|
|
|
|
def _predict_scores(self, model:dict[str, Any], row:dict[str, Any]) -> dict[str, float]:
|
|
return self._predict_scores_softmax_v2(model, row)
|
|
|
|
def _predict_scores_softmax_v2(self, model:dict[str, Any], row:dict[str, Any]) -> dict[str, float]:
|
|
features = extract_feature_values(row)
|
|
weights = model.get("weights", {})
|
|
bias = model.get("bias", {})
|
|
scores:dict[str, float] = {}
|
|
|
|
for move in MOVES:
|
|
move_weights = weights.get(move, {})
|
|
score = float(bias.get(move, 0.0))
|
|
for name, value in features.items():
|
|
score += float(move_weights.get(name, 0.0)) * float(value)
|
|
scores[move] = score
|
|
return scores
|