diff options
Diffstat (limited to 'diplomacy/communication/requests.py')
-rw-r--r-- | diplomacy/communication/requests.py | 506 |
1 files changed, 394 insertions, 112 deletions
diff --git a/diplomacy/communication/requests.py b/diplomacy/communication/requests.py index 6f99f15..f16c33c 100644 --- a/diplomacy/communication/requests.py +++ b/diplomacy/communication/requests.py @@ -15,10 +15,70 @@ # with this program. If not, see <https://www.gnu.org/licenses/>. # ============================================================================== """ Client -> Server requests. - Notes: - If an error occurred on server-side while handling a request, client will receive - a ResponseException containing message about handling error. Request exceptions - are currently not more typed on client-side. + + This module contains the definition of request (as classes) + that a client can send to Diplomacy server implemented in this project. + + The client -> server communication follows this procedure: + + - Client sends a request to server. + All requests have parameters that must be filled by client before being sent. + - Server replies with a response, which is either an error response or a valid response. + - Client receives and handles server response. + + - If server response is an error, client converts it to a typed exception and raises it. + - If server response is a valid response, client return either the response data directly, + or make further treatments and return a derived data. + + Diplomacy package actually provides 2 clients: the Python client and the web front-end. + + Web front-end provides user-friendly forms to collect required request parameters, + makes all request calls internally, and then uses them to update graphical user interface. + So, when using front-end, you don't need to get familiar with underlying protocol, and documentation + in this module won't be really useful for you. + + Python client consists of three classes (:class:`.Connection`, :class:`.Channel` and + :class:`.NetworkGame`) which provide appropriate methods to automatically send requests, handle + server response, and either raise an exception (if server returns an error) or return a client-side + wrapped data (if server returns a valid response) where requests were called. Thus, these methods + still need to receive request parameters, and you need to know what kind of data they can return. + So, if you use Python client, you will need the documentation in this module, which describes, for + each request: + + - the request parameters (important) + - the server valid responses (less interesting) + - the Python client returned values (important) + + All requests classes inherit from :class:`._AbstractRequest` which require parameters + ``name`` (from parant class :class:`.NetworkData`), ``request_id`` and ``re_sent``. + These parameters are automatically filled by the client. + + From parent class :class:`._AbstractRequest`, we get 3 types of requests: + + - public requests, which directly inherit from :class:`._AbstractRequest`. + - channel requests, inherited from :class:`._AbstractChannelRequest`, which requires additional + parameter ``token``. Token is retrieved by client when he connected to server using + connection request :class:`.SignIn`, and is then used to create a :class:`.Channel` object. + Channel object will be responsible for sending all other channel requests, automatically + filling token field for these requests. + - game requests, intherited from :class:`._AbstractGameRequest`, which itself inherit from + :class:`._AbstractChannelRequest`, and requires additional parameters ``game_id``, ``game_role`` + and ``phase`` (game short phase name). Game ID, role and phase are retrieved for a specific game + by the client when he joined a game through one of featured :class:`.Channel` methods which return + a :class:`.NetworkGame` object. Network game will then be responsible for sending all other + game requests, automatically filling game ID, role and phase for these requests. + + Then, all other requests derived directly from either abstract request class, abstract channel + request class, or abstract game request class, may require additional parameters, and if so, these + parameters will need to be filled by the user, by passing them to related client methods. + + Check :class:`.Connection` for available public request methods (and associated requests). + + Check :class:`.Channel` for available channel request methods (and associated requests). + + Check :class:`.NetworkGame` for available game request methods (and associated requests). + + Then come here to get parameters and returned values for associated requests. """ import inspect import logging @@ -34,18 +94,17 @@ LOGGER = logging.getLogger(__name__) class _AbstractRequest(NetworkData): """ Abstract request class. - Field request_id is auto-filled if not defined. + Field **request_id** is auto-filled if not defined. - Field name is auto-filled with snake case version of request class name. + Field **name** is auto-filled with snake case version of request class name. - Field re_sent is False by default. It should be set to True if request is re-sent by client + Field **re_sent** is False by default. It should be set to True if request is re-sent by client (currently done by Connection object when reconnecting). - Timestamp field is auto-set with current local timestamp if not defined. + **Timestamp** field is auto-set with current local timestamp if not defined. For game request Synchronize, timestamp should be game latest timestamp instead (see method NetworkGame.synchronize()). """ - __slots__ = ['request_id', 're_sent'] header = { strings.REQUEST_ID: str, @@ -57,6 +116,7 @@ class _AbstractRequest(NetworkData): level = None def __init__(self, **kwargs): + """ Constructor. """ self.request_id = None # type: str self.re_sent = None # type: bool super(_AbstractRequest, self).__init__(**kwargs) @@ -70,7 +130,6 @@ class _AbstractChannelRequest(_AbstractRequest): """ Abstract class representing a channel request. Token field is automatically filled by a Channel object before sending request. """ - __slots__ = ['token'] header = parsing.update_model(_AbstractRequest.header, { strings.TOKEN: str @@ -85,7 +144,6 @@ class _AbstractGameRequest(_AbstractChannelRequest): """ Abstract class representing a game request. Game ID, game role and phase fields are automatically filled by a NetworkGame object before sending request. """ - __slots__ = ['game_id', 'game_role', 'phase'] header = parsing.extend_model(_AbstractChannelRequest.header, { @@ -115,7 +173,17 @@ class _AbstractGameRequest(_AbstractChannelRequest): # ==================== class GetDaidePort(_AbstractRequest): - """ Get game DAIDE port """ + """ Public request to get DAIDE port opened for a game. + + :param game_id: ID of game for which yu want to get DAIDE port + :type game_id: str + :return: + + - Server: :class:`.DataPort` + - Client: int - DAIDE port + + :raise diplomacy.utils.exceptions.DaidePortException: if there is no DAIDE port associated to given game ID. + """ __slots__ = ['game_id'] params = { strings.GAME_ID: str @@ -126,9 +194,20 @@ class GetDaidePort(_AbstractRequest): super(GetDaidePort, self).__init__(**kwargs) class SignIn(_AbstractRequest): - """ SignIn request. - Expected response: responses.DataToken - Expected response handler result: diplomacy.client.channel.Channel + """ Connection request. Log in or sign in to server. + + :param username: account username + :param password: account password + :param create_user: if True, server must create user. If False, server must login user. + :return: + + - Server: :class:`.DataToken` + - Client: a :class:`.Channel` object presenting user connected to the server. + If any sign in error occurs, raise an appropriate :class:`.ResponseException`. + + :type username: str + :type password: str + :type create_user: bool """ __slots__ = ['username', 'password', 'create_user'] params = { @@ -148,9 +227,47 @@ class SignIn(_AbstractRequest): # ================= class CreateGame(_AbstractChannelRequest): - """ CreateGame request. - Expected response: responses.DataGame - Expected response handler result: diplomacy.client.network_game.NetworkGame + """ Channel request to create a game. + + :param game_id: game ID. If not provided, a game ID will be generated. + :param n_controls: number of controlled powers required to start the game. + A power becomes controlled when a player joins the game to control this power. + Game won't start as long it does not have this number of controlled powers. + Game will stop (to ``forming`` state) if the number of controlled powers decrease under + this number (e.g. when powers are kicked, eliminated, or when a player controlling a power + leaves the game). If not provided, set with the number of powers on the map (e.g. ``7`` + on standard map). + :param deadline: (default ``300``) time (in seconds) for the game to wait before + processing a phase. ``0`` means no deadline, ie. game won't process a phase until either + all powers submit orders and turn off wait flag, or a game master forces game to process. + :param registration_password: password required to join the game. + If not provided, anyone can join the game. + :param power_name: power to control once game is created. + + - If provided, the user who send this request will be joined to the game as a player + controlling this power. + - If not provided, the user who send this request will be joined to the game as an + omniscient observer (ie. able to see everything in the game, including user messages). + Plus, as game creator, user will also be a game master, ie. able to send master requests, + e.g. to force game processing. + + :param state: game initial state (for expert users). + :param map_name: (default ``'standard'``) map to play on. + You can retrieve maps available on server by sending request :class:`GetAvailableMaps`. + :param rules: list of strings - game rules (for expert users). + :type game_id: str, optional + :type n_controls: int, optional + :type deadline: int, optional + :type registration_password: str, optional + :type power_name: str, optional + :type state: dict, optional + :type map_name: str, optional + :type rules: list, optional + :return: + + - Server: :class:`.DataGame` + - Client: a :class:`.NetworkGame` object representing a client version of the + game created and joined. Either a power game (if power name given) or an omniscient game. """ __slots__ = ['game_id', 'power_name', 'state', 'map_name', 'rules', 'n_controls', 'deadline', 'registration_password'] @@ -177,9 +294,15 @@ class CreateGame(_AbstractChannelRequest): super(CreateGame, self).__init__(**kwargs) class DeleteAccount(_AbstractChannelRequest): - """ DeleteAccount request. - Expected response: responses.Ok - Expected response handler result: None + """ Channel request to delete an account. + + :param username: name of user to delete account + + - if **not** given, then account to delete will be the one of user sending this request. + - if **provided**, then user submitting this request must have administrator privileges. + + :type username: str, optional + :return: None """ __slots__ = ['username'] params = { @@ -191,9 +314,25 @@ class DeleteAccount(_AbstractChannelRequest): super(DeleteAccount, self).__init__(**kwargs) class GetDummyWaitingPowers(_AbstractChannelRequest): - """ GetDummyWaitingPowers request. - Expected response: response.DataGamesToPowerNames - Expected response handler result: {dict mapping game IDs to lists of dummy powers names} + """ Channel request to get games with dummy waiting powers. + A dummy waiting power is a dummy (not controlled) power: + + - not yet eliminated, + - without orders submitted (for current game phase), + - but able to submit orders (for current game phase), + - and who is waiting for orders. + + It's a non-controlled orderable free power, which is then best suited to be controlled + by an automated player (e.g. a bot, or a learning algorithm). + + :param buffer_size: maximum number of powers to return. + :type buffer_size: int + :return: + + - Server: :class:`.DataGamesToPowerNames` + - Client: a dictionary mapping a game ID to a list of dummy waiting power names, + such that the total number of power names in the entire dictionary does not exceed + given buffer size. """ __slots__ = ['buffer_size'] params = { @@ -205,16 +344,26 @@ class GetDummyWaitingPowers(_AbstractChannelRequest): super(GetDummyWaitingPowers, self).__init__(**kwargs) class GetAvailableMaps(_AbstractChannelRequest): - """ GetAvailableMaps request. - Expected response: responses.DataMaps - Expected response handler result: {map name => [map power names]} + """ Channel request to get maps available on server. + + :return: + + - Server: :class:`.DataMaps` + - Client: a dictionary associating a map name to a dictionary of information related + to the map. You can especially check key ``'powers'`` to get the list of map power names. """ __slots__ = [] class GetPlayablePowers(_AbstractChannelRequest): - """ GetPlayablePowers request. - Expected response: responses.DataPowerNames - Expected response handler result: [power names] + """ Channel request to get the list of playable powers for a game. + A playable power is a dummy (uncontrolled) power not yet eliminated. + + :param game_id: ID of game to get playable powers + :type game_id: str + :return: + + - Server: :class:`.DataPowerNames` + - Client: set of playable power names for given game ID. """ __slots__ = ['game_id'] params = { @@ -226,9 +375,32 @@ class GetPlayablePowers(_AbstractChannelRequest): super(GetPlayablePowers, self).__init__(**kwargs) class JoinGame(_AbstractChannelRequest): - """ JoinGame request. - Expected response: responses.DataGame - Expected response handler result: diplomacy.client.network_game.NetworkGame + """ Channel request to join a game. + + :param game_id: ID of game to join + :param power_name: if provided, name of power to control. Otherwise, + user wants to observe game without playing. + :param registration_password: password to join game. If omitted while + game requires a password, server will return an error. + :type game_id: str + :type power_name: str, optional + :type registration_password: str, optional + :return: + + - Server: :class:`.DataGame` + - Client: a :class:`.NetworkGame` object representing the client game, which is either: + + - a power game (if power name was given), meaning that this network game allows user + to play a power + - an observer game, if power was not given and user does not have omniscient privileges + for this game. Observer role allows user to watch game phases changes, orders submitted + and orders results for each phase, but he can not see user messages and he can not + send any request that requires game master privileges. + - an omniscient game, if power was not given and user does have game master privileges. + Omniscient role allows user to see everything in the game, including user messages. + If user does only have omniscient privileges for this game, he can't do anything more, + If he does have up to game master privileges, then he can also send requests that + require game master privileges. """ __slots__ = ['game_id', 'power_name', 'registration_password'] params = { @@ -244,10 +416,22 @@ class JoinGame(_AbstractChannelRequest): super(JoinGame, self).__init__(**kwargs) class JoinPowers(_AbstractChannelRequest): - """ JoinPowers request to join many powers of a game with one query. - Useful to control many powers while still working only with 1 client game instance. - Expected response: responses.Ok - Expected response handler result: None + """ Channel request to join many powers of a game with one request. + + This request is mostly identical to :class:`.JoinGame`, except that list of power names + is mandatory. It's useful to allow the user to control many powers while still working + with 1 client game instance. + + :param game_id: ID of game to join + :param power_names: list of power names to join + :param registration_password: password to join the game + :type game_id: str + :type power_names: list, optional + :type registration_password: str, optionl + :return: None. If request succeeds, then the user is registered as player for all + given power names. The user can then simply join game to one of these powers (by sending + a :class:`.JoinGame` request), and he will be able to manage all the powers through + the client game returned by :class:`.JoinGame`. """ __slots__ = ['game_id', 'power_names', 'registration_password'] params = { @@ -263,9 +447,23 @@ class JoinPowers(_AbstractChannelRequest): super(JoinPowers, self).__init__(**kwargs) class ListGames(_AbstractChannelRequest): - """ ListGames request. - Expected response: responses.DataGames - Expected response handler result: responses.DataGames + """ Channel request to find games. + + :param game_id: if provided, look for games with game ID either containing or contained into this game ID. + :param status: if provided, look for games with this status. + :param map_name: if provided, look for games with this map name. + :param include_protected: (default True) tell if we must look into games protected by a password + :param for_omniscience: (default False) tell if we look for games where request user can be at least omniscient. + :type game_id: str, optional + :type status: str, optional + :type map_name: str, optional + :type include_protected: bool optional + :type for_omniscience: bool, optional + :return: + + - Server: :class:`.DataGames` + - Client: a list of :class:`.DataGameInfo` objects, each containing + a bunch of information about a game found. If no game found, list will be empty. """ __slots__ = ['game_id', 'status', 'map_name', 'include_protected', 'for_omniscience'] params = { @@ -285,36 +483,53 @@ class ListGames(_AbstractChannelRequest): super(ListGames, self).__init__(**kwargs) class GetGamesInfo(_AbstractChannelRequest): - """ Request used to get info for a given list of game IDs. - Expected response: responses.DataGames - Expected response handler result: responses.DataGames + """ Channel request to get information for a given list of game indices. + + :param games: list of game ID. + :type games: list + :return: + + - Server: :class:`.DataGames` + - Client: a list of :class:`.DataGameInfo` objects. """ __slots__ = ['games'] params = { strings.GAMES: parsing.SequenceType(str) } + def __init__(self, **kwargs): self.games = [] super(GetGamesInfo, self).__init__(**kwargs) class Logout(_AbstractChannelRequest): - """ Logout request. - Expected response: responses.Ok - Expected response handler result: None - """ + """ Channel request to logout. Returns nothing. """ __slots__ = [] class UnknownToken(_AbstractChannelRequest): - """ Request to tell server that a channel token is unknown. - Expected response: Nothing - Client does not even wait for a server response. - Expected response handler result: None + """ Channel request to tell server that a channel token is unknown. + + .. note:: + + Client does not even wait for a server response when sending this request, + which acts more like a "client notification" sent to server. """ __slots__ = [] class SetGrade(_AbstractChannelRequest): - """ SetGrade request. - Expected response: responses.Ok - Expected response handler result: None + """ Channel request to modify the grade of a user. + Require admin privileges to change admin grade, and at least game master privileges + to change omniscient or moderator grade. + + :param grade: grade to update (``'omniscient'``, ``'admin'`` or ``'moderator'``) + :param grade_update: how to make update (``'promote'`` or ``'demote'``) + :param username: user for which the grade must be modified + :param game_id: ID of game for which the grade must be modified. + Required only for ``'moderator'`` and ``'omniscient'`` grade. + :type grade: str + :type grade_update: str + :type username: str + :type game_id: str, optional + :return: None """ __slots__ = ['grade', 'grade_update', 'username', 'game_id'] params = { @@ -336,9 +551,11 @@ class SetGrade(_AbstractChannelRequest): # ============== class ClearCenters(_AbstractGameRequest): - """ ClearCenters request. - Expected response: responses.Ok - Expected response handler result: None + """ Game request to clear supply centers. See method :meth:`.Game.clear_centers`. + + :param power_name: if given, clear centers for this power. Otherwise, clear centers for all powers. + :type power_name: str, optional + :return: None """ __slots__ = ['power_name'] params = { @@ -350,9 +567,11 @@ class ClearCenters(_AbstractGameRequest): super(ClearCenters, self).__init__(**kwargs) class ClearOrders(_AbstractGameRequest): - """ ClearOrders request. - Expected response: responses.Ok - Expected response handler result: None + """ Game request to clear orders. + + :param power_name: if given, clear orders for this power. Otherwise, clear orders for all powers. + :type power_name: str, optional + :return: None """ __slots__ = ['power_name'] params = { @@ -364,9 +583,11 @@ class ClearOrders(_AbstractGameRequest): super(ClearOrders, self).__init__(**kwargs) class ClearUnits(_AbstractGameRequest): - """ ClearUnits request. - Expected response: responses.Ok - Expected response handler result: None + """ Game request to clear units. + + :param power_name: if given, clear units for this power. Otherwise, clear units for all powers. + :type power_name: str, optional + :return: None """ __slots__ = ['power_name'] params = { @@ -378,25 +599,31 @@ class ClearUnits(_AbstractGameRequest): super(ClearUnits, self).__init__(**kwargs) class DeleteGame(_AbstractGameRequest): - """ DeleteGame request. - Expected response: responses.Ok - Expected response handler result: None - """ + """ Game request to delete a game. Require game master privileges. Returns nothing. """ __slots__ = [] phase_dependent = False class GetAllPossibleOrders(_AbstractGameRequest): - """ GetAllPossibleOrders request. - Expected response: response.DataPossibleOrders - Expected response handler result: response.DataPossibleOrders + """ Game request to get all possible orders. + Return (server and client) a :class:`.DataPossibleOrders` object + containing possible orders and orderable locations. """ __slots__ = [] class GetPhaseHistory(_AbstractGameRequest): - """ Get a list of game phase data from game history for given phases interval. + """ Game request to get a list of game phase data from game history for given phases interval. A phase can be either None, a phase name (string) or a phase index (integer). - Expected response: responses.DataGamePhases - Expected response handler result: [GamePhaseData objects] + See :meth:`.Game.get_phase_history` about how phases are used to retrieve game phase data. + + :param from_phase: phase from which to look in game history + :param to_phase: phase up to which to look in game history + :type from_phase: str | int, optional + :type to_phase: str | int, optional + :return: + + - Server: DataGamePhases + - Client: a list of :class:`.GamePhaseData` objects corresponding to game phases + found between ``from_phase`` and ``to_phase`` in game history. """ __slots__ = ['from_phase', 'to_phase'] params = { @@ -411,37 +638,44 @@ class GetPhaseHistory(_AbstractGameRequest): super(GetPhaseHistory, self).__init__(**kwargs) class LeaveGame(_AbstractGameRequest): - """ LeaveGame request. - Expected response: responses.Ok - Expected response handler result: None + """ Game request to leave a game (logout from game). If request power name is set + (ie. request user was a player), then power will become uncontrolled. + Otherwise, user will be signed out from its observer (or omniscient) role. + Returns nothing. """ __slots__ = [] class ProcessGame(_AbstractGameRequest): - """ ProcessGame request. - Expected response: responses.Ok - Expected response handler result: None - """ + """ Game request to force a game processing. Require master privileges. Return nothing. """ __slots__ = [] class QuerySchedule(_AbstractGameRequest): - """ Query server for info about current scheduling for a game. - Expected response: response.DataGameSchedule - Expected response handler result: response.DataGameSchedule + """ Game request to get info about current scheduling for a game in server. + Returns (server and client) a :class:`.DataGameSchedule` object. """ __slots__ = [] class SaveGame(_AbstractGameRequest): - """ Get game saved format in JSON. - Expected response: response.DataSavedGame - Expected response handler result: response.DataSavedGame + """ Game request to get game exported in JSON format. + + :return: + + - Server: :class:`.DataSavedGame` + - Client: dict - the JSON dictionary. """ __slots__ = [] class SendGameMessage(_AbstractGameRequest): - """ SendGameMessage request. - Expected response: responses.DataTimeStamp - Expected response handler result: None + """ Game message to send a user request. + + :param message: message to send. See :class:`.Message` for more info. + message sender must be request user role (ie. power role, in such case). + Message time sent must not be defined, it will be allocated by server. + :type message: Message + :return: + + - Server: :class:`.DataTimeStamp` + - Client: nothing (returned timestamp is just used to update message locally) """ __slots__ = ['message'] params = { @@ -453,9 +687,15 @@ class SendGameMessage(_AbstractGameRequest): super(SendGameMessage, self).__init__(**kwargs) class SetDummyPowers(_AbstractGameRequest): - """ SetDummyPowers request. - Expected response: responses.Ok - Expected response handler result: None + """ Game request to set dummy powers. Require game master privileges. + If given powers are controlled, related players are kicked + and powers become dummy (uncontrolled). + + :param power_names: list of power names to set dummy. If not provided, will be all map power names. + :param username: if provided, only power names controlled by this user will be set dummy. + :type power_names: list, optional + :type user_name: str, optional + :return: None """ __slots__ = ['username', 'power_names'] params = { @@ -469,9 +709,17 @@ class SetDummyPowers(_AbstractGameRequest): super(SetDummyPowers, self).__init__(**kwargs) class SetGameState(_AbstractGameRequest): - """ Request to set a game state. - Expected response: responses.Ok - Expected response handler result: None + """ Game request to set a game state (for exper users). Require game master privileges. + + :param state: game state + :param orders: dictionary mapping a power name to a list of orders strings + :param results: dictionary mapping a unit to a list of order result strings + :param messages: dictionary mapping a timestamp to a message + :type state: dict + :type orders: dict + :type results: dict + :type messages: dict + :return: None """ __slots__ = ['state', 'orders', 'results', 'messages'] params = { @@ -489,9 +737,18 @@ class SetGameState(_AbstractGameRequest): super(SetGameState, self).__init__(**kwargs) class SetGameStatus(_AbstractGameRequest): - """ SetGameStatus request. - Expected response: responses.Ok - Expected response handler result: None + """ Game request to force game status (only if new status differs from previous one). + Require game master privileges. + + :param status: game status to set. + Either ``'forming'``, ``'active'``, ``'paused'``, ``'completed'`` or ``'canceled'``. + + - If new status is ``'completed'``, game will be forced to draw. + - If new status is ``'active'``, game will be forced to start. + - If new status is ``'paused'``, game will be forced to pause. + - If new status is ``'canceled'``, game will be canceled and become invalid. + :type status: str + :return: None """ __slots__ = ['status'] params = { @@ -503,9 +760,16 @@ class SetGameStatus(_AbstractGameRequest): super(SetGameStatus, self).__init__(**kwargs) class SetOrders(_AbstractGameRequest): - """ SetOrders request. - Expected response: responses.Ok - Expected response handler result: None + """ Game request to set orders for a power. + + :param power_name: power name. If not given, request user must be a game player, + and power is inferred from request game role. + :param orders: list of power orders. + :param wait: if provided, wait flag to set for this power. + :type power_name: str, optional + :type orders: list + :type wait: bool, optional + :return: None """ __slots__ = ['power_name', 'orders', 'wait'] params = { @@ -521,9 +785,14 @@ class SetOrders(_AbstractGameRequest): super(SetOrders, self).__init__(**kwargs) class SetWaitFlag(_AbstractGameRequest): - """ SetWaitFlag request. - Expected response: responses.Ok - Expected response handler result: None + """ Game request to set orders for a power. + + :param power_name: power name. If not given, request user must be a game player, + and power if inferred from request game role. + :param wait: wait flag to set. + :type power_name: str, optional + :type wait: bool + :return: None """ __slots__ = ['power_name', 'wait'] params = { @@ -537,9 +806,13 @@ class SetWaitFlag(_AbstractGameRequest): super(SetWaitFlag, self).__init__(**kwargs) class Synchronize(_AbstractGameRequest): - """ Synchronize request. - Expected response: responses.DataGameInfo - Expected response handler result: DataGameInfo + """ Game request to force synchronization of client game with server game. + If necessary, server will send appropriate notifications to client game so that it can + be up to date with server game state. + + :param timestamp: timestamp since which client game needs to synchronize. + :type timestamp: int + :return: (server and client) a :class:`.DataGameInfo` object. """ __slots__ = ['timestamp'] params = { @@ -552,11 +825,18 @@ class Synchronize(_AbstractGameRequest): super(Synchronize, self).__init__(**kwargs) class Vote(_AbstractGameRequest): - """ Vote request. - For powers only. - Allow a power to vote about game draw for current phase. - Expected response: responses.Ok - Expected response handler result: None + """ Game request to vote for draw decision. + If number of pro-draw votes > number of con-draw votes for current phase, + then server will automatically draw the game and send appropriate notifications. + Votes are reset after a game processing. + + :param power_name: power name who wants to vote. If not provided, request user must be a game player, + and power name will be inferred from request game role. + :param vote: vote to set. Either ``'yes'`` (power votes for draw), ``'no'`` (power votes against draw), + or ``'neutral'`` (power does not want to decide). + :type power_name: str, optional + :type vote: str + :return: None """ __slots__ = ['power_name', 'vote'] params = { @@ -570,7 +850,9 @@ class Vote(_AbstractGameRequest): super(Vote, self).__init__(**kwargs) def parse_dict(json_request): - """ Parse a JSON dictionary expected to represent a request. Raise an exception if parsing failed. + """ Parse a JSON dictionary expected to represent a request. + Raise an exception if parsing failed. + :param json_request: JSON dictionary. :return: a request class instance. :type json_request: dict |