def test_play_poacher_with_empty_supply_piles(self): for num_empty_supply_piles in (1, 2, 3): cards_in_supply = [CopperCard, SilverCard, GoldCard] starting_deck = [CopperCard] * 20 game_state = self.create_game_state( starting_deck, supply=UnorderedCardStack(cards_in_supply)) p1_agent = TestAgent('p1') p2_agent = TestAgent('p2') game_state.set_agents([p1_agent, p2_agent]) supply = game_state.get_location( Location(None, LocationName.SUPPLY)) # empty supply piles for i in range(num_empty_supply_piles): supply.extract([cards_in_supply[i]]) player = game_state.get_current_player_name() start_hand_size = 5 game_state.draw(start_hand_size) PoacherCard.play(game_state) hand = game_state.get_location(Location(player, LocationName.HAND)) self.assertEqual( hand.size(), start_hand_size + 1 - num_empty_supply_piles, 'Player should draw 1 card and then discard one for each empty supply pile.' ) discard = game_state.get_location( Location(player, LocationName.DISCARD)) self.assertEqual( discard.size(), num_empty_supply_piles, 'Player should have discarded 1 card for each empty supply pile.' ) self.assertEqual( game_state.get_counter(CounterId(None, CounterName.COINS)), 1, 'Poacher should give the player one extra coin.') self.assertEqual( game_state.get_counter(CounterId(None, CounterName.ACTIONS)), 1, 'Poacher should give the player one extra action.')
def play(cls, game_state): """ +1 card +1 action Look through your discard pile. You may put that card on top of your deck """ game_state.draw(1) game_state.update_counter(counter_id=CounterId(None, CounterName.ACTIONS), delta=1) decision = HarbingerDecision(game_state) agent = game_state.get_agent(game_state.get_current_player_name()) chosen_cards = agent.make_decision(decision) assert decision.is_valid(chosen_cards) if chosen_cards: game_state.move(cards=chosen_cards, number=None, from_location=Location(agent.name(), LocationName.DISCARD), from_position=None, to_location=Location(agent.name(), LocationName.DRAW_PILE), to_position=StackPosition.TOP, event_type=CardEventType.MOVE)
def test_no_cards_in_deck_or_discard(self): game_state = self.create_game_state([]) player = game_state.get_current_player_name() VassalCard.play(game_state) discard = game_state.get_location( Location(player, LocationName.DISCARD)) draw_pile = game_state.get_location( Location(player, LocationName.DRAW_PILE)) self.assertEqual(draw_pile.size(), 0) self.assertEqual(discard.size(), 0)
def test_top_card_is_action_choose_not_to_play_it(self): game_state = self.create_game_state([VassalCard, CopperCard]) game_state.set_agents([NeverVassalAgent('p1'), NeverVassalAgent('p2')]) player = game_state.get_current_player_name() VassalCard.play(game_state) discard = game_state.get_location( Location(player, LocationName.DISCARD)) draw_pile = game_state.get_location( Location(player, LocationName.DRAW_PILE)) self.assertEqual(discard.peek(), VassalCard) self.assertEqual(draw_pile.peek(), CopperCard)
def buy(self, card): """ Causes the current player to buy the card. """ player = self.get_current_player_name() self.move(cards=[card], number=None, from_location=Location(None, LocationName.SUPPLY), from_position=None, to_location=Location(player, LocationName.DISCARD), to_position=None, event_type=CardEventType.BUY)
def gain_to_top_of_deck(self, card, player=None): """ Causes the player to gain the card to the top of their draw pile """ player = player or self.get_current_player_name() self.move(cards=[card], number=None, from_location=Location(None, LocationName.SUPPLY), from_position=None, to_location=Location(player, LocationName.DRAW_PILE), to_position=StackPosition.TOP, event_type=CardEventType.GAIN)
def test_play(self): game_state = self.create_default_game_state() current_player = game_state.get_current_player_name() hand_location = Location(current_player, LocationName.HAND) hand = game_state.get_location(hand_location) hand.add(1) game_state.play(1) in_play = game_state.get_location(Location(current_player, LocationName.IN_PLAY)) self.assertEqual(in_play.size(), 1) self.assertEqual(hand.size(), 0) game_state.discard_location(Location(current_player, LocationName.IN_PLAY)) self.assertEqual(in_play.size(), 0)
def trash(self, card, player=None): """ Causes the player to trash the card. """ player = player or self.get_current_player_name() self.move(cards=[card], number=None, from_location=Location(player, LocationName.HAND), from_position=None, to_location=Location(None, LocationName.TRASH), to_position=None, event_type=CardEventType.TRASH)
def test_no_cards_in_deck_some_in_discard(self): game_state = self.create_game_state([]) player = game_state.get_current_player_name() discard = game_state.get_location( Location(player, LocationName.DISCARD)) discard.add([CopperCard, CopperCard]) VassalCard.play(game_state) discard = game_state.get_location( Location(player, LocationName.DISCARD)) self.assertEqual(discard.peek(), CopperCard) draw_pile = game_state.get_location( Location(player, LocationName.DRAW_PILE)) self.assertEqual(draw_pile.peek(), CopperCard)
def gain(self, card, player=None, to_location=None, to_position=None): """ Causes the player to gain the card. """ player = player or self.get_current_player_name() to_location = to_location or Location(player, LocationName.DISCARD) self.move(cards=[card], number=None, from_location=Location(None, LocationName.SUPPLY), from_position=None, to_location=to_location, to_position=to_position, event_type=CardEventType.GAIN)
def _create_locations(self, player_names): """ Create a dict of `Location` -> CardStack for all locations in a starting game state given the list of player names. """ locations = {} for loc_name, stack_class in STACK_CLASS_BY_LOCATION_NAME.items(): stack_class = STACK_CLASS_BY_LOCATION_NAME[loc_name] if loc_name in GLOBAL_LOCATIONS: locations[Location(None, loc_name)] = stack_class() else: for player in player_names: locations[Location(player, loc_name)] = stack_class() return locations
def play(self, card, player=None, from_location=None): """ Causes the player to play the card. This just moves the card to play from the player's hand it does not trigger the cards logic to be executed. """ player = player or self.get_current_player_name() from_location = from_location or Location(player, LocationName.HAND) self.move(cards=[card], number=None, from_location=from_location, from_position=None, to_location=Location(player, LocationName.IN_PLAY), to_position=None, event_type=CardEventType.PLAY)
def test_create_game_state(self): supply = UnorderedCardStack() supply.add(cards=[1]) supply.add(cards=[2, 2]) names = ['p1', 'p2'] logger = TestLogger() game_state = GameState( player_names=names, supply=supply, starting_deck=[], logger=logger ) # Check trash exists trash = game_state.get_location(Location(None, LocationName.TRASH)) self.assertEqual(type(trash), UnorderedCardStack) # Check player hands exist for name in names: hand = game_state.get_location(Location(name, LocationName.HAND)) self.assertEqual(type(trash), UnorderedCardStack)
def discard(self, cards, player=None, location=None): """ Causes the player to discard the cards. The first card in the list will be the one on top of the discard pile after discarding them. """ if not cards: return player = player or self.get_current_player_name() location = location or Location(player, LocationName.HAND) self.move(cards=cards, number=None, from_location=location, from_position=None, to_location=Location(player, LocationName.DISCARD), to_position=None, event_type=CardEventType.DISCARD)
def test_top_card_not_action(self): game_state = self.create_game_state([CopperCard, CopperCard]) player = game_state.get_current_player_name() VassalCard.play(game_state) discard = game_state.get_location( Location(player, LocationName.DISCARD)) self.assertEqual(discard.peek(), CopperCard)
def __init__(self, game_state): player = game_state.get_current_player_name() cards = list( game_state.get_location(Location(player, LocationName.HAND))) self.options = cards self.min = 0 self.max = len(cards)
def test_upgrade_treasure(self): game_state = self.create_game_state( [MineCard, CopperCard, CopperCard, SilverCard]) p1_agent = MineUpgradeAgent('p1') p2_agent = MineUpgradeAgent('p2') game_state.set_agents([p1_agent, p2_agent]) game_state.draw(4) player = game_state.get_current_player_name() MineCard.play(game_state) self.assertEqual(p1_agent.get_decision_count(MineTrashDecision), 1) self.assertEqual(p1_agent.get_decision_count(MineGainDecision), 1) hand = game_state.get_location(Location(player, LocationName.HAND)) self.assertEqual(hand.size(), 4) self.assertTrue(GoldCard in list(hand)) trash = game_state.get_location(Location(None, LocationName.TRASH)) self.assertTrue(SilverCard in list(trash))
def __init__(self, game_state): hand = game_state.get_location( Location(game_state.get_current_player_name(), LocationName.HAND)) self.options = [] if CopperCard in hand: self.options = [CopperCard] self.min = 0 self.max = 1
def __init__(self, player_names, supply, starting_deck, logger): """ Parameters: player_names (list of str): List of the names of players in the order of turns. supply (`UnorderedCardStack`): Starting supply for the game. starting_deck (`UnorderedCardStack`): Cards each player will start with. logger (`GameLogger`): All changes to game state will be logged here. """ self.logger = logger self.player_names = player_names self._counters = self._create_counters(player_names) self._locations = self._create_locations(player_names) for player in player_names: stack = self.get_location(Location(player, LocationName.DRAW_PILE)) stack.add(list(starting_deck)) self._locations[Location(None, LocationName.SUPPLY)] = supply self._current_player_index = 0
def game_over(self): supply = self.game_state.get_location( Location(None, LocationName.SUPPLY)) if supply.distribution.count(ProvinceCard) == 0: return True cards_to_counts = supply.distribution.cards_to_counts() if list(cards_to_counts.values()).count(0) >= 3: return True return False
def draw(self, number, player=None): """ Draws `number` cards to the given player's hand. """ player = player or self.get_current_player_name() self.shuffle_discard_in_if_insufficient_cards(number, player) draw_stack = self.get_location(Location(player, LocationName.DRAW_PILE)) number_to_draw = min(number, draw_stack.size()) if number_to_draw == 0: return self.move(cards=None, number=number_to_draw, from_location=Location(player, LocationName.DRAW_PILE), from_position=StackPosition.TOP, to_location=Location(player, LocationName.HAND), to_position=None, event_type=CardEventType.DRAW)
def __init__(self, game_state): player = game_state.get_current_player_name() cards = set( game_state.get_location(Location(player, LocationName.HAND))) treasure_cards = [ card for card in cards if issubclass(card, TreasureCard) ] self.options = treasure_cards self.min = 0 self.max = 1
def __init__(self, game_state): supply = game_state.get_location(Location(None, LocationName.SUPPLY)) options = [] for card, count in supply.distribution.cards_to_counts().items(): if count > 0 and card.cost(game_state) <= 4: options.append(card) self.options = options # Player must gain something if possible self.min = 1 if options else 0 self.max = 1
def _shuffle_discard_into_draw(self, player): """ Shuffle the discard pile of the player and put it under their draw pile. """ discard_location = Location(player, LocationName.DISCARD) number = self.get_location(discard_location).size() discard_is_empty_so_no_action_required = (number == 0) if discard_is_empty_so_no_action_required: return self.shuffle(discard_location) self.move( cards=None, number=number, from_location=Location(player, LocationName.DISCARD), from_position=StackPosition.TOP, to_location=Location(player, LocationName.DRAW_PILE), to_position=StackPosition.BOTTOM, event_type=CardEventType.MOVE, )
def _print_location(self, known_state, location): if known_state is None: print('%s contents not known.' % location.name) return location = Location(known_state.viewing_player, location) info = known_state.get_location_info(location) if info.stack: print(','.join([str(card) for card in info.stack])) else: print('%s is empty.' % location.name.name)
def shuffle_discard_in_if_insufficient_cards(self, number, player=None): """ Prepare the player's draw pile for taking number cards from it by shuffling in the discard pile if needed. """ player = player or self.get_current_player_name() draw_stack = self.get_location(Location(player, LocationName.DRAW_PILE)) if draw_stack.size() < number: self._shuffle_discard_into_draw(player)
def __init__(self, game_state, max_cost): player = game_state.get_current_player_name() cards = set( game_state.get_location(Location(None, LocationName.SUPPLY))) treasure_cards_meeting_cost_constraint = [ card for card in cards if issubclass(card, TreasureCard) and card.cost(game_state) <= max_cost ] self.options = treasure_cards_meeting_cost_constraint self.min = 1 self.max = 1
def test_top_cards_are_actions_choose_to_play_them(self): game_state = self.create_game_state( [VassalCard, VassalCard, CopperCard, CopperCard]) game_state.set_agents( [AlwaysVassalAgent('p1'), AlwaysVassalAgent('p2')]) player = game_state.get_current_player_name() VassalCard.play(game_state) discard = game_state.get_location( Location(player, LocationName.DISCARD)) in_play = game_state.get_location( Location(player, LocationName.IN_PLAY)) draw_pile = game_state.get_location( Location(player, LocationName.DRAW_PILE)) self.assertEqual(in_play.size(), 2) # Doesn't include the artificially played one self.assertEqual(discard.size(), 1) self.assertEqual(draw_pile.size(), 1) self.assertEqual(discard.peek(), CopperCard) self.assertEqual(draw_pile.peek(), CopperCard)
def get_deck(self, player): """ Returns a list of Cards in the players deck. "Deck" refers to all cards belonging to the player including the ones in their hand, discard, in play, and set aside. """ cards = [] for loc_name in STACK_CLASS_BY_LOCATION_NAME: if loc_name in GLOBAL_LOCATIONS: continue cards += list(self.get_location(Location(player, loc_name))) return cards
def test_draw_when_enough_cards_in_draw_pile(self): game_state = self.create_default_game_state() current_player = game_state.get_current_player_name() draw_pile_location = Location(current_player, LocationName.DRAW_PILE) draw_pile = game_state.get_location(draw_pile_location) for i in range(10): draw_pile.add(i) game_state.draw(5) self.assertEqual(draw_pile.size(), 5) hand_location = Location(current_player, LocationName.HAND) hand = game_state.get_location(hand_location) self.assertEqual(hand.size(), 5) event = ( CardMoveEvent( [9, 8, 7, 6, 5], draw_pile_location, StackPosition.TOP, hand_location, None, CardEventType.DRAW ) ) self.assertEqual( game_state.logger.get_log(), [event] )