Beispiel #1
0
 def add_player(self, user: User):
     if user.game is not None:
         raise InvalidGameState("user_in_game", "user already in game")
     if len(self.players) >= self.options.player_limit:
         raise InvalidGameState("game_full", "the game is full")
     # check that there are enough white cards to distribute
     if self.game_running:
         total_cards_available = self.white_deck.total_cards() + sum(
             len(player.hand) for player in self.players)
         if total_cards_available < (config.game.hand_size +
                                     2) * (len(self.players) + 1):
             raise InvalidGameState(
                 "too_few_white_cards",
                 "too few white cards in the game for any more players")
     # create the user
     player = Player(user)
     self.players.append(player)
     user.added_to_game(self, player)
     # sync state to players
     self.send_event({
         "type": "player_join",
         "player": player.to_event_json(),
     })
     self.send_updates(full_resync=True, to=player)
     self.send_updates(UpdateType.players)
Beispiel #2
0
    def _handle_kick_player(self, content: dict):
        try:
            user_id = UserID(UUID(hex=content["user"]))
        except (KeyError, ValueError):
            raise InvalidRequest("invalid user")

        if user_id == self.user.id:
            raise InvalidGameState("self_kick", "can't kick yourself")
        try:
            player = self.user.game.players.find_by("id", user_id)
        except KeyError:
            raise InvalidGameState("player_not_in_game",
                                   "the player is not in the game")

        self.user.game.remove_player(player, LeaveReason.host_kick)
Beispiel #3
0
 def remove_player(self, player: Player, reason: LeaveReason):
     if player not in self.players:
         raise InvalidGameState("user_not_in_game", "user not in game")
     self.send_event({
         "type": "player_leave",
         "player": player.to_event_json(),
         "reason": reason.name,
     })
     was_host = player == self.host
     # notify the user object while game state is still valid
     player.user.removed_from_game()
     # remove the player now so they won't get further messages
     self.players.remove(player)
     # send updates to the player now to ensure they get notified
     self._send_pending_updates(to=player)
     # nuke the game if no players remain
     if len(self.players) == 0:
         # TODO: need to cancel tasks or something?
         self.server.remove_game(self)
         return
     # end the game if only 2 players remain
     if len(self.players) <= 2 and self.game_running:
         self.send_event({"type": "too_few_players"})
         self.stop_game()
         return
     # cancel the round if the card czar leaves (but if they idled, this will be handled by _judge_idle_timer)
     if player == self.card_czar and reason != LeaveReason.idle \
             and self.state in (GameState.playing, GameState.judging):
         self.send_event({"type": "card_czar_leave"})
         # TODO: check if the side effects of _cancel_round() affect the rest of this function
         self._cancel_round()
     if was_host:
         self.send_event({
             "type": "host_leave",
             "old_host": player.to_event_json(),
             "new_host": self.host.to_event_json(),
         })
     # discard the player's hand
     self.white_deck.discard_all(player.hand)
     # discard the player's played cards if round not decided yet
     if self.state in (GameState.playing, GameState.judging
                       ) and player.id in self.current_round.white_cards:
         played_cards = self.current_round.white_cards.pop(player.id)
         self.white_deck.discard_all(played_cards)
         # make sure to sync the played cards if necessary
         self.send_updates(UpdateType.game)
     # check if all remaining players have played
     if self.state == GameState.playing:
         self._check_all_played()
     # sync state to players
     self.send_updates(UpdateType.players)
Beispiel #4
0
 def choose_winner(self, round_id: RoundID, winning_card: WhiteCardID):
     if self.state != GameState.judging:
         raise InvalidGameState(
             "invalid_round_state",
             "the winner is not being chosen for the round")
     if round_id != self.current_round.id:
         raise InvalidGameState("wrong_round",
                                "the round is not being played")
     # figure out the winner from the winning card
     try:
         winner_id = single(
             player
             for player, cards in self.current_round.white_cards.items()
             if cards[0].slot_id == winning_card)
     except ValueError:
         raise InvalidGameState("invalid_winner", "no such card played")
     winner = self.players.find_by("id", winner_id)
     self.card_czar.idle_rounds = 0
     # count the score and start the next round
     self.current_round.winner = winner
     winner.score += 1
     self._set_state(GameState.round_ended)
     # sync state to players
     self.send_updates(UpdateType.game, UpdateType.players)
