aboutsummaryrefslogtreecommitdiff
path: root/diplomacy/server
diff options
context:
space:
mode:
Diffstat (limited to 'diplomacy/server')
-rw-r--r--diplomacy/server/request_managers.py15
-rw-r--r--diplomacy/server/server.py47
-rw-r--r--diplomacy/server/server_game.py20
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):