def test_leaf_node(self): game_state = _get_game_state_with_one_card_left() play_jack_clubs = PlayCardAction(PlayerId.ONE, Card(Suit.CLUBS, CardValue.JACK)) game_state = play_jack_clubs.execute(game_state) mcts = Mcts(PlayerId.TWO) root_node = mcts.build_tree(game_state) self.assertIsNone(root_node.parent) self.assertEqual(1, len(root_node.children)) self.assertEqual([], root_node.untried_actions) self.assertTrue(root_node.fully_expanded) self.assertFalse(root_node.terminal) self.assertEqual(PlayerId.TWO, root_node.player) self.assertFalse(root_node.fully_simulated) action = list(root_node.children.keys())[0] self.assertEqual( PlayCardAction(PlayerId.TWO, Card(Suit.SPADES, CardValue.JACK)), action) leaf: Node = root_node.children[action] self.assertIs(root_node, leaf.parent) self.assertIsNone(leaf.children) self.assertIsNone(leaf.untried_actions) self.assertTrue(leaf.fully_expanded) self.assertTrue(leaf.terminal) self.assertEqual(PlayerId.ONE, leaf.player) self.assertAlmostEqual(0.33, leaf.ucb, delta=0.01) self.assertTrue(leaf.fully_simulated)
def _on_lead_do_not_follow_suit(self, game_view: GameState) -> PlayerAction: # If the cards that cannot be won by the opponent can get us to the end, # start playing them. action = self._play_winning_cards(game_view) if action is not None: return action # If we cannot win yet, and we have a marriage, announce it. If the Ace and # Ten from that suit cannot be in the opponents hand, play the King. if self._marriage_suit is not None: logging.debug("HeuristicPlayer: Announcing marriage for %s", self._marriage_suit) king = Card(self._marriage_suit, CardValue.KING) ten = Card(self._marriage_suit, CardValue.TEN) ace = Card(self._marriage_suit, CardValue.ACE) if (ten in self._my_cards or ten in self._played_cards) and \ (ace in self._my_cards or ace in self._played_cards): return AnnounceMarriageAction(self.id, king) return AnnounceMarriageAction(self.id, king.marriage_pair) # If we expect that the opponent has more trumps and we have big cards # (i.e., tens or aces), play one of the high card to force the opponent to # either play a trump or give up a lot of points. card = self._maybe_trump_control(game_view) if card is not None: return PlayCardAction(self.id, card) # Discard one of the small cards. card = self._best_discard(game_view) logging.debug("HeuristicPlayer: Discarding %s", card) return PlayCardAction(self.id, card)
def test_actions_after_the_opponent_played_one_card_talon_is_empty(self): game_state = get_game_state_with_empty_talon_for_tests() action = PlayCardAction(PlayerId.ONE, Card(Suit.SPADES, CardValue.TEN)) game_state = action.execute(game_state) actions = get_available_actions(game_state) self.assertEqual(set(actions), set(get_available_actions(game_state.next_player_view()))) expected_actions = [ PlayCardAction(PlayerId.TWO, Card(Suit.CLUBS, CardValue.KING)), PlayCardAction(PlayerId.TWO, Card(Suit.CLUBS, CardValue.JACK)), PlayCardAction(PlayerId.TWO, Card(Suit.CLUBS, CardValue.QUEEN)), ] self.assertSetEqual(set(expected_actions), set(actions)) game_state = get_game_state_with_empty_talon_for_tests() with GameStateValidator(game_state): game_state.next_player = PlayerId.TWO action = PlayCardAction(PlayerId.TWO, Card(Suit.CLUBS, CardValue.JACK)) game_state = action.execute(game_state) actions = get_available_actions(game_state) self.assertEqual(set(actions), set(get_available_actions(game_state.next_player_view()))) expected_actions = [ PlayCardAction(PlayerId.ONE, Card(Suit.CLUBS, CardValue.ACE)), ] self.assertSetEqual(set(expected_actions), set(actions))
def test_duck_puzzle(self): game_view = get_game_view_for_duck_puzzle() action = self._mcts_player.request_next_action(game_view) print(f"Selected action: {action}") expected_actions = { PlayCardAction(PlayerId.ONE, Card(Suit.SPADES, CardValue.ACE)), PlayCardAction(PlayerId.ONE, Card(Suit.SPADES, CardValue.TEN)) } self.assertIn(action, expected_actions)
def test_no_marriage_available(self): self.assertIsNone(get_best_marriage( [ PlayCardAction(PlayerId.ONE, Card.from_string("jh")), PlayCardAction(PlayerId.ONE, Card.from_string("ad")), PlayCardAction(PlayerId.ONE, Card.from_string("js")), PlayCardAction(PlayerId.ONE, Card.from_string("ts")), PlayCardAction(PlayerId.ONE, Card.from_string("qc")), ], Suit.SPADES))
def test_must_follow_suit_must_use_higher_trump(self): """ Player.ONE plays the trump Queen. Player.TWO has three trump cards. The valid cards are only the trump King and Ace. """ game_state = get_game_state_with_empty_talon_for_tests() with GameStateValidator(game_state): ace_clubs = game_state.cards_in_hand.one[0] queen_clubs = game_state.cards_in_hand.two[3] game_state.cards_in_hand.two[3] = ace_clubs game_state.cards_in_hand.one[0] = queen_clubs action = PlayCardAction(PlayerId.ONE, queen_clubs) self.assertTrue(action.can_execute_on(game_state)) game_state = action.execute(game_state) num_legal_cards = 0 valid_cards = [Card(Suit.CLUBS, CardValue.KING), Card(Suit.CLUBS, CardValue.ACE)] for card in game_state.cards_in_hand[PlayerId.TWO]: action = PlayCardAction(PlayerId.TWO, card) is_legal_card = action.can_execute_on(game_state) self.assertEqual(card in valid_cards, is_legal_card, msg=f"{card}") self.assertEqual(is_legal_card, action.can_execute_on(game_state.next_player_view())) if is_legal_card: num_legal_cards += 1 self.assertEqual(2, num_legal_cards)
def test_must_follow_suit_can_play_any_higher_card_same_suit(self): """ Player.ONE has two cards of the same suit as the card played by Player.TWO, both higher. Player.ONE also has a trump card. Any of the two cards from the same suit as the card played by Player.TWO are valid. """ game_state = get_game_state_for_tests() with GameStateValidator(game_state): queen_hearts = game_state.cards_in_hand.one[0] queen_clubs = game_state.cards_in_hand.two[4] game_state.cards_in_hand.two[4] = queen_hearts game_state.cards_in_hand.one[0] = queen_clubs game_state.close_talon() game_state.next_player = PlayerId.TWO self.assertTrue(game_state.must_follow_suit()) jack_spades = Card(Suit.SPADES, CardValue.JACK) action = PlayCardAction(PlayerId.TWO, jack_spades) self.assertTrue(action.can_execute_on(game_state)) game_state = action.execute(game_state) num_legal_cards = 0 for card in game_state.cards_in_hand[PlayerId.ONE]: action = PlayCardAction(PlayerId.ONE, card) is_legal_card = action.can_execute_on(game_state) self.assertEqual(card.suit == Suit.SPADES, is_legal_card, msg=f"{card}") self.assertEqual(is_legal_card, action.can_execute_on(game_state.next_player_view())) if is_legal_card: num_legal_cards += 1 self.assertEqual(2, num_legal_cards)
def test_play_trick_talon_closed_opponent_cannot_follow_suit_or_trump(self): game_state = get_game_state_for_tests() with GameStateValidator(game_state): trick = game_state.won_tricks.one.pop(0) game_state.talon.append(trick.one) game_state.talon.append(trick.two) game_state.trick_points.one -= trick.one.card_value game_state.trick_points.one -= trick.two.card_value trick = game_state.won_tricks.two.pop(-1) game_state.talon.append(trick.one) game_state.talon.append(trick.two) game_state.trick_points.two -= trick.one.card_value game_state.trick_points.two -= trick.two.card_value game_state.next_player = PlayerId.TWO game_state.close_talon() self.assertEqual([False, False, False, False, False], [card.public for card in game_state.talon]) action = PlayCardAction(PlayerId.TWO, Card(Suit.DIAMONDS, CardValue.QUEEN)) self.assertTrue(action.can_execute_on(game_state)) game_state = action.execute(game_state) self.assertEqual([False, False, False, False, False], [card.public for card in game_state.talon]) action = PlayCardAction(PlayerId.ONE, Card(Suit.HEARTS, CardValue.QUEEN)) self.assertTrue(action.can_execute_on(game_state)) game_state = action.execute(game_state) self.assertEqual([True, False, False, True, True], [card.public for card in game_state.talon])
def _not_on_lead_action(self, game_view: GameState) -> PlayerAction: if game_view.must_follow_suit(): card = self._not_on_lead_follow_suit(game_view) else: card = self._not_on_lead_do_not_follow_suit(game_view) logging.debug("HeuristicPlayer: Playing %s", card) return PlayCardAction(self.id, card)
def test_save_marriages(self): self._run_test_cases_with_option("save_marriages", [False, True], [ # Do not break a marriage to win a Jack. { "cards_in_hand": (["qd", "kd", "ad", "js", "qs"], [None, None, None, None, None]), "trump": Suit.CLUBS, "trump_card": "ac", "talon": [None, None, None, None, None, None, None, None, None], "current_trick": (None, "jd"), "expected_action": [ PlayCardAction(PlayerId.ONE, Card.from_string("kd")), PlayCardAction(PlayerId.ONE, Card.from_string("js")), ] }, ])
def test_trump_marriage_is_preferred(self): self.assertIn(get_best_marriage( [ PlayCardAction(PlayerId.ONE, Card.from_string("jh")), PlayCardAction(PlayerId.ONE, Card.from_string("ad")), PlayCardAction(PlayerId.ONE, Card.from_string("js")), PlayCardAction(PlayerId.ONE, Card.from_string("ts")), PlayCardAction(PlayerId.ONE, Card.from_string("qc")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("kh")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("qh")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("ks")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("qs")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("kc")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("qc")), ], Suit.SPADES), { AnnounceMarriageAction(PlayerId.ONE, Card.from_string("ks")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("qs")), })
def test_who_laughs_last_puzzle(self): # Part one game_state = get_game_state_for_who_laughs_last_puzzle() action = self._mcts_player.request_next_action( game_state.next_player_view()) expected_actions = { PlayCardAction(PlayerId.ONE, Card(Suit.HEARTS, CardValue.KING)), PlayCardAction(PlayerId.ONE, Card(Suit.HEARTS, CardValue.QUEEN)) } self.assertIn(action, expected_actions) game_state = action.execute(game_state) if action.card.card_value == CardValue.KING: self.assertEqual(PlayerPair(19, 26), game_state.trick_points) else: self.assertEqual(PlayerPair(18, 26), game_state.trick_points) # Part two game_state = self._play_against_another_mcts_player_until_the_end( game_state) self.assertEqual(0, game_state.game_points.two)
def test_equality(self): self.assertEqual( PlayCardAction(PlayerId.ONE, Card(Suit.DIAMONDS, CardValue.ACE)), PlayCardAction(PlayerId.ONE, Card(Suit.DIAMONDS, CardValue.ACE))) self.assertNotEqual( PlayCardAction(PlayerId.ONE, Card(Suit.DIAMONDS, CardValue.ACE)), PlayCardAction(PlayerId.TWO, Card(Suit.DIAMONDS, CardValue.ACE))) self.assertNotEqual( PlayCardAction(PlayerId.ONE, Card(Suit.DIAMONDS, CardValue.KING)), PlayCardAction(PlayerId.ONE, Card(Suit.DIAMONDS, CardValue.ACE))) self.assertNotEqual( PlayCardAction(PlayerId.TWO, Card(Suit.DIAMONDS, CardValue.KING)), PlayCardAction(PlayerId.ONE, Card(Suit.DIAMONDS, CardValue.ACE))) self.assertNotEqual( PlayCardAction(PlayerId.TWO, Card(Suit.DIAMONDS, CardValue.KING)), AnnounceMarriageAction(PlayerId.TWO, Card(Suit.DIAMONDS, CardValue.KING)))
def test_who_laughs_last_part_two_player_one_always_wins(self): game_state = get_game_state_for_who_laughs_last_puzzle() mcts = Mcts(PlayerId.ONE) root_node = mcts.build_tree(game_state) actions = root_node.best_actions() self.assertEqual( [PlayCardAction(PlayerId.ONE, Card(Suit.HEARTS, CardValue.KING))], actions) game_state = actions[0].execute(game_state) self.assertEqual(PlayerPair(19, 26), game_state.trick_points) self._assert_player_one_always_wins(game_state)
def test_one_card_left_for_each_player(self): game_state = _get_game_state_with_one_card_left() mcts = Mcts(PlayerId.ONE) root_node = mcts.build_tree(game_state) self.assertIsNone(root_node.parent) self.assertEqual(1, len(root_node.children)) self.assertEqual([], root_node.untried_actions) self.assertTrue(root_node.fully_expanded) self.assertFalse(root_node.terminal) self.assertEqual(PlayerId.ONE, root_node.player) self.assertFalse(root_node.fully_simulated) action = list(root_node.children.keys())[0] self.assertEqual( PlayCardAction(PlayerId.ONE, Card(Suit.CLUBS, CardValue.JACK)), action) player_two_node: Node = root_node.children[action] print(str(player_two_node)) self.assertIs(root_node, player_two_node.parent) self.assertEqual(1, len(player_two_node.children)) self.assertEqual([], player_two_node.untried_actions) self.assertTrue(player_two_node.fully_expanded) self.assertFalse(player_two_node.terminal) self.assertEqual(PlayerId.TWO, player_two_node.player) self.assertAlmostEqual(-0.33, player_two_node.ucb, delta=0.01) self.assertTrue(player_two_node.fully_simulated) action = list(player_two_node.children.keys())[0] self.assertEqual( PlayCardAction(PlayerId.TWO, Card(Suit.SPADES, CardValue.JACK)), action) leaf: Node = player_two_node.children[action] self.assertIs(player_two_node, leaf.parent) self.assertIsNone(leaf.children) self.assertIsNone(leaf.untried_actions) self.assertTrue(leaf.fully_expanded) self.assertTrue(leaf.terminal) self.assertEqual(PlayerId.ONE, leaf.player) self.assertAlmostEqual(0.33, leaf.ucb, delta=0.01) self.assertTrue(leaf.fully_simulated)
def test_play_trick_winner_has_pending_marriage_points(self): game_state = get_game_state_for_tests() with GameStateValidator(game_state): for trick in game_state.won_tricks[PlayerId.TWO]: game_state.trick_points[PlayerId.TWO] -= trick.one.card_value game_state.trick_points[PlayerId.TWO] -= trick.two.card_value game_state.talon.extend([trick.one, trick.two]) game_state.trick_points = PlayerPair(22, 0) game_state.won_tricks[PlayerId.TWO] = [] first_talon_card = game_state.talon[0] second_talon_card = game_state.talon[1] queen_hearts = game_state.cards_in_hand[PlayerId.ONE][0] action = PlayCardAction(PlayerId.ONE, queen_hearts) game_state = action.execute(game_state) self.assertEqual(queen_hearts, game_state.current_trick[PlayerId.ONE]) self.assertEqual(PlayerId.TWO, game_state.next_player) self.assertEqual([Suit.DIAMONDS], game_state.marriage_suits[PlayerId.TWO]) self.assertEqual(0, game_state.trick_points[PlayerId.TWO]) queen_clubs = game_state.cards_in_hand[PlayerId.TWO][4] action = PlayCardAction(PlayerId.TWO, queen_clubs) game_state = action.execute(game_state) self.assertEqual(PlayerPair(None, None), game_state.current_trick) self.assertEqual(PlayerPair(22, 26), game_state.trick_points) self.assertEqual(PlayerPair(queen_hearts, queen_clubs), game_state.won_tricks[PlayerId.TWO][-1]) self.assertFalse(queen_hearts in game_state.cards_in_hand[PlayerId.ONE]) self.assertFalse(queen_clubs in game_state.cards_in_hand[PlayerId.TWO]) self.assertTrue(second_talon_card in game_state.cards_in_hand[PlayerId.ONE]) self.assertTrue(first_talon_card in game_state.cards_in_hand[PlayerId.TWO]) self.assertEqual(PlayerId.TWO, game_state.next_player)
def test_play_trick_talon_has_more_than_one_card(self): game_state = get_game_state_with_multiple_cards_in_the_talon_for_tests() first_talon_card = game_state.talon[0] second_talon_card = game_state.talon[1] queen_hearts = game_state.cards_in_hand[PlayerId.ONE][0] action = PlayCardAction(PlayerId.ONE, queen_hearts) game_state = action.execute(game_state) self.assertEqual(queen_hearts, game_state.current_trick[PlayerId.ONE]) self.assertEqual(PlayerId.TWO, game_state.next_player) queen_diamonds = game_state.cards_in_hand[PlayerId.TWO][0] action = PlayCardAction(PlayerId.TWO, queen_diamonds) game_state = action.execute(game_state) self.assertEqual(PlayerPair(None, None), game_state.current_trick) self.assertEqual(PlayerPair(28, 33), game_state.trick_points) self.assertEqual(PlayerPair(queen_hearts, queen_diamonds), game_state.won_tricks[PlayerId.ONE][-1]) self.assertFalse(queen_hearts in game_state.cards_in_hand[PlayerId.ONE]) self.assertFalse(queen_diamonds in game_state.cards_in_hand[PlayerId.TWO]) self.assertTrue(first_talon_card in game_state.cards_in_hand[PlayerId.ONE]) self.assertTrue(second_talon_card in game_state.cards_in_hand[PlayerId.TWO]) self.assertEqual([Card(Suit.CLUBS, CardValue.TEN)], game_state.talon) self.assertEqual(PlayerId.ONE, game_state.next_player)
def test_actions_when_player_is_to_lead_talon_is_empty(self): game_state = get_game_state_with_empty_talon_for_tests() actions = get_available_actions(game_state) self.assertEqual(set(actions), set(get_available_actions(game_state.next_player_view()))) expected_actions = [ PlayCardAction(PlayerId.ONE, Card(Suit.CLUBS, CardValue.ACE)), PlayCardAction(PlayerId.ONE, Card(Suit.HEARTS, CardValue.KING)), PlayCardAction(PlayerId.ONE, Card(Suit.HEARTS, CardValue.TEN)), PlayCardAction(PlayerId.ONE, Card(Suit.SPADES, CardValue.TEN)), ] self.assertSetEqual(set(expected_actions), set(actions)) game_state = get_game_state_with_empty_talon_for_tests() with GameStateValidator(game_state): game_state.next_player = PlayerId.TWO actions = get_available_actions(game_state) self.assertEqual(set(actions), set(get_available_actions(game_state.next_player_view()))) expected_actions = [ PlayCardAction(PlayerId.TWO, Card(Suit.DIAMONDS, CardValue.JACK)), AnnounceMarriageAction(PlayerId.TWO, Card(Suit.CLUBS, CardValue.KING)), PlayCardAction(PlayerId.TWO, Card(Suit.CLUBS, CardValue.JACK)), AnnounceMarriageAction(PlayerId.TWO, Card(Suit.CLUBS, CardValue.QUEEN)), ] self.assertSetEqual(set(expected_actions), set(actions))
def test_the_last_trump_puzzle(self): game_view = get_game_view_for_the_last_trump_puzzle() action = self._mcts_player.request_next_action(game_view) print(f"Selected action: {action}") self.assertEqual( PlayCardAction(PlayerId.ONE, Card(Suit.HEARTS, CardValue.TEN)), action) game_state = populate_game_view(game_view, get_unseen_cards(game_view)) game_state = action.execute(game_state) game_state = self._play_against_another_mcts_player_until_the_end( game_state) self.assertEqual(0, game_state.game_points.two)
def test_can_close_talon(self): self._run_test_cases_with_option("can_close_talon", [False, True], [ # The talon is already closed. Discard the smallest trump card. { "cards_in_hand": (["ts", "qs"], ["as", None]), "trump": Suit.SPADES, "trump_card": "ks", "talon": [None, None, None, None, None, None, None, None, None], "won_tricks": ([("qh", "qd")], [("tc", "ac"), ("jh", "jc")]), "player_that_closed_the_talon": PlayerId.ONE, "opponent_points_when_talon_was_closed": 0, "expected_action": [ PlayCardAction(PlayerId.ONE, Card.from_string("qs")), PlayCardAction(PlayerId.ONE, Card.from_string("qs")), ] }, # There is a high chance the opponent has a Heart or a Club. { "cards_in_hand": (["qh", "ah", "ad", "kc", "kd"], [None, None, None, None, None]), "trump": Suit.SPADES, "trump_card": "as", "talon": [None], "won_tricks": ([("qs", "ac"), ("ks", "jd")], [("tc", "js"), ("jh", "jc")]), "marriage_suits": ([Suit.SPADES], []), "expected_action": [ PlayCardAction(PlayerId.ONE, Card.from_string("qh")), CloseTheTalonAction(PlayerId.ONE), ] }, # There is a small chance to win if we close the talon. { "cards_in_hand": (["qh", "qd", "ac", "ks", "tc"], [None, None, None, None, None]), "trump": Suit.HEARTS, "trump_card": "ah", "talon": [None, None, None, None, None, None, None, None, None], "expected_action": [ PlayCardAction(PlayerId.ONE, Card.from_string("qd")), PlayCardAction(PlayerId.ONE, Card.from_string("qd")), ] }, # Make sure the player computes the winning probabilities as if suits must # be followed. { "cards_in_hand": (["qc", "ts", "ah", "as", "ad"], [None, None, None, None, None]), "trump": Suit.SPADES, "trump_card": "js", "talon": [None, None, None], "won_tricks": ([("td", "kd"), ("qd", "jc")], [("jh", "kh")]), "expected_action": [ PlayCardAction(PlayerId.ONE, Card.from_string("qc")), CloseTheTalonAction(PlayerId.ONE), ] }, ])
def test_must_follow_trump_can_discard_any_card(self): game_state = get_game_state_for_tests() with GameStateValidator(game_state): game_state.next_player = PlayerId.TWO game_state.close_talon() action = PlayCardAction(PlayerId.TWO, game_state.cards_in_hand[PlayerId.TWO][0]) game_state = action.execute(game_state) for card in game_state.cards_in_hand[PlayerId.ONE]: action = PlayCardAction(PlayerId.ONE, card) self.assertTrue(action.can_execute_on(game_state)) self.assertTrue(action.can_execute_on(game_state.next_player_view()))
def test_non_trump_marriages_are_preferred_randomly(self): self.assertIn(get_best_marriage( [ PlayCardAction(PlayerId.ONE, Card.from_string("jh")), PlayCardAction(PlayerId.ONE, Card.from_string("ad")), PlayCardAction(PlayerId.ONE, Card.from_string("js")), PlayCardAction(PlayerId.ONE, Card.from_string("ts")), PlayCardAction(PlayerId.ONE, Card.from_string("qc")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("kh")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("qh")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("ks")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("qs")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("kc")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("qc")), ], Suit.DIAMONDS), { AnnounceMarriageAction(PlayerId.ONE, Card.from_string("kh")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("qh")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("ks")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("qs")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("kc")), AnnounceMarriageAction(PlayerId.ONE, Card.from_string("qc")), })
def test(self): action = PlayCardAction(PlayerId.ONE, Card(Suit.SPADES, CardValue.ACE)) class TestPlayer(AIPlayer): def request_next_action(self, game_view: GameState, game_points: Optional[ PlayerPair[int]] = None) -> PlayerAction: return action ai_player = TestPlayer(PlayerId.ONE, True) player = ComputerPlayer(ai_player) self.assertTrue(player.is_cheater()) game_state = get_game_state_for_tests() callback = Mock() player.request_next_action(game_state, callback) callback.assert_called() self.assertEqual(action, callback.call_args.args[0])
def _on_lead_follow_suit(self, game_view: GameState) -> PlayerAction: # If talon is depleted, the probabilities here would be either 0 or 1. If # we have any card with 100% winning prob, play it. If the talon is closed, # play the card with the highest probability to win (can be smaller than 1). probabilities = self._get_winning_prob(game_view) logging.debug("HeuristicPlayer: Card win probabilities:\n%s", _pprint(probabilities)) max_prob = max(probabilities.values()) logging.debug( "HeuristicPlayer: The maximum winning probability is %.2f", max_prob) if max_prob > 0: # TODO: Set a threshold here. # If we have a marriage and the king has the same winning chance as the # maximum among all the other cards in hand, prefer to announce the # marriage. if self._marriage_suit is not None: king = Card(self._marriage_suit, CardValue.KING) king_prob = probabilities.get(king, 0) logging.debug("HeuristicPlayer: %s probability: %.2f", king, king_prob) if king_prob == max_prob: logging.debug( "HeuristicPlayer: Announcing the marriage for %s", king) return AnnounceMarriageAction(self.id, king) # Play a random card among the ones with the highest chance to win the # next trick. card_to_play = random.choice([ card for card, prob in probabilities.items() if prob == max_prob ]) logging.debug( "HeuristicPlayer: Play a card with the maximum win probability %s", card_to_play) return self._play_card_or_marriage(card_to_play) # If there is no chance we win the next trick and we have a marriage, # announce it. if self._marriage_suit is not None: return AnnounceMarriageAction( self.id, Card(self._marriage_suit, CardValue.KING)) # Discard one of the small cards. card_to_play = self._best_discard(game_view) return PlayCardAction(self.id, card_to_play)
def test(self): self.render(None) event = Event() player = OutOfProcessComputerPlayer(_OutOfProcessTestPlayer, (PlayerId.ONE, True, event)) self.assertTrue(player.is_cheater()) game_state = get_game_state_for_tests() callback = Mock() player.request_next_action(game_state, callback, PlayerPair(2, 3)) callback.assert_not_called() self.advance_frames(5) callback.assert_not_called() event.set() self.wait_for_mock_callback(callback, 5) self.assertEqual( PlayCardAction(PlayerId.ONE, Card(Suit.SPADES, CardValue.ACE)), callback.call_args.args[0]) player.cleanup()
def test_must_follow_suit_must_use_trump(self): """Player.TWO has no hearts, so they must use one of the trump cards.""" game_state = get_game_state_for_tests() game_state.close_talon() self.assertTrue(game_state.must_follow_suit()) action = PlayCardAction(PlayerId.ONE, game_state.cards_in_hand[PlayerId.ONE][0]) self.assertTrue(action.can_execute_on(game_state)) game_state = action.execute(game_state) num_legal_cards = 0 for card in game_state.cards_in_hand[PlayerId.TWO]: action = PlayCardAction(PlayerId.TWO, card) is_legal_card = action.can_execute_on(game_state) self.assertEqual(card.suit == game_state.trump, is_legal_card, msg=f"{card}") self.assertEqual(is_legal_card, action.can_execute_on(game_state.next_player_view())) if is_legal_card: num_legal_cards += 1 self.assertEqual(3, num_legal_cards)
def test_avoid_direct_loss(self): self._run_test_cases_with_option("avoid_direct_loss", [False, True], [ # Play a trump card instead of discarding the smallest non trump card. { "cards_in_hand": (["jh", "td", "th", "kh", "ad"], [None, None, None, None, None]), "trump": Suit.HEARTS, "trump_card": "ah", "talon": [None], "won_tricks": ([], [("kd", "tc"), ("js", "qs"), ("ts", "as"), ("jc", "ac")]), "current_trick": (None, "qc"), "expected_action": [ PlayCardAction(PlayerId.ONE, Card.from_string("td")), PlayCardAction(PlayerId.ONE, Card.from_string("jh")), ] }, # Play the smallest card instead of discarding a card from an exhausted # suit. { "cards_in_hand": (["jh", "td", "th", "tc", "ad"], [None, None, None, None, None]), "trump": Suit.SPADES, "trump_card": "as", "talon": [None], "won_tricks": ([], [("jc", "qc"), ("kc", "ts"), ("ac", "js"), ("jd", "kh")]), "marriage_suits": ([], [Suit.HEARTS]), "current_trick": (None, "ks"), "expected_action": [ PlayCardAction(PlayerId.ONE, Card.from_string("tc")), PlayCardAction(PlayerId.ONE, Card.from_string("jh")), ] }, # There is no direct loss; discard a small card. { "cards_in_hand": (["jh", "td", "ts", "tc", "ad"], [None, None, None, None, None]), "trump": Suit.SPADES, "trump_card": "as", "talon": [None, None, None, None, None, None, None, None, None], "current_trick": (None, "kh"), "expected_action": [ PlayCardAction(PlayerId.ONE, Card.from_string("jh")), PlayCardAction(PlayerId.ONE, Card.from_string("jh")), ] }, ])
def test_must_follow_suit_cannot_play_lower_card_same_suit(self): """ Player.ONE has two cards of the same suit as the card played by Player.TWO, one lower and one higher. Player.ONE also has a trump card. The only valid card is the higher card having the same suit as the card played by Player.TWO. """ game_state = get_game_state_for_tests() with GameStateValidator(game_state): ten_spades = game_state.cards_in_hand.one[3] jack_spades = game_state.cards_in_hand.two[3] game_state.cards_in_hand.two[3] = ten_spades game_state.cards_in_hand.one[3] = jack_spades queen_hearts = game_state.cards_in_hand.one[0] queen_clubs = game_state.cards_in_hand.two[4] game_state.cards_in_hand.two[4] = queen_hearts game_state.cards_in_hand.one[0] = queen_clubs game_state.close_talon() game_state.next_player = PlayerId.TWO self.assertTrue(game_state.must_follow_suit()) action = PlayCardAction(PlayerId.TWO, ten_spades) self.assertTrue(action.can_execute_on(game_state)) game_state = action.execute(game_state) ace_spades = Card(Suit.SPADES, CardValue.ACE) num_legal_cards = 0 for card in game_state.cards_in_hand[PlayerId.ONE]: action = PlayCardAction(PlayerId.ONE, card) is_legal_card = action.can_execute_on(game_state) self.assertEqual(card == ace_spades, is_legal_card, msg=f"{card}") self.assertEqual(is_legal_card, action.can_execute_on(game_state.next_player_view())) if is_legal_card: num_legal_cards += 1 self.assertEqual(1, num_legal_cards)
def test_elimination_play_player_two(self): game_state = get_game_state_for_elimination_play_puzzle() action = PlayCardAction(PlayerId.ONE, Card(Suit.CLUBS, CardValue.JACK)) game_state = action.execute(game_state) action = PlayCardAction(PlayerId.TWO, Card(Suit.CLUBS, CardValue.KING)) game_state = action.execute(game_state) self.assertEqual(34, game_state.trick_points.one) self.assertEqual(40, game_state.trick_points.two) mcts = Mcts(PlayerId.TWO) root_node = mcts.build_tree(game_state) for action, child in root_node.children.items(): print(action, child) for action, child in root_node.children.items(): self.assertTrue(child.fully_simulated) self.assertEqual(PlayerId.ONE, child.player) self.assertAlmostEqual(0.33, child.ucb, delta=0.01, msg=action)
def test_play_trick_talon_closed_opponent_cannot_follow_suit(self): game_state = get_game_state_for_tests() with GameStateValidator(game_state): trick = game_state.won_tricks.two.pop(0) game_state.talon.append(trick.one) game_state.talon.append(trick.two) trick = game_state.won_tricks.two.pop(0) game_state.talon.append(trick.one) game_state.talon.append(trick.two) game_state.trick_points.two = 0 game_state.close_talon() self.assertEqual([False, False, False, False, False], [card.public for card in game_state.talon]) action = PlayCardAction(PlayerId.ONE, Card(Suit.HEARTS, CardValue.TEN)) self.assertTrue(action.can_execute_on(game_state)) game_state = action.execute(game_state) self.assertEqual([False, False, False, False, False], [card.public for card in game_state.talon]) action = PlayCardAction(PlayerId.TWO, Card(Suit.CLUBS, CardValue.JACK)) self.assertTrue(action.can_execute_on(game_state)) game_state = action.execute(game_state) self.assertEqual([False, True, True, False, False], [card.public for card in game_state.talon])