Beispiel #5
0
 def start_game(self):
     """Start the game, resetting it if it has ended."""
     # reset the game if one has already been played
     if self.state == GameState.game_ended:
         self.stop_game()
     if self.state != GameState.not_started:
         raise InvalidGameState("game_already_started",
                                "game is already ongoing")
     # ensure that there are enough players
     if len(self.players) < 3:
         raise InvalidGameState("too_few_players", "too few players")
     # prepare the deck and ensure that there are enough cards
     # TODO make these errors another type instead of InvalidGameState?
     self._build_decks()
     if self.black_deck.total_cards() == 0:
         raise InvalidGameState("too_few_black_cards",
                                "no black cards in selected packs")
     if self.white_deck.total_cards() < (config.game.hand_size + 2) * len(
             self.players):
         raise InvalidGameState(
             "too_few_white_cards",
             "too few white cards in selected packs for this many players")
     # start the game
     self._start_next_round()
Beispiel #6
0
 def _handle_game_options(self, content: dict):
     changes = {}
     for field in fields(GameOptions):
         if field.name in content:
             if self.user.game.game_running and field.name not in GameOptions.updateable_ingame:
                 raise InvalidGameState(
                     "option_locked",
                     f"{field.name} can't be changed while the game is ongoing"
                 )
             value = content[field.name]
             if field.name == "card_packs":
                 try:
                     value = tuple(
                         self.server.card_packs.find_by(
                             "id", CardPackID(UUID(uuid)))
                         for uuid in value)
                 except (TypeError, ValueError, KeyError):
                     raise InvalidRequest("invalid card_packs list")
             changes[field.name] = value
     try:
         new_options = replace(self.user.game.options, **changes)
         self.user.game.update_options(new_options)
     except ConfigError as ex:
         raise GameError("invalid_options", str(ex)) from None
Beispiel #7
0
 def play_white_cards(self, round_id: RoundID, player: Player,
                      cards: Sequence[Tuple[WhiteCardID, Optional[str]]]):
     if self.state != GameState.playing:
         raise InvalidGameState(
             "invalid_round_state",
             "white cards are not being played for the round")
     if round_id != self.current_round.id:
         raise InvalidGameState("wrong_round",
                                "the round is not being played")
     if not self.current_round.needs_to_play(player):
         raise InvalidGameState(
             "already_played",
             "you already played white cards for the round")
     # validate the cards
     if len(set(slot_id for slot_id, _ in cards)) != len(cards):
         raise InvalidGameState("invalid_white_cards",
                                "duplicate cards chosen")
     if len(cards) != self.current_round.black_card.pick_count:
         raise InvalidGameState("invalid_white_cards",
                                "wrong number of cards chosen")
     # find the cards in the player's hand
     cards_to_play = []
     for slot_id, text in cards:
         try:
             card = single(card for card in player.hand
                           if card.slot_id == slot_id)
         except ValueError:
             raise InvalidGameState("card_not_in_hand",
                                    "you do not have the chosen cards")
         if text is not None:
             card = card.write_blank(text)
         cards_to_play.append(card)
     # play the cards from the hand
     for card in cards_to_play:
         player.play_card(card)
     self.current_round.white_cards[player.id] = cards_to_play
     player.idle_rounds = 0
     # start judging if necessary
     self._check_all_played()
     # sync state to players
     self.send_updates(UpdateType.players)
     self.send_updates(UpdateType.hand, UpdateType.game, to=player)
Beispiel #8
0
 def write_blank(self, text: str) -> WhiteCard:
     if not self.blank:
         raise InvalidGameState("invalid_white_cards",
                                "card is not a blank")
     return WhiteCard(self.slot_id, text, True)
Beispiel #9
0
 def play_card(self, card: WhiteCard):
     for hand_card in self.hand:
         if hand_card.slot_id == card.slot_id:
             self.hand.remove(hand_card)
             return
     raise InvalidGameState("card_not_in_hand", "you do not have the card")
Beispiel #10
0
 def added_to_game(self, game: Game, player: Player):
     assert not self.game, "user already in game"
     if not self.connection:
         raise InvalidGameState("user_not_connected", "user not connected")
     self.game = game
     self.player = player
Beispiel #11
0
 def _wrapped(self: GameConnection, content: dict):
     if self.user.player != self.user.game.host:
         raise InvalidGameState("user_not_host", "you are not the host")
     return method(self, content)
Beispiel #12
0
 def _wrapped(self: GameConnection, content: dict):
     if self.user.player != self.user.game.card_czar:
         raise InvalidGameState("user_not_czar",
                                "you are not the card czar")
     return method(self, content)
Beispiel #13
0
 def _wrapped(self: GameConnection, content: dict):
     if not self.user.game:
         raise InvalidGameState("user_not_in_game", "user not in game")
     return method(self, content)