Files
bridzik/bridzik.py
T
2021-04-18 17:22:26 +02:00

343 lines
14 KiB
Python

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 '<Card {}_{}>'.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()
}
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()
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