diff options
Diffstat (limited to 'diplomacy/daide')
-rw-r--r-- | diplomacy/daide/request_managers.py | 628 |
1 files changed, 628 insertions, 0 deletions
diff --git a/diplomacy/daide/request_managers.py b/diplomacy/daide/request_managers.py new file mode 100644 index 0000000..9e37407 --- /dev/null +++ b/diplomacy/daide/request_managers.py @@ -0,0 +1,628 @@ +# ============================================================================== +# Copyright (C) 2019 - Philip Paquette +# +# 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 <https://www.gnu.org/licenses/>. +# ============================================================================== +""" DAIDE request managers """ +import random +from tornado import gen +from tornado.concurrent import Future +from diplomacy.communication import requests as internal_requests +from diplomacy.daide import ADM_MESSAGE_ENABLED, DEFAULT_LEVEL, clauses, notifications, requests, responses, tokens, \ + utils +from diplomacy.daide.clauses import parse_order_to_bytes, parse_bytes +from diplomacy.engine.message import Message +from diplomacy.server import request_managers as internal_request_managers +from diplomacy.server.user import DaideUser +from diplomacy.utils import errors as err, exceptions, strings, splitter +from diplomacy.utils.order_results import OK + +# ================= +# Request managers. +# ================= + +@gen.coroutine +def on_name_request(server, request, connection_handler, game): + """ Manage NME request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + username = connection_handler.get_name_variant() + request.client_name + + try: + server.assert_token(connection_handler.token, connection_handler) + except exceptions.TokenException: + connection_handler.token = None + + if not connection_handler.token: + user_exists = server.users.has_username(username) + + sign_in_request = internal_requests.SignIn(username=username, + password='1234', + create_user=not user_exists) + + try: + token_response = yield internal_request_managers.handle_request(server, sign_in_request, connection_handler) + connection_handler.token = token_response.data + if not isinstance(server.users.get_user(username), DaideUser): + daide_user = DaideUser(passcode=random.randint(1, 8191), + client_name=request.client_name, + client_version=request.client_version, + **server.users.get_user(username).to_dict()) + server.users.replace_user(username, daide_user) + server.save_data() + except exceptions.UserException: + return [responses.REJ(bytes(request))] + + # find next available power + power_name = [power_name for power_name, power in game.powers.items() if not power.is_controlled()] + if not power_name: + return [responses.REJ(bytes(request))] + + return [responses.YES(bytes(request)), responses.MAP(game.map.name)] + +def on_observer_request(server, request, connection_handler, game): + """ Manage OBS request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + del server, connection_handler, game # Unused args + return [responses.REJ(bytes(request))] # No DAIDE observeres allowed + +@gen.coroutine +def on_i_am_request(server, request, connection_handler, game): + """ Manage IAM request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + power_name, passcode = request.power_name, request.passcode + + # find user + username = None + for user in server.users.values(): + if not isinstance(user, DaideUser): + continue + is_passcode_valid = bool(user.passcode == passcode) + if is_passcode_valid and game.is_controlled_by(power_name, user.username): + username = user.username + break + + if username is None: + return [responses.REJ(bytes(request))] + + try: + server.assert_token(connection_handler.token, connection_handler) + except exceptions.TokenException: + connection_handler.token = None + + if not connection_handler.token: + sign_in_request = internal_requests.SignIn(username=username, + password='1234', + create_user=False) + + try: + token_response = yield internal_request_managers.handle_request(server, sign_in_request, connection_handler) + connection_handler.token = token_response.data + except exceptions.UserException: + return [responses.REJ(bytes(request))] + + join_game_request = internal_requests.JoinGame(game_id=game.game_id, + power_name=power_name, + registration_password=None, + token=connection_handler.token) + + yield internal_request_managers.handle_request(server, join_game_request, connection_handler) + + return [responses.YES(bytes(request))] + +def on_hello_request(server, request, connection_handler, game): + """ Manage HLO request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + _, daide_user, _, power_name = utils.get_user_connection(server.users, game, connection_handler) + + # User not in game + if not daide_user or not power_name: + return [responses.REJ(bytes(request))] + + passcode = daide_user.passcode + level = DEFAULT_LEVEL + deadline = game.deadline + rules = game.rules + + return [responses.HLO(power_name, passcode, level, deadline, rules)] + +def on_map_request(server, request, connection_handler, game): + """ Manage MAP request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + del server, request, connection_handler # Unused args + return [responses.MAP(game.map.name)] + +def on_map_definition_request(server, request, connection_handler, game): + """ Manage MDF request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + del server, request, connection_handler # Unused args + return [responses.MDF(game.map_name)] + +def on_supply_centre_ownership_request(server, request, connection_handler, game): + """ Manage SCO request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + del server, request, connection_handler # Unused args + power_centers = {power.name: power.centers for power in game.powers.values()} + return [responses.SCO(power_centers, game.map_name)] + +def on_current_position_request(server, request, connection_handler, game): + """ Manage NOW request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + del server, request, connection_handler # Unused args + units = {power.name: power.units for power in game.powers.values()} + retreats = {power.name: power.retreats for power in game.powers.values()} + return [responses.NOW(game.get_current_phase(), units, retreats)] + +def on_history_request(server, request, connection_handler, game): + """ Manage HST request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + history_responses = [] + + _, _, _, power_name = utils.get_user_connection(server.users, game, connection_handler) + phase, current_phase = request.phase, game.get_current_phase() + phase_order = game.order_history.get(phase, None) + phase_result = game.result_history.get(phase, None) + + if phase_result is None: + return [responses.REJ(bytes(request))] + + next_phase = game.map.phase_abbr(game.map.find_next_phase(game.map.phase_long(phase))) + next_phase_state = game.state_history.get(next_phase, None) + + while next_phase_state is None and next_phase != current_phase: + next_phase = game.map.phase_abbr(game.map.find_next_phase(game.map.phase_long(next_phase))) + next_phase_state = game.state_history.get(next_phase, None) + + if next_phase == current_phase: + next_phase_state = game.get_state() + + phase = splitter.PhaseSplitter(phase) + next_phase = splitter.PhaseSplitter(next_phase) + + # ORD responses + for order in phase_order[power_name]: + order = splitter.OrderSplitter(order) + + # WAIVE + if len(order) == 1: + order.order_type = ' '.join([power_name, order.order_type]) + results = [OK] + else: + results = phase_result[order.unit] + order.unit = ' '.join([power_name, order.unit]) + + if order.supported_unit: + order.supported_unit = ' '.join([power_name, order.supported_unit]) + + order_bytes = parse_order_to_bytes(phase.phase_type, order) + history_responses.append(notifications.ORD(phase.input_str, order_bytes, [result.code for result in results])) + + # SCO response + history_responses.append(responses.SCO(next_phase_state['centers'], game.map.name)) + + # NOW response + units = {power_name: [unit for unit in units + if not unit.startswith('*')] for power_name, units in next_phase_state['units'].items()} + retreats = next_phase_state['retreats'].copy() + history_responses.append(responses.NOW(next_phase.input_str, units, retreats)) + + return history_responses + +@gen.coroutine +def on_submit_orders_request(server, request, connection_handler, game): + """ Manage SUB request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + _, _, token, power_name = utils.get_user_connection(server.users, game, connection_handler) + + if request.phase and not request.phase == game.get_current_phase(): + return [responses.REJ(bytes(request))] + + request.token = token + + power = game.get_power(power_name) + initial_power_adjusts = power.adjust[:] + initial_power_orders = [] + initial_game_errors = game.error[:] + + order_responses = [] + + # Parsing lead token and turn + _, request_bytes = parse_bytes(clauses.SingleToken, bytes(request)) + _, request_bytes = parse_bytes(clauses.Turn, request_bytes, on_error='ignore') + + # Validate each order individually + while request_bytes: + daide_order, request_bytes = parse_bytes(clauses.Order, request_bytes) + order = str(daide_order) + + set_orders_request = internal_requests.SetOrders(power_name=request.power_name, + orders=[order], + game_id=request.game_id, + game_role=request.power_name, + phase=request.phase, + token=request.token) + yield internal_request_managers.handle_request(server, set_orders_request, connection_handler) + + new_power_adjusts = [adjust for adjust in power.adjust if adjust not in initial_power_adjusts] + new_power_orders = {id: val for id, val in power.orders.items() if id not in initial_power_orders} + new_game_errors = [error.code for error in game.error if error not in initial_game_errors] + + if not new_power_adjusts and not new_power_orders and not new_game_errors: + new_game_errors.append((err.GAME_ORDER_NOT_ALLOWED % order).code) + + order_responses.append(responses.THX(bytes(daide_order), new_game_errors)) + + # Setting orders + set_orders_request = internal_requests.SetOrders(power_name=request.power_name, + orders=request.orders, + game_id=request.game_id, + game_role=request.power_name, + phase=request.phase, + token=request.token) + yield internal_request_managers.handle_request(server, set_orders_request, connection_handler) + + # Returning results and missing orders + order_responses.append(responses.MIS(game.get_current_phase(), power)) + return order_responses + +def on_missing_orders_request(server, request, connection_handler, game): + """ Manage MIS request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + _, _, _, power_name = utils.get_user_connection(server.users, game, connection_handler) + if not power_name: + return [responses.REJ(bytes(request))] + return [responses.MIS(game.get_current_phase(), game.get_power(power_name))] + +@gen.coroutine +def on_go_flag_request(server, request, connection_handler, game): + """ Manage GOF request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + _, _, token, power_name = utils.get_user_connection(server.users, game, connection_handler) + + set_wait_flag_request = internal_requests.SetWaitFlag(power_name=power_name, + wait=False, + game_id=request.game_id, + game_role=power_name, + phase=game.get_current_phase(), + token=token) + yield internal_request_managers.handle_request(server, set_wait_flag_request, connection_handler) + + if not game.get_power(power_name).order_is_set: + set_orders_request = internal_requests.SetOrders(power_name=power_name, + orders=[], + game_id=request.game_id, + game_role=power_name, + phase=game.get_current_phase(), + token=token) + yield internal_request_managers.handle_request(server, set_orders_request, connection_handler) + + return [responses.YES(bytes(request))] + +def on_time_to_deadline_request(server, request, connection_handler, game): + """ Manage TME request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + del server, connection_handler, game # Unused args + return [responses.REJ(bytes(request))] + +@gen.coroutine +def on_draw_request(server, request, connection_handler, game): + """ Manage DRW request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + _, _, token, power_name = utils.get_user_connection(server.users, game, connection_handler) + + vote_request = internal_requests.Vote(power_name=power_name, + vote=strings.YES, + game_role=power_name, + phase=game.get_current_phase(), + game_id=game.game_id, + token=token) + yield internal_request_managers.handle_request(server, vote_request, connection_handler) + + return [responses.YES(bytes(request))] + +@gen.coroutine +def on_send_message_request(server, request, connection_handler, game): + """ Manage SND request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + _, _, token, power_name = utils.get_user_connection(server.users, game, connection_handler) + + message = ' '.join([str(tokens.Token(from_bytes=(request.message_bytes[i], request.message_bytes[i+1]))) + for i in range(0, len(request.message_bytes), 2)]) + + for recipient_power_name in request.powers: + game_message = Message(sender=power_name, + recipient=recipient_power_name, + phase=game.get_current_phase(), + message=message) + send_game_message_request = internal_requests.SendGameMessage(power_name=power_name, + message=game_message, + game_role=power_name, + phase=game.get_current_phase(), + game_id=game.game_id, + token=token) + yield internal_request_managers.handle_request(server, send_game_message_request, connection_handler) + + return [responses.YES(bytes(request))] + +@gen.coroutine +def on_not_request(server, request, connection_handler, game): + """ Manage NOT request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + _, _, token, power_name = utils.get_user_connection(server.users, game, connection_handler) + + response = None + not_request = request.request + + # Cancelling orders + if isinstance(not_request, requests.SUB): + if not_request.orders: # cancel one order + pass + else: + clear_orders_request = internal_requests.ClearOrders(power_name=power_name, + game_id=game.game_id, + game_role=power_name, + phase=game.get_current_phase(), + token=token) + yield internal_request_managers.handle_request(server, clear_orders_request, connection_handler) + response = responses.YES(bytes(request)) + + # Cancel wait flag + elif isinstance(not_request, requests.GOF): + set_wait_flag_request = internal_requests.SetWaitFlag(power_name=power_name, + wait=True, + game_id=game.game_id, + game_role=power_name, + phase=game.get_current_phase(), + token=token) + yield internal_request_managers.handle_request(server, set_wait_flag_request, connection_handler) + response = responses.YES(bytes(request)) + + # Cancel get deadline request + elif isinstance(not_request, requests.TME): + response = responses.REJ(bytes(request)) + + # Cancel vote + elif isinstance(not_request, requests.DRW): + vote_request = internal_requests.Vote(power_name=power_name, + vote=strings.NEUTRAL, + game_role=power_name, + phase=game.get_current_phase(), + game_id=game.game_id, + token=token) + yield internal_request_managers.handle_request(server, vote_request, connection_handler) + response = responses.YES(bytes(request)) + + # Returning response + return [response if response else responses.REJ(bytes(request))] + +@gen.coroutine +def on_accept_request(server, request, connection_handler, game): + """ Manage YES request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + _, _, token, power_name = utils.get_user_connection(server.users, game, connection_handler) + + response = None + accept_response = request.response_bytes + lead_token, _ = parse_bytes(clauses.SingleToken, accept_response) + + if bytes(lead_token) == bytes(tokens.MAP): + + # find next available power + if not power_name: + power_names = sorted([power_name for power_name, power in game.powers.items() if not power.is_controlled()]) + if not power_names: + return [responses.OFF()] + power_name = power_names[0] + + join_game_request = internal_requests.JoinGame(game_id=game.game_id, + power_name=power_name, + registration_password=None, + token=token) + yield internal_request_managers.handle_request(server, join_game_request, connection_handler) + + return [response] if response else None + +def on_reject_request(server, request, connection_handler, game): + """ Manage REJ request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + del server, connection_handler, game # Unused args + response = None + reject_response = request.response_bytes + lead_token, _ = parse_bytes(clauses.SingleToken, reject_response) + + if bytes(lead_token) == bytes(tokens.MAP): + response = responses.OFF() + + return [response] if response else None + +def on_parenthesis_error_request(server, request, connection_handler, game): + """ Manage PAR request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + del server, request, connection_handler, game # Unused args + +def on_syntax_error_request(server, request, connection_handler, game): + """ Manage ERR request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + del server, request, connection_handler, game # Unused args + +def on_admin_message_request(server, request, connection_handler, game): + """ Manage ADM request + :param server: server which receives the request + :param request: request to manage + :param connection_handler: connection handler from which the request was sent + :param game: the game + :return: the list of responses + """ + del server, connection_handler, game # Unused args + if not ADM_MESSAGE_ENABLED: + return [responses.REJ(bytes(request))] + return None + +# Mapping dictionary from request class to request handler function. +MAPPING = { + requests.NameRequest: on_name_request, + requests.ObserverRequest: on_observer_request, + requests.IAmRequest: on_i_am_request, + requests.HelloRequest: on_hello_request, + requests.MapRequest: on_map_request, + requests.MapDefinitionRequest: on_map_definition_request, + requests.SupplyCentreOwnershipRequest: on_supply_centre_ownership_request, + requests.CurrentPositionRequest: on_current_position_request, + requests.HistoryRequest: on_history_request, + requests.SubmitOrdersRequest: on_submit_orders_request, + requests.MissingOrdersRequest: on_missing_orders_request, + requests.GoFlagRequest: on_go_flag_request, + requests.TimeToDeadlineRequest: on_time_to_deadline_request, + requests.DrawRequest: on_draw_request, + requests.SendMessageRequest: on_send_message_request, + requests.NotRequest: on_not_request, + requests.AcceptRequest: on_accept_request, + requests.RejectRequest: on_reject_request, + requests.ParenthesisErrorRequest: on_parenthesis_error_request, + requests.SyntaxErrorRequest: on_syntax_error_request, + requests.AdminMessageRequest: on_admin_message_request +} + +def handle_request(server, request, connection_handler): + """ (coroutine) Find request handler function for associated request, run it and return its result. + :param server: a Server object to pass to handler function. + :param request: a request object to pass to handler function. + See diplomacy.communication.requests for possible requests. + :param connection_handler: a ConnectionHandler object to pass to handler function. + :return: (future) either None or a response object. + See module diplomacy.communication.responses for possible responses. + """ + request_handler_fn = MAPPING.get(type(request), None) + if not request_handler_fn: + raise exceptions.RequestException() + + game = server.get_game(request.game_id) + + # Game not found + if not game or game.is_game_completed or game.is_game_canceled: + future = Future() + future.set_result([responses.REJ(bytes(request))]) + return future + + if gen.is_coroutine_function(request_handler_fn): + # Throw the future returned by this coroutine. + return request_handler_fn(server, request, connection_handler, game) + # Create and return a future. + future = Future() + try: + result = request_handler_fn(server, request, connection_handler, game) + future.set_result(result) + except exceptions.DiplomacyException as exc: + future.set_exception(exc) + + return future |