async def handle_login(self, client: Client, msg: ProtocolMessage): params: Dict[str, Any] = msg.parameters answer: Optional[ProtocolMessage] = None if client.state is not ClientConnectionState.NOT_CONNECTED: answer = ProtocolMessage.create_error( ErrorCode.ILLEGAL_STATE_ALREADY_LOGGED_IN) elif len(params["username"]) > ProtocolConfig.USERNAME_MAX_LENGTH: answer = ProtocolMessage.create_error( ErrorCode.SYNTAX_USERNAME_TOO_LONG) else: login_successful: bool = self.login_user(params["username"], client) if not login_successful: answer = ProtocolMessage.create_error( ErrorCode.PARAMETER_USERNAME_ALREADY_EXISTS) else: client.state = ClientConnectionState.GAME_SELECTION #self.users[username].state = ClientConnectionState.GAME_SELECTION client.username = params["username"] self.users[client.username] = client self.print_client( client, "Client successfully logged in with '{}'".format( client.username)) await self.send_games_to_user(client) if answer is not None: await self.send(client, answer) self.print_stats()
async def handle_msg(self, client: Client, msg: ProtocolMessage): if msg.missing_or_unkown_param: answer: ProtocolMessage = ProtocolMessage.create_error( ErrorCode.SYNTAX_MISSING_OR_UNKNOWN_PARAMETER) await self.send(client, answer) # No other command is permitted if the client is not logged in elif client.state is ClientConnectionState.NOT_CONNECTED and msg.type is not ProtocolMessageType.LOGIN: answer = ProtocolMessage.create_error( ErrorCode.ILLEGAL_STATE_NOT_LOGGED_IN) await self.send(client, answer) elif msg.type == ProtocolMessageType.LOGIN: await self.handle_login(client, msg) elif msg.type == ProtocolMessageType.LOGOUT: await self.handle_logout(client, msg) elif msg.type == ProtocolMessageType.CHAT_SEND: await self.handle_chat_send(client, msg) elif msg.type == ProtocolMessageType.GET_GAMES: await self.handle_get_games(client, msg) elif msg.type == ProtocolMessageType.CREATE_GAME: await self.handle_create_game(client, msg) # handle_cancel handles the case when the user has not created a game elif msg.type == ProtocolMessageType.CANCEL: await self.handle_cancel(client, msg) # handle_join handles the case that they created a game themselves elif msg.type == ProtocolMessageType.JOIN: await self.handle_join(client, msg) # all the following messages are only valid when playing elif not client.state == ClientConnectionState.PLAYING: answer = ProtocolMessage.create_error( ErrorCode.ILLEGAL_STATE_NOT_IN_GAME) await self.send(client, answer) elif msg.type == ProtocolMessageType.PLACE: await self.handle_place(client, msg) elif msg.type == ProtocolMessageType.ABORT: await self.handle_abort(client, msg) elif msg.type == ProtocolMessageType.MOVE: await self.handle_move(client, msg) elif msg.type == ProtocolMessageType.SHOOT: await self.handle_shoot(client, msg)
async def handle_move(self, client: Client, msg: ProtocolMessage): our_ctrl: GameController = self.user_game_ctrl[client.username] other_ctrl: GameController = self.user_game_ctrl[ our_ctrl.opponent_name] try: positions: Positions = our_ctrl.run(msg) except BattleshipError as e: answer: ProtocolMessage = ProtocolMessage.create_error( e.error_code) await self.send(client, answer) return except Exception as e: raise e our_ctrl.timeout_counter = 0 our_ctrl.cancel_timeout() # notify params = {} if not len(positions.positions) == 0: params["positions"] = positions msg_moved: ProtocolMessage = ProtocolMessage.create_single( ProtocolMessageType.MOVED, params) await self.send(other_ctrl.client, msg_moved) await self.send(our_ctrl.client, msg_moved) our_ctrl.run(msg_moved) other_ctrl.run(msg_moved) other_ctrl.start_timeout(self.handle_timeout_wrapper)
async def handle_create_game(self, client: Client, msg: ProtocolMessage): if client.state in [ ClientConnectionState.GAME_CREATED, ClientConnectionState.PLAYING ]: # TODO: in the case of GAME_CREATED, this is more or less in line with the RFC # TODO: in the case of PLAYING, a better error code would be nice msg_error = ProtocolMessage.create_error( ErrorCode.ILLEGAL_STATE_NUMBER_OF_GAMES_LIMIT_EXCEEDED) await self.send(client, msg_error) return game_id: int = ServerLobbyController.next_game_id ServerLobbyController.next_game_id += 1 game_controller: GameController = await GameController.create_from_msg( game_id, client, self.loop, msg, client.username) if game_controller is not None: client.state = ClientConnectionState.GAME_CREATED self.user_gid[client.username] = game_id self.games[game_id] = (game_controller, None) self.user_game_ctrl[client.username] = game_controller # and send the game to all users game_msg: ProtocolMessage = game_controller.to_game_msg() await self.msg_to_all(game_msg) self.print_stats() # the game controller already sends the error messages, so this is fine. # TODO: move this here to be consistent. else: pass
async def handle_chat_send(self, client: Client, msg: ProtocolMessage): params: Dict[str, Any] = msg.parameters answer: Optional[ProtocolMessage] = None forward: ProtocolMessage text: str = params["text"] recipient: str = params["username"] if len(text) > ProtocolConfig.CHAT_MAX_TEXT_LENGTH: answer = ProtocolMessage.create_error( ErrorCode.SYNTAX_MESSAGE_TEXT_TOO_LONG) # check if the message is for all users elif recipient == "": forward = ProtocolMessage.create_single( ProtocolMessageType.CHAT_RECV, { "sender": client.username, "recipient": "", "text": text }) await self.msg_to_all_but_one(forward, client.username) self.print_client( client, "Forwarding chat message to all logged in users but {}".format( client.username)) elif recipient not in self.users: answer = ProtocolMessage.create_error( ErrorCode.PARAMETER_USERNAME_DOES_NOT_EXIST) else: forward = ProtocolMessage.create_single( ProtocolMessageType.CHAT_RECV, { "sender": client.username, "recipient": recipient, "text": text }) await self.msg_to_user(forward, recipient) self.print_client( client, "Forwarding chat message to '{}'".format(recipient)) if answer is not None: await self.send(client, answer)
async def handle_cancel(self, client: Client, msg: ProtocolMessage): if client.state == ClientConnectionState.GAME_CREATED: game_id: int = self.user_gid[client.username] del self.user_gid[client.username] del self.user_game_ctrl[client.username] del self.games[game_id] client.state = ClientConnectionState.GAME_SELECTION await self.send_delete_game(game_id) else: msg_error: ProtocolMessage = ProtocolMessage.create_error( ErrorCode.PARAMETER_UNKNOWN_GAME_ID) await self.send(client, msg_error)
async def handle_shoot(self, client: Client, msg: ProtocolMessage): our_ctrl: GameController = self.user_game_ctrl[client.username] other_ctrl: GameController = self.user_game_ctrl[ our_ctrl.opponent_name] try: hit: bool = other_ctrl.run(msg) except BattleshipError as e: answer = ProtocolMessage.create_error(e.error_code) await self.send(client, answer) return except Exception as e: raise e our_ctrl.timeout_counter = 0 our_ctrl.cancel_timeout() if hit: sunk: bool = other_ctrl.ship_sunk_at_pos( msg.parameters["position"].horizontal, msg.parameters["position"].vertical) msg_hit: ProtocolMessage = ProtocolMessage.create_single( ProtocolMessageType.HIT, { "sunk": sunk, "position": msg.parameters["position"] }) await self.send(other_ctrl.client, msg_hit) await self.send(our_ctrl.client, msg_hit) our_ctrl.run(msg_hit) other_ctrl.run(msg_hit) our_ctrl.start_timeout(self.handle_timeout_wrapper) if other_ctrl.all_ships_sunk(): await self.end_game_with_reason(our_ctrl, other_ctrl, EndGameReason.YOU_WON, EndGameReason.OPPONENT_WON) else: msg_fail: ProtocolMessage = ProtocolMessage.create_single( ProtocolMessageType.FAIL, {"position": msg.parameters["position"]}) await self.send(other_ctrl.client, msg_fail) await self.send(our_ctrl.client, msg_fail) our_ctrl.run(msg_fail) other_ctrl.run(msg_fail) other_ctrl.start_timeout(self.handle_timeout_wrapper)
async def handle_place(self, client: Client, msg: ProtocolMessage): our_ctrl: GameController = self.user_game_ctrl[client.username] other_ctrl: GameController = self.user_game_ctrl[ our_ctrl.opponent_name] try: our_ctrl.run(msg) except BattleshipError as e: # TODO: maybe check if it's not an internal error answer: ProtocolMessage = ProtocolMessage.create_error( e.error_code) await self.send(client, answer) return except Exception as e: raise e # notify the other msg_placed: ProtocolMessage = ProtocolMessage.create_single( ProtocolMessageType.PLACED) await self.send(other_ctrl.client, msg_placed) # if both are on waiting, the game can start if our_ctrl.state == GameState.WAITING and other_ctrl.state == GameState.WAITING: # who starts? starting_ctrl: GameController waiting_ctrl: GameController (starting_ctrl, waiting_ctrl) = ( our_ctrl, other_ctrl) if randrange(2) == 1 else (other_ctrl, our_ctrl) youstart: ProtocolMessage = ProtocolMessage.create_single( ProtocolMessageType.YOUSTART) await self.send(starting_ctrl.client, youstart) starting_ctrl.run(youstart) starting_ctrl.timeout_counter = 0 starting_ctrl.start_timeout(self.handle_timeout_wrapper) youwait: ProtocolMessage = ProtocolMessage.create_single( ProtocolMessageType.WAIT) await self.send(waiting_ctrl.client, youwait) waiting_ctrl.run(youwait) # TODO: fix this, should be merged with state, is this even used anymore? starting_ctrl._game_started = True waiting_ctrl._game_started = True
async def msg_callback(msg: ProtocolMessage): logging.debug("< {}".format(msg)) # if we receive a non error message, we take this as a hint that we are successfully logged in if lobby_controller.state is ClientConnectionState.NOT_CONNECTED and not msg.type == ProtocolMessageType.ERROR: lobby_controller.state = ClientConnectionState.CONNECTED if msg.type == ProtocolMessageType.ERROR: pass elif msg.type == ProtocolMessageType.GAMES: await lobby_controller.handle_games(msg) elif msg.type == ProtocolMessageType.GAME: await lobby_controller.handle_game(msg) elif msg.type == ProtocolMessageType.DELETE_GAME: await lobby_controller.handle_delete_game(msg) elif msg.type == ProtocolMessageType.CHAT_RECV: if in_lobby_or_battle is True: await lobby_controller.handle_chat_recv(msg) elif msg.type == ProtocolMessageType.HIT: await lobby_controller.handle_hit(msg) elif msg.type == ProtocolMessageType.WAIT: await lobby_controller.handle_wait(msg) elif msg.type == ProtocolMessageType.YOUSTART: await lobby_controller.handle_youstart(msg) elif msg.type == ProtocolMessageType.TIMEOUT: await lobby_controller.handle_timeout(msg) elif msg.type == ProtocolMessageType.FAIL: await lobby_controller.handle_fail(msg) elif msg.type == ProtocolMessageType.ENDGAME: await lobby_controller.handle_endgame(msg) elif msg.type == ProtocolMessageType.MOVED: await lobby_controller.handle_moved(msg) elif msg.type == ProtocolMessageType.STARTGAME: await lobby_controller.handle_start_game(msg) elif msg.type == ProtocolMessageType.PLACED: await lobby_controller.handle_placed(msg) elif msg.type == ProtocolMessageType.NONE: err: ProtocolMessage = ProtocolMessage.create_error( ErrorCode.UNKNOWN) await lobby_controller.client.send(err) # add the other types if needed else: pass
async def handle_join(self, client: Client, msg: ProtocolMessage): answer: Optional[ProtocolMessage] = None game_id: int = msg.parameters["game_id"] if not client.state == ClientConnectionState.GAME_SELECTION: # TODO: this is not really the right error message, but… there is no other answer = ProtocolMessage.create_error( ErrorCode.ILLEGAL_STATE_GAME_ALREADY_STARTED) # there is no available game with the specified game_ID (error code 104) elif not game_id in self.games.keys(): answer = ProtocolMessage.create_error( ErrorCode.PARAMETER_UNKNOWN_GAME_ID) # the message lacks the password parameter although a password is required (error code 105) elif self.games[game_id][ 0].options == GameOptions.PASSWORD and not "password" in msg.parameters: answer = ProtocolMessage.create_error( ErrorCode.PARAMETER_PASSWORD_REQUIRED) # a password is required for the game, but the given password is incorrect (error code 106) elif self.games[game_id][ 0].options == GameOptions.PASSWORD and not msg.parameters[ "password"] == self.games[game_id][0].password: answer = ProtocolMessage.create_error( ErrorCode.PARAMETER_INVALID_PASSWORD) # the user wants to join his own game (error code 107) elif self.games[game_id][0].username == client.username: answer = ProtocolMessage.create_error( ErrorCode.PARAMETER_ILLEGAL_JOIN) # the game has already started (error code 8) elif not self.games[game_id][0].state == GameState.IN_LOBBY: answer = ProtocolMessage.create_error( ErrorCode.ILLEGAL_STATE_GAME_ALREADY_STARTED) # Everything ok, let them play else: # setup a game_controller for the other one game_controller1: GameController = self.games[game_id][0] game_controller1.opponent_name = client.username game_controller1.state = GameState.PLACE_SHIPS client1: Client = self.games[game_id][0].client game_controller2: GameController = GameController.create_from_existing_for_opponent( game_controller1, client) game_controller2.state = GameState.PLACE_SHIPS self.games[game_id] = (self.games[game_id][0], game_controller2) self.user_gid[client.username] = game_id # this is already done for the other user self.user_game_ctrl[client.username] = game_controller2 # set client states client.state = ClientConnectionState.PLAYING client1.state = ClientConnectionState.PLAYING # send startgame messages await self.send(client, game_controller2.to_start_game_msg()) await self.send(client1, game_controller1.to_start_game_msg()) # inform the other users the game is no longer available await self.send_delete_game(game_id) self.print_stats() if answer is not None: await self.send(client, answer)