def two_player_mock(_: int) -> List[List[Card.Name]]: p0_cards = [Card.Name(r, s) for r in SETS[Half.MINOR] for s in Suit] p0_cards.remove(MISSING_CARD) return [ p0_cards, [Card.Name(r, s) for r in SETS[Half.MAJOR] for s in Suit] + [MISSING_CARD], [], [] ]
def test_memorizing(game): diamonds_player = game.players[0] clubs_player = game.players[1] move = diamonds_player.asks(clubs_player).to_give(Card.Name(5, Suit.CLUBS)) game.commit_move(move) # `clubs_player` knows that `diamonds_player` doesn't have the # 3 of clubs, because `clubs_player` has that card. `diamonds_player` # also must not have the 5 of clubs. `diamonds_player` might have the # 1, 2, 4, or 6 of clubs. possession_list = [ Card.State.MIGHT_POSSESS, Card.State.MIGHT_POSSESS, Card.State.DOES_NOT_POSSESS, Card.State.MIGHT_POSSESS, Card.State.DOES_NOT_POSSESS, Card.State.MIGHT_POSSESS ] for i in range(6): assert clubs_player.knowledge[diamonds_player][ Card.Name(i + 1, Suit.CLUBS) ] == possession_list[i] # `diamonds_player` must have at least one clubs assert clubs_player.suit_knowledge[diamonds_player][ HalfSuit(Half.MINOR, Suit.CLUBS) ] == 1 # `diamonds_player` knows that all other players know they don't have # the 5 of clubs for p in diamonds_player.dummy_players: assert diamonds_player.dummy_players[p].knowledge[ diamonds_player ][Card.Name(5, Suit.CLUBS)] == Card.State.DOES_NOT_POSSESS
def _get_valid_moves(self, player: Player, use_all_knowledge: bool) -> Set[Move]: """ Return a set of valid `Moves` that a `Player` might make. Parameters ---------- player : Player The `Player` that is making the `Move` use_all_knowledge : bool If True, then only return `Moves` that give us information. Specifically, do not ask for a `Card` that you know a `Player` certainly does not have. If False, include `Moves` even if we know the other `Player` does not have the `Card`. It might still be useful to ask for a `Card` in this case because it signals to our teammates whether we do or do not have a `Card`. """ moves = [] for p in self.game.players: moves.extend([ player.asks(p).to_give(Card.Name(r, s)) for r in SETS[Half.MINOR] | SETS[Half.MAJOR] for s in Suit if player.valid_ask(p, Card.Name(r, s), use_all_knowledge) ]) return set(moves)
def test_card_transfer(game): diamonds_player = game.players[0] clubs_player = game.players[1] assert Card.Name(3, Suit.CLUBS) in clubs_player.hand move = diamonds_player.asks(clubs_player).to_give(Card.Name(3, Suit.CLUBS)) game.commit_move(move) assert Card.Name(3, Suit.CLUBS) not in clubs_player.hand assert Card.Name(3, Suit.CLUBS) in diamonds_player.hand assert game.turn == diamonds_player
def _calculate_claim(self, half: HalfSuit) -> Dict[Card.Name, Actor]: """ Indicate who on our team has each card. """ team = self.unique_id % 2 return { Card.Name(i, half.suit): Actor(p) for p in range(team, len(self.knowledge), 2) for i in SETS[half.half] if self.knowledge[Actor(p)][Card.Name( i, half.suit)] == Card.State.DOES_POSSESS }
def test_learning_from_claims(single_player_game): claim = {Card.Name(r, Suit.CLUBS): Actor(0) for r in SETS[Half.MINOR]} assert single_player_game.commit_claim(Actor(0), claim) player_1 = single_player_game.players[1] assert all([ player_1.knowledge[Actor(0)][ Card.Name(r, Suit.CLUBS) ] == Card.State.DOES_POSSESS for r in SETS[Half.MINOR] ]) single_player_game.commit_text( 'CLAIM 0 1D 0 2D 0 3D 0 4D 0 5D 0 6D' ) assert single_player_game.claims[ HalfSuit(Half.MINOR, Suit.DIAMONDS) ] == Team.EVEN
def _claim_for_half_suit(self, h: HalfSuit) -> Dict[Card.Name, Actor]: possessions: Dict[Card.Name, Actor] = {} for c in [Card.Name(r, h.suit) for r in SETS[h.half]]: for p in self.players: if c in p.hand: possessions[c] = p return possessions
def single_mock(_: int) -> List[List[Card.Name]]: return [ [Card.Name(r, s) for r in SETS[Half.MINOR] | SETS[Half.MAJOR] for s in Suit], [], [], [] ]
def _cards_in_half(self, player: Actor, half: HalfSuit) -> int: """ Return how many `Cards` this `Player` certainly DOES have in the half set. """ return sum([ self.knowledge[player][Card.Name( c, half.suit)] == Card.State.DOES_POSSESS for c in SETS[half.half] ])
def _basic_validity(self, card: Card.Name) -> bool: """ Return whether we can legally ask for this `Card`. """ if not any([ Card.Name(r, card.suit) in self.hand for r in SETS[card.half_suit().half] ]): return False if card in self.hand: return False if card.half_suit() in self.claims: return False return True
def test_evaluate_claims(game): player_0 = game.players[0] assert len(player_0.evaluate_claims()) == 0 game.commit_text('1 3C') for i in (1, 4, 5, 6): game.commit_move(player_0.asks(game.players[3]) .to_give(Card.Name(i, Suit.CLUBS))) claims = player_0.evaluate_claims() assert HalfSuit(Half.MINOR, Suit.CLUBS) in claims and len(claims) == 1 # Ensure that we don't make claims for the opposing team assert len(game.players[1].evaluate_claims()) == 0
def __init__(self, interrogator: Actor, respondent: Actor, card: Card.Name): if card in interrogator.hand: raise ValueError("A player cannot ask for a card they possess") if sum([ Card.Name(c, card.suit) in interrogator.hand for c in SETS[card.half_suit().half] ]) == 0: raise ValueError("The player needs at least one card in the set") self.interrogator = interrogator self.respondent = respondent self.card = card
def get_mock_hands(_: int) -> List[List[Card.Name]]: return [ [Card.Name(2, Suit.DIAMONDS), Card.Name(2, Suit.CLUBS)], [Card.Name(3, Suit.CLUBS)], [], [ Card.Name(1, Suit.CLUBS), Card.Name(4, Suit.CLUBS), Card.Name(5, Suit.CLUBS), Card.Name(6, Suit.CLUBS) ] ]
def _deduce_holds_remaining(self, player: Actor, c_name: Card.Name) -> None: """ If the min. number of `Cards` the `Player` must have in a half suit is equal to (6 - number of `Cards` they certainly don't have in the half suit), we can deduce the `Player` has the remaining `Cards`. """ if self._cards_not_in_half(player, c_name.half_suit( )) + self.suit_knowledge[player][c_name.half_suit()] == 6: # The `Player` must possess the remaining `Cards` for r in SETS[c_name.half_suit().half]: other_card = Card.Name(r, c_name.suit) if self.knowledge[player][ other_card] != Card.State.DOES_NOT_POSSESS: self._memorize(Knowledge.that(player).has(other_card))
def test_wrong_claim_conditions(game): game.commit_move(game.players[0].asks( game.players[1]).to_give(MISSING_CARD)) # Discard if we have all of the cards claims_0 = game.players[0].evaluate_claims() wrong_player = claims_0.pop(HalfSuit(Half.MINOR, Suit.DIAMONDS)) wrong_player[Card.Name(3, Suit.DIAMONDS)] = Actor(2) game.commit_claim(Actor(0), wrong_player) assert game.claims[HalfSuit(Half.MINOR, Suit.DIAMONDS)] == Team.DISCARD # Award to the other team if we do not have all of the cards claims_1 = game.players[1].evaluate_claims() wrong_team = claims_1.pop(HalfSuit(Half.MAJOR, Suit.DIAMONDS)) for c in wrong_team: wrong_team[c] = Actor(0) game.commit_claim(Actor(0), wrong_team) assert game.claims[HalfSuit(Half.MAJOR, Suit.DIAMONDS)] == Team.ODD
def _identify_complete_info(self, player: Actor) -> None: """ If the number of `Cards` a `Player` is holding is equal to sums of the minimum number of `Cards` they must have in some subset of the suits, then the `Player` must have 0 `Cards` in all other suits. If we know all of the `Cards` a `Player` has, then they must not have any other `Cards`. """ if self._has_minimum_cards(player) == self.n_cards[player]: for s in self._suits_with_no_cards(player): for rank in MINOR | MAJOR: self._memorize( Knowledge.that(player).lacks(Card.Name(rank, s))) if self._know_with_certainty(player) == self.n_cards[player]: for c_name in self.knowledge[player]: if self.knowledge[player][c_name] != Card.State.DOES_POSSESS: self._memorize(Knowledge.that(player).lacks(c_name))
def serialize(self) -> List[int]: """ Serialize this `Player`'s state as a list of integers. """ output = [self.unique_id] # Order the suits and ranks so the serialization is consistent _ord_suits = [Suit.CLUBS, Suit.DIAMONDS, Suit.HEARTS, Suit.SPADES] _ord_ranks = list(range(1, 7)) + list(range(8, 14)) for i in range(len(self.knowledge)): for s, j in [(s, j) for j in _ord_ranks for s in _ord_suits]: output.append(self.knowledge[Actor(i)][Card.Name(j, s)].value) for h, s in [(h, s) for h in Half for s in _ord_suits]: output.append(self.suit_knowledge[Actor(i)][HalfSuit(h, s)]) output.append(self.n_cards[Actor(i)]) for k in range(len(self.dummy_players)): output.extend(self.dummy_players[Actor(k)].serialize()) return output
""" Basic tests for the `Literature` class. """ from typing import List import pytest from literature.actor import Actor from literature.card import (Card, Suit, HalfSuit, Half) from literature.constants import SETS from literature.literature import Literature, Team MISSING_CARD = Card.Name(3, Suit.CLUBS) def two_player_mock(_: int) -> List[List[Card.Name]]: p0_cards = [Card.Name(r, s) for r in SETS[Half.MINOR] for s in Suit] p0_cards.remove(MISSING_CARD) return [ p0_cards, [Card.Name(r, s) for r in SETS[Half.MAJOR] for s in Suit] + [MISSING_CARD], [], [] ] @pytest.fixture() def game(): # Give two players all of the cards return Literature(4, hands_fn=two_player_mock, turn_picker=lambda: 0) def test_game_not_complete(game): # There is no winner before the game is complete
def commit_claim(self, player: Actor, possessions: Dict[Card.Name, Actor]) -> bool: """ Return whether or not the claim was successfully made. If the claim was successful, note that the `Team` successfully made the claim. We do not currently penalize incorrect claims, since the bots will never make a claim with uncertainty. Parameters ---------- player : Actor The Player that is making the claim possessions : Dict[Card.Name, Actor] A map from card name to the Player who possesses the card. The map must contain an entry for every card in the half suit, and all Players in the map must belong to the same team. """ _validate_possessions(player, possessions) claimed = set() _random_key = list(possessions.keys())[0] half_suit = _random_key.half_suit() if half_suit in self.actual_possessions: raise ValueError('{} has already been claimed'.format(half_suit)) # Once a claim is submitted, all players must show the cards they # have for that half suit actual = self._claim_for_half_suit(half_suit) self.actual_possessions[half_suit] = actual for p in self.players: p.memorize_claim(actual) team = Team(player.unique_id % 2) for c, a in possessions.items(): # Get the actual `Player` object, since `commit_claims` can # take an `Actor`. real_player = self._actor_to_player(a) if c in real_player.hand: claimed.add(c) if sum([Card.Name(r, half_suit.suit) in claimed for r in SETS[half_suit.half]]) != 6: self.logger.info('Team {0} failed to claim {1}'.format(team, half_suit)) if any(p.unique_id % 2 != player.unique_id % 2 for p in actual.values()): other = Team((player.unique_id + 1) % 2) self.logger.info('The claim goes to team {0}'.format(other)) self.claims[half_suit] = other else: self.logger.info('The claim is discarded') self.claims[half_suit] = Team.DISCARD return False self.claims[half_suit] = team self.logger.info('Team {0} successfully claimed {1}'.format(team, half_suit)) # Change the turn if needed. By default, the turn goes to the player # who made the claim. self.turn = self._actor_to_player(player) self._switch_turn_if_no_cards() return True
def __init__(self, unique_id: int, hand: List[Card.Name] = [], n_players: Optional[int] = None, dummy: bool = False): """ Parameters ---------- unique_id : int A unique ID for this `Player`. The teams are split into even and odd `unique_id` values. hand : List[Card.Name] The names of the `Cards` that this `Player` possesses n_players : Optional[int] The number of `Players` in the game. If the value is None, then the `Player` object can only be used as a key in a `dict`. dummy : bool An indicator whether this `Player` object is a dummy `Player`, purely used to keep track of what information other `Players` have. A dummy `Player` should not instantiate its own dummy `Players`. """ hand_set = set(hand) super().__init__(unique_id, hand_set) if n_players is None: return # The following three variables define the game state # `self.knowledge` represents whether each `Player` definitely # does, does not, or might have each card. self.knowledge: Dict[Actor, Dict[Card.Name, Card.State]] = PrintableDict() # `self.suit_knowledge` represents the minimum number of `Cards` # a `Player` must have of this half suit self.suit_knowledge: Dict[Actor, Dict[HalfSuit, int]] = PrintableDict() # `self.n_cards` is the number of `Cards` each `Player` has self.n_cards: Dict[Union[Actor, int], int] = PrintableDict() # `self.dummy_players` tells us what we know other `Players` know # about each other. Initialize this list if `self` is not a dummy. self.dummy_players: Dict[Actor, "Player"] = { Actor(i): Player(i, hand=[], n_players=n_players, dummy=True) for i in range(n_players) if not dummy } # `self.claims` simply keeps tracks of the claims that have been made self.claims: Set[HalfSuit] = set() # Initialize knowledge _cards = [Card.Name(i, suit) for i in MAJOR | MINOR for suit in Suit] for i in range(n_players): p = Actor(i) # Every player might possess any `Card` at the beginning self.knowledge[p] = PrintableDict( {name: Card.State.MIGHT_POSSESS for name in _cards}) self.suit_knowledge[p] = PrintableDict( {HalfSuit(h, s): 0 for h in Half for s in Suit}) self.n_cards[p] = int(48 / n_players) # Memorize that we don't have `Cards` that we didn't receive for c_name in [Card.Name(i, s) for i in MINOR | MAJOR for s in Suit]: if c_name not in hand_set: self._memorize(Knowledge.that(self).lacks(c_name)) # Memorize that we have `Cards` that we received for card in hand_set: self._memorize(Knowledge.that(self).has(card)) self.suit_knowledge[self][card.half_suit()] += 1