feat/refactor/fix: Players, turn, deals

parent 1ad38c08
......@@ -49,4 +49,12 @@ class Card:
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
return int(self.value.value)
def lowest_value_and_rest():
lowValue: Value = Value.Two
otherValues = list(Value)
otherValues.remove(lowValue)
return lowValue, otherValues
\ No newline at end of file
import itertools
from collections import defaultdict
from dataclasses import dataclass
from typing import List, Dict, Optional
from server.model.hand import Hand
from server.model.animals import random_animal_name
from server.model.card import Card
from server.model.deck import Deck
@dataclass
class Announce:
bet: Optional[Hand] = None
@property
def menteur(self):
return not self.bet
class Player:
def __init__(self, name: str = None):
if not name:
name = random_animal_name()
self.name: str = name
self.hand: Hand = Hand()
def __str__(self):
return f"Player {self.name}"
def give(self, card: 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()
from server.model.hand import Hand
from server.model.players import Player
class Game:
......@@ -48,6 +16,7 @@ class Game:
self.deck: Deck = deck
self.players: List[Player] = players
self.defeats: Dict[Player, int] = defaultdict(lambda: 0)
self.current_bet: Optional[Hand] = None
@property
def global_hand(self) -> Hand:
......@@ -64,6 +33,8 @@ class Game:
self.players.remove(loser)
else:
print(f"{loser} lost the round, now playing with {self.defeats[loser] + 1} cards!")
self.players.remove(loser)
self.players.insert(0, loser)
winner = self.players[0]
print(f"Game over - {winner.name} wins with {len(winner.hand)} cards!")
......@@ -77,49 +48,95 @@ class Game:
# Distribution
self.deck.reset()
for current_player in self.players:
current_player.give(self.deck.random_card())
current_player.clear()
draw = []
for i in range(self.defeats[current_player] + 1):
draw.append(self.deck.random_card())
print(f"Drew {draw} for {current_player}.")
for d in draw:
current_player.give(d)
# Tour
current_bet: Optional[Hand] = None
self.current_bet = None
last_player = None
for current_player in itertools.cycle(self.players):
print(f"| {current_player}'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"{current_player} 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" {current_player} bets {current_bet}.")
else: # Menteur! Who lost the round?
menteur = self.is_menteur(current_bet)
print(f"{current_player} says Menteur... {'Bravo!' if menteur else 'FAIL!'}")
loser = last_player if menteur else current_player
print(f"{loser} lost the round!")
self.count_defeat(loser)
return loser
loser = self.play_turn(current_player, last_player)
if loser is not None:
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(c.value) for c in self.global_hand.cards.copy()] # Keep only values
to_find = [Card(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 a {card}!")
print(f"Didn't find {bet} in {self.global_hand}: MENTEUR!")
return False #
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
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
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
while not self.current_bet: # Ask a valid bet
announce = current_player.announce(self.current_bet)
if announce.bet:
self.current_bet = announce.bet
print(f"{current_player} starts the round: {self.current_bet}")
else: # Next player, announce or menteur
announce = 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 = current_player.announce(self.current_bet)
self.current_bet = announce.bet
# Valid bet:
print(f" {current_player} bets {self.current_bet}.")
else: # Menteur! Who lost the round?
print(f" {current_player} says Menteur!")
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
......@@ -22,4 +22,4 @@ class Deck:
def reset(self):
self.cards.extend(self.defausse)
self.defausse.clear()
shuffle(self.cards)
\ No newline at end of file
shuffle(self.cards)
from collections import Counter
from typing import List
from server.model.card import Card
from server.model.card import Card, Value
class Hand:
......@@ -23,9 +23,17 @@ class Hand:
return "|".join([str(c) for c in self.cards])
def give(self, other: Card):
"""
Adds another card to this hand.
:raises ValueError if the card is already in this hand.
"""
if (other.value, other.color) in [(c.value, c.color) for c in self.cards]:
raise ValueError(f"TRICHEUR! {other} already in this hand: {self.cards}!")
self.cards.append(other)
def value(self):
""" Scores this hand according to the Poker Menteur rules."""
counter = Counter([c.value for c in self.cards])
has_pair = False
......@@ -87,4 +95,12 @@ class Hand:
analysis_log += f"\t-> score=\t{score}"
# print(analysis_log)
return score
\ No newline at end of file
return score
@property
def is_menteur(self) -> bool:
return len(self.cards) == 0
@property
def is_carre_as(self) -> bool:
return len(self.cards) == 4 and all([c.value == Value.Ace for c in self.cards])
from server.model.hand import Hand
from random import choice
from typing import List
from server.model.card import Value, Color, Card
from server.model.hand import Hand
def carre(value) -> Hand:
......@@ -57,3 +60,32 @@ def double_pair(value: Value, other: Value = None):
Card(other, Color.Hearts),
Card(other, Color.Clubs)
])
def all_options() -> List[Hand]:
hands = []
carres = [carre(value) for value in Value]
brelans = [brelan(value) for value in Value]
pairs = [pair(value) for value in Value]
singles = [single(value) for value in Value]
hands.extend(carres)
hands.extend(brelans)
hands.extend(pairs)
hands.extend(singles)
for b in brelans:
for p in pairs:
if any([c in b.cards for c in p.cards]): # Invalid full
print(f"Invalid full: {b.cards}-{p.cards}")
else:
hands.append(Hand([*b.cards, *p.cards]))
return hands
options = all_options()
def random_option() -> Hand:
return choice(options)
from abc import abstractmethod, ABC
from dataclasses import dataclass
from typing import Optional
from server.model.animals import random_animal_name
from server.model.card import Card
from server.model.hand import Hand
from server.model.hands import random_option
@dataclass
class Announce:
bet: Optional[Hand] = None
@property
def menteur(self):
return not self.bet
class Player(ABC):
def __init__(self, name: str = None):
if not name:
name = random_animal_name()
self.name: str = name
self.hand: Hand = Hand()
def __str__(self):
return f"Player {self.name}"
def __repr__(self):
return str(self)
def give(self, card: Card):
self.hand.give(card)
def clear(self):
self.hand.cards.clear()
@abstractmethod
def announce(self, current_bet: Optional[Hand]) -> Announce:
"""
Announces a bet or Menteur, based on the current bet.
:param current_bet:
:return:
"""
return Announce()
class NaivePlayer(Player, ABC):
""" A naive player that only trusts what they sees:
bets his hand, or menteur if his hand is lower. """
def __str__(self):
return "Naive " + super().__str__()
def announce(self, current_bet: Optional[Hand]) -> Announce:
if not current_bet or self.hand.value() > current_bet.value():
return Announce(self.hand)
else:
return Announce()
class RandomPlayer(Player, ABC):
""" A weird player that never says menteur, always betting a random option. """
def __str__(self):
return "Random " + super().__str__()
def announce(self, current_bet: Optional[Hand]) -> Announce:
if current_bet and not current_bet.is_menteur and current_bet.is_carre_as:
return Announce()
else:
return Announce(random_option())
class MenteurPlayer(Player, ABC):
""" A crazy player that always says menteur. """
def __str__(self):
return "Menteur " + super().__str__()
def announce(self, current_bet: Optional[Hand]) -> Announce:
hand = random_option()
return Announce(hand)
from unittest import TestCase
from server.model.data import Player
from server.model.card import Value, Color, Card, lowest_value_and_rest
from server.model.deck import Deck
from server.model.hand import Hand
from server.model.card import Value, Color, Card
from server.model.hands import full, brelan, pair, single, double_pair, carre
from server.model.known import ACE_OF_HEARTS, PAIR_ACE, SINGLE_ACE, DOUBLE_PAIR_ACE, BRELAN_ACE, FULL_ACE
from server.model.players import RandomPlayer
class TestDeck(TestCase):
......@@ -30,7 +30,7 @@ class TestPlayer(TestCase):
def setUp(self) -> None:
super().setUp()
self.player = Player()
self.player = RandomPlayer()
def testHand(self):
self.assertEqual(0, len(self.player.hand), "Begin no cards")
......@@ -42,18 +42,15 @@ class TestPlayer(TestCase):
self.assertEqual(Value.Ace, self.player.hand[0].value, "Is Ace")
self.assertEqual(Color.Hearts, self.player.hand[0].color, "of Hearts")
def testTricheur(self):
with self.assertRaises(ValueError, msg="Giving someone a card they already have is an error"):
self.player.give(ACE_OF_HEARTS)
self.player.give(ACE_OF_HEARTS)
def testDefeats(self):
pass
def lowest_value_and_rest():
lowValue: Value = Value.Two
otherValues = list(Value)
otherValues.remove(lowValue)
return lowValue, otherValues
class TestHand(TestCase):
def setUp(self) -> None:
self.hand = Hand()
......@@ -126,27 +123,6 @@ class TestHand(TestCase):
self.assertGreater(high_score, pair_score, f"Brelan[{high_hand}] > Pair[Ace]")
self.assertGreater(high_score, single_score, f"Brelan[{high_hand}] > Ace")
def testFulls(self):
# AssertionError: 24060 not greater than 24060 :
# Full[Three of ♥|Three of ♣|Three of ♠|Four of ♥|Four of ♣]
# > Full[Two of ♥|Two of ♣|Two of ♠|Four of ♥|Four of ♣]]
full1 = Hand([Card(v) for v in [
Value.Three,
Value.Three,
Value.Three,
Value.Four,
Value.Four,
]])
full2 = Hand([Card(v) for v in [
Value.Two,
Value.Two,
Value.Two,
Value.Four,
Value.Four,
]])
self.assertGreater(full1.value(), full2.value(), "Full1 > full2 (threes >> twos)")
def testFull(self):
low_value, other_values = lowest_value_and_rest()
......@@ -202,3 +178,25 @@ class TestHand(TestCase):
self.assertGreater(high_score, double_pair_score, f"Carre[{high_hand}] > Pair[Ace]")
self.assertGreater(high_score, pair_score, f"Carre[{high_hand}] > Pair[Ace]")
self.assertGreater(high_score, single_score, f"Carre[{high_hand}] > Ace")
# Specifics
def testFulls(self):
# AssertionError: 24060 not greater than 24060 :
# Full[Three of ♥|Three of ♣|Three of ♠|Four of ♥|Four of ♣]
# > Full[Two of ♥|Two of ♣|Two of ♠|Four of ♥|Four of ♣]]
full1 = Hand([Card(v) for v in [
Value.Three,
Value.Three,
Value.Three,
Value.Four,
Value.Four,
]])
full2 = Hand([Card(v) for v in [
Value.Two,
Value.Two,
Value.Two,
Value.Four,
Value.Four,
]])
self.assertGreater(full1.value(), full2.value(), "Full1 > full2 (threes >> twos)")
from unittest import TestCase
from server.model.data import Game, Player
from server.model.data import Game
from server.model.players import MenteurPlayer, NaivePlayer, RandomPlayer
class TestGame(TestCase):
def setUp(self) -> None:
super().setUp()
self.player1 = Player("PLN")
self.player2 = Player("Nassim")
self.player1 = RandomPlayer("PLN")
self.player2 = NaivePlayer("Nassim")
self.game = Game([self.player1, self.player2])
def test_global_hand(self) -> None:
......@@ -23,11 +24,23 @@ class TestGame(TestCase):
self.assertTrue(card2 in self.game.global_hand, "Global hand should contain player2's first card")
self.assertTrue(card3 in self.game.global_hand, "Global hand should contain player2's second card")
def test_add_player(self):
self.game.add_player(RandomPlayer("Foo"))
self.assertEqual(3, len(self.game.players), "Should be added")
self.game.add_player(RandomPlayer("Foo"))
self.assertEqual(3, len(self.game.players), "Should not add duplicate")
def test_turn(self):
menteur = MenteurPlayer()
naive = NaivePlayer()
self.game = Game([menteur, naive])
self.game.new_turn()
self.assertEqual(1, len(self.game.defeats.values()), "There should have been one defeat.")
self.assertTrue(1 in self.game.defeats.values(), "A player should have one defeat.")
loser = [p for p in self.game.players if self.game.defeats[p]][0]
self.assertEqual(self.game.players[0], loser, "The loser should be first to play.")
def test_full_game(self):
self.game.new_game()
......
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