diff options
Diffstat (limited to 'diplomacy/server')
-rw-r--r-- | diplomacy/server/request_managers.py | 15 | ||||
-rw-r--r-- | diplomacy/server/server.py | 47 | ||||
-rw-r--r-- | diplomacy/server/server_game.py | 20 |
3 files changed, 54 insertions, 28 deletions
diff --git a/diplomacy/server/request_managers.py b/diplomacy/server/request_managers.py index fdc4564..5fa434e 100644 --- a/diplomacy/server/request_managers.py +++ b/diplomacy/server/request_managers.py @@ -132,13 +132,16 @@ def on_create_game(server, request, connection_handler): initial_state=state, n_controls=request.n_controls, deadline=request.deadline, - registration_password=request.registration_password) - server_game.server = server + registration_password=request.registration_password, + server=server) # Make sure game creator will be a game master (set him as moderator if he's not an admin). if not server.users.has_admin(username): server_game.promote_moderator(username) + # Register game on server. + server.add_new_game(server_game) + # Register game creator, as either power player or omniscient observer. if power_name: server_game.control(power_name, username, token) @@ -147,10 +150,6 @@ def on_create_game(server, request, connection_handler): server_game.add_omniscient_token(token) client_game = server_game.as_omniscient_game(username) - # Register game on server. - server.add_new_game(server_game) - server.start_new_daide_server(game_id) - # Start game immediately if possible (e.g. if it's a solitaire game). if server_game.game_can_start(): server.start_game(server_game) @@ -217,7 +216,6 @@ def on_delete_game(server, request, connection_handler): level = verify_request(server, request, connection_handler, observer_role=False, power_role=False) server.delete_game(level.game) server.unschedule_game(level.game) - server.stop_daide_server(level.game.game_id) Notifier(server, ignore_tokens=[request.token]).notify_game_deleted(level.game) def on_get_dummy_waiting_powers(server, request, connection_handler): @@ -270,7 +268,8 @@ def on_get_daide_port(server, request, connection_handler): del connection_handler daide_port = server.get_daide_port(request.game_id) if daide_port is None: - raise exceptions.DaidePortException('Invalid game id or game\'s DAIDE server is not started for that game') + raise exceptions.DaidePortException( + "Invalid game id %s or game's DAIDE server is not started for that game" % request.game_id) return responses.DataPort(data=daide_port, request_id=request.request_id) def on_get_playable_powers(server, request, connection_handler): diff --git a/diplomacy/server/server.py b/diplomacy/server/server.py index 4a1647c..ee585c3 100644 --- a/diplomacy/server/server.py +++ b/diplomacy/server/server.py @@ -53,6 +53,7 @@ import os from random import randint import socket import signal +from typing import Dict, Set import tornado import tornado.web @@ -227,13 +228,13 @@ class Server(): self.backup_delay_seconds = constants.DEFAULT_BACKUP_DELAY_SECONDS self.ping_seconds = constants.DEFAULT_PING_SECONDS self.users = None # type: Users # Users and administrators usernames. - self.available_maps = {} # type: dict{str, set()} # {"map_name" => set("map_power")} + self.available_maps = {} # type: Dict[str, Set[str]] # {"map_name" => set("map_power")} self.maps_mtime = 0 # Latest maps modification date (used to manage maps cache in server object). # Server games loaded on memory (stored on disk). # Saved separately (each game in one JSON file). # Each game also stores tokens connected (player tokens, observer tokens, omniscient tokens). - self.games = {} # type: dict{str, ServerGame} + self.games = {} # type: Dict[str, ServerGame] # Dictionary mapping game IDs to dummy power names. self.games_with_dummy_powers = {} # type: dict{str, set} @@ -404,6 +405,12 @@ class Server(): # Game was processed normally. # Send game updates to powers, observers and omniscient observers. yield notifier.notify_game_processed(server_game, previous_phase_data, current_phase_data) + + # If game is completed, we must close associated DAIDE port. + if server_game.is_game_done: + self.stop_daide_server(server_game.game_id) + + # Game must be stopped if not active. return not server_game.is_game_active @gen.coroutine @@ -596,7 +603,7 @@ class Server(): If such game is already stored in server object, return it. Else, load it from disk but ** does not store it in server object **. To load and immediately store a game object in server object, please use method get_game(). - Method load_game() is convenient where you want to iterate over all games in server database + Method load_game() is convenient when you want to iterate over all games in server database without taking memory space. :param game_id: ID of game to load. :return: a ServerGame object @@ -621,15 +628,22 @@ class Server(): # This should be an internal server error. raise exc + # def add_new_game(self, server_game): - """ Add a new game data on server in memory. This does not save the game on disk. + """ Add a new game data on server in memory and perform any addition processing. + This does not save the game on disk. :type server_game: ServerGame """ + # Register game on memory. self.games[server_game.game_id] = server_game + # Start DAIDE server for this game. + self.start_new_daide_server(server_game.game_id) + # def get_game(self, game_id): """ Return game saved on server matching given game ID. Raise an exception if game ID not found. - Return game if already loaded on memory, else load it from disk, store it and return it. + Return game if already loaded on memory, else load it from disk, store it, + perform any loading/addition processing and return it. :param game_id: ID of game to load. :return: a ServerGame object. :rtype: ServerGame @@ -639,35 +653,43 @@ class Server(): LOGGER.debug('Game loaded: %s', game_id) # Check dummy powers for this game as soon as it's loaded from disk. self.register_dummy_power_names(server_game) + # Register game on memory. self.games[server_game.game_id] = server_game + # Start DAIDE server for this game. + self.start_new_daide_server(server_game.game_id) # We have just loaded game from disk. Start it if necessary. if not server_game.start_master and server_game.has_expected_controls_count(): # We may have to start game. - stop = False if server_game.does_not_wait(): # We must process game. - process_result = server_game.process() - stop = process_result is None or process_result[-1] + server_game.process() self.save_game(server_game) - if not stop: + # Game must be scheduled only if active. + if server_game.is_game_active: LOGGER.debug('Game loaded and scheduled: %s', server_game.game_id) self.schedule_game(server_game) return server_game + # def delete_game(self, server_game): - """ Delete given game from server (both from memory and disk). + """ Delete given game from server (both from memory and disk) and perform any post-deletion processing. :param server_game: game to delete :type server_game: ServerGame """ if not (server_game.is_game_canceled or server_game.is_game_completed): server_game.set_status(strings.CANCELED) game_filename = os.path.join(self.games_path, '%s.json' % server_game.game_id) + backup_game_filename = get_backup_filename(game_filename) if os.path.isfile(game_filename): os.remove(game_filename) + if os.path.isfile(backup_game_filename): + os.remove(backup_game_filename) self.games.pop(server_game.game_id, None) self.backup_games.pop(server_game.game_id, None) self.games_with_dummy_powers.pop(server_game.game_id, None) self.dispatched_dummy_powers.pop(server_game.game_id, None) + # Stop DAIDE server associated to this game. + self.stop_daide_server(server_game.game_id) @gen.coroutine def schedule_game(self, server_game): @@ -821,7 +843,7 @@ class Server(): def start_new_daide_server(self, game_id, port=None): """ Start a new DAIDE TCP server to handle DAIDE clients connections :param game_id: game id to pass to the DAIDE server - :param port: the port to use. If None, an available random prot will be used + :param port: the port to use. If None, an available random port will be used """ if port in self.daide_servers: raise RuntimeError('Port already in used by a DAIDE server') @@ -837,12 +859,13 @@ class Server(): daide_server = DaideServer(self, game_id) daide_server.listen(port) self.daide_servers[port] = daide_server - LOGGER.info('DAIDE server running on port %d', port) + LOGGER.info('DAIDE server running for game %s on port %d', game_id, port) return port def stop_daide_server(self, game_id): """ Stop one or all DAIDE TCP server :param game_id: game id of the DAIDE server. If None, all servers will be stopped + :type game_id: str """ for port in list(self.daide_servers.keys()): server = self.daide_servers[port] diff --git a/diplomacy/server/server_game.py b/diplomacy/server/server_game.py index 6ea349e..4e90152 100644 --- a/diplomacy/server/server_game.py +++ b/diplomacy/server/server_game.py @@ -23,6 +23,7 @@ from diplomacy.utils.game_phase_data import GamePhaseData class ServerGame(Game): """ ServerGame class. Properties: + - server: (optional) server (Server object) that handles this game. - omniscient_usernames (only for server games): set of usernames allowed to be omniscient observers for this game. - moderator_usernames (only for server games): @@ -40,9 +41,9 @@ class ServerGame(Game): strings.OMNISCIENT_USERNAMES: parsing.DefaultValueType(parsing.SequenceType(str, sequence_builder=set), ()), }) - def __init__(self, **kwargs): + def __init__(self, server=None, **kwargs): # Reference to a Server instance. - self.server = None # type: diplomacy.Server + self.server = server # type: diplomacy.Server self.omniscient_usernames = None # type: set self.moderator_usernames = None # type: set self.observer = None # type: Power @@ -154,11 +155,9 @@ class ServerGame(Game): """ Return a player game data object copy of this game for given power name. """ for_username = self.get_power(power_name).get_controller() game = Game.from_dict(self.to_dict()) - game.controlled_powers = self.get_controlled_power_names(for_username) game.error = [] game.message_history = self.get_message_history(power_name) game.messages = self.get_messages(power_name) - game.observer_level = self.get_observer_level(for_username) game.phase_abbr = game.current_short_phase related_power_names = self.get_related_power_names(power_name) for power in game.powers.values(): # type: Power @@ -168,36 +167,41 @@ class ServerGame(Game): power.vote = strings.NEUTRAL power.orders.clear() game.role = power_name + game.controlled_powers = self.get_controlled_power_names(for_username) + game.observer_level = self.get_observer_level(for_username) + game.daide_port = self.server.get_daide_port(self.game_id) if self.server else None return game def as_omniscient_game(self, for_username): """ Return an omniscient game data object copy of this game. """ game = Game.from_dict(self.to_dict()) - game.controlled_powers = self.get_controlled_power_names(for_username) game.message_history = self.get_message_history(strings.OMNISCIENT_TYPE) game.messages = self.get_messages(strings.OMNISCIENT_TYPE) - game.observer_level = self.get_observer_level(for_username) game.phase_abbr = game.current_short_phase for power in game.powers.values(): # type: Power power.role = strings.OMNISCIENT_TYPE power.tokens.clear() game.role = strings.OMNISCIENT_TYPE + game.controlled_powers = self.get_controlled_power_names(for_username) + game.observer_level = self.get_observer_level(for_username) + game.daide_port = self.server.get_daide_port(self.game_id) if self.server else None return game def as_observer_game(self, for_username): """ Return an observer game data object copy of this game. """ game = Game.from_dict(self.to_dict()) - game.controlled_powers = self.get_controlled_power_names(for_username) game.error = [] game.message_history = self.get_message_history(strings.OBSERVER_TYPE) game.messages = self.get_messages(strings.OBSERVER_TYPE) - game.observer_level = self.get_observer_level(for_username) game.phase_abbr = game.current_short_phase for power in game.powers.values(): # type: Power power.role = strings.OBSERVER_TYPE power.tokens.clear() power.vote = strings.NEUTRAL game.role = strings.OBSERVER_TYPE + game.controlled_powers = self.get_controlled_power_names(for_username) + game.observer_level = self.get_observer_level(for_username) + game.daide_port = self.server.get_daide_port(self.game_id) if self.server else None return game def cast(self, role, for_username): |