import itertools from collections import defaultdict from typing import List, Dict, Optional from server.game.manager import ClientManager from server.game.message import MessageToPlayer from server.model.card import Card from server.model.deck import Deck from server.model.hand import Hand from server.model.players import Player, Announce from server.model.value import Value class Game: def __init__(self, players=None, deck: Deck = Deck(), manager: Optional[ClientManager] = None ): if players is None: players = [] self.deck: Deck = deck self.players: List[Player] = players self.defeats: Dict[Player, int] = defaultdict(lambda: 0) self.current_bet: Optional[Hand] = None self.manager = manager async def message(self, message: MessageToPlayer, *to: Player, extra=None ) -> None: if self.manager: if not to: to = self.players for player in to: await self.manager.send(player, message, extra) @property def global_hand(self) -> Hand: all_cards = [c for p in self.players for c in p.hand.cards] return Hand(cards=all_cards) async def new_game(self): print(f"Game starting with {self.players}!") self.deck.reset() while len(self.players) > 1: loser = await self.new_turn() if self.defeats[loser] == 5: print(f"{loser} is eliminated!") await self.message(MessageToPlayer.Lose, loser) self.players.remove(loser) else: print(f"{loser} lost the round, now playing with {self.defeats[loser] + 1} cards!") winner = self.players[0] await self.message(MessageToPlayer.Win, winner) await self.message(MessageToPlayer.WinnerIs, extra=winner) print(f"Game over - {winner.name} wins with {len(winner.hand)} cards!") async def new_turn(self) -> Player: """ Runs a turn of the game. :return: the player that lost this turn. """ # Distribution self.deck.reset() await self.message(MessageToPlayer.WaitTurn) for current_player in self.players: current_player.clear() count_player_cards = self.defeats[current_player] + 1 print(f"Drawing {count_player_cards} card(s) for {current_player}: ", end="") for i in range(count_player_cards): card = self.deck.random_card() current_player.give(card) print(f"{card}") await self.message(MessageToPlayer.GiveHand, current_player, extra=current_player.hand.json()) print(f"Cards sent.") # Tour self.current_bet = None last_player = None for current_player in itertools.cycle(self.players): loser = await self.play_turn(current_player, last_player) if loser is not None: self.players.remove(loser) self.players.insert(0, loser) await self.message(MessageToPlayer.LoseRound, extra=loser.name) return loser last_player = current_player # TODO: Put next first player first of list def is_menteur(self, bet: Hand): """ Is this bet a menteur? :param bet: :return: """ to_scan = [Card(value=c.value) for c in self.global_hand.cards.copy()] # Keep only values to_find = [Card(value=c.value) for c in bet.cards] menteur = False for card in to_find: if card in to_scan: to_scan.remove(card) continue else: print(f"Missing {card}!") menteur = True if menteur: print(f"Didn't find {bet} in {self.global_hand}: MENTEUR!") return menteur def count_defeat(self, loser): self.defeats[loser] += 1 print(f"Total defeats for {loser}: {self.defeats[loser]}") def add_player(self, player: Player): """ Adds a player to the game. :return: True if the player was accepted (unique names). """ if any([s.name == player.name for s in self.players]): return False else: self.players.append(player) return True async def play_turn(self, current_player: Player, last_player: Player) -> Optional[Player]: """ Runs a turn, eventually returning a loser. :param current_player: :param last_player: :return: """ print(f"| {current_player}'s turn >") if not self.current_bet: # First player, has to bet something print("Game: First awaiting current bet") await self.message(MessageToPlayer.YourTurn, current_player, extra=self.current_bet) while not self.current_bet: # Ask a valid bet print("Game: While no bet, awaiting") announce = await current_player.announce(self.current_bet) if announce.bet: self.current_bet = announce.bet print(f"{current_player} starts the round: {self.current_bet}") print("Game: Awaiting start announce") await self.message(MessageToPlayer.Announce, extra={"player": current_player.name, "announce": announce.json()}) else: print(f"You cannot say Menteur on first round, {current_player}!") else: # Next player, announce or menteur # Wait, is the announce the last possible one? if len(self.current_bet.cards) == 4 and all([c.value is Value.Ace for c in self.current_bet.cards]): print("CARRE D'AS!") announce = Announce() # MENTEUR obligatoire else: print("Game: Awaiting bet") await self.message(MessageToPlayer.YourTurn, current_player, extra=self.current_bet) announce = await current_player.announce(self.current_bet) if announce.bet: while announce.bet.value() < self.current_bet.value(): # Bad announce! print(f"Invalid bet by {current_player}, {announce.bet} < {self.current_bet}!") announce = await current_player.announce(self.current_bet) self.current_bet = announce.bet # Valid bet: print(f" {current_player} bets {self.current_bet}.") self.message(MessageToPlayer.Announce, extra={"player": current_player, "announce": announce}) else: # Menteur! Who lost the round? menteur = self.is_menteur(self.current_bet) loser = last_player if menteur else current_player self.count_defeat(loser) print(f"{current_player} says Menteur... {'Bravo!' if menteur else 'FAIL!'}") return loser