예제 #1
0
    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')
예제 #2
0
    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')
예제 #3
0
    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')
예제 #4
0
 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')
예제 #5
0
    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()
예제 #6
0
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}')
예제 #7
0
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
예제 #8
0
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}')
예제 #9
0
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()
예제 #10
0
    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())