refactor: Move tested classes, prepare game

parent b62cff4c
from dataclasses import dataclass
from enum import Enum
from typing import Optional
class Value(Enum):
Two = 1
Three = 2
Four = 3
Five = 4
Six = 5
Seven = 6
Eight = 7
Nine = 8
Ten = 9
Jack = 10
Queen = 11
King = 12
Ace = 13
class Color(Enum):
Hearts = "♥"
Spades = "♠"
Clubs = "♣"
Diamonds = "♦"
@dataclass(frozen=True)
class Card:
value: Value
color: Optional[Color] = None
def __cmp__(self, other: "Card"):
my = self.score()
their = other.score()
return (my > their) - (my < their)
def __lt__(self, other: "Card"):
return self.score() < other.score()
def __gt__(self, other: "Card"):
return self.score() > other.score()
def __eq__(self, other: "Card"):
return self.score() == other.score()
def __str__(self) -> str:
return f"{self.value.name} of {self.color.value if self.color else '?'}"
def score(self) -> int:
return int(self.value.value)
\ No newline at end of file
from collections import defaultdict, Counter import itertools
from collections import defaultdict
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum
from random import shuffle, randrange
from typing import List, Dict, Optional from typing import List, Dict, Optional
from model.animals import random_animal_name from model.animals import random_animal_name
from model.card import Card
from model.deck import Deck
from model.hand import Hand
class Value(Enum): @dataclass
Two = 1 class Announce:
Three = 2 bet: Optional[Hand] = None
Four = 3
Five = 4
Six = 5
Seven = 6
Eight = 7
Nine = 8
Ten = 9
Jack = 10
Queen = 11
King = 12
Ace = 13
@property
class Color(Enum): def menteur(self):
Hearts = "♥" return not self.bet
Spades = "♠"
Clubs = "♣"
Diamonds = "♦"
@dataclass(frozen=True)
class Card:
value: Value
color: Optional[Color] = None
def __cmp__(self, other: "Card"):
my = self.score()
their = other.score()
return (my > their) - (my < their)
def __lt__(self, other: "Card"):
return self.score() < other.score()
def __gt__(self, other: "Card"):
return self.score() > other.score()
def __eq__(self, other: "Card"):
return self.score() == other.score()
def __str__(self) -> str:
return f"{self.value.name} of {self.color.value if self.color else '?'}"
def score(self) -> int:
return int(self.value.value)
class Hand:
def __init__(self, cards: List[Card] = None):
if cards is None:
cards = []
self.cards: List[Card] = cards
def __len__(self) -> int:
return len(self.cards)
def __getitem__(self, item):
return self.cards[item]
def __repr__(self):
return "|".join([str(c) for c in self.cards])
def give(self, other: Card):
self.cards.append(other)
def value(self):
counter = Counter([c.value for c in self.cards])
print("Counter:", counter)
has_pair = False
has_double_pair = False
has_brelan = False
has_full = False
has_carre = False
highest_card = None
pair = None
double_pair = None
brelan = None
carre = None
for element, count in counter.items():
element_cards = [c for c in self.cards if c.value == element]
card = element_cards[0] # Note we take a random color
highest_card = max(highest_card, card) if highest_card else card
if count == 2:
if has_pair:
has_double_pair = True
double_pair = max(pair, card)
pair = min(pair, card)
else:
has_pair = True
pair = max(pair, card) if pair else card
if count == 3:
has_brelan = True
brelan = max(brelan, card) if brelan else card
if has_brelan and has_pair:
has_full = True
if count == 4:
has_carre = True
carre = max(carre, card) if carre else card
print(" | ".join([
f"ANALYSIS",
f"Carre[{carre}]" if has_carre else "no carre",
f"Full[{brelan}|{pair}]" if has_full else "no full",
f"Brelan[{brelan}]" if has_brelan else "no carre",
f"Double paire[{double_pair}|{pair}]" if has_double_pair else "no Dpaire",
f"Paire[{pair}]" if has_pair else "no paire",
f"Card[{highest_card}]"
]), end=" ")
# Finished counting, let's return scores
if has_carre:
score = (20 ** 5) * carre.score()
elif has_full:
score = (20 ** 4) * brelan.score() + 20 * pair.score()
elif has_brelan:
score = (20 ** 3) * brelan.score()
elif has_double_pair:
score = (20 ** 2) * double_pair.score() + pair.score()
elif has_pair:
score = 20 * pair.score()
else:
score = highest_card.score()
print("\t-> score=\t", score)
return score
class Deck:
def __init__(self):
self.cards = [Card(v, c) for v in Value for c in Color]
self.defausse = []
def __len__(self):
return len(self.cards)
def random_card(self) -> Card:
if not self.cards:
self.reset()
card = self.cards.pop(randrange(len(self.cards)))
self.defausse.append(card)
return card
def reset(self):
self.cards.extend(self.defausse)
self.defausse.clear()
shuffle(self.cards)
class Player: class Player:
name: str = random_animal_name() def __init__(self, name: str = None):
hand: Hand = Hand() if not name:
name = random_animal_name()
self.name: str = name
self.hand: Hand = Hand()
def give(self, card: Card): def give(self, card: Card):
self.hand.give(card) self.hand.give(card)
def announce(self, current_bet: Optional[Hand]) -> Announce:
""" A naive player that only trusts what they sees:
bets his hand, or menteur if his hand is lower. """
if not current_bet or self.hand.value() > current_bet.value():
return Announce(self.hand)
else:
return Announce()
class Game: class Game:
def __init__(self, deck: Deck = Deck(), players=None): def __init__(self, players=None, deck: Deck = Deck()):
if players is None: if players is None:
players = [] players = []
...@@ -178,22 +46,77 @@ class Game: ...@@ -178,22 +46,77 @@ class Game:
self.players: List[Player] = players self.players: List[Player] = players
self.defeats: Dict[Player, int] = defaultdict(lambda: 0) self.defeats: Dict[Player, int] = defaultdict(lambda: 0)
@property
def global_hand(self) -> Hand:
return Hand([c for p in self.players for c in p.hand])
def new_game(self): def new_game(self):
self.deck.reset() self.deck.reset()
while len(self.players) > 1: while len(self.players) > 1:
self.new_turn() loser = self.new_turn()
if self.defeats[loser] == 5:
print(f"Player {loser} is eliminated!")
self.players.remove(loser)
else:
print(f"Player {loser} lost the round, now playing with {self.defeats[loser] + 1} cards!")
winner = self.players[0] winner = self.players[0]
print(f"Game over - Player {winner.name} wins with {len(winner.hand)} cards!") print(f"Game over - Player {winner.name} wins with {len(winner.hand)} cards!")
pass
def new_turn(self): def new_turn(self) -> Player:
"""
Runs a turn of the game.
:return: the player that lost this turn.
"""
# Distribution # Distribution
shuffle(self.deck) self.deck.reset()
for p in self.players: for current_player in self.players:
p.give(self.deck.random_card()) current_player.give(self.deck.random_card())
# Tour # Tour
for p in self.players: current_bet: Optional[Hand] = None
announce = p.announce(current_bet) last_player = None
for current_player in itertools.cycle(self.players):
print(f"|Player {current_player.name}'s turn >")
if not current_bet: # First player, has to bet something
while not current_bet:
announce = current_player.announce(current_bet)
if announce.bet:
current_bet = announce.bet
print(f"Player {current_player.name} starts the round: {current_bet}")
else: # Next player, announce or menteur
announce = current_player.announce(current_bet)
if announce.bet:
while announce.bet.value() < current_bet.value(): # Bad announce!
print(f"Invalid bet, {announce.bet} < {current_bet}!")
announce = current_player.announce(current_bet)
current_bet = announce.bet
print(f"Player {current_player.name} bets {current_bet}.")
else: # Menteur! Who lost the round?
menteur = self.is_menteur(current_bet)
print(f"Player {current_player.name} says Menteur... {'Bravo!' if menteur else 'FAIL!'}")
loser = last_player if menteur else current_player
print(f"Player {loser.name} lost the round!")
self.count_defeat(loser)
return loser
last_player = current_player
# TODO: Put next first player first of list # TODO: Put next first player first of list
def is_menteur(self, bet: Hand):
to_scan = [Card(c.value) for c in self.global_hand.cards.copy()] # Keep only values
to_find = [Card(c.value) for c in bet.cards]
for card in to_find:
if card in to_scan:
to_scan.remove(card)
continue
else:
print(f"Missing a {card}!")
print(f"Didn't find {bet} in {self.global_hand}: MENTEUR!")
return False #
def count_defeat(self, loser):
self.defeats[loser] += 1
from random import randrange, shuffle
from model.card import Card, Value, Color
class Deck:
def __init__(self):
self.cards = [Card(v, c) for v in Value for c in Color]
self.defausse = []
def __len__(self):
return len(self.cards)
def random_card(self) -> Card:
if not self.cards:
self.reset()
card = self.cards.pop(randrange(len(self.cards)))
self.defausse.append(card)
return card
def reset(self):
self.cards.extend(self.defausse)
self.defausse.clear()
shuffle(self.cards)
\ No newline at end of file
from collections import Counter
from typing import List
from model.card import Card
class Hand:
def __init__(self, cards: List[Card] = None):
if cards is None:
cards = []
self.cards: List[Card] = cards
def __contains__(self, item: Card):
return item in self.cards
def __len__(self) -> int:
return len(self.cards)
def __getitem__(self, item):
return self.cards[item]
def __repr__(self):
return "|".join([str(c) for c in self.cards])
def give(self, other: Card):
self.cards.append(other)
def value(self):
counter = Counter([c.value for c in self.cards])
has_pair = False
has_double_pair = False
has_brelan = False
has_full = False
has_carre = False
highest_card = None
pair = None
double_pair = None
brelan = None
carre = None
for element, count in counter.items():
element_cards = [c for c in self.cards if c.value == element]
card = element_cards[0] # Note we take a random color
highest_card = max(highest_card, card) if highest_card else card
if count == 2:
if has_pair:
has_double_pair = True
double_pair = max(pair, card)
pair = min(pair, card)
else:
has_pair = True
pair = max(pair, card) if pair else card
if count == 3:
has_brelan = True
brelan = max(brelan, card) if brelan else card
if has_brelan and has_pair:
has_full = True
if count == 4:
has_carre = True
carre = max(carre, card) if carre else card
analysis_log = " | ".join([
f"ANALYSIS",
f"Carre[{carre}]" if has_carre else "no carre",
f"Full[{brelan}|{pair}]" if has_full else "no full",
f"Brelan[{brelan}]" if has_brelan else "no carre",
f"Double paire[{double_pair}|{pair}]" if has_double_pair else "no Dpaire",
f"Paire[{pair}]" if has_pair else "no paire",
f"Card[{highest_card}]"
])
# Finished counting, let's return scores
if has_carre:
score = (20 ** 5) * carre.score()
elif has_full:
score = (20 ** 4) * brelan.score() + 20 * pair.score()
elif has_brelan:
score = (20 ** 3) * brelan.score()
elif has_double_pair:
score = (20 ** 2) * double_pair.score() + pair.score()
elif has_pair:
score = 20 * pair.score()
else:
score = highest_card.score()
analysis_log += f"\t-> score=\t{score}"
# print(analysis_log)
return score
\ No newline at end of file
from model.data import Hand, Card, Color, Value from model.hand import Hand
from model.card import Value, Color, Card
def carre(value) -> Hand: def carre(value) -> Hand:
......
from model.card import Value, Color, Card
from model.hands import pair, single, double_pair, brelan, full, carre
ACE_OF_HEARTS = Card(Value.Ace, Color.Hearts)
ACE_OF_SPADES = Card(Value.Ace, Color.Spades)
ACE_OF_DIAMONDS = Card(Value.Ace, Color.Diamonds)
ACE_OF_CLUBS = Card(Value.Ace, Color.Clubs)
PAIR_ACE = pair(Value.Ace)
SINGLE_ACE = single(Value.Ace)
DOUBLE_PAIR_ACE = double_pair(Value.Ace)
BRELAN_ACE = brelan(Value.Ace)
FULL_ACE = full(Value.Ace, Value.King)
CARRE_ACE = carre(Value.Ace)
from unittest import TestCase from unittest import TestCase
from model.data import Deck, Player, Card, Value, Color, Hand from model.data import Player
from model.deck import Deck
from model.hand import Hand
from model.card import Value, Color, Card
from model.hands import full, brelan, pair, single, double_pair, carre from model.hands import full, brelan, pair, single, double_pair, carre
from model.known import ACE_OF_HEARTS, PAIR_ACE, SINGLE_ACE, DOUBLE_PAIR_ACE, BRELAN_ACE, FULL_ACE
ACE_OF_HEARTS = Card(Value.Ace, Color.Hearts)
ACE_OF_SPADES = Card(Value.Ace, Color.Spades)
ACE_OF_DIAMONDS = Card(Value.Ace, Color.Diamonds)
ACE_OF_CLUBS = Card(Value.Ace, Color.Clubs)
PAIR_ACE = pair(Value.Ace)
SINGLE_ACE = single(Value.Ace)
DOUBLE_PAIR_ACE = double_pair(Value.Ace)
BRELAN_ACE = brelan(Value.Ace)
FULL_ACE = full(Value.Ace, Value.King)
class TestDeck(TestCase): class TestDeck(TestCase):
...@@ -154,7 +147,6 @@ class TestHand(TestCase): ...@@ -154,7 +147,6 @@ class TestHand(TestCase):
]]) ]])
self.assertGreater(full1.value(), full2.value(), "Full1 > full2 (threes >> twos)") self.assertGreater(full1.value(), full2.value(), "Full1 > full2 (threes >> twos)")
def testFull(self): def testFull(self):
low_value, other_values = lowest_value_and_rest() low_value, other_values = lowest_value_and_rest()
...@@ -184,7 +176,6 @@ class TestHand(TestCase): ...@@ -184,7 +176,6 @@ class TestHand(TestCase):
self.assertGreater(high_score, pair_score, f"Full[{high_hand}] > Pair[Ace]") self.assertGreater(high_score, pair_score, f"Full[{high_hand}] > Pair[Ace]")
self.assertGreater(high_score, single_score, f"Full[{high_hand}] > Ace") self.assertGreater(high_score, single_score, f"Full[{high_hand}] > Ace")
def testCarre(self): def testCarre(self):
low_value, other_values = lowest_value_and_rest() low_value, other_values = lowest_value_and_rest()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment