from server.Files import read_file from server.GameStorage import GameStorage from snakes.TemplateSnake import TemplateSnake from server.SnakeBuilder import SnakeBuilder from datetime import datetime from flask import Flask from flask import request import logging, json, os, re class Server: default_snake_config = {"apiversion":"1","author":"","color":"#888888","head":"default","tail":"default"} def __init__(self, data_path:str, snake_type:str, debug:bool=False, store_game_when_win_and_moves_are_bigger_as:int=10): self.debug = debug self.snake_type = snake_type self.config_file = os.path.join(data_path, 'data', 'snake-config.json') self.data_path = data_path self.store_game_state = False self.store_game_when_win_and_moves_are_bigger_as = store_game_when_win_and_moves_are_bigger_as self.running_games:dict[str, GameStorage] = {} self.running_snake:dict[str, TemplateSnake] = {} self.app = Flask("Battlesnake") @self.app.get("/") def on_info(): return self._info() @self.app.post("/start") def on_start(): game_state = request.get_json() self._start(game_state) return "ok" @self.app.post("/move") def on_move(): game_state = request.get_json() return self._move(game_state) @self.app.post("/end") def on_end(): game_state = request.get_json() self._end(game_state) return "ok" @self.app.after_request def identify_server(response): response.headers.set( "server", "battlesnake/github/starter-snake-python" ) return response def run(self, host:str="0.0.0.0", port:str="8000", debug:bool=False): logging.getLogger("werkzeug").setLevel(logging.ERROR) print(f"\nRunning Battlesnake at http://{host}:{port} with the {' '.join(re.findall('[A-Z][^A-Z]*', self.snake_type))}") self.app.run(host=host, port=port, debug=debug) def _read_json_config_or_create(self): snake_config = read_file(self.config_file, json.load) if not snake_config: snake_config = self._override_snake_config_with_environment_variables(self.default_snake_config) return self._override_snake_config_with_environment_variables(snake_config) def _override_snake_config_with_environment_variables(self, config:dict[str]): for key in ["author", "color", "head", "tail"]: if os.environ.get(f"SNAKE_{key.upper()}", None): config[key.lower()] = os.environ.get(f"SNAKE_{key.upper()}") return config def enable_store_game_state(self): self.store_game_state = True # info is called when you create your Battlesnake on play.battlesnake.com # and controls your Battlesnake's appearance # TIP: If you open your Battlesnake URL in a browser you should see this data def _info(self) -> dict: snake_config = self._read_json_config_or_create() print("INFO Snake:", snake_config) return snake_config # start is called when your Battlesnake begins a game def _start(self, game_state:dict): if self.store_game_state: self.running_games[game_state["game"]["id"]] = GameStorage( self.snake_type, path=os.path.join(self.data_path, 'data'), no_store_turns=self.store_game_when_win_and_moves_are_bigger_as ) self.running_games[game_state["game"]["id"]].start_new_game(game_state["game"], game_state["board"], game_state["you"]) self.running_snake[game_state["game"]["id"]] = SnakeBuilder.build(self.snake_type) print("GAME START:", game_state["game"]) # move is called when your Battlesnake game is running game def _move(self, game_state:dict) -> dict: try: next_move = self.running_snake[game_state["game"]["id"]].choose_move(game_state) except KeyError: self.running_snake[game_state["game"]["id"]] = SnakeBuilder.build(self.snake_type) next_move = self.running_snake[game_state["game"]["id"]].choose_move(game_state) if self.store_game_state: try: self.running_games[game_state["game"]["id"]].add_moves(game_state["turn"], game_state["board"], next_move) if self.debug: print(self.running_games[game_state["game"]["id"]]) except KeyError: pass if self.debug: print("TURN:", f'{game_state["turn"]:3},', "MOVE:", f"{next_move:5}") return {"move": next_move} # end is called when your Battlesnake finishes a game def _end(self, game_state:dict): if self.store_game_state: snake = self.running_snake[game_state["game"]["id"]] try: self.running_games[game_state["game"]["id"]].add_end_state(game_state["board"], snake.get_history(), game_state["turn"]) self.running_games[game_state["game"]["id"]].save( f"{snake.__class__.__name__}_{datetime.now().strftime('%H-%M-%S')}_{game_state['game']['id']}.json", callback=json.dump, indent=2, ensure_ascii=False ) del self.running_games[game_state["game"]["id"]] except KeyError: print(f"ERROR: Can't find Game {game_state['game']['id']} in Storage") print("GAME ENDED: Winner is", [ x["name"] for x in game_state["board"]['snakes']]) del self.running_snake[game_state["game"]["id"]]