Example #1
0
def get_player_expandable_nodes(game: Game, color: Color):
    node_sets = game.state.board.find_connected_components(color)
    enemy_colors = [
        enemy_color for enemy_color in game.state.colors
        if enemy_color != color
    ]
    enemy_node_ids = set()
    for enemy_color in enemy_colors:
        enemy_node_ids.update(
            get_player_buildings(game.state, enemy_color,
                                 BuildingType.SETTLEMENT))
        enemy_node_ids.update(
            get_player_buildings(game.state, enemy_color, BuildingType.CITY))

    expandable_node_ids = [
        node_id for node_set in node_sets for node_id in node_set
        if node_id not in enemy_node_ids  # not plowed
    ]  # not exactly "buildable_node_ids" b.c. we could expand from non-buildable nodes
    return expandable_node_ids
Example #2
0
def prune_robber_actions(current_color, game, actions):
    """Eliminate all but the most impactful tile"""
    enemy_color = next(filter(lambda c: c != current_color, game.state.colors))
    enemy_owned_tiles = set()
    for node_id in get_player_buildings(game.state, enemy_color,
                                        BuildingType.SETTLEMENT):
        enemy_owned_tiles.update(game.state.board.map.adjacent_tiles[node_id])
    for node_id in get_player_buildings(game.state, enemy_color,
                                        BuildingType.CITY):
        enemy_owned_tiles.update(game.state.board.map.adjacent_tiles[node_id])

    robber_moves = set(
        filter(
            lambda a: a.action_type == ActionType.MOVE_ROBBER and game.state.
            board.map.tiles[a.value[0]] in enemy_owned_tiles,
            actions,
        ))

    production_features = build_production_features(True)

    def impact(action):
        game_copy = game.copy()
        game_copy.execute(action)

        our_production_sample = production_features(game_copy, current_color)
        enemy_production_sample = production_features(game_copy, current_color)
        production = value_production(our_production_sample, "P0")
        enemy_production = value_production(enemy_production_sample, "P1")

        return enemy_production - production

    most_impactful_robber_action = max(
        robber_moves, key=impact)  # most production and variety producing
    actions = filter(
        # lambda a: a.action_type != action_type or a == most_impactful_robber_action,
        lambda a: a.action_type != ActionType.MOVE_ROBBER or a in robber_moves,
        actions,
    )
    return actions
Example #3
0
    def after(self, game: Game):
        winner = game.winning_color()
        if winner is None:
            return  # throw away data

        for color in game.state.colors:
            cities = len(
                get_player_buildings(game.state, color, BuildingType.CITY))
            settlements = len(
                get_player_buildings(game.state, color,
                                     BuildingType.SETTLEMENT))
            longest = get_longest_road_color(game.state) == color
            largest = get_largest_army(game.state)[0] == color
            devvps = get_dev_cards_in_hand(game.state, color, VICTORY_POINT)

            self.cities[color] += cities
            self.settlements[color] += settlements
            self.longest[color] += longest
            self.largest[color] += largest
            self.devvps[color] += devvps

        self.num_games += 1
Example #4
0
def test_play_many_games():
    for _ in range(10):  # play 10 games
        players = [
            RandomPlayer(Color.RED),
            RandomPlayer(Color.BLUE),
            RandomPlayer(Color.WHITE),
            RandomPlayer(Color.ORANGE),
        ]
        game = Game(players)
        game.play()

        # Assert everything looks good
        for color in game.state.colors:
            cities = len(
                get_player_buildings(game.state, color, BuildingType.CITY))
            settlements = len(
                get_player_buildings(game.state, color,
                                     BuildingType.SETTLEMENT))
            longest = get_longest_road_color(game.state) == color
            largest = get_largest_army(game.state)[0] == color
            devvps = get_dev_cards_in_hand(game.state, color, VICTORY_POINT)
            assert (settlements + 2 * cities + 2 * longest + 2 * largest +
                    devvps) == get_actual_victory_points(game.state, color)
