def get_next_moves(self): """computes the next moves available from the current state Returns: List of AbstractMove: a list of the next moves """ if self.is_initialisation_phase(): return self._get_initialisation_moves() if self.current_dice_number != 7: empty_move = CatanMove(self.board.get_robber_land()) moves = [empty_move] else: moves = [ CatanMove(land) for land in self.board.get_lands_to_place_robber_on() ] moves = self._get_all_possible_development_cards_exposure_moves(moves) # _get_all_possible_trade_moves is assuming it's after dev_cards moves and nothing else moves = self._get_all_possible_trade_moves(moves) moves = self._get_all_possible_paths_moves(moves) moves = self._get_all_possible_settlements_moves(moves) moves = self._get_all_possible_cities_moves(moves) moves = self._get_all_possible_development_cards_purchase_count_moves( moves) return moves
def test_largest_army_is_updated(self): for i in range(6): if i % 2 == 1: # on player2 turn, don't do anything self.state.make_move( CatanMove(self.state.board.get_robber_land())) continue # assert no-one has largest army yet player, threshold = self.state._get_largest_army_player_and_size() self.assertEqual(player, None) self.assertEqual(threshold, 2) # add knight dev-card self.players[0].add_unexposed_development_card( DevelopmentCard.Knight) # expose the knight card move = CatanMove(self.state.board.get_robber_land()) move.development_card_to_be_exposed = DevelopmentCard.Knight self.state.make_move(move) player, threshold = self.state._get_largest_army_player_and_size() self.assertEqual(player, self.players[0]) self.assertEqual(threshold, 3)
def test_get_current_player(self): self.assertEqual(self.state.get_current_player(), self.players[0]) self.state.make_move(CatanMove(self.state.board.get_robber_land())) self.state.make_random_move() self.assertEqual(self.state.get_current_player(), self.players[1]) self.state.make_move(CatanMove(self.state.board.get_robber_land())) self.state.make_random_move() self.assertEqual(self.state.get_current_player(), self.players[0])
def test_get_all_possible_path_moves(self): self.state.board.set_location(self.players[0], 0, Colony.Settlement) self.state.board.set_location(self.players[0], 7, Colony.Settlement) self.state.board.set_path(self.players[0], (3, 0), Road.Paved) self.state.board.set_path(self.players[0], (3, 7), Road.Paved) self.state.board.set_location(self.players[1], 39, Colony.Settlement) self.state.board.set_location(self.players[1], 40, Colony.Settlement) self.state.board.set_path(self.players[1], (39, 44), Road.Paved) self.state.board.set_path(self.players[1], (40, 44), Road.Paved) self.state.turns_count = 4 empty_move = CatanMove(self.state.board.get_robber_land()) moves = [empty_move] moves = self.state._get_all_possible_paths_moves(moves) self.assertEqual(moves, [empty_move]) self.players[0].add_resource(Resource.Lumber) self.players[0].add_resource(Resource.Brick) # First player moves = [empty_move] moves = self.state._get_all_possible_paths_moves(moves) self.assertEqual(len(moves), 4) # move to next player self.state.make_move(empty_move) self.state.make_random_move(RandomMove(2, 0.1, self.state)) self.players[1].add_resource(Resource.Lumber) self.players[1].add_resource(Resource.Brick) # Second player moves = [empty_move] moves = self.state._get_all_possible_paths_moves(moves) self.assertEqual(len(moves), 6)
def get_random_move(self): if self.current_dice_number != 7: move = CatanMove(self.board.get_robber_land()) else: moves = [ CatanMove(land) for land in self.board.get_lands_to_place_robber_on() ] move = moves[np.random.randint(len(moves))] player = self.get_current_player() if player.has_unexposed_development_card(): move = self._get_random_dev_card_exposure_move(move, player) move = self._get_random_trade_move(move, player) move = self._get_random_paths_move(move, player) move = self._get_random_settlements_move(move, player) move = self._get_random_cities_move(move, player) move = self._get_random_card_purchases_count_move(move, player) return move
def test_get_all_possible_trade_moves_empty_move_not_enough_resources( self): empty_move = CatanMove(self.state.board.get_robber_land()) moves = [empty_move] moves = self.state._get_all_possible_trade_moves(moves) assert moves == [empty_move] for _ in range(3): for resource in Resource: self.players[0].add_resource(resource) moves = self.state._get_all_possible_trade_moves(moves) assert moves == [empty_move]
def test_unmake_move(self): self.players[0].add_resources_and_piece_for_settlement() move = CatanMove(self.state.board.get_robber_land()) move.locations_to_be_set_to_settlements.append(0) self.state.make_move(move) self.assertListEqual( self.state.board.get_locations_colonised_by_player( self.players[0]), [0]) self.state.unmake_move(move)
def _pretend_to_make_a_move(self, move: CatanMove): player = self.get_current_player() player.update_resources(move.resources_updates, AbstractPlayer.add_resource) previous_robber_land_placement = self.board.get_robber_land() self.board.set_robber_land(move.robber_placement_land) move.robber_placement_land = previous_robber_land_placement if move.development_card_to_be_exposed == DevelopmentCard.RoadBuilding: self._apply_road_building_dev_card_side_effect(1) elif move.development_card_to_be_exposed == DevelopmentCard.Monopoly: assert move.monopoly_card is not None for other_player in self.players: if other_player is player: continue resource = move.monopoly_card resource_count = other_player.get_resource_count(resource) move.monopoly_card_debt[other_player] = resource_count other_player.remove_resource(resource, resource_count) player.add_resource(resource, resource_count) if move.development_card_to_be_exposed is not None: player.expose_development_card(move.development_card_to_be_exposed) self._unexposed_dev_cards_counters[ move.development_card_to_be_exposed] -= 1 assert self._unexposed_dev_cards_counters[ move.development_card_to_be_exposed] >= 0 for exchange in move.resources_exchanges: player.trade_resources( exchange.source_resource, exchange.target_resource, exchange.count, self._calc_curr_player_trade_ratio(exchange.source_resource)) for path in move.paths_to_be_paved: self.board.set_path(player, path, Road.Paved) player.remove_resources_and_piece_for_road() for loc1 in move.locations_to_be_set_to_settlements: self.board.set_location(player, loc1, Colony.Settlement) player.remove_resources_and_piece_for_settlement() for loc2 in move.locations_to_be_set_to_cities: self.board.set_location(player, loc2, Colony.City) player.remove_resources_and_piece_for_city() for count in range(0, move.development_cards_to_be_purchased_count): player.remove_resources_for_development_card()
def _update_largest_army(self, move: CatanMove): if move.development_card_to_be_exposed != DevelopmentCard.Knight: return player_with_largest_army, size_threshold = self._get_largest_army_player_and_size( ) army_size = self.get_current_player().get_exposed_knights_count() if army_size > size_threshold: self._player_with_largest_army.append( (self.get_current_player(), army_size)) move.did_get_largest_army_card = True
def test_get_all_possible_trade_moves_single_trade(self): empty_move = CatanMove(self.state.board.get_robber_land()) moves = [empty_move] moves = self.state._get_all_possible_trade_moves(moves) assert moves == [empty_move] self.players[0].add_resource(Resource.Lumber, 4) moves = self.state._get_all_possible_trade_moves(moves) assert moves[0] == empty_move assert len(moves[1].resources_exchanges) == 1 assert len(moves[2].resources_exchanges) == 1 assert len(moves[3].resources_exchanges) == 1 assert len(moves[4].resources_exchanges) == 1 assert len(moves) == 5
def _update_longest_road(self, move: CatanMove): if len(move.paths_to_be_paved) == 0: return player_with_longest_road, length_threshold = self._get_longest_road_player_and_length( ) longest_road_length = self.board.get_longest_road_length_of_player( self.get_current_player()) if longest_road_length > length_threshold: self._player_with_longest_road.append( (self.get_current_player(), longest_road_length)) move.did_get_longest_road_card = True
def _get_random_cities_move(self, move: CatanMove, player): self._pretend_to_make_a_move(move) locations = self.board.get_settlements_by_player(player) num_cities = min(len(locations), player.amount_of_cities_can_afford()) new_cities_locations = [] if num_cities > 0: num_cities = np.random.randint(num_cities) for i in range(num_cities): j = np.random.randint(len(locations)) new_cities_locations.append(locations.pop(j)) self._unpretend_to_make_a_move(move) move.locations_to_be_set_to_cities = new_cities_locations return move
def test_get_all_possible_development_cards_exposure_moves(self): self.state.board.set_location(self.players[0], 0, Colony.Settlement) self.state.board.set_location(self.players[0], 7, Colony.Settlement) self.state.board.set_path(self.players[0], (3, 0), Road.Paved) self.state.board.set_path(self.players[0], (3, 7), Road.Paved) self.state.board.set_location(self.players[1], 39, Colony.Settlement) self.state.board.set_location(self.players[1], 40, Colony.Settlement) self.state.board.set_path(self.players[1], (39, 44), Road.Paved) self.state.board.set_path(self.players[1], (40, 44), Road.Paved) self.state.turns_count = 4 empty_move = CatanMove(self.state.board.get_robber_land()) moves = [empty_move] moves = self.state._get_all_possible_development_cards_exposure_moves( moves) self.assertEqual(moves, [empty_move]) self.players[0].add_unexposed_development_card( DevelopmentCard.RoadBuilding) self.players[0].add_unexposed_development_card( DevelopmentCard.YearOfPlenty) self.players[0].add_unexposed_development_card( DevelopmentCard.Monopoly) self.players[0].add_unexposed_development_card(DevelopmentCard.Knight) self.players[0].add_unexposed_development_card( DevelopmentCard.VictoryPoint) moves = self.state._get_all_possible_development_cards_exposure_moves( moves) self.assertEqual(moves[0], empty_move) all_dev_cards = [move.development_card_to_be_exposed for move in moves] for dev_card_type in DevelopmentCard: assert dev_card_type in all_dev_cards self.assertEqual(len(moves), 41) knight_dev_applied_moves = [ move for move in moves if move.development_card_to_be_exposed == DevelopmentCard.Knight ] self.assertEqual(len(knight_dev_applied_moves), 18) y_o_p_dev_applied_moves = [ move for move in moves if move.development_card_to_be_exposed == DevelopmentCard.YearOfPlenty ] self.assertEqual(len(y_o_p_dev_applied_moves), 15) monopoly_dev_applied_moves = [ move for move in moves if move.development_card_to_be_exposed == DevelopmentCard.Monopoly ] self.assertEqual(len(monopoly_dev_applied_moves), 5)
def _get_random_paths_move(self, move: CatanMove, player): min_paths = 0 paving_option = {} if move.development_card_to_be_exposed == DevelopmentCard.RoadBuilding: min_paths = 2 self._pretend_to_make_a_move(move) if player.can_pave_road(): max_paths = player.amount_of_roads_can_afford() if min_paths < max_paths: num_roads_to_pave = np.random.randint(min_paths, max_paths) else: num_roads_to_pave = np.random.randint(max_paths) paving_option = frozenset( self._get_random_paving_option(num_roads_to_pave, player)) self._unpretend_to_make_a_move(move) move.paths_to_be_paved = paving_option return move
def _add_settlements_to_initialisation_moves(self): empty_move = CatanMove(self.board.get_robber_land()) moves = [empty_move] moves = self._get_all_possible_settlements_moves(moves) moves.remove(empty_move) for move in moves: assert len(move.resources_exchanges) == 0 assert move.development_card_to_be_exposed is None assert len(move.paths_to_be_paved) == 0 assert len(move.locations_to_be_set_to_settlements) == 1 assert len(move.locations_to_be_set_to_cities) == 0 assert move.development_cards_to_be_purchased_count == 0 assert not move.did_get_largest_army_card assert not move.did_get_longest_road_card assert (move.robber_placement_land == self.board.get_robber_land() or move.robber_placement_land is None) return moves
def test_get_all_possible_trade_moves_different_ratio_non_generic(self): empty_move = CatanMove(self.state.board.get_robber_land()) moves = [empty_move] moves = self.state._get_all_possible_trade_moves(moves) assert moves == [empty_move] self.players[0].add_resource(Resource.Lumber, 1) moves = self.state._get_all_possible_trade_moves(moves) assert moves == [empty_move] self.players[0].add_resource(Resource.Lumber, 1) self.state.board._locations_by_harbors[Harbor.HarborLumber].append(0) self.state.board.set_location(self.players[0], 0, Colony.Settlement) moves = self.state._get_all_possible_trade_moves(moves) assert moves[0] == empty_move assert len(moves[1].resources_exchanges) == 1 assert len(moves[2].resources_exchanges) == 1 assert len(moves[3].resources_exchanges) == 1 assert len(moves[4].resources_exchanges) == 1 assert len(moves) == 5
def _unpretend_to_make_a_move(self, move: CatanMove): player = self.get_current_player() for count in range(0, move.development_cards_to_be_purchased_count): player.add_resources_for_development_card() for loc2 in move.locations_to_be_set_to_cities: self.board.set_location(player, loc2, Colony.Settlement) player.add_resources_and_piece_for_city() for loc1 in move.locations_to_be_set_to_settlements: self.board.set_location(player, loc1, Colony.Uncolonised) player.add_resources_and_piece_for_settlement() for path in move.paths_to_be_paved: self.board.set_path(player, path, Road.Unpaved) player.add_resources_and_piece_for_road() for exchange in move.resources_exchanges: player.un_trade_resources( exchange.source_resource, exchange.target_resource, exchange.count, self._calc_curr_player_trade_ratio(exchange.source_resource)) if move.development_card_to_be_exposed is not None: player.un_expose_development_card( move.development_card_to_be_exposed) self._unexposed_dev_cards_counters[ move.development_card_to_be_exposed] += 1 if move.development_card_to_be_exposed == DevelopmentCard.RoadBuilding: self._revert_road_building_dev_card_side_effect(1) elif move.development_card_to_be_exposed == DevelopmentCard.Monopoly: assert move.monopoly_card is not None for other_player in self.players: if other_player is player: continue resource = move.monopoly_card resource_count = move.monopoly_card_debt[other_player] other_player.add_resource(resource, resource_count) player.remove_resource(resource, resource_count) robber_land_placement_to_undo = self.board.get_robber_land( ) # this is done just in case, probably redundant self.board.set_robber_land(move.robber_placement_land) move.robber_placement_land = robber_land_placement_to_undo # this is done just in case, probably redundant player.update_resources(move.resources_updates, AbstractPlayer.remove_resource)
def _get_random_dev_card_exposure_move(self, move: CatanMove, player) -> CatanMove: cards = [None] for card, a in player.unexposed_development_cards.items(): if card != DevelopmentCard.VictoryPoint and a > 0: cards.append(card) dev_card = np.random.choice(cards) if dev_card == DevelopmentCard.Knight and move.robber_placement_land == self.board.get_robber_land( ): lands = self.board.get_lands_to_place_robber_on() land = lands[np.random.randint(len(lands))] move.robber_placement_land = land elif dev_card == DevelopmentCard.Monopoly: num = np.random.randint(5) if num == 0: chosen_resource = Resource.Brick elif num == 1: chosen_resource = Resource.Lumber elif num == 2: chosen_resource = Resource.Wool elif num == 3: chosen_resource = Resource.Grain else: chosen_resource = Resource.Ore move.monopoly_card = chosen_resource elif dev_card == DevelopmentCard.YearOfPlenty: chosen_resources = [] for i in range(2): r = np.random.randint(5) if r == 0: chosen_resources.append(Resource.Brick) elif r == 1: chosen_resources.append(Resource.Lumber) elif r == 2: chosen_resources.append(Resource.Wool) elif r == 3: chosen_resources.append(Resource.Grain) else: chosen_resources.append(Resource.Ore) if chosen_resources[0] != chosen_resources[ 1]: # two different cards move.resources_updates[chosen_resources[0]] = 1 move.resources_updates[chosen_resources[1]] = 1 else: # same card twice move.resources_updates[chosen_resources[0]] = 2 move.development_card_to_be_exposed = dev_card return move
def test_get_all_possible_trade_moves_empty_move_no_resources(self): empty_move = CatanMove(self.state.board.get_robber_land()) moves = [empty_move] moves = self.state._get_all_possible_trade_moves(moves) assert moves == [empty_move]
def test_probability_calculation_given_card_used(self): # given this board self.state.board.set_location(self.players[0], 0, Colony.Settlement) self.state.board.set_location(self.players[0], 7, Colony.Settlement) self.state.board.set_path(self.players[0], (3, 0), Road.Paved) self.state.board.set_path(self.players[0], (3, 7), Road.Paved) self.state.board.set_location(self.players[1], 39, Colony.Settlement) self.state.board.set_location(self.players[1], 40, Colony.Settlement) self.state.board.set_path(self.players[1], (39, 44), Road.Paved) self.state.board.set_path(self.players[1], (40, 44), Road.Paved) self.state.turns_count = 4 # buy two knight cards self.state.make_move(CatanMove(self.state.board.get_robber_land())) purchase_count = 2 two_knight_cards_expected_probability = (15 / 26) * (14 / 25) two_knights_counters = { card: purchase_count if card is DevelopmentCard.Knight else 0 for card in DevelopmentCard } self.state.make_random_move( RandomMove( 2, two_knight_cards_expected_probability * self.state.probabilities_by_dice_values[2], self.state, two_knights_counters)) # make empty move self.state.make_move(CatanMove(self.state.board.get_robber_land())) self.state.make_random_move( RandomMove(2, self.state.probabilities_by_dice_values[2], self.state)) # expose one knight move = CatanMove(self.state.board._lands[0]) move.development_card_to_be_exposed = DevelopmentCard.Knight self.state.make_move(move) self.state.make_random_move( RandomMove(2, self.state.probabilities_by_dice_values[2], self.state)) # get all purchase options of two cards options = self.state._get_all_possible_development_cards_purchase_options( purchase_count) # assert the number of options is (25 choose 2) (because there are 25 unexposed cards) number_of_cards_combinations = sum( 1 for _ in combinations_with_replacement(DevelopmentCard, purchase_count)) self.assertEqual(len(options), number_of_cards_combinations) # assert all options are different for option1, option2 in combinations(options, 2): self.assertNotEqual(option1, option2) self.assertNotEqual(option1.purchased_cards_counters, option2.purchased_cards_counters) # assert all probabilities are valid probabilities, and there are no moves where probability = 0 for option in options: self.assertLess(option.probability, 1) self.assertGreater(option.probability, 0) # this assertion is just to make sure I'm testing the probability of the right options assert options[0].purchased_cards_counters == two_knights_counters # assert the probability is right, given one knight was exposed two_knight_cards_expected_probability = (14 / 25) * (13 / 24) two_knight_cards_actual_probability = options[0].probability self.assertAlmostEqual(two_knight_cards_expected_probability, two_knight_cards_actual_probability)