Step 4: Exploring the Map

The Plan

If ants can't see any food, they just sit there doing nothing. HunterBot does this too. We need a way to get our ants to look for more food. If we can track all of the locations we haven't seen yet, then we can send free ants to go scout that location. If they find more food, then the food gathering code should kick in.

The Implementation

To track information about what hasn't been seen, we are going to create a class level variable. This will be a list of every location on the map. Each turn, if we can see the location, then we will remove it from the list. We can then start sending free ants to the location left in the list. Once the entire map is explored, the list will be empty and this section of code will be ignored. Food is more important to get right away, so we'll do this as a second priority.

The Code

Put this code in the do_setup method of the bot. You should replace the pass statement. This code will only be run once after our bot learns the size of the map.

    self.unseen = []
    for row in range(ants.rows):
        for col in range(ants.cols):
            self.unseen.append((row, col))

Notice we are using self.unseen; self is a reference to our bot class and we will need to use it to reference our unseen list in the do_turn method. We create a list with a nested loop for every combination of row and column values. (Note: this could be a large list and not very memory efficient. This is just the easiest way to make the code look nice. You'll probably want to try and use a different technique for a real bot.)

Add the following code after the gather food section and before the unblocking hill section:

    # explore unseen areas
    for loc in self.unseen[:]:
        if ants.visible(loc):
            self.unseen.remove(loc)
    for ant_loc in ants.my_ants():
        if ant_loc not in orders.values():
            unseen_dist = []
            for unseen_loc in self.unseen:
                dist = ants.distance(ant_loc, unseen_loc)
                unseen_dist.append((dist, unseen_loc))
            unseen_dist.sort()
            for dist, unseen_loc in unseen_dist:
                if do_move_location(ant_loc, unseen_loc):
                    break

First we trim the list of unseen squares by looping through every one and checking if we can see it. We use yet another starter bot helper function:

  • ants.visible takes a location and returns a True if it is in the view radius of any ant. This function is written to be fairly efficient so that calling it multiple times won't cause a big slowdown. (It can still be improved.) You shouldn't try and modify a list while you are looping through it, so we use the list copy shortcut [:] in the for loop to make sure the list we are looping through is different than the list we are removing locations from.

Next we loop through all the ants and make sure they haven't been given an order yet. If not, we then create a list of all the squares we haven't seen yet and order them by distance. This is the same technique we used for the gather food code. We then go through the list and find the first unseen square that we can start moving toward. (Note: at the beginning of a game on a large map, checking the distance to every unseen square is probably very slow. This could be done better.)

Add the unseenTiles Set under the order declaration:

    private Map<Tile, Tile> orders = new HashMap<Tile, Tile>();

    private Set<Tile> unseenTiles;

The unseenTiles will be a set of all tiles we have not seen during the game. (Note: this could be a large list and not very memory efficient. This is just the easiest way to make the code look nice. You'll probably want to try and use a different technique for a real bot.)

Next, add the following code near the top of the doTurn function, just below the foodTargets declaration:

        // add all locations to unseen tiles set, run once
        if (unseenTiles == null) {
            unseenTiles = new HashSet<Tile>();
            for (int row = 0; row < ants.getRows(); row++) {
                for (int col = 0; col < ants.getCols(); col++) {
                    unseenTiles.add(new Tile(row, col));
                }
            }
        }
        // remove any tiles that can be seen, run each turn
        for (Iterator<Tile> locIter = unseenTiles.iterator(); locIter.hasNext(); ) {
            Tile next = locIter.next();
            if (ants.isVisible(next)) {
                locIter.remove();
            }
        }

The first part initializes the list to every Tile in the game. It will only run during the first turn. You shouldn't try and modify a collection while you are looping through it, so we use an iterator object, which allows for safe removal of Tiles while we loop through the list and check the visibility.

Last, add this code section between finding close food and unblocking your own hills:

        // explore unseen areas
        for (Tile antLoc : sortedAnts) {
            if (!orders.containsValue(antLoc)) {
                List<Route> unseenRoutes = new ArrayList<Route>();
                for (Tile unseenLoc : unseenTiles) {
                    int distance = ants.getDistance(antLoc, unseenLoc);
                    Route route = new Route(antLoc, unseenLoc, distance);
                    unseenRoutes.add(route);
                }
                Collections.sort(unseenRoutes);
                for (Route route : unseenRoutes) {
                    if (doMoveLocation(route.getStart(), route.getEnd())) {
                        break;
                    }
                }
            }
        }

Here, for every ant that doesn't have an order yet (we are checking the orders HashMap using the containsValue() method), we calculate the distance to every other unseen location. Then we sort the ArrayList so the shortest distances are first. We are using another help function from the starter bot.

  • ants.isVisible(Tile) takes a location and returns a true if it is in the view radius of any ant. This function is written to be fairly efficient so that calling it multiple times won't cause a big slowdown. (It can still be improved.)

This is the same technique we used for the gather food code. We then go through the list and find the first and closest unseen square that we can start moving toward. (Note: at the beginning of a game on a large map, checking the distance to every unseen square is probably very slow. This could be done better.)

The Results

Let's run the bot again and see how we do.

C:\aichallenge>tutorial.cmd
running for 60 turns
                  ant_count c_turns climb? cutoff food r_turn ranking_bots s_alive s_hills score  w_turn winning
turn    0 stats:   [1,1,0]     0    [1,1]    -     18    0        None      [1,1]   [1,1]  [1,1]   0     None
turn    1 stats:   [1,1,0]     0    [1,1]    -     16    1       [0,0]      [1,1]   [1,1]  [1,1]   1     [0,1]
turn    2 stats:   [2,1,0]     0    [1,1]    -     16    1       [0,0]      [1,1]   [1,1]  [1,1]   1     [0,1]
...
turn   60 stats:   [9,2,0]     0    [1,1]    -     0     1       [0,0]      [1,1]   [1,1]  [1,1]   1     [0,1]
score 1 1
status survived survived
playerturns 60 60

Here is the replay:

# [ { "embedded": true, "game": "4 - Exploring the Map" }, 600, 600, { "speedFactor": 0, "speedFastest": 2, "speedSlowest": 2, "zoom": 1 }, "example_games/tutorial.4.replay" ]

Look at that, we got all the food! Our ants are now roaming around the map so that they can see everything. Unfortunately we haven't taken out the enemy hill yet, so let's work on that next.

Next

On to Step 5: Attacking Enemy Hills