Example #5
0
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 []
Example #6
0
    def production_features(game: Game, p0_color: Color):
        # P0_WHEAT_PRODUCTION, P0_ORE_PRODUCTION, ..., P1_WHEAT_PRODUCTION, ...
        features = {}
        board = game.state.board
        robbed_nodes = set(
            board.map.tiles[board.robber_coordinate].nodes.values())
        for resource in RESOURCES:
            for i, color in iter_players(game.state.colors, p0_color):
                production = 0
                for node_id in get_player_buildings(game.state, color,
                                                    BuildingType.SETTLEMENT):
                    if consider_robber and node_id in robbed_nodes:
                        continue
                    production += get_node_production(game.state.board,
                                                      node_id, resource)
                for node_id in get_player_buildings(game.state, color,
                                                    BuildingType.CITY):
                    if consider_robber and node_id in robbed_nodes:
                        continue
                    production += 2 * get_node_production(
                        game.state.board, node_id, resource)
                features[f"{prefix}P{i}_{resource}_PRODUCTION"] = production

        return features
Example #7
0
def expansion_features(game: Game, p0_color: Color):
    global STATIC_GRAPH
    MAX_EXPANSION_DISTANCE = 3  # exclusive

    features = {}

    # For each connected component node, bfs_edges (skipping enemy edges and nodes nodes)
    empty_edges = set(get_edges(game.state.board.map.land_nodes))
    for i, color in iter_players(game.state.colors, p0_color):
        empty_edges.difference_update(
            get_player_buildings(game.state, color, BuildingType.ROAD))
    searchable_subgraph = STATIC_GRAPH.edge_subgraph(empty_edges)

    board_buildable_node_ids = game.state.board.buildable_node_ids(
        p0_color, True
    )  # this should be the same for all players. TODO: Can maintain internally (instead of re-compute).

    for i, color in iter_players(game.state.colors, p0_color):
        expandable_node_ids = get_player_expandable_nodes(game, color)

        def skip_blocked_by_enemy(neighbor_ids):
            for node_id in neighbor_ids:
                node_color = game.state.board.get_node_color(node_id)
                if node_color is None or node_color == color:
                    yield node_id  # not owned by enemy, can explore

        # owned_edges = get_player_buildings(state, color, BuildingType.ROAD)
        dis_res_prod = {
            distance: {k: 0
                       for k in RESOURCES}
            for distance in range(MAX_EXPANSION_DISTANCE)
        }
        for node_id in expandable_node_ids:
            if node_id in board_buildable_node_ids:  # node itself is buildable
                for resource in RESOURCES:
                    production = get_node_production(game.state.board, node_id,
                                                     resource)
                    dis_res_prod[0][resource] = max(production,
                                                    dis_res_prod[0][resource])

            if node_id not in searchable_subgraph.nodes():
                continue  # must be internal node, no need to explore

            bfs_iteration = nx.bfs_edges(
                searchable_subgraph,
                node_id,
                depth_limit=MAX_EXPANSION_DISTANCE - 1,
                sort_neighbors=skip_blocked_by_enemy,
            )

            paths = {node_id: []}
            for edge in bfs_iteration:
                a, b = edge
                path_until_now = paths[a]
                distance = len(path_until_now) + 1
                paths[b] = paths[a] + [b]

                if b not in board_buildable_node_ids:
                    continue

                # means we can get to node b, at distance=d, starting from path[0]
                for resource in RESOURCES:
                    production = get_node_production(game.state.board, b,
                                                     resource)
                    dis_res_prod[distance][resource] = max(
                        production, dis_res_prod[distance][resource])

        for distance, res_prod in dis_res_prod.items():
            for resource, prod in res_prod.items():
                features[f"P{i}_{resource}_AT_DISTANCE_{int(distance)}"] = prod

    return features
Example #8
0
def get_owned_or_buildable(game, color, board_buildable):
    return frozenset(
        get_player_buildings(game.state, color, BuildingType.SETTLEMENT) +
        get_player_buildings(game.state, color, BuildingType.CITY) +
        board_buildable)
Example #9
0
def create_board_tensor(game: Game, p0_color: Color):
    """Creates a tensor of shape (WIDTH=21, HEIGHT=11, CHANNELS).

    1 x n hot-encoded planes (2 and 1s for city/settlements).
    1 x n planes for the roads built by each player.
    5 tile resources planes, one per resource.
    1 robber plane (to note nodes blocked by robber).
    6 port planes (one for each resource and one for the 3:1 ports)

    Example:
        - To see WHEAT plane: tf.transpose(board_tensor[:,:,3])
    """
    # add 4 hot-encoded color multiplier planes (nodes), and 4 edge planes. 8 planes
    color_multiplier_planes = []
    node_map, edge_map = get_node_and_edge_maps()
    for _, color in iter_players(tuple(game.state.colors), p0_color):
        node_plane = tf.zeros((WIDTH, HEIGHT))
        edge_plane = tf.zeros((WIDTH, HEIGHT))

        indices = []
        updates = []
        for node_id in get_player_buildings(game.state, color, BuildingType.SETTLEMENT):
            indices.append(node_map[node_id])
            updates.append(1)
        for node_id in get_player_buildings(game.state, color, BuildingType.CITY):
            indices.append(node_map[node_id])
            updates.append(2)
        if len(indices) > 0:
            node_plane = tf.tensor_scatter_nd_update(node_plane, indices, updates)

        indices = []
        updates = []
        for edge in get_player_buildings(game.state, color, BuildingType.ROAD):
            indices.append(edge_map[edge])
            updates.append(1)
        if len(indices) > 0:
            edge_plane = tf.tensor_scatter_nd_update(edge_plane, indices, updates)

        color_multiplier_planes.append(node_plane)
        color_multiplier_planes.append(edge_plane)
    color_multiplier_planes = tf.stack(color_multiplier_planes, axis=2)  # axis=channels

    # add 5 node-resource probas, add color edges
    resource_proba_planes = tf.zeros((WIDTH, HEIGHT, 5))
    resources = [i for i in RESOURCES]
    tile_map = get_tile_coordinate_map()
    for (coordinate, tile) in game.state.board.map.land_tiles.items():
        if tile.resource is None:
            continue  # there is already a 3x5 zeros matrix there (everything started as a 0!).

        # Tile looks like:
        # [0.33, 0, 0.33, 0, 0.33]
        # [   0, 0,    0, 0,    0]
        # [0.33, 0, 0.33, 0, 0.33]
        proba = 0 if tile.number is None else number_probability(tile.number)
        (y, x) = tile_map[coordinate]  # returns values in (row, column) math def
        channel_idx = resources.index(tile.resource)
        indices = [[x + i, y + j, channel_idx] for j in range(3) for i in range(5)]
        updates = (
            [proba, 0, proba, 0, proba] + [0, 0, 0, 0, 0] + [proba, 0, proba, 0, proba]
        )
        resource_proba_planes = tf.tensor_scatter_nd_add(
            resource_proba_planes, indices, updates
        )

    # add 1 robber channel
    robber_plane = tf.zeros((WIDTH, HEIGHT, 1))
    (y, x) = tile_map[game.state.board.robber_coordinate]
    indices = [[x + i, y + j, 0] for j in range(3) for i in range(5)]
    updates = [1, 0, 1, 0, 1] + [0, 0, 0, 0, 0] + [1, 0, 1, 0, 1]
    robber_plane = tf.tensor_scatter_nd_add(robber_plane, indices, updates)

    # Q: Would this be simpler as boolean features for each player?
    # add 6 port channels (5 resources + 1 for 3:1 ports)
    # for each port, take index and take node_id coordinates
    port_planes = tf.zeros((WIDTH, HEIGHT, 6))
    for resource, node_ids in game.state.board.map.port_nodes.items():
        channel_idx = 5 if resource is None else resources.index(resource)
        indices = []
        updates = []
        for node_id in node_ids:
            (x, y) = node_map[node_id]
            indices.append([x, y, channel_idx])
            updates.append(1)
        port_planes = tf.tensor_scatter_nd_add(port_planes, indices, updates)

    return tf.concat(
        [color_multiplier_planes, resource_proba_planes, robber_plane, port_planes],
        axis=2,
    )