def test_play_monopoly_player_steals_cards(): player_to_act = SimplePlayer(Color.RED) player_to_steal_from_1 = SimplePlayer(Color.BLUE) player_to_steal_from_2 = SimplePlayer(Color.ORANGE) players = [player_to_act, player_to_steal_from_1, player_to_steal_from_2] state = State(players) player_deck_replenish(state, player_to_act.color, MONOPOLY) player_deck_replenish(state, player_to_steal_from_1.color, ORE, 3) player_deck_replenish(state, player_to_steal_from_1.color, WHEAT, 1) player_deck_replenish(state, player_to_steal_from_2.color, ORE, 2) player_deck_replenish(state, player_to_steal_from_2.color, WHEAT, 1) action_to_execute = Action(player_to_act.color, ActionType.PLAY_MONOPOLY, ORE) apply_action(state, action_to_execute) assert player_num_resource_cards(state, player_to_act.color, ORE) == 5 assert player_num_resource_cards(state, player_to_steal_from_1.color, ORE) == 0 assert player_num_resource_cards(state, player_to_steal_from_1.color, WHEAT) == 1 assert player_num_resource_cards(state, player_to_steal_from_2.color, ORE) == 0 assert player_num_resource_cards(state, player_to_steal_from_2.color, WHEAT) == 1
def maritime_trade_possibilities(state, color) -> List[Action]: trade_offers = set() # Get lowest rate per resource port_resources = set(state.board.get_player_port_resources(color)) rates: Dict[FastResource, int] = { WOOD: 4, BRICK: 4, SHEEP: 4, WHEAT: 4, ORE: 4 } if None in port_resources: rates = {WOOD: 3, BRICK: 3, SHEEP: 3, WHEAT: 3, ORE: 3} for resource in port_resources: if resource != None: rates[resource] = 2 # For resource in hand for resource in RESOURCES: amount = player_num_resource_cards(state, color, resource) if amount >= rates[resource]: resource_out: List[Any] = [resource] * rates[resource] resource_out += [None] * (4 - rates[resource]) for j_resource in RESOURCES: if (resource != j_resource and freqdeck_count( state.resource_freqdeck, j_resource) > 0): trade_offer = tuple(resource_out + [j_resource]) trade_offers.add(trade_offer) return list( map(lambda t: Action(color, ActionType.MARITIME_TRADE, t), trade_offers))
def robber_possibilities(state, color) -> List[Action]: actions = [] for (coordinate, tile) in state.board.map.land_tiles.items(): if coordinate == state.board.robber_coordinate: continue # ignore. must move robber. # each tile can yield a (move-but-cant-steal) action or # several (move-and-steal-from-x) actions. to_steal_from = set() # set of player_indexs for _, node_id in tile.nodes.items(): building = state.board.buildings.get(node_id, None) if building is not None: candidate_color = building[0] if (player_num_resource_cards(state, candidate_color) >= 1 and color != candidate_color # can't play yourself ): to_steal_from.add(candidate_color) if len(to_steal_from) == 0: actions.append( Action(color, ActionType.MOVE_ROBBER, (coordinate, None, None))) else: for enemy_color in to_steal_from: actions.append( Action(color, ActionType.MOVE_ROBBER, (coordinate, enemy_color, None))) return actions
def test_cant_buy_more_than_max_card(): players = [SimplePlayer(Color.RED), SimplePlayer(Color.BLUE)] state = State(players) with pytest.raises(ValueError): # not enough money apply_action( state, Action(players[0].color, ActionType.BUY_DEVELOPMENT_CARD, None)) player_deck_replenish(state, players[0].color, SHEEP, 26) player_deck_replenish(state, players[0].color, WHEAT, 26) player_deck_replenish(state, players[0].color, ORE, 26) for i in range(25): apply_action( state, Action(players[0].color, ActionType.BUY_DEVELOPMENT_CARD, None)) # assert must have all victory points assert player_num_dev_cards(state, players[0].color) == 25 assert get_dev_cards_in_hand(state, players[0].color, VICTORY_POINT) == 5 with pytest.raises(ValueError): # not enough cards in bank apply_action( state, Action(players[0].color, ActionType.BUY_DEVELOPMENT_CARD, None)) assert player_num_resource_cards(state, players[0].color) == 3
def test_buying_road_is_payed_for(): players = [SimplePlayer(Color.RED), SimplePlayer(Color.BLUE)] state = State(players) state.is_initial_build_phase = False state.board.build_settlement(players[0].color, 3, True) action = Action(players[0].color, ActionType.BUILD_ROAD, (3, 4)) player_freqdeck_add( state, players[0].color, freqdeck_from_listdeck([WOOD, BRICK]), ) apply_action(state, action) assert player_num_resource_cards(state, players[0].color, WOOD) == 0 assert player_num_resource_cards(state, players[0].color, BRICK) == 0
def simple_feature_vector(game, p0_color): key = player_key(game.state, p0_color) f_public_vps = game.state.player_state[f"P0_VICTORY_POINTS"] f_enemy_public_vps = game.state.player_state[f"P1_VICTORY_POINTS"] production_features = build_production_features(True) our_production_sample = production_features(game, p0_color) enemy_production_sample = production_features(game, p0_color) f_longest_road_length = game.state.player_state["P0_LONGEST_ROAD_LENGTH"] f_enemy_longest_road_length = game.state.player_state["P1_LONGEST_ROAD_LENGTH"] hand_sample = resource_hand_features(game, p0_color) distance_to_city = ( max(2 - hand_sample["P0_WHEAT_IN_HAND"], 0) + max(3 - hand_sample["P0_ORE_IN_HAND"], 0) ) / 5.0 # 0 means good. 1 means bad. distance_to_settlement = ( max(1 - hand_sample["P0_WHEAT_IN_HAND"], 0) + max(1 - hand_sample["P0_SHEEP_IN_HAND"], 0) + max(1 - hand_sample["P0_BRICK_IN_HAND"], 0) + max(1 - hand_sample["P0_WOOD_IN_HAND"], 0) ) / 4.0 # 0 means good. 1 means bad. f_hand_synergy = (2 - distance_to_city - distance_to_settlement) / 2 f_num_in_hand = player_num_resource_cards(game.state, p0_color) # blockability buildings = game.state.buildings_by_color[p0_color] owned_nodes = buildings[BuildingType.SETTLEMENT] + buildings[BuildingType.CITY] owned_tiles = set() for n in owned_nodes: owned_tiles.update(game.state.board.map.adjacent_tiles[n]) # f_num_tiles = len(owned_tiles) # f_num_buildable_nodes = len(game.state.board.buildable_node_ids(p0_color)) # f_hand_devs = player_num_dev_cards(game.state, p0_color) f_army_size = game.state.player_state[f"P1_PLAYED_KNIGHT"] f_enemy_army_size = game.state.player_state[f"P0_PLAYED_KNIGHT"] vector = { # Where to place. Note winning is best at all costs "PUBLIC_VPS": f_public_vps, "ENEMY_PUBLIC_VPS": f_enemy_public_vps, # "NUM_TILES": f_num_tiles, # "BUILDABLE_NODES": f_num_buildable_nodes, # Hand, when to hold and when to use. "HAND_SYNERGY": f_hand_synergy, "HAND_RESOURCES": f_num_in_hand, # "HAND_DEVS": f_hand_devs, # Other "ROAD_LENGTH": f_longest_road_length, "ENEMY_ROAD_LENGTH": f_enemy_longest_road_length, "ARMY_SIZE": f_army_size, "ENEMY_ARMY_SIZE": f_enemy_army_size, } vector = {**vector, **our_production_sample} vector = {**vector, **enemy_production_sample} return vector
def test_moving_robber_steals_correctly(): players = [SimplePlayer(Color.RED), SimplePlayer(Color.BLUE)] state = State(players) player_deck_replenish(state, players[1].color, WHEAT, 1) state.board.build_settlement(Color.BLUE, 3, initial_build_phase=True) action = Action(players[0].color, ActionType.MOVE_ROBBER, ((2, 0, -2), None, None)) apply_action(state, action) assert player_num_resource_cards(state, players[0].color) == 0 assert player_num_resource_cards(state, players[1].color) == 1 action = Action( players[0].color, ActionType.MOVE_ROBBER, ((0, 0, 0), players[1].color, WHEAT), ) apply_action(state, action) assert player_num_resource_cards(state, players[0].color) == 1 assert player_num_resource_cards(state, players[1].color) == 0
def test_play_year_of_plenty_gives_player_resources(): players = [SimplePlayer(Color.RED), SimplePlayer(Color.BLUE)] state = State(players) player_to_act = players[0] player_deck_replenish(state, player_to_act.color, YEAR_OF_PLENTY, 1) action_to_execute = Action(player_to_act.color, ActionType.PLAY_YEAR_OF_PLENTY, [ORE, WHEAT]) apply_action(state, action_to_execute) for card_type in RESOURCES: if card_type == ORE or card_type == WHEAT: assert player_num_resource_cards(state, player_to_act.color, card_type) == 1 assert freqdeck_count(state.resource_freqdeck, card_type) == 18 else: assert player_num_resource_cards(state, player_to_act.color, card_type) == 0 assert freqdeck_count(state.resource_freqdeck, card_type) == 19 assert get_dev_cards_in_hand(state, player_to_act.color, YEAR_OF_PLENTY) == 0
def test_trade_execution(): players = [ SimplePlayer(Color.RED), SimplePlayer(Color.BLUE), SimplePlayer(Color.WHITE), SimplePlayer(Color.ORANGE), ] state = State(players) player_deck_replenish(state, players[0].color, BRICK, 4) trade_offer = tuple([BRICK] * 4 + [ORE]) action = Action(players[0].color, ActionType.MARITIME_TRADE, trade_offer) apply_action(state, action) assert player_num_resource_cards(state, players[0].color) == 1 assert sum(state.resource_freqdeck) == 19 * 5 + 4 - 1
def resource_hand_features(game: Game, p0_color: Color): # P0_WHEATS_IN_HAND, P0_WOODS_IN_HAND, ... # P0_ROAD_BUILDINGS_IN_HAND, P0_KNIGHT_IN_HAND, ..., P0_VPS_IN_HAND # P0_ROAD_BUILDINGS_PLAYABLE, P0_KNIGHT_PLAYABLE, ... # P0_ROAD_BUILDINGS_PLAYED, P0_KNIGHT_PLAYED, ... # P1_ROAD_BUILDINGS_PLAYED, P1_KNIGHT_PLAYED, ... # TODO: P1_WHEATS_INFERENCE, P1_WOODS_INFERENCE, ... # TODO: P1_ROAD_BUILDINGS_INFERENCE, P1_KNIGHT_INFERENCE, ... state = game.state player_state = state.player_state features = {} for i, color in iter_players(game.state.colors, p0_color): key = player_key(game.state, color) if color == p0_color: for resource in RESOURCES: features[f"P0_{resource}_IN_HAND"] = player_state[ key + f"_{resource}_IN_HAND"] for card in DEVELOPMENT_CARDS: features[f"P0_{card}_IN_HAND"] = player_state[ key + f"_{card}_IN_HAND"] features[f"P0_HAS_PLAYED_DEVELOPMENT_CARD_IN_TURN"] = player_state[ key + "_HAS_PLAYED_DEVELOPMENT_CARD_IN_TURN"] for card in DEVELOPMENT_CARDS: if card == VICTORY_POINT: continue # cant play VPs features[f"P{i}_{card}_PLAYED"] = player_state[key + f"_PLAYED_{card}"] features[f"P{i}_NUM_RESOURCES_IN_HAND"] = player_num_resource_cards( state, color) features[f"P{i}_NUM_DEVS_IN_HAND"] = player_num_dev_cards(state, color) return features
def fn(game, p0_color): production_features = build_production_features(True) our_production_sample = production_features(game, p0_color) enemy_production_sample = production_features(game, p0_color) production = value_production(our_production_sample, "P0") enemy_production = value_production(enemy_production_sample, "P1", False) key = player_key(game.state, p0_color) longest_road_length = get_longest_road_length(game.state, p0_color) reachability_sample = reachability_features(game, p0_color, 2) features = [ f"P0_0_ROAD_REACHABLE_{resource}" for resource in RESOURCES ] reachable_production_at_zero = sum( [reachability_sample[f] for f in features]) features = [ f"P0_1_ROAD_REACHABLE_{resource}" for resource in RESOURCES ] reachable_production_at_one = sum( [reachability_sample[f] for f in features]) hand_sample = resource_hand_features(game, p0_color) features = [f"P0_{resource}_IN_HAND" for resource in RESOURCES] distance_to_city = (max(2 - hand_sample["P0_WHEAT_IN_HAND"], 0) + max(3 - hand_sample["P0_ORE_IN_HAND"], 0)) / 5.0 # 0 means good. 1 means bad. distance_to_settlement = (max(1 - hand_sample["P0_WHEAT_IN_HAND"], 0) + max(1 - hand_sample["P0_SHEEP_IN_HAND"], 0) + max(1 - hand_sample["P0_BRICK_IN_HAND"], 0) + max(1 - hand_sample["P0_WOOD_IN_HAND"], 0)) / 4.0 # 0 means good. 1 means bad. hand_synergy = (2 - distance_to_city - distance_to_settlement) / 2 num_in_hand = player_num_resource_cards(game.state, p0_color) discard_penalty = params["discard_penalty"] if num_in_hand > 7 else 0 # blockability buildings = game.state.buildings_by_color[p0_color] owned_nodes = buildings[BuildingType.SETTLEMENT] + buildings[ BuildingType.CITY] owned_tiles = set() for n in owned_nodes: owned_tiles.update(game.state.board.map.adjacent_tiles[n]) num_tiles = len(owned_tiles) # TODO: Simplify to linear(?) num_buildable_nodes = len( game.state.board.buildable_node_ids(p0_color)) longest_road_factor = (params["longest_road"] if num_buildable_nodes == 0 else 0.1) return float( game.state.player_state[f"{key}_VICTORY_POINTS"] * params["public_vps"] + production * params["production"] + enemy_production * params["enemy_production"] + reachable_production_at_zero * params["reachable_production_0"] + reachable_production_at_one * params["reachable_production_1"] + hand_synergy * params["hand_synergy"] + num_buildable_nodes * params["buildable_nodes"] + num_tiles * params["num_tiles"] + num_in_hand * params["hand_resources"] + discard_penalty + longest_road_length * longest_road_factor + player_num_dev_cards(game.state, p0_color) * params["hand_devs"] + get_played_dev_cards(game.state, p0_color, "KNIGHT") * params["army_size"])