def test_small_hand_is_not_complete(): hand = Hand() hand.add(Card(rank=Rank.JACK, suit=Suit.HEART)) # 0: JH hand.add(Card(rank=Rank.QUEEN, suit=Suit.HEART)) # 1: QH md = MeldDetector(*hand.cards) assert(md.is_complete_hand == False)
def test_sets_must_be_three_long_to_count(): hand = Hand() hand.add(Card(rank=Rank.JACK, suit=Suit.HEART)) # 0: JH hand.add(Card(rank=Rank.JACK, suit=Suit.DIAMOND)) # 1: JD md = MeldDetector(*hand.cards) md._detect_all_melds() assert(len(md._melds) == 0)
class Player(): """Base class for a player in a Gin Rummy game.""" def __init__(self, contestant_id: str = None, handtype: type = None): """Create a new Player using the [optionally] specified type of Hand. Args: contestant_id (str): [optional] ID value of the entity operating this player handtype (Hand subclass): [optional] Allows Hand classes with different behaviors (e.g. HandWithMelds) to be used by Player """ self.game = None self.contestant_id = contestant_id if handtype is None: self.hand = Hand() else: if issubclass(handtype, Hand): self.hand = handtype() else: raise PylgrumInternalError( "Type {} is not a ".format(handtype) + "subclass of Hand.") def join_game(self, game: 'game.Game'): """Join player to a game.""" if not game: raise PylgrumError("Can't join game with None value") self.game = game def receive_card(self, card: Card) -> None: """Add a card to the hand.""" self.hand.add(card) def turn_start(self, move: Move) -> None: """Called by a Game to begin a turn. (abstract) Args: move (Move): the Move object used to hold/transfer move details A move involves adding either the discard or the top of the draw pile to the player's hand, then discarding. This method handles the first part of that process, and populates the in-progress move with details that the game will execute. After this call, the Move must specify the card source (draw or discard pile) that the player has chosen. Note that while the last-discarded card is passed as an argument to this method, that is only as a convenience. If the player wants the discard, they do not directly "take" it from the argument here, but rather set a move with card_source==DISCARD_STACK. """ def turn_finish(self, move: Move) -> None: """Called by a Game to finish a turn. (abstract)
def hand_with_sets(): """A hand with two sets. Should find 6 sets: (QH, QC, QD) (2S, 2H, 2D, 2C) - 5 permutations of this """ hand = Hand() for card in Card.from_text( "JH", "QH", "QC", "QD", "2S", "3C", "2H", "6S", "2D", "2C" ): hand.add(card) yield hand
def hand_with_simple_sets_and_runs(): """A hand with non-overlapping sets and runs. Should find 6 melds: 2C 2S 2H (1 set) 4D 5D 6D (1 run) 9S 10S JS (1 set) 4C This hand should have 4 points of deadwood (1 deadwood card) """ hand = Hand() for card in Card.from_text( "2C", "2S", "2H", "4D", "5D", "6D", "9S", "10S", "JS", "4C" ): hand.add(card) yield hand
def hand_with_complex_sets_and_runs(): """A hand with overlapping sets and runs. Should find 10 melds: 2C 2S 2H 2D (5 permutations of set) 3D 4D 5D (3 perms of run (including 2 above): 234, 345, 2345) 3S (1 set - 333) 3C AC (1 run - A23 (2 is above)) This hand should have 0 points of deadwood (0 deadwood cards) """ hand = Hand() for card in Card.from_text( "2C", "2S", "2H", "2D", "3D", "4D", "5D", "3S", "3C", "AC" ): hand.add(card) yield hand
def test_optimal_meld_scenario_8(): h = Hand() for card in Card.from_text( "4C", "4S", "3S", "2S", "5H", "4H", "3H", "AH", "3D", "2D" ): h.add(card) ## in this scenario, there are two equally optimal outcomes optimal_expected_option1 = [ Meld(Card.from_text("4C", "4S", "4H")), Meld(Card.from_text("3H", "3S", "3D")) ] optimal_expected_option2 = [ Meld(Card.from_text("4S", "3S", "2S")), Meld(Card.from_text("5H", "4H", "3H")) ] md = MeldDetector(*h.cards) md.detect_optimal_melds() assert(len(md.optimal_hand.melds) == len(optimal_expected_option1)) # works for either is_option_1 = True is_option_2 = True for expected_meld in optimal_expected_option1: if expected_meld not in md.optimal_hand.melds: is_option_1 = False for expected_meld in optimal_expected_option2: if expected_meld not in md.optimal_hand.melds: is_option_2 = False assert(not (is_option_1 and is_option_2)) #highlander principle assert(is_option_1 or is_option_2) # b/c the two are equiv. this is true regardless of option assert(md.optimal_hand.deadwood_value == 10) assert(md.optimal_hand.deadwood_count == 4)
def test_optimal_meld_scenario_9(): # this is a relatively simple scenario - only one card is overused h = Hand() for card in Card.from_text( "6D", "6C", "6H", "2H", "3H", "4H", "2S", "2C" ): h.add(card) optimal_expected = [ Meld(Card.from_text("6D", "6C", "6H")), Meld(Card.from_text("2H", "3H", "4H")) ] md = MeldDetector(*h.cards) md.detect_optimal_melds() assert(len(md.optimal_hand.melds) == len(optimal_expected)) for expected_meld in optimal_expected: assert(expected_meld in md.optimal_hand.melds) assert(md.optimal_hand.deadwood_value == 4) assert(md.optimal_hand.deadwood_count == 2)
def test_optimal_meld_scenario_6(): h = Hand() for card in Card.from_text( "4S", "3S", "2S", "AS", "3H", "2H", "AH", "4D", "3D", "2D" ): h.add(card) optimal_expected = [ Meld(Card.from_text("4S", "3S", "2S", "AS")), Meld(Card.from_text("3H", "2H", "AH")), Meld(Card.from_text("4D", "3D", "2D")) ] md = MeldDetector(*h.cards) md.detect_optimal_melds() assert(len(md.optimal_hand.melds) == len(optimal_expected)) for expected_meld in optimal_expected: assert(expected_meld in md.optimal_hand.melds) assert(md.optimal_hand.deadwood_value == 0) assert(md.optimal_hand.deadwood_count == 0)
def test_optimal_meld_scenario_1(): h = Hand() for card in Card.from_text( "10S", "9S", "8S", "8H", "9C", "8C", "7C", "6C", "5C", "KD" ): h.add(card) optimal_expected = [ Meld(Card.from_text("10S", "9S", "8S")), Meld(Card.from_text("9C", "8C", "7C", "6C", "5C")) ] md = MeldDetector(*h.cards) md.detect_optimal_melds() assert(len(md.optimal_hand.melds) == len(optimal_expected)) for expected_meld in optimal_expected: assert(expected_meld in md.optimal_hand.melds) assert(md.optimal_hand.deadwood_value == 18) assert(md.optimal_hand.deadwood_count == 2)
def hand_with_overlapping_sets_and_runs(): """A hand with obviously overlapping sets and runs. Should find 6 melds: 2C 2S 2H 2D (5 permutations of set) 4D 5D 6D (1 run) 8S 10S JS This hand should have 28 points of deadwood (3 deadwood cards) The optimal sets from this hand are: 2C 2S 2H 2D 4D 5D 6D """ hand = Hand() for card in Card.from_text( "2C", "2S", "2H", "2D", "4D", "5D", "6D", "8S", "10S", "JS" ): hand.add(card) yield hand
def test_runs_must_be_same_suit(): hand = Hand() hand.add(Card(rank=Rank.JACK, suit=Suit.HEART)) hand.add(Card(rank=Rank.QUEEN, suit=Suit.HEART)) hand.add(Card(rank=Rank.KING, suit=Suit.CLUB)) md = MeldDetector(*hand.cards) md._detect_all_melds() assert(len(md._melds) == 0)
def test_too_many_cards(self): """Implicitly tests the add() override in Hand, too.""" h = Hand() self.assertEqual(h.size(), 0) h.add(Card(rank=Rank.QUEEN, suit=Suit.HEART)) # 0 : QH h.add(Card(rank=Rank.JACK, suit=Suit.DIAMOND)) # 1 : JD h.add(Card(rank=Rank.ACE, suit=Suit.CLUB)) # 2 : AC h.add(Card(rank=Rank.KING, suit=Suit.SPADE)) # 3 : KS h.add(Card(rank=Rank.TWO, suit=Suit.HEART)) # 4 : 2H h.add(Card(rank=Rank.THREE, suit=Suit.DIAMOND)) # 5 : 3D h.add(Card(rank=Rank.FOUR, suit=Suit.CLUB)) # 6 : 4C h.add(Card(rank=Rank.FIVE, suit=Suit.SPADE)) # 7 : 5S h.add(Card(rank=Rank.TEN, suit=Suit.HEART)) # 8 : 10H h.add(Card(rank=Rank.NINE, suit=Suit.DIAMOND)) # 9 : 9D h.add(Card(rank=Rank.EIGHT, suit=Suit.CLUB)) # 10: 8C self.assertEqual(h.size(), 11) ## a full hand with self.assertRaises(OverdealtHandError): h.add(Card(rank=Rank.SEVEN, suit=Suit.SPADE))
def hand_with_complex_runs(): """Multiple suits, not in order, overlapping runs Should find 7 runs: (3H, 4H, 5H), (4H, 5H, 6H), (5H, 6H, 7H), (3H, 4H, 5H, 6H), (4H, 5H, 6H, 7H), (3H, 4H, 5H, 6H, 7H), (9C, 10C, JC) """ hand = Hand() hand.add(Card(rank=Rank.ACE, suit=Suit.DIAMOND)) hand.add(Card(rank=Rank.ACE, suit=Suit.HEART)) hand.add(Card(rank=Rank.THREE, suit=Suit.HEART)) hand.add(Card(rank=Rank.SEVEN, suit=Suit.HEART)) hand.add(Card(rank=Rank.FOUR, suit=Suit.HEART)) hand.add(Card(rank=Rank.SIX, suit=Suit.HEART)) hand.add(Card(rank=Rank.JACK, suit=Suit.CLUB)) hand.add(Card(rank=Rank.FIVE, suit=Suit.HEART)) hand.add(Card(rank=Rank.TEN, suit=Suit.SPADE)) hand.add(Card(rank=Rank.NINE, suit=Suit.CLUB)) hand.add(Card(rank=Rank.TEN, suit=Suit.CLUB)) yield hand
def hand_with_simple_runs(): """A simple, same-suit, in-order hand with two runs. Should find two runs: (JH, QH, KH) (AS, 2S, 3S) """ hand = Hand() hand.add(Card(rank=Rank.JACK, suit=Suit.HEART)) # 0: JH hand.add(Card(rank=Rank.QUEEN, suit=Suit.HEART)) # 1: QH hand.add(Card(rank=Rank.KING, suit=Suit.HEART)) # 2: KH hand.add(Card(rank=Rank.KING, suit=Suit.SPADE)) # 3: KS hand.add(Card(rank=Rank.TWO, suit=Suit.HEART)) # 4: 2H hand.add(Card(rank=Rank.THREE, suit=Suit.DIAMOND)) # 5: 3D hand.add(Card(rank=Rank.ACE, suit=Suit.SPADE)) # 6: AS hand.add(Card(rank=Rank.TWO, suit=Suit.SPADE)) # 7: 2S hand.add(Card(rank=Rank.THREE, suit=Suit.SPADE)) # 8: 3S hand.add(Card(rank=Rank.NINE, suit=Suit.DIAMOND)) # 9: 9D yield hand