aboutsummaryrefslogtreecommitdiff
path: root/diplomacy/daide
diff options
context:
space:
mode:
authorSatya Ortiz-Gagne <satya.ortiz-gagne@mila.quebec>2019-06-10 10:10:55 -0400
committerPhilip Paquette <pcpaquette@gmail.com>2019-06-14 15:08:29 -0400
commit9628cdadbf5a6380098168846dc51e4feadef6ad (patch)
treef6b8bb6ca6794053f88f80691fac70ac58c99e80 /diplomacy/daide
parent4979f43baba8c7377471bbf65e7b5c003bf65406 (diff)
DAIDE - Implemented request_managers
- Created DaideUser object - Implemented managers are: 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
Diffstat (limited to 'diplomacy/daide')
-rw-r--r--diplomacy/daide/request_managers.py628
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