from enum import Enum from random import shuffle from json import JSONEncoder class BridzikException(Exception): pass class RuleException(Exception): pass class Card_colors(Enum): HEARTS = 'červeň' LEAVES = 'zeleň' ACORNS = 'žaluď' BELLS = 'guľa' def __eq__(self, other): if self.__class__ is other.__class__: return self.name == other.name return NotImplemented class Card_values(Enum): C7 = 1 C8 = 2 C9 = 3 C10 = 4 LOWER = 5 UPPER = 6 KING = 7 ACE = 8 def __ge__(self, other): if self.__class__ is other.__class__: return self.value >= other.value return NotImplemented def __gt__(self, other): if self.__class__ is other.__class__: return self.value > other.value return NotImplemented def __le__(self, other): if self.__class__ is other.__class__: return self.value <= other.value return NotImplemented def __lt__(self, other): if self.__class__ is other.__class__: return self.value < other.value return NotImplemented def __eq__(self, other): if self.__class__ is other.__class__: return self.name == other.name return NotImplemented class Card(): def __init__(self, color: Card_colors, value: Card_values): self.color = color self.value = value def __eq__(self, other): if self.__class__ is other.__class__: return self.color == other.color \ and self.value == other.value return NotImplemented def __str__(self): return '{}_{}'.format(self.color.name, self.value.name) def __repr__(self): return ''.format(self.color.name, self.value.name) class JSONEncoder(JSONEncoder): def default(self, obj): if isinstance(obj, Card): return { 'color': obj.color.name, 'value': obj.value.name } return JSONEncoder.default(self, obj) cards = [Card(color, value) for value in Card_values for color in Card_colors] class Bridzik(): def __init__(self): self.series = [Series(0, 0)] def play_card(self, player: int, card: Card): if self.is_completed(): raise BridzikException('Hra je ukoncena.') self.series[-1].play_card(player, card) if self.series[-1].is_completed() and not self.is_completed(): self.series.append(Series(len(self.series))) def add_player_guess(self, player: int, guess): if self.is_completed(): raise BridzikException('Hra je ukoncena.') self.series[-1].add_player_guess(player, guess) def get_status(self, player: int): status = {} last_series = self.series[-1] if not self.is_completed(): status['active_player'] = last_series.get_last_round().get_active_player() status['active_round_guesses'] = last_series.get_last_round().guesses status['player_cards'] = last_series.get_last_round().player_cards[player] if last_series.get_last_round().is_guessing_completed(): status['active_round_stashes'] = last_series.get_last_round().get_stashes_winner_summary() status['active_stash'] = { 'first_player': last_series.get_last_round().get_last_stash().first_player, 'cards': last_series.get_last_round().get_last_stash().get_cards() } if self.get_previous_stash(): status['previous_stash'] = { 'first_player': self.get_previous_stash().first_player, 'cards': self.get_previous_stash().get_cards() } status['standings'] = [s.get_standings() for s in self.series] return status def is_completed(self): return len(self.series) == 4 and self.series[-1].is_completed() def get_previous_stash(self): if len(self.series[-1].get_last_round().stashes) > 1: return self.series[-1].get_last_round().stashes[-2] elif len(self.series[-1].rounds) > 1: return self.series[-1].rounds[-2].get_last_stash() elif len(self.series) > 1: return self.series[-2].get_last_round().get_last_stash() return None class Series(): def __init__(self, series_number: int, first_player: int = None): self.series_number = series_number self.first_player = first_player if first_player else series_number self.rounds = [] self.start_new_round() def add_player_guess(self, player: int, guess: int): if self.get_last_round().is_completed(): raise BridzikException('Seria je ukoncena') if player not in [0, 1, 2, 3]: raise BridzikException('Cislo hraca musi byt 0, 1, 2 alebo 3.') if self.get_last_round().is_guessing_completed(): raise BridzikException('Tipovanie ukoncene') if player != self.get_last_round().get_active_player(): raise BridzikException('Hrac nie je na tahu') self.get_last_round().add_player_guess(player, guess) def play_card(self, player: int, card: Card): if self.get_last_round().is_completed(): raise BridzikException('Seria je ukoncena') if player not in [0, 1, 2, 3]: raise BridzikException('Cislo hraca musi byt 0, 1, 2 alebo 3.') if not self.get_last_round().is_guessing_completed(): raise BridzikException('Tipovanie nie je ukoncene.') if player != self.get_last_round().get_active_player(): raise BridzikException('Hrac nie je na tahu') self.get_last_round().play_card(player, card) if self.get_last_round().is_completed() and not self.is_completed(): self.start_new_round() def is_completed(self): return len(self.rounds) == 8 and self.get_last_round().is_completed() def get_standings(self): return [r.get_points_summary() for r in self.rounds if r.is_completed()] def get_last_round(self): return self.rounds[-1] if self.rounds else None def start_new_round(self): if self.is_completed(): raise BridzikException('Seria je ukoncena.') round_number = len(self.rounds) if round_number != 0 and not self.get_last_round().is_completed(): raise BridzikException('Predchadzajuce kolo nie je ukoncene') self.rounds.append( Round(round_number, (self.first_player + round_number) % 4) ) class Round(): def __init__(self, round_number: int, first_player: int, cards: []=cards, shuffler = shuffle): # vyrob kopku pre toto kolo a priprav prazdne objekty if round_number not in [i for i in range(8)]: raise BridzikException('Neplatne cislo kola.') if first_player not in [0, 1, 2, 3]: raise BridzikException('Cislo hraca musi byt 0, 1, 2 alebo 3.') self.round_number = round_number self.first_player = first_player self.deal_starting_cards(cards, shuffler) self.guesses = {} self.stashes = [] def add_player_guess(self, player: int, guess: int): if player not in [0, 1, 2, 3]: raise BridzikException('Cislo hraca musi byt 0, 1, 2 alebo 3.') if player in self.guesses: raise BridzikException('Pre tohto hraca uz bol zadany tip.') if player != self.get_active_player(): raise BridzikException('Nie je mozne pridat tip mimo poradia.') if guess < 0 or guess > 8 - self.round_number: raise BridzikException('Tip musi byt vacsi ako 0 a nie vacsia ako pocet kariet.') if len(self.guesses) < 3: self.guesses[player] = guess elif 8 - self.round_number != guess + sum([self.guesses[player] for player in self.guesses]): self.guesses[player] = guess self.start_new_stash() else: raise BridzikException('Nie je mozne zadat tip. Sucet sa rovna poctu kopok v hre.') def play_card(self, player, card: Card): if self.is_completed(): raise BridzikException('Kolo je ukoncene.') if not self.is_guessing_completed(): raise BridzikException('Tipovanie nie je ukoncene.') if self.get_last_stash().get_active_player() != player: raise BridzikException('Hrac nie je na tahu.') if card not in self.player_cards[player]: raise BridzikException('Hrac nema tuto kartu na ruke.') if self.get_last_stash().get_first_card(): if [c for c in self.player_cards[player] if c.color == \ self.get_last_stash().get_first_card().color] == []: # nema farbu if [c for c in self.player_cards[player] if c.color == \ Card_colors['HEARTS']] == []: # nema cerven pass elif card.color != Card_colors['HEARTS']: # ma cerven ale nezahral raise BridzikException('Musi zahrat cerven, ktory ma.') elif card.color != self.get_last_stash().get_first_card().color: # ma farbu ale nezahral raise BridzikException('Musi zahrat farbu.') self.player_cards[player] = [c for c in self.player_cards[player] if c != card] self.get_last_stash().add_card(player, card) if self.get_last_stash().is_completed() and not self.is_completed(): self.start_new_stash() def get_highest_guessing_player(self) -> int: if not self.is_guessing_completed(): raise BridzikException('Tipovanie nebolo skoncene.') round_highest_guessing_player = self.first_player for i in range(1, 4): if self.guesses[round_highest_guessing_player] < self.guesses[(i+self.first_player) % 4]: round_highest_guessing_player = (i+self.first_player) % 4 return round_highest_guessing_player def get_active_player(self): if not self.is_completed(): if not self.is_guessing_completed(): return (self.first_player + len(self.guesses)) % 4 return self.get_last_stash().get_active_player() raise BridzikException('Kolo je uzavrete') def is_completed(self): """Round completion status""" return len(self.stashes) == 8 - self.round_number \ and self.get_last_stash().is_completed() def is_guessing_completed(self): return len(self.guesses) == 4 def get_points_summary(self) -> []: points_summary = [0]*4 stashes_summary = self.get_stashes_winner_summary() if self.is_completed(): for player in range(4): if self.guesses[player] == stashes_summary[player]: points_summary[player] = 10 + self.guesses[player] return points_summary else: raise BridzikException('Kolo nie je ukoncene') def get_stashes_winner_summary(self) -> []: summary = [0]*4 for stash in self.stashes: if stash.is_completed(): summary[stash.get_winner()] += 1 return summary def get_last_stash(self): return self.stashes[-1] if self.stashes else None def deal_starting_cards(self, cards: [], shuffler = shuffle): self.round_cards = cards.copy() shuffler(self.round_cards) self.round_cards = self.round_cards[(4*self.round_number):] self.player_cards = {} for player_number in range(4): self.player_cards[player_number] = self.round_cards[ player_number * (8-self.round_number) : (player_number + 1) * (8-self.round_number) ] self.player_starting_cards = self.player_cards.copy() def start_new_stash(self): if self.is_completed(): raise BridzikException('Kolo je ukoncene.') if len(self.stashes) == 0: self.stashes.append(Stash(self.get_highest_guessing_player())) elif not self.get_last_stash().is_completed(): raise BridzikException('Predchadzajuca kopka nie je ukoncena.') else: self.stashes.append(Stash(self.get_last_stash().get_winner())) class Stash(): def __init__(self, first_player: int): if first_player not in [0, 1, 2, 3]: raise BridzikException('Cislo hraca musi byt 0, 1, 2 alebo 3.') self._cards = {} self.first_player = first_player def add_card(self, player: int, card: Card): if player not in [0, 1, 2, 3]: raise BridzikException('Cislo hraca musi byt 0, 1, 2 alebo 3.') if player in self._cards: raise BridzikException('Pre tohto hraca uz bola pridana karta') self._cards[player] = card def get_first_card(self) -> Card: """Return copy of first card in stash""" return Card( self._cards[self.first_player].color, self._cards[self.first_player].value ) if len(self._cards) > 0 else None def get_winner(self) -> int: if not self.is_completed(): raise BridzikException('Nie je mozne urcit vitaza, kopka nie je dohrana.') winner = self.first_player winning_card = self.get_first_card() for player_number in range(4): if self._cards[player_number].color == self.get_first_card().color \ or self._cards[player_number].color == Card_colors['HEARTS']: if self._cards[player_number].color == winning_card.color: if self._cards[player_number].value >= winning_card.value: winning_card = self._cards[player_number] winner = player_number elif self._cards[player_number].color == Card_colors['HEARTS']: winning_card = self._cards[player_number] winner = player_number return winner def get_active_player(self) -> int: if not self.is_completed(): return (self.first_player + len(self._cards)) % 4 else: raise BridzikException('Kopka je ukoncena.') def get_cards(self) -> {}: return self._cards.copy() def is_completed(self) -> bool: return len(self._cards) == 4