async def msg_hat_add_words(self, message: msg.HatAddWords, user: User, ws: WebSocketServerProtocol): # Check state and data if not isinstance(self.state, HatFillState): reply = rerr( 'wrong-state', f'current state is {self.state}', ) self._info(f'User {user.name} put words: wrong state') await self.room.user_send(user.user_id, reply, ws) return l_words = len(message.words) if l_words != self.hat_words_per_user: reply = rerr( 'wrong-data', f'wrong words length, {self.hat_words_per_user} exp', ) self._info(f'User {user.name} put words: wrong num {l_words}') await self.room.user_send(user.user_id, reply, ws) return # Put words, update state for word in message.words: self.hat.put(word) self.state.users.add(user.user_id) self._info(f'User {user.name} put words to hat') # Broadcast await self._broadcast_game_state('user-put-words')
async def msg_word_guessed(self, message: msg.Empty, user: User, ws: WebSocketServerProtocol): # Check state and data if not isinstance(self.state, RoundState): reply = rerr( 'wrong-state', f'current state is {self.state}', ) self._info(f'User {user} word guessed: wrong state') await self.room.user_send(user.user_id, reply, ws) return if user.user_id != self.state.user_from.user.user_id: reply = rerr( 'wrong-data', f'this user is not asking', ) self._info(f'User {user} word guessed: wrong user. ' f'Asking user is {self.state.user_from}') await self.room.user_send(user.user_id, reply, ws) return # Update user score self._info(f'User {user}: user {self.state.user_to} scored') self.state.user_to.score += 1 self.state.user_to.add_guessed_word(self.state.word) # Remove word self.hat.remove(self.state.word) self.state.guessed_words.append(self.state.word) # Broadcast msg_user_to = self.state.user_to.to_message() await self._broadcast_game_state( 'user-guessed', { 'user': msg_user_to, 'word': self.state.word, }, ) # Check if round didn't stop. if isinstance(self.state, RoundState): # Make word again if len(self.hat): self.state.word = self.hat.get() self.state.user_from.state.word = self.state.word await self._send_user_state(self.state.user_from) else: await self._stop_round('out-of-words')
async def event_kick_user(self, message: msg.UserId, ws: WebSocketServerProtocol): user_id = message.user_id try: user = self.users[user_id] log.info(f'User {user.user.name} kicked by admin') except KeyError: await self.room.admin_send(rerr('no-such-user'), sock=ws) log.info(f'Wrong user {message.user_id} kick requested by admin') return del self.users[user_id] broadcast_msg = msg.UserId(user_id=user_id) await self.room.broadcast(rmsg('remove-user', broadcast_msg)) await self.room.kick(user_id) if isinstance(self.state, HatFillState): if user_id in self.state.users: self.state.users.remove(user_id) await self._broadcast_game_state('remove-user') if ( isinstance(self.state, RoundState) and ( user_id == self.state.user_from.user.user_id or user_id == self.state.user_to.user.user_id ) ): await self._stop_round(reason='kicked user')
async def kick(self, user_id: int): user = self.users[user_id] log.debug(f'Kicking user {user_id}') socks = list(user) for sock in socks: await sock.send(rerr('kick')) await sock.close(1000, 'kick')
async def msg_admin_hat_complete(self, msg: msg.HatFillEnd, ws: WebSocketServerProtocol): if not isinstance(self.state, HatFillState): reply = rerr( 'wrong-state', f'current state is {self.state}', ) self._info(f'Admin hat end: wrong state {self.state}') await self.room.admin_send(reply, ws) return if msg.ignore_not_full: self._info('Admin requested hat fill completion, any hat state') if not self.state.users: self._info('Hat empty. Deny') await self.room.admin_send( rerr('hat-empty', message='Hat empty'), sock=ws, ) return else: self._info('Admin requested hat fill completion') if not self._check_hat_full(): self._info('Hat not full. Deny') await self.room.admin_send( rerr('hat-not-full', message='Hat not full'), sock=ws, ) return if len(self.users) < 2: self._info(f'User count is {len(self.users)}. Deny hat fill end') await self.room.admin_send( rerr('users-not-enough', message='Not enough users'), sock=ws, ) return self._info('Hat completed. Changing state') await self._change_state(GameStandbyState()) await self._save_state()
async def _serve(pr: persister.Persister, ws: WebSocketServerProtocol, path: str): ip = remote_addr(ws) if RE_GAME_PATH.match(path): game_id = RE_GAME_PATH.matches.group(1) log.info(f'New connection from {ip} to game {game_id}') await serve_game(ws, game_id, False, pr) elif RE_ADMIN_PATH.match(path): game_id = RE_ADMIN_PATH.matches.group(1) log.info(f'New admin connection from {ip} to game {game_id}') await serve_game(ws, game_id, True, pr) elif RE_NEW_GAME_PATH.match(path): log.info(f'New game session connection from {ip}') await create_game(ws, pr) else: await ws.send(rerr('wrong-path', 'Wrong path')) await ws.close(1002, f'Wrong path {path}')
async def auth(websocket: WebSocketServerProtocol) -> Optional[User]: ip = remote_addr(websocket) try: tag, message = await recv(websocket, msg.AuthRequest) except ProtocolError as err: log.info(f'Remote socket {ip} failed to authenticate: {err}') return None user_name = message.user_name if user_name == 'admin': await websocket.send(rerr('auth-error', f'wrong name {user_name}')) return None user_id = adler32(user_name.encode()) user = User(user_id, user_name) await websocket.send(rmsg('auth-ok', user.to_msg())) log.info(f'Connection {ip} authenticated as {user}') return user
async def serve( pr: persister.Persister, ws: WebSocketServerProtocol, path: str, ): ip = remote_addr(ws) try: await _serve(pr=pr, ws=ws, path=path) except ConnectionClosed as err: log.info(f'Connection {ip} closed ({err.code})') except ProtocolError as err: log.info(f'Connection {ip}: protocol error: {err}') try: await ws.send(rerr('protocol-error', 'Protocol error')) await ws.close(1002, f'protocol error') except Exception as err: log.debug(f'Connection {ip}: Error closing failed socket: {err}') pass except Exception as err: log.exception(f'Exception in handle {ip} path {path}')
async def serve_game(ws: WebSocketServerProtocol, game_id: str, admin: bool, pr: persister.Persister): ip = remote_addr(ws) try: room = rooms[game_id] except KeyError: try: game = await load_game(game_id=game_id, pr=pr) rooms[game_id] = game.room room = rooms[game_id] log.info(f'Loaded game {game_id} from persister') except persister.GameDoesNotExist: log.info(f'{ip} is trying to join non-existent game {game_id}') await ws.send(rerr('wrong-game', 'Wrong game')) await ws.close() return if admin: await room.serve_admin(ws) else: await room.serve_user(ws) await ws.close()
async def msg_admin_start_round(self, message: msg.AdminStartRound, ws: WebSocketServerProtocol): # Check state and data if not isinstance(self.state, GameStandbyState): reply = rerr( 'wrong-state', f'current state is {self.state}', ) self._info(f'Admin start round: wrong state') await self.room.admin_send(reply, ws) return if not len(self.hat): reply = rerr('hat-empty', 'hat is empty') self._info(f'Admin start round: hat empty') await self.room.admin_send(reply, ws) return try: user_from = self.users[message.user_id_from] user_to = self.users[message.user_id_to] except KeyError as err: reply = rerr( 'wrong-data', f'wrong user provided: {err}', ) self._info(f'Admin start round: wrong user id: {err}') await self.room.admin_send(reply, ws) return # Prepare word and objects word = self.hat.get() # Update state old_state = self.state round_num = self.round_num + 1 round_timer = Timer(time.time(), float(self.round_length)) await self._change_state( RoundState(user_from, user_to, word, round_timer), 'round-start', ) self._info(f'Admin starts round {round_num}: {user_from} -> {user_to}') try: # Update answering state user_to.state = UserStateAnswering(round_timer, user_from) res = await self._send_user_state(user_to) if not res: reply = rerr( 'unavailable-user', user_to.user.name, ) self._info(f'Answering user {user_from} is unavaiable') await self.room.admin_send(reply, ws) raise StateChangeFailed() # Update asking state user_from.state = UserStateAsking(round_timer, word, user_from) res = await self._send_user_state(user_from) if not res: reply = rerr( 'unavailable-user', user_from.user.name, ) self._info(f'Asking user {user_from} is unavaiable') await self.room.admin_send(reply, ws) raise StateChangeFailed() except Exception as err: self._warning(f'Aborting the round {round_num}, error: {err}') await self._change_state(old_state, 'round-cancel') if not isinstance(err, StateChangeFailed): raise return # Start task to update state in timeout self.round_num = round_num self.state.start_ts = time.time() await asyncio.create_task(self.await_stop_round())