def test_invalid_meld_init(self): m = Meld() with self.assertRaises(InvalidMeldError): m = Meld(Card(rank=Rank.TWO, suit=Suit.HEART), Card(rank=Rank.SIX, suit=Suit.CLUB)) self.assertFalse(m.all_same_rank) self.assertFalse(m.all_same_suit) self.assertFalse(m.is_run) self.assertFalse(m.is_set) self.assertFalse(m.complete) self.assertEqual(m.size(), 0) # failed init should have emptied meld
def test_optimal_melds_chosen_from_hand_with_overlapping_melds(hand_with_overlapping_sets_and_runs): expected_melds = [ Meld(Card.from_text("2C", "2S", "2D", "2H")), Meld(Card.from_text("4D", "5D", "6D")) ] md = MeldDetector(*hand_with_overlapping_sets_and_runs.cards) md.detect_optimal_melds() assert(len(md.optimal_hand.melds) == 2) for meld in expected_melds: assert(meld in md.optimal_hand.melds) assert(md.optimal_hand.deadwood_count == 3) assert(md.optimal_hand.deadwood_value == 28)
def test_optimal_melds_chosen_from_complex_set(hand_with_complex_sets_and_runs): expected_melds = [ Meld(Card.from_text("2D", "2S", "2H")), Meld(Card.from_text("3D", "4D", "5D")), Meld(Card.from_text("AC", "2C", "3C")) ] md = MeldDetector(*hand_with_complex_sets_and_runs.cards) md.detect_optimal_melds() assert(len(md.optimal_hand.melds) == 3) for meld in expected_melds: assert(meld in md.optimal_hand.melds) assert(md.optimal_hand.deadwood_value == 3)
def test_optimal_melds_chosen_from_simple_hand(hand_with_simple_sets_and_runs): expected_melds = [ Meld(Card.from_text("2C", "2S", "2H")), Meld(Card.from_text("9S", "10S", "JS")), Meld(Card.from_text("4D", "5D", "6D")) ] md = MeldDetector(*hand_with_simple_sets_and_runs.cards) md.detect_optimal_melds() assert(len(md.optimal_hand.melds) == 3) for meld in expected_melds: assert(meld in md.optimal_hand.melds) assert(md.optimal_hand.deadwood_count == 1) assert(md.optimal_hand.deadwood_value == 4)
def remove_from_meld(self, meld: Meld, card: Card) -> None: """Remove a card from a meld. Args: meld (Meld): the meld to add to card (Card): the card to add Side effects: * Updates _card_to_meld_id map accordingly * Removes entry from _card_to_meld_id if empty """ self._card_to_meld_id[card].remove(id(meld)) if len(self._card_to_meld_id[card]) == 0: del self._card_to_meld_id[card] meld.remove(meld.find(card))
def create_meld(self, *cards) -> Meld: """Create a new [potential] meld within the hand. Args: *cards (Card list): [optional] cards to add to the meld Any cards added must be valid - an attempt to create an invalid meld will fail completely (i.e. no cards will be added, no meld created). Raises InvalidMeldError. """ # note: no check against redundant melds new_meld = Meld() added = [] valid = True for card in cards: try: self.add_to_meld(new_meld, card) except InvalidMeldError: valid = False else: added.append(card) if not valid: for card in added: self.remove_from_meld(new_meld, card) raise InvalidMeldError("non-meld passed " + "to HandWithMeld:create_meld()") self._melds.append(new_meld) self._meld_id_to_meld[id(new_meld)] = new_meld return new_meld
def test_not_same_rank(self): m = Meld(Card(rank=Rank.TWO, suit=Suit.HEART), Card(rank=Rank.THREE, suit=Suit.HEART)) self.assertFalse(m.all_same_rank) self.assertTrue(m.all_same_suit) self.assertFalse(m.is_run) self.assertFalse(m.is_set) self.assertFalse(m.complete)
def add_to_meld(self, meld: Meld, card: Card) -> None: """Add a card to a meld. Args: meld (Meld): the meld to add to card (Card): the card to add Side effects: Updates _card_to_meld_id map accordingly Raises InvalidMeldError. """ meld.add(card) if card in self._card_to_meld_id.keys(): self._card_to_meld_id[card].add(id(meld)) else: self._card_to_meld_id[card] = set([id(meld)])
def test_partial_set(self): m = Meld(Card(rank=Rank.TWO, suit=Suit.HEART), Card(rank=Rank.TWO, suit=Suit.DIAMOND)) self.assertTrue(m.all_same_rank) self.assertFalse(m.all_same_suit) self.assertFalse(m.is_run) self.assertFalse(m.is_set) self.assertFalse(m.complete)
def test_add_sorts(self): c2 = Card(rank=Rank.TWO, suit=Suit.CLUB) c3 = Card(rank=Rank.THREE, suit=Suit.CLUB) c4 = Card(rank=Rank.FOUR, suit=Suit.CLUB) m = Meld(c2) m.add(c4) m.add(c3) self.assertFalse(m.all_same_rank) self.assertTrue(m.all_same_suit) self.assertTrue(m.is_run) self.assertFalse(m.is_set) self.assertTrue(m.complete) # cards were implicitly re-ordered by add() self.assertEqual(m.cards[0], c2) self.assertEqual(m.cards[1], c3) self.assertEqual(m.cards[2], c4)
def test_large_valid_run_grows_to_inside_straight(self): """A run becomes incomplete after inside straight conversion.""" m = Meld(Card(rank=Rank.TWO, suit=Suit.HEART), Card(rank=Rank.THREE, suit=Suit.HEART), Card(rank=Rank.FOUR, suit=Suit.HEART)) self.assertTrue(m.all_same_suit) self.assertFalse(m.all_same_rank) self.assertTrue(m.is_run) self.assertFalse(m.is_set) self.assertTrue(m.complete) m.add(Card(rank=Rank.SIX, suit=Suit.HEART)) self.assertEqual(m.size(), 4) self.assertTrue(m.all_same_suit) self.assertFalse(m.all_same_rank) self.assertFalse(m.is_run) self.assertFalse(m.is_set) self.assertFalse(m.complete)
def test_singleton_add(self): """Test adding a card to a single-card partial meld. This is significant b/c until the 2nd card is added, any single-card partial meld is both a potential run and set. """ m = Meld(Card(rank=Rank.KING, suit=Suit.CLUB)) self.assertTrue(m.all_same_rank) self.assertTrue(m.all_same_suit) self.assertFalse(m.is_run) self.assertFalse(m.is_set) self.assertFalse(m.complete) m.add(Card(rank=Rank.TWO, suit=Suit.CLUB)) self.assertFalse(m.all_same_rank) self.assertTrue(m.all_same_suit) self.assertFalse(m.is_run) self.assertFalse(m.is_set) self.assertFalse(m.complete)
def test_invalid_add_does_not_break_complete_run(self): """A bad addition to a complete run doesn't break it.""" m = Meld(Card(rank=Rank.TWO, suit=Suit.HEART), Card(rank=Rank.THREE, suit=Suit.HEART), Card(rank=Rank.FOUR, suit=Suit.HEART)) self.assertTrue(m.all_same_suit) self.assertFalse(m.all_same_rank) self.assertTrue(m.is_run) self.assertFalse(m.is_set) self.assertTrue(m.complete) with self.assertRaises(InvalidMeldError): m.add(Card(rank=Rank.SIX, suit=Suit.CLUB)) self.assertEqual(m.size(), 3) self.assertTrue(m.all_same_suit) self.assertFalse(m.all_same_rank) self.assertTrue(m.is_run) self.assertFalse(m.is_set) self.assertTrue(m.complete)
def test_full_run(self): # these cards are intentionally out of order - order is not # reliable for cards added at init time m = Meld(Card(rank=Rank.TWO, suit=Suit.HEART), Card(rank=Rank.FOUR, suit=Suit.HEART), Card(rank=Rank.THREE, suit=Suit.HEART)) self.assertTrue(m.all_same_suit) self.assertFalse(m.all_same_rank) self.assertTrue(m.is_run) self.assertFalse(m.is_set) self.assertTrue(m.complete)
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_overlapping_set_and_run_detection(hand_with_complex_sets_and_runs): expected_melds = [ Meld(Card.from_text("2C", "2S", "2D", "2H")), Meld(Card.from_text("2S", "2D", "2H")), Meld(Card.from_text("2C", "2D", "2H")), Meld(Card.from_text("2C", "2S", "2H")), Meld(Card.from_text("2C", "2S", "2D")), Meld(Card.from_text("2D", "3D", "4D")), Meld(Card.from_text("3D", "4D", "5D")), Meld(Card.from_text("2D", "3D", "4D", "5D")), Meld(Card.from_text("3D", "3S", "3C")), Meld(Card.from_text("AC", "2C", "3C")), ] md = MeldDetector(*hand_with_complex_sets_and_runs.cards) md._detect_all_melds() assert(len(md._melds) == 10) for meld in expected_melds: assert(meld in md._melds)
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 test_invalid_set_add(self): m = Meld(Card(rank=Rank.TWO, suit=Suit.HEART), Card(rank=Rank.TWO, suit=Suit.DIAMOND)) self.assertTrue(m.all_same_rank) self.assertFalse(m.all_same_suit) self.assertFalse(m.is_run) self.assertFalse(m.is_set) self.assertFalse(m.complete) self.assertEqual(m.size(), 2) with self.assertRaises(InvalidMeldError): m.add(Card(rank=Rank.THREE, suit=Suit.CLUB)) self.assertEqual(m.size(), 2)
def test_complex_run_detection(hand_with_complex_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) expected_melds = [ Meld(Card.from_text("3H", "4H", "5H")), Meld(Card.from_text("4H", "5H", "6H")), Meld(Card.from_text("5H", "6H", "7H")), Meld(Card.from_text("3H", "4H", "5H", "6H")), Meld(Card.from_text("4H", "5H", "6H", "7H")), Meld(Card.from_text("3H", "4H", "5H", "6H", "7H")), Meld(Card.from_text("9C", "10C", "JC")) ] md = MeldDetector(*hand_with_complex_runs.cards) md._detect_all_melds() assert(len(md._melds) == 7) for meld in expected_melds: assert(meld in md._melds)
def test_melds_are_equal_if_same_cards(self): m1 = Meld(Card.from_text("2H", "JH", "AH")) m2 = Meld(Card.from_text("AH", "2H", "JH")) self.assertEqual(m1, m2)
def test_remove_last(self): """Test removing the last card in a meld (leaving it empty).""" c = Card(rank=Rank.KING, suit=Suit.CLUB) m = Meld(c) m.remove(m.find(c))