예제 #1
0
    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)
예제 #2
0
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], [], []
    ]
예제 #3
0
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
예제 #4
0
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
예제 #5
0
 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
     }
예제 #6
0
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
예제 #7
0
 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
예제 #8
0
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],
        [],
        [],
        []
    ]
예제 #9
0
 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]
     ])
예제 #10
0
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
예제 #11
0
 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
예제 #12
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
예제 #13
0
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)
        ]
    ]
예제 #14
0
 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))
예제 #15
0
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
예제 #16
0
    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))
예제 #17
0
    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
예제 #18
0
""" 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
예제 #19
0
 def lacks(self, card: Card.Name) -> ConcreteKnowledge:
     return ConcreteKnowledge(self.player,
                              Card(card, Card.State.DOES_NOT_POSSESS))
예제 #20
0
 def _name_to_card(self, c_name: Card.Name):
     if c_name in self.hand:
         return Card(c_name, Card.State.DOES_POSSESS)
     return Card(c_name, Card.State.DOES_NOT_POSSESS)
예제 #21
0
    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
예제 #22
0
    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