summaryrefslogtreecommitdiff
path: root/swiftstory/client.py
blob: cd5e82a46bd5d0e09958daeafa0fd63fd1100902 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
""" 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)