# ============================================================================== # Copyright (C) 2019 - Philip Paquette, Steven Bocco # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU Affero General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more # details. # # You should have received a copy of the GNU Affero General Public License along # with this program. If not, see . # ============================================================================== """ Response managers (client side). One manager corresponds to one request, except for requests that don't need specific manager (in such case, method default_manager() is used). Each manager is a function with name format "on_", expecting a request context and a response as parameters. """ # pylint: disable=unused-argument from diplomacy.client.game_instances_set import GameInstancesSet from diplomacy.client.network_game import NetworkGame from diplomacy.client.channel import Channel from diplomacy.communication import requests, responses from diplomacy.engine.game import Game from diplomacy.utils import exceptions from diplomacy.utils.game_phase_data import GamePhaseData class RequestFutureContext(): """ Helper class to store a context around a request (with future for response management, related connection and optional related game). """ __slots__ = ['request', 'future', 'connection', 'game'] def __init__(self, request, future, connection, game=None): """ Initialize a request future context. :param request: a request object (see diplomacy.communication.requests about possible classes). :param future: a tornado Future object. :param connection: a diplomacy.Connection object. :param game: (optional) a NetworkGame object (from module diplomacy.client.network_game). :type request: requests._AbstractRequest | requests._AbstractGameRequest :type future: tornado.concurrent.Future :type connection: diplomacy.Connection :type game: diplomacy.client.network_game.NetworkGame """ self.request = request self.future = future self.connection = connection self.game = game request_id = property(lambda self: self.request.request_id) token = property(lambda self: self.request.token) channel = property(lambda self: self.connection.channels[self.request.token]) def new_channel(self, token): """ Create, store (in associated connection), and return a new channel with given token. """ channel = Channel(self.connection, token) self.connection.channels[token] = channel return channel def new_game(self, received_game): """ Create, store (in associated connection) and return a new network game wrapping given game data. Returned game is already in appropriate type (observer game, omniscient game or power game). :param received_game: game sent by server (Game object) :type received_game: Game """ game = NetworkGame(self.channel, received_game) if game.game_id not in self.channel.game_id_to_instances: self.channel.game_id_to_instances[game.game_id] = GameInstancesSet(game.game_id) self.channel.game_id_to_instances[game.game_id].add(game) return game def remove_channel(self): """ Remove associated channel (inferred from request token) from associated connection. """ del self.connection.channels[self.channel.token] def delete_game(self): """ Delete local game instances corresponding to game ID in associated request. """ assert hasattr(self.request, 'game_id') assert self.game is not None and self.game.game_id == self.request.game_id if self.request.game_id in self.channel.game_id_to_instances: del self.channel.game_id_to_instances[self.request.game_id] def default_manager(context, response): """ Default manager called for requests that don't have specific management. If response is OK, return None. If response is a UniqueData, return response data field. Else, return response. Expect response to be either OK or a UniqueData (containing only 1 field intended to be returned by server for associated request). :param context: request context :param response: response received :return: None, or data if response is a UniqueData. """ if isinstance(response, responses.UniqueData): return response.data if isinstance(response, responses.Ok): return None return response def on_create_game(context, response): """ Manage response for request CreateGame. :param context: request context :param response: response received :return: a new network game :type context: RequestFutureContext :type response: responses.DataGame """ return context.new_game(response.data) def on_delete_account(context, response): """ Manage response for request DeleteAccount. :param context: request context :param response: response received :return: None :type context: RequestFutureContext """ context.remove_channel() def on_delete_game(context, response): """ Manage response for request DeleteGame. :param context: request context :param response: response received :return: None :type context: RequestFutureContext """ context.delete_game() def on_get_phase_history(context, response): """ Manage response for request GetPhaseHistory. :param context: request context :param response: response received :return: a list of game states :type context: RequestFutureContext :type response: responses.DataGamePhases """ phase_history = response.data for game_phase in phase_history: # type: diplomacy.utils.game_phase_data.GamePhaseData Game.extend_phase_history(context.game, game_phase) return phase_history def on_join_game(context, response): """ Manage response for request JoinGame. :param context: request context :param response: response received :return: a new network game :type response: responses.DataGame """ return context.new_game(response.data) def on_leave_game(context, response): """ Manage response for request LeaveGame. :param context: request context :param response: response received :return: None :type context: RequestFutureContext """ context.delete_game() def on_logout(context, response): """ Manage response for request Logout. :param context: request context :param response: response received :return: None :type context: RequestFutureContext """ context.remove_channel() def on_send_game_message(context, response): """ Manage response for request SendGameMessage. :param context: request context :param response: response received :return: None :type context: RequestFutureContext :type response: responses.DataTimeStamp """ request = context.request # type: requests.SendGameMessage message = request.message message.time_sent = response.data Game.add_message(context.game, message) def on_set_game_state(context, response): """ Manage response for request SetGameState. :param context: request context :param response: response received :return: None :type context: RequestFutureContext """ request = context.request # type: requests.SetGameState context.game.set_phase_data(GamePhaseData(name=request.state['name'], state=request.state, orders=request.orders, messages=request.messages, results=request.results)) def on_set_game_status(context, response): """ Manage response for request SetGameStatus. :param context: request context :param response: response received :return: None :type context: RequestFutureContext """ request = context.request # type: requests.SetGameStatus Game.set_status(context.game, request.status) def on_set_orders(context, response): """ Manage response for request SetOrders. :param context: request context :param response: response received :return: None :type context: RequestFutureContext """ request = context.request # type: requests.SetOrders orders = request.orders if Game.is_player_game(context.game): assert context.game.power.name == context.request.game_role Game.set_orders(context.game, request.game_role, orders) else: Game.set_orders(context.game, request.power_name, orders) def on_clear_orders(context, response): """ Manage response for request ClearOrders. :param context: request context :param response: response received :return: None :type context: RequestFutureContext """ request = context.request # type: requests.ClearOrders Game.clear_orders(context.game, request.power_name) def on_clear_centers(context, response): """ Manage response for request ClearCenters. :param context: request context :param response: response received :return: None :type context: RequestFutureContext """ request = context.request # type: requests.ClearCenters Game.clear_centers(context.game, request.power_name) def on_clear_units(context, response): """ Manage response for request ClearUnits. :param context: request context :param response: response received :return: None :type context: RequestFutureContext """ request = context.request # type: requests.ClearUnits Game.clear_units(context.game, request.power_name) def on_set_wait_flag(context, response): """ Manage response for request SetWaitFlag. :param context: request context :param response: response received :return: None :type context: RequestFutureContext """ request = context.request # type: requests.SetWaitFlag wait = request.wait if Game.is_player_game(context.game): assert context.game.power.name == context.request.game_role Game.set_wait(context.game, request.game_role, wait) else: Game.set_wait(context.game, request.power_name, wait) def on_sign_in(context, response): """ Manage response for request SignIn. :param context: request context :param response: response received :return: a new channel :type context: RequestFutureContext :type response: responses.DataToken """ return context.new_channel(response.data) def on_vote(context, response): """ Manage response for request VoteAboutDraw. :param context: request context :param response: response received :return: None :type context: RequestFutureContext """ request = context.request # type: requests.Vote vote = request.vote assert Game.is_player_game(context.game) assert context.game.power.name == context.request.game_role context.game.power.vote = vote # Mapping dictionary from request class to response handler function. MAPPING = { requests.ClearCenters: on_clear_centers, requests.ClearOrders: on_clear_orders, requests.ClearUnits: on_clear_units, requests.CreateGame: on_create_game, requests.DeleteAccount: on_delete_account, requests.DeleteGame: on_delete_game, requests.GetAllPossibleOrders: default_manager, requests.GetAvailableMaps: default_manager, requests.GetDaidePort: default_manager, requests.GetDummyWaitingPowers: default_manager, requests.GetPlayablePowers: default_manager, requests.GetPhaseHistory: on_get_phase_history, requests.JoinGame: on_join_game, requests.JoinPowers: default_manager, requests.LeaveGame: on_leave_game, requests.ListGames: default_manager, requests.GetGamesInfo: default_manager, requests.Logout: on_logout, requests.ProcessGame: default_manager, requests.QuerySchedule: default_manager, requests.SaveGame: default_manager, requests.SendGameMessage: on_send_game_message, requests.SetDummyPowers: default_manager, requests.SetGameState: on_set_game_state, requests.SetGameStatus: on_set_game_status, requests.SetGrade: default_manager, requests.SetOrders: on_set_orders, requests.SetWaitFlag: on_set_wait_flag, requests.SignIn: on_sign_in, requests.Synchronize: default_manager, requests.Vote: on_vote, } def handle_response(context, response): """ Call appropriate handler for given response with given request context. :param context: request context. :param response: response received. :return: value returned by handler. """ handler = MAPPING.get(type(context.request), None) if not handler: raise exceptions.DiplomacyException( 'No response handler available for request class %s' % type(context.request).__name__) return handler(context, response)