""" Module that defines the client class. """ import asyncio import logging from typing import Optional import websockets.exceptions from websockets.server import WebSocketServerProtocol from swiftstory.game import Game from swiftstory.game_manager import GameManager from swiftstory.exception import WrongAction, UnsupportedLanguage, JoinError from swiftstory.player import Player class Client: """ Represent a client. A client manages a (web)socket to communicate with the outside world. It also manages the associated player when in a game. """ def __init__(self, socket: WebSocketServerProtocol, game_manager: GameManager) -> None: self.game: Optional[Game] = None self.game_manager: GameManager = game_manager self.socket = socket self.player: Optional[Player] = None def join_game(self, game_name: str, lang: Optional[str]) -> str: """ Attempt to join a game and return an answer. """ if self.game is not None: raise WrongAction('You are already in a game') if lang is None: lang = 'en' try: game = self.game_manager.find_by_name(game_name, lang) except UnsupportedLanguage as e: raise JoinError(f"unsupported language: {str(e)}") from e self.player = Player() status = game.try_join(self.player) self.game = game self.monitor_player() return status def play_white_card(self, card_id: int) -> str: """ Play a card and return an answer. """ if self.game is None: raise WrongAction('You have to join a game first') if self.player is None: raise ValueError("Player is None") return self.game.try_play_card(self.player, card_id) def pick_black_card(self) -> str: """ Pick a black card (and become the judge) and return an answer. """ if self.game is None: raise WrongAction('You have to join a game first') if self.player is None: raise ValueError("Player is None") return self.game.try_become_judge(self.player) def collect_cards(self) -> str: """ Collect the played cards and return an answer. """ if self.game is None: raise WrongAction('You have to join a game first') if self.player is None: raise ValueError("Player is None") return self.game.try_collect_cards(self.player) def designate_card(self, card_id: Optional[int]) -> str: """ Designate the best card and return an answer. """ if self.game is None: raise WrongAction('You have to join a game first') if self.player is None: raise ValueError("Player is None") return self.game.try_designate_card(self.player, card_id) def view_player_cards(self) -> str: """ View our own cards and return an answer. """ if self.game is None: raise WrongAction('You have to join a game first') if self.player is None: raise ValueError("Player is None") return self.game.try_view_player_cards(self.player) def view_played_cards(self) -> str: """ View the cards played at this round and return an answer. """ if self.game is None: raise WrongAction('You have to join a game first') if self.player is None: raise ValueError("Player is None") return self.game.try_view_played_cards(self.player) def view_black_card(self) -> str: """ View the current black card and return an answer. """ if self.game is None: raise WrongAction('You have to join a game first') if self.player is None: raise ValueError("Player is None") return self.game.try_view_black_card(self.player) def monitor_player(self) -> None: """ Start monitoring the player for notifications and send them. """ async def f() -> None: assert self.player is not None while True: try: notif = await self.player.notifications.get() await self.socket.send(notif) except websockets.exceptions.ConnectionClosed: logging.warning("Recipient has disconnected.") asyncio.create_task(f()) def disconnect(self) -> None: """ Detach from the player. """ if self.player is not None: if self.game is None: raise ValueError("Disconnect from inexistent game.") self.game.disconnect(self.player)