def player_features(game: Game, p0_color: Color): # P0_ACTUAL_VPS # P{i}_PUBLIC_VPS, P1_PUBLIC_VPS, ... # P{i}_HAS_ARMY, P{i}_HAS_ROAD, P1_HAS_ARMY, ... # P{i}_ROADS_LEFT, P{i}_SETTLEMENTS_LEFT, P{i}_CITIES_LEFT, P1_... # P{i}_HAS_ROLLED, P{i}_LONGEST_ROAD_LENGTH features = dict() for i, color in iter_players(game.state.colors, p0_color): key = player_key(game.state, color) if color == p0_color: features["P0_ACTUAL_VPS"] = game.state.player_state[ key + "_ACTUAL_VICTORY_POINTS"] features[f"P{i}_PUBLIC_VPS"] = game.state.player_state[ key + "_VICTORY_POINTS"] features[f"P{i}_HAS_ARMY"] = game.state.player_state[key + "_HAS_ARMY"] features[f"P{i}_HAS_ROAD"] = game.state.player_state[key + "_HAS_ROAD"] features[f"P{i}_ROADS_LEFT"] = game.state.player_state[ key + "_ROADS_AVAILABLE"] features[f"P{i}_SETTLEMENTS_LEFT"] = game.state.player_state[ key + "_SETTLEMENTS_AVAILABLE"] features[f"P{i}_CITIES_LEFT"] = game.state.player_state[ key + "_CITIES_AVAILABLE"] features[f"P{i}_HAS_ROLLED"] = game.state.player_state[key + "_HAS_ROLLED"] features[f"P{i}_LONGEST_ROAD_LENGTH"] = game.state.player_state[ key + "_LONGEST_ROAD_LENGTH"] return features
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 city_possibilities(state, color) -> List[Action]: key = player_key(state, color) has_money = player_resource_freqdeck_contains(state, color, CITY_COST_FREQDECK) has_cities_available = state.player_state[f"{key}_CITIES_AVAILABLE"] > 0 if has_money and has_cities_available: return [ Action(color, ActionType.BUILD_CITY, node_id) for node_id in get_player_buildings( state, color, BuildingType.SETTLEMENT) ] else: return []
def road_building_possibilities(state, color) -> List[Action]: key = player_key(state, color) has_money = player_resource_freqdeck_contains(state, color, ROAD_COST_FREQDECK) has_roads_available = state.player_state[f"{key}_ROADS_AVAILABLE"] > 0 if has_money and has_roads_available: buildable_edges = state.board.buildable_edges(color) return [ Action(color, ActionType.BUILD_ROAD, edge) for edge in buildable_edges ] else: return []
def winning_color(self) -> Union[Color, None]: """Gets winning color Returns: Union[Color, None]: Might be None if game truncated by TURNS_LIMIT """ result = None for color in self.state.colors: key = player_key(self.state, color) if ( self.state.player_state[f"{key}_ACTUAL_VICTORY_POINTS"] >= self.vps_to_win ): result = color return result
def decide(self, game: Game, playable_actions): if len(playable_actions) == 1: return playable_actions[0] best_value = float("-inf") best_actions = [] for action in playable_actions: game_copy = game.copy() game_copy.execute(action) key = player_key(game_copy.state, self.color) value = game_copy.state.player_state[f"{key}_ACTUAL_VICTORY_POINTS"] if value == best_value: best_actions.append(action) if value > best_value: best_value = value best_actions = [action] return random.choice(best_actions)
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 settlement_possibilities(state, color, initial_build_phase=False) -> List[Action]: if initial_build_phase: buildable_node_ids = state.board.buildable_node_ids( color, initial_build_phase=True) return [ Action(color, ActionType.BUILD_SETTLEMENT, node_id) for node_id in buildable_node_ids ] else: key = player_key(state, color) has_money = player_resource_freqdeck_contains( state, color, SETTLEMENT_COST_FREQDECK) has_settlements_available = ( state.player_state[f"{key}_SETTLEMENTS_AVAILABLE"] > 0) if has_money and has_settlements_available: buildable_node_ids = state.board.buildable_node_ids(color) return [ Action(color, ActionType.BUILD_SETTLEMENT, node_id) for node_id in buildable_node_ids ] else: return []
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"])