LetaBot’s wall 4 - placing buildings to form the wall
Continuing from yesterday, here is how RecursiveWall places buildings to try to create a wall. It is of course recursive. It wants a vector of building types and a current search depth.
void BuildingManager::RecursiveWall(std::vectorBuildings, int depth){
The call is like this. (The three bits of code here are in different places in the source.) The numbers representing buildings are used as indexes into the SpaceArray mentioned yesterday, which has the information needed to compute the gaps between buildings. I’m not sure why the two depots get different indexes into SpaceArray (which has the same data for both). Is it so they can be told apart in maps, so that building adjacency can be found? Anyway, depth is initially 0.
WallSound = false; std::vectorBuildings; Buildings.push_back(2);//Barracks Buildings.push_back(3);//Supply depot Buildings.push_back(4);//Supply depot RecursiveWall(Buildings,0);
The code is full of implementation details that don’t matter at this level of discussion (and includes some redundant code), so here is pseudocode. CanWall() is a utility function to figure out whether a building can be placed at a given location. mapWall() maintains a 2D map of building locations. I should also mention, though I didn’t realize it until today, that WalledOffAStar() returns false if a wall exists and true if you can walk through, despite its name and some comments. It’s confusing.
RecursiveWall (vector Buildings, int depth) :
if (WallSound) { // we found a wall
return; // so stop the search
}
if (depth == Buildings.size()) { // all buildings are placed
walkable = WalledOffAStar (...) // can we walk through?
if (not walkable) { // no, try lifting the barracks
gate = WalledOffAStar (..., ignoring barracks)
if (gate) { // can we walk through now?
WallSound = true; // yes, we found a wall
BarracksWall = ...; // record the building placements
}
}
return; // either way, we're done
} else {
int BuildType = Buildings[depth]; // next building to place
for (int x, y inside the bounding box) {
if (CanWall (BuildType, x, y)) {
mapWall (x, y, BuildType, BuildType); // update data structures
BWAPI::TilePosition thisTile = BWAPI::TilePosition (x, y);
BuildingPlace.push_back (thisTile);
RecursiveWall (Buildings, depth+1); // recursive call
BuildingPlace.pop_back(); // roll back updates
mapWall (x, y, BuildType, BUILDABLE);
}
}
}
Wow, it’s a little hard to follow even boiled down this much. WallSound is initialized to false before the call. So WallSound ends up true only if both the wall is sound and lifting the barracks opens it (so that the barracks is a functional part of the wall and not an appendix to a wall of supply depots). Older versions of LetaBot sometimes got that wrong and walled themselves permanently into their bases.
RecursiveWall does not know which of the buildings in its vector are the same, so if one building fails to place at any location it simply continues on to the next. As far as it knows, a good wall might still be possible with the remaining buildings. An optimization might be to prune the search appropriately when building placements fail. Is it too rare to matter?
Here’s what CanWall does, again in pseudocode. It is given the building’s type and proposed location and returns whether the location is good to build on. Buildings are required to be adjacent to an already placed building. It knows that the barracks is first and lets it be “adjacent to itself.”
CanWall (buildingType, buildingX, buildingY) :
nextToB = false; // adjacent to an existing building?
for (int x, y in the building's footprint from SpaceArray) {
if (outside the map) return false;
if (not buildable) return false;
if (covers Closest or Furthest) return false;
if (useHighGround and this is not high ground) return false;
if (buildingType == barracks) nextToB = true;
if (adjacent to an already placed building) nextToB = true;
}
return nextToB;
The code runs the “adjacent to an existing building” loop even for a barracks. For efficiency, it could skip adjacency checks (the inner loop and thus the most expensive part of the code) once nextToB turns true, since it never goes false again. The compiler may be smart enough to lift the “is this building a barracks?” test above the outer loop, since it doesn’t depend on x and y, but it would be tidier to code it that way from the start.
Next: Discussion of walling code.
Comments
krasi0 on :
Jay Scott on :