def test_hand_with_overused_cards_is_valid_if_only_one_is_complete(self): hm = HandWithMelds() (qh, qd, qc, kd, jd, jh, js) = Card.from_text("QH", "QD", "QC", "KD", "JD", "JH", "JS") hm.add([qh, qd, qc, kd, jd, jh, js]) hm.create_meld(qh, qd, qc) # qd overlaps between this hm.create_meld(kd, qd) # and this, but only the first is complete hm.create_meld(jd, jh, js) # no overlap on this one self.assertTrue(hm.is_valid)
def test_is_deadwood_is_true_for_cards_in_incomplete_meld(self): hm = HandWithMelds() (qh, qd, qc, kd, threec) = Card.from_text("QH", "QD", "QC", "KD", "3C") cards = [qh, qd, qc, kd, threec] hm.add(cards) hm.create_meld(qh, qd, qc) hm.create_meld(threec) expected_deadwood = [False, False, False, True, True] for idx, card in enumerate(cards): self.assertEqual(hm.is_deadwood(card), expected_deadwood[idx])
def setUp(self): self.h = HandWithMelds() self.h.add(Card(rank=Rank.QUEEN, suit=Suit.HEART)) # 0 : QH self.h.add(Card(rank=Rank.JACK, suit=Suit.DIAMOND)) # 1 : JD self.h.add(Card(rank=Rank.ACE, suit=Suit.CLUB)) # 2 : AC self.h.add(Card(rank=Rank.KING, suit=Suit.SPADE)) # 3 : KS self.h.add(Card(rank=Rank.TWO, suit=Suit.HEART)) # 4 : 2H self.h.add(Card(rank=Rank.THREE, suit=Suit.DIAMOND)) # 5 : 3D self.h.add(Card(rank=Rank.FOUR, suit=Suit.CLUB)) # 6 : 4C self.h.add(Card(rank=Rank.FIVE, suit=Suit.SPADE)) # 7 : 5S self.h.add(Card(rank=Rank.TEN, suit=Suit.HEART)) # 8 : 10H self.h.add(Card(rank=Rank.NINE, suit=Suit.DIAMOND)) # 9 : 9D self.h.add(Card(rank=Rank.EIGHT, suit=Suit.CLUB)) # 10: 8C
def test_deadwood_count_includes_cards_not_in_complete_melds(self): hm = HandWithMelds() (qh, qd, qc, kd) = Card.from_text("QH", "QD", "QC", "KD") hm.add([qh, qd, qc, kd]) hm.create_meld(qh, qd, qc) hm.create_meld(kd) self.assertEqual(hm.deadwood_count, 1)
def test_hand_with_no_overused_cards_is_valid(self): hm = HandWithMelds() (qh, qd, qc, kd) = Card.from_text("QH", "QD", "QC", "KD") hm.add([qh, qd, qc, kd]) hm.create_meld(qh, qd, qc) hm.create_meld(kd) self.assertTrue(hm.is_valid)
def test_deadwood_returns_cards_not_in_complete_meld(self): # this test covers deadwood in no melds and deadwood in incomplete meld hm = HandWithMelds() (qh, qd, qc, kd, threec) = Card.from_text("QH", "QD", "QC", "KD", "3C") cards = [qh, qd, qc, kd, threec] hm.add(cards) hm.create_meld(qh, qd, qc) hm.create_meld(threec) expected_deadwood = [threec, kd] for card in expected_deadwood: self.assertTrue(card in hm.deadwood())
def test_deadwood_value_sums_points_of_cards_not_in_complete_melds(self): hm = HandWithMelds() (qh, qd, qc, kd, threec) = Card.from_text("QH", "QD", "QC", "KD", "3C") hm.add([qh, qd, qc, kd, threec]) hm.create_meld(qh, qd, qc) hm.create_meld(threec) # the three and the kd are both deadwood here self.assertEqual(hm.deadwood_value, 13)
def test_hand_with_overused_cards_is_invalid(self): hm = HandWithMelds() (qh, qd, qc, kd, jd, jh) = Card.from_text("QH", "QD", "QC", "KD", "JD", "JH") hm.add([qh, qd, qc, kd, jd, jh]) hm.create_meld(qh, qd, qc) # qd overlaps between this hm.create_meld(kd, qd, jd) # and this hm.create_meld(jd, jh) # overlap here, but incomplete self.assertFalse(hm.is_valid)
def __init__(self, *cards) -> None: """Create a MeldDetector, optionally initialized from a set of cards. Args: hand (Hand): [optional] hand to initialize from If provided, the cards the MeldDetector examines will be initialized to match the contents of the provided Hand. """ super().__init__() self.optimal_hand = HandWithMelds() if cards: self.add(cards) self.optimal_hand.add(cards) # flag used to indicate if detection has been done - cleared when the # underlying card stack changes such that we need to re-dectect self._detected = False
class MeldDetector(HandWithMelds): """MeldDetector finds the optimal set of melds within a hand. This is used by the Game to confirm validity of a win claimed by a player, but can also be used by various player classes to either provide hints to a human player or to drive optimal play by machine players. **IMPORTANT NOTE** MeldDetector breaks the guarantee some of its class ancestors make about card ordering. Order of cards is not guaranteed, and will change at runtime depending on other methods called. For that reason, inherited methods that only make sense if card order is stable will raise """ def __init__(self, *cards) -> None: """Create a MeldDetector, optionally initialized from a set of cards. Args: hand (Hand): [optional] hand to initialize from If provided, the cards the MeldDetector examines will be initialized to match the contents of the provided Hand. """ super().__init__() self.optimal_hand = HandWithMelds() if cards: self.add(cards) self.optimal_hand.add(cards) # flag used to indicate if detection has been done - cleared when the # underlying card stack changes such that we need to re-dectect self._detected = False @property def cards(self): """Return a set with the cards in this hand. Ancestor classes return a list, but our order is unstable. """ return set(self._cards) @property def is_complete_hand(self) -> bool: """True if there are 10 or 11 cards in the hand.""" return len(self.cards) == 10 or len(self.cards) == 11 # These do not make sense for HandDetector instances, and will not behave # in a useful, reliable, or helpful way. Make that clear by raising def remove(self, i: int): # no use case for this yet, but if needed it could be implemented as a # combination of super().find() and super().remove() raise NotImplementedError def get(self, i: int): raise NotImplementedError def find(self, targetcard: Card): # parent method returns an index raise NotImplementedError def draw(self): raise NotImplementedError def peek(self): # "top" of stack is meaningless here raise NotImplementedError # The following methods all simply wrap their superclass implementations for # the purpose of unsetting our _detected flag. def add(self, newcard: Card) -> None: """Extends base method to add a card to the hand.""" super().add(newcard) self._sort_cards() def detect_optimal_melds(self) -> None: """Find the best set of melds in the hand.""" self._detect_all_melds() overused = self.melds_with_overused_cards(complete=True) # print("** non-overused melds: {}".format( # self.melds_with_no_overused_cards(complete=True) # )) # print("** overused melds: {}\n".format( # self.melds_with_overused_cards(complete=True) # )) # if no cards are in multiple melds, then "optimal" is easy if len(overused) == 0: for meld in self._melds: self.optimal_hand.create_meld(*meld.cards) else: # # A complete hand can only use each card once (no "overuse"), but # if we got here it means the set of all potential melds includes # some that overuse at least some cards. # # By definition, the "optimal" set of melds is that which leaves # the smallest deadwood (by point value). # # This algorithm brute-forces its way to discovering the optimal set. # # might IMPROVEME by making a set (need to define __hash__ on HandWithMelds) possible_hands = [] melds_with_overuse = self.melds_with_overused_cards(complete=True) # Compute every possible ordering of them. A winning hand can never # use more than 3 melds, so we can look just at that length. meld_orderings = permutations(melds_with_overuse, min(3, len(melds_with_overuse))) for ordering in meld_orderings: possible_hands.append( self._solve_hand_for_melds_in_order(ordering)) best_hand = None for hand_being_evaluated in possible_hands: try: if best_hand is None: # hack so we raise before setting best_hand assert hand_being_evaluated.deadwood_value best_hand = hand_being_evaluated # print("initializing best_hand: {}".format(best_hand.melds)) elif hand_being_evaluated.deadwood_value < best_hand.deadwood_value: best_hand = hand_being_evaluated # print("new best hand: {}".format(best_hand.melds)) except InvalidHand: # print("skipping invalid hand: {}".format(hand_being_evaluated.melds)) continue # print("FINAL best hand: {} (deadwood={})".format( # best_hand.melds, best_hand.deadwood_value # )) self.optimal_hand = best_hand def _sort_cards(self) -> None: """Sort the cards by suit then rank.""" self._detected = False self._cards.sort() def _detect_all_melds(self) -> None: """Compute all complete melds in the hand. Note: This will find all complete melds, even if some cards are used in more than one. """ if self._detected is False: self._find_runs() self._find_complete_sets() self._detected = True def _find_runs(self): """Find all runs in the hand and create melds for them. Note: *all* runs are found, even those that are subsets of other runs. If the 3, 4, 5, and 6 of hearts are in the hand, three runs will be found - (3,4,5), (4,5,6), and (3,4,5,6). """ def all_runs_in_sequences(sequences): # generator that yields a tuple for every run (sequence w/ len>=3) # in the input list of sequences # # e.g. input 2,3,4,5 yields (2,3,4),(3,4,5),(2,3,4,5) for seq in sequences: # print("seq is {}".format(seq)) for seq_len in range(3, len(seq) + 1): # will be null if len<3 # print("seq_len is {}".format(seq_len)) yield zip(*(seq[i:] for i in range(seq_len))) self._sort_cards() # print("going into run detection, cards: {}".format(self._cards)) runs = [ run for suit_group in self.group_by_suit() for sublist in all_runs_in_sequences( MeldDetector.get_all_sequences(suit_group)) for run in sublist ] for run in runs: self.create_meld(*list(run)) def _find_complete_sets(self): """Find all complete sets in the hand and create melds for them. Note: *all* sets are found, even those that are subsets of other sets. """ sorted_by_rank = sorted(self._cards, key=lambda card: card.rank.value) grouped_by_rank = groupby(sorted_by_rank, key=lambda card: card.rank) for _, cards_of_rank_x in grouped_by_rank: cards = list(cards_of_rank_x) if len(cards) == 4: # 4 distinct 3-long sets, 1 4-long set for combo in combinations(cards, 3): self.create_meld(*combo) self.create_meld(*cards) elif len(cards) == 3: self.create_meld(*cards) def _solve_hand_for_melds_in_order(self, ordering) -> HandWithMelds: """Return the possible hand created by resolving meld conflicts in the specified order.""" # Notation: in this context, "solving" a meld means making # it no longer a card-overusing meld (by removing the cards # in this meld from other melds). # init the new hand in which we'll describe this possible solution possible_hand = HandWithMelds() possible_hand.add(self.cards) for meld in filter(lambda m: m.complete, self.melds): possible_hand.create_meld(*meld.cards) for meld_to_solve in ordering: # earlier meld resolution could have either removed this meld # b/c it became invalid or solved it - in either case we've # nothing more to do if meld_to_solve not in possible_hand.melds: continue if meld_to_solve not in possible_hand.melds_with_overused_cards( complete=True): continue # print('=' * 50) # print("resolving overused meld {}".format(meld_to_solve)) cards_to_solve = list( filter( lambda card: len(possible_hand.melds_using_card(card)) > 1, meld_to_solve.cards)) for card in cards_to_solve: # remove the current card from all other melds # print(". resolving card {}".format(card)) # print(" hand: {}".format(possible_hand.melds)) melds_losing_this_card = filter( # pylint: disable=cell-var-from-loop lambda meld: meld != meld_to_solve, # pylint: enable=cell-var-from-loop possible_hand.melds_using_card(card)) for meld in melds_losing_this_card: # print(" . removing {} from meld {} in potential new hand".format(card, meld)) possible_hand.remove_from_meld(meld, card) if not meld.complete: # print(" .. that made meld incomplete - removing it") possible_hand.remove_meld(meld) # print(" hand now: {}".format(possible_hand.melds)) # print("resolution of {} yielded hand: {}".format(meld_to_solve, possible_hand.melds)) return possible_hand
def _solve_hand_for_melds_in_order(self, ordering) -> HandWithMelds: """Return the possible hand created by resolving meld conflicts in the specified order.""" # Notation: in this context, "solving" a meld means making # it no longer a card-overusing meld (by removing the cards # in this meld from other melds). # init the new hand in which we'll describe this possible solution possible_hand = HandWithMelds() possible_hand.add(self.cards) for meld in filter(lambda m: m.complete, self.melds): possible_hand.create_meld(*meld.cards) for meld_to_solve in ordering: # earlier meld resolution could have either removed this meld # b/c it became invalid or solved it - in either case we've # nothing more to do if meld_to_solve not in possible_hand.melds: continue if meld_to_solve not in possible_hand.melds_with_overused_cards( complete=True): continue # print('=' * 50) # print("resolving overused meld {}".format(meld_to_solve)) cards_to_solve = list( filter( lambda card: len(possible_hand.melds_using_card(card)) > 1, meld_to_solve.cards)) for card in cards_to_solve: # remove the current card from all other melds # print(". resolving card {}".format(card)) # print(" hand: {}".format(possible_hand.melds)) melds_losing_this_card = filter( # pylint: disable=cell-var-from-loop lambda meld: meld != meld_to_solve, # pylint: enable=cell-var-from-loop possible_hand.melds_using_card(card)) for meld in melds_losing_this_card: # print(" . removing {} from meld {} in potential new hand".format(card, meld)) possible_hand.remove_from_meld(meld, card) if not meld.complete: # print(" .. that made meld incomplete - removing it") possible_hand.remove_meld(meld) # print(" hand now: {}".format(possible_hand.melds)) # print("resolution of {} yielded hand: {}".format(meld_to_solve, possible_hand.melds)) return possible_hand
class TestHandWithMelds(unittest.TestCase): def setUp(self): self.h = HandWithMelds() self.h.add(Card(rank=Rank.QUEEN, suit=Suit.HEART)) # 0 : QH self.h.add(Card(rank=Rank.JACK, suit=Suit.DIAMOND)) # 1 : JD self.h.add(Card(rank=Rank.ACE, suit=Suit.CLUB)) # 2 : AC self.h.add(Card(rank=Rank.KING, suit=Suit.SPADE)) # 3 : KS self.h.add(Card(rank=Rank.TWO, suit=Suit.HEART)) # 4 : 2H self.h.add(Card(rank=Rank.THREE, suit=Suit.DIAMOND)) # 5 : 3D self.h.add(Card(rank=Rank.FOUR, suit=Suit.CLUB)) # 6 : 4C self.h.add(Card(rank=Rank.FIVE, suit=Suit.SPADE)) # 7 : 5S self.h.add(Card(rank=Rank.TEN, suit=Suit.HEART)) # 8 : 10H self.h.add(Card(rank=Rank.NINE, suit=Suit.DIAMOND)) # 9 : 9D self.h.add(Card(rank=Rank.EIGHT, suit=Suit.CLUB)) # 10: 8C def test_empty_meld_creation(self): self.assertEqual(len(self.h._melds), 0) self.h.create_meld() self.assertEqual(len(self.h._melds), 1) self.assertEqual(len(self.h._card_to_meld_id.keys()), 0) def test_nonempty_meld_creation(self): jack_d = self.h.cards[1] three_d = self.h.cards[5] self.h.create_meld(jack_d, three_d) self.assertEqual(len(self.h._melds), 1) self.assertEqual(len(self.h._card_to_meld_id.keys()), 2) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[jack_d]) def test_invalid_meld_creation(self): jack_d = self.h.cards[1] two_h = self.h.cards[4] with self.assertRaises(InvalidMeldError): self.h.create_meld(jack_d, two_h) self.assertEqual(len(self.h._melds), 0) self.assertEqual(len(self.h._card_to_meld_id.keys()), 0) def test_singleton_meld_creation(self): two_h = self.h.cards[4] self.h.create_meld(two_h) self.assertEqual(len(self.h._melds), 1) def test_card_add(self): jack_d = self.h.cards[1] three_d = self.h.cards[5] self.h.create_meld(jack_d) self.assertEqual(len(self.h._melds), 1) self.assertEqual(len(self.h._card_to_meld_id.keys()), 1) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[jack_d]) self.h.add_to_meld(self.h._melds[0], three_d) self.assertEqual(len(self.h._melds), 1) self.assertEqual(len(self.h._card_to_meld_id.keys()), 2) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[three_d]) def test_card_add_by_idx(self): jack_d = self.h.cards[1] three_d = self.h.cards[5] self.h.create_meld(jack_d) self.assertEqual(len(self.h._melds), 1) self.assertEqual(len(self.h._card_to_meld_id.keys()), 1) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[jack_d]) self.h.add_to_meld_by_idx(0, 5) self.assertEqual(len(self.h._melds), 1) self.assertEqual(len(self.h._card_to_meld_id.keys()), 2) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[three_d]) def test_card_add_invalid(self): jack_d = self.h.cards[1] three_d = self.h.cards[5] two_h = self.h.cards[4] self.h.create_meld(jack_d, three_d) self.assertEqual(len(self.h._melds), 1) self.assertEqual(len(self.h._card_to_meld_id.keys()), 2) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[jack_d]) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[three_d]) with self.assertRaises(InvalidMeldError): self.h.add_to_meld(self.h._melds[0], two_h) self.assertEqual(len(self.h._melds), 1) self.assertEqual(len(self.h._card_to_meld_id.keys()), 2) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[jack_d]) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[three_d]) def test_card_in_n_melds(self): jack_d = self.h.cards[1] three_d = self.h.cards[5] self.h.create_meld(jack_d, three_d) self.assertEqual(len(self.h._melds), 1) self.assertEqual(len(self.h._card_to_meld_id.keys()), 2) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[jack_d]) self.assertEqual(len(self.h._card_to_meld_id[jack_d]), 1) # add one card to a second meld self.h.create_meld(jack_d) self.assertEqual(len(self.h._melds), 2) self.assertEqual(len(self.h._card_to_meld_id.keys()), 2) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[jack_d]) self.assertTrue( id(self.h._melds[1]) in self.h._card_to_meld_id[jack_d]) self.assertEqual(len(self.h._card_to_meld_id[jack_d]), 2) # and remove it self.h.remove_from_meld(self.h._melds[1], jack_d) self.assertEqual(len(self.h._melds), 2) self.assertEqual(len(self.h._card_to_meld_id.keys()), 2) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[jack_d]) self.assertFalse( id(self.h._melds[1]) in self.h._card_to_meld_id[jack_d]) self.assertEqual(len(self.h._card_to_meld_id[jack_d]), 1) def test_melds_with_overused_cards(self): jack_d = self.h.cards[1] three_d = self.h.cards[5] m1 = self.h.create_meld(jack_d, three_d) m2 = self.h.create_meld(jack_d) m3 = self.h.create_meld(self.h.cards[4]) overused = self.h.melds_with_overused_cards() self.assertIn(m1, overused) self.assertIn(m2, overused) self.assertNotIn(m3, overused) def test_melds_using_card(self): jack_d = self.h.cards[1] three_d = self.h.cards[5] self.h.create_meld(jack_d, three_d) self.h.create_meld(jack_d) self.h.create_meld(three_d) melds_using_jack = self.h.melds_using_card(jack_d) self.assertTrue(self.h._melds[0] in melds_using_jack) self.assertTrue(self.h._melds[1] in melds_using_jack) self.assertFalse(self.h._melds[2] in melds_using_jack) def test_card_remove(self): jack_d = self.h.cards[1] three_d = self.h.cards[5] self.h.create_meld(jack_d, three_d) self.assertEqual(len(self.h._melds), 1) self.assertEqual(len(self.h._card_to_meld_id.keys()), 2) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[jack_d]) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[three_d]) self.h.remove_from_meld(self.h._melds[0], three_d) self.assertEqual(len(self.h._card_to_meld_id.keys()), 1) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[jack_d]) with self.assertRaises(KeyError): self.h._card_to_meld_id[three_d] def test_meld_remove_empty(self): self.assertEqual(len(self.h._melds), 0) self.h.create_meld() self.assertEqual(len(self.h._melds), 1) self.assertEqual(len(self.h._card_to_meld_id.keys()), 0) self.h.remove_meld(self.h._melds[0]) self.assertEqual(len(self.h._melds), 0) def test_meld_remove_nonempty(self): jack_d = self.h.cards[1] three_d = self.h.cards[5] self.h.create_meld(jack_d, three_d) self.assertEqual(len(self.h._melds), 1) self.assertEqual(len(self.h._card_to_meld_id.keys()), 2) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[jack_d]) self.assertEqual(len(self.h._card_to_meld_id[jack_d]), 1) # add one card to a second meld self.h.create_meld(jack_d) self.assertEqual(len(self.h._melds), 2) self.assertEqual(len(self.h._card_to_meld_id.keys()), 2) self.assertTrue( id(self.h._melds[0]) in self.h._card_to_meld_id[jack_d]) self.assertTrue( id(self.h._melds[1]) in self.h._card_to_meld_id[jack_d]) self.assertEqual(len(self.h._card_to_meld_id[jack_d]), 2) # remove the first meld removed_meld = self.h._melds[0] retained_meld = self.h._melds[1] self.h.remove_meld(removed_meld) self.assertEqual(len(self.h._melds), 1) self.assertEqual(len(self.h._card_to_meld_id.keys()), 1) self.assertTrue(id(retained_meld) in self.h._card_to_meld_id[jack_d]) self.assertFalse(id(removed_meld) in self.h._card_to_meld_id[jack_d]) self.assertEqual(len(self.h._card_to_meld_id[jack_d]), 1) with self.assertRaises(KeyError): self.h._card_to_meld_id[three_d] def test_hand_with_no_melds_is_valid(self): hm = HandWithMelds() self.assertTrue(hm.is_valid) def test_hand_with_no_overused_cards_is_valid(self): hm = HandWithMelds() (qh, qd, qc, kd) = Card.from_text("QH", "QD", "QC", "KD") hm.add([qh, qd, qc, kd]) hm.create_meld(qh, qd, qc) hm.create_meld(kd) self.assertTrue(hm.is_valid) def test_hand_with_overused_cards_is_valid_if_only_one_is_complete(self): hm = HandWithMelds() (qh, qd, qc, kd, jd, jh, js) = Card.from_text("QH", "QD", "QC", "KD", "JD", "JH", "JS") hm.add([qh, qd, qc, kd, jd, jh, js]) hm.create_meld(qh, qd, qc) # qd overlaps between this hm.create_meld(kd, qd) # and this, but only the first is complete hm.create_meld(jd, jh, js) # no overlap on this one self.assertTrue(hm.is_valid) def test_hand_with_overused_cards_is_invalid(self): hm = HandWithMelds() (qh, qd, qc, kd, jd, jh) = Card.from_text("QH", "QD", "QC", "KD", "JD", "JH") hm.add([qh, qd, qc, kd, jd, jh]) hm.create_meld(qh, qd, qc) # qd overlaps between this hm.create_meld(kd, qd, jd) # and this hm.create_meld(jd, jh) # overlap here, but incomplete self.assertFalse(hm.is_valid) def test_is_deadwood_is_true_for_cards_not_in_any_meld(self): hm = HandWithMelds() (qh, qd, qc, kd, threec) = Card.from_text("QH", "QD", "QC", "KD", "3C") cards = [qh, qd, qc, kd, threec] hm.add(cards) hm.create_meld(qh, qd, qc) expected_deadwood = [False, False, False, True, True] for idx, card in enumerate(cards): self.assertEqual(hm.is_deadwood(card), expected_deadwood[idx]) def test_is_deadwood_is_true_for_cards_in_incomplete_meld(self): hm = HandWithMelds() (qh, qd, qc, kd, threec) = Card.from_text("QH", "QD", "QC", "KD", "3C") cards = [qh, qd, qc, kd, threec] hm.add(cards) hm.create_meld(qh, qd, qc) hm.create_meld(threec) expected_deadwood = [False, False, False, True, True] for idx, card in enumerate(cards): self.assertEqual(hm.is_deadwood(card), expected_deadwood[idx]) def test_deadwood_returns_cards_not_in_complete_meld(self): # this test covers deadwood in no melds and deadwood in incomplete meld hm = HandWithMelds() (qh, qd, qc, kd, threec) = Card.from_text("QH", "QD", "QC", "KD", "3C") cards = [qh, qd, qc, kd, threec] hm.add(cards) hm.create_meld(qh, qd, qc) hm.create_meld(threec) expected_deadwood = [threec, kd] for card in expected_deadwood: self.assertTrue(card in hm.deadwood()) def test_deadwood_count_includes_cards_not_in_complete_melds(self): hm = HandWithMelds() (qh, qd, qc, kd) = Card.from_text("QH", "QD", "QC", "KD") hm.add([qh, qd, qc, kd]) hm.create_meld(qh, qd, qc) hm.create_meld(kd) self.assertEqual(hm.deadwood_count, 1) def test_deadwood_value_sums_points_of_cards_not_in_complete_melds(self): hm = HandWithMelds() (qh, qd, qc, kd, threec) = Card.from_text("QH", "QD", "QC", "KD", "3C") hm.add([qh, qd, qc, kd, threec]) hm.create_meld(qh, qd, qc) hm.create_meld(threec) # the three and the kd are both deadwood here self.assertEqual(hm.deadwood_value, 13)
def test_hand_with_no_melds_is_valid(self): hm = HandWithMelds() self.assertTrue(hm.is_valid)