#include "MapTools.h"
#include "BuildingPlacer.h"
#include "InformationManager.h"

const double pi = 3.14159265358979323846;

using namespace UAlbertaBot;

MapTools & MapTools::Instance()
{
	static MapTools instance;
	return instance;
}

MapTools::MapTools()
	//: _rows(BWAPI::Broodwar->mapHeight())
	//, _cols(BWAPI::Broodwar->mapWidth())
{
	//UAB_ASSERT(_rows > 0 && _cols > 0, "empty map");
	/*
	_map = std::vector<bool>(_rows*_cols, true);
	_units = std::vector<bool>(_rows*_cols, false);
	_fringe = std::vector<short int>(_rows*_cols, 0);
	*/

	// Figure out which tiles are walkable and buildable.
	setBWAPIMapData();
	setChokepointData();

	// Figure out if start locations are connected
	_hasIslandBases = false;
	BWAPI::TilePosition startLocation = BWAPI::Broodwar->self()->getStartLocation();
	for (auto& area : BWEMmap.Areas())
	{
		for (auto& base : area.Bases())
		{		
			if (base.Starting() && base.Location() != startLocation)
			{
				if (BWEMmap.GetPath(BWAPI::Position(base.Location()), BWAPI::Position(startLocation)).empty())
				{
					_hasIslandBases = true;
					break;
				}
			}
		}
	}
}
/*
// return the index of the 1D array from (row,col)
inline int MapTools::getIndex(int row, int col)
{
	return row * _cols + col;
}
*/
/*
// Is the tile unexplored?
bool MapTools::unexplored(DistanceMap & dmap, const int index) const
{
	return index != -1 &&            // index is valid
		dmap[index] == -1 &&         // tile doesn't have a known distance
		_map[index];                 // the map says it's walkable
}
*/
//From Locutus
void MapTools::setChokepointData()
{
	// Get all of the BWEM chokepoints
	std::set<const BWEM::ChokePoint*> chokes;
	for (const auto & area : BWEMmap.Areas())
		for (const BWEM::ChokePoint * choke : area.ChokePoints())
			chokes.insert(choke);

	// Store a ChokeData object for each choke
	for (const BWEM::ChokePoint * choke : chokes)
	{
		choke->SetExt(new ChokeData(choke));
		ChokeData & chokeData = *((ChokeData*)choke->Ext());
		BWAPI::Position chokeCenter(choke->Center());

		// Compute the choke width
		// Because the ends are themselves walkable tiles, we need to add a bit of padding to estimate the actual walkable width of the choke
		int width = BWAPI::Position(choke->Pos(choke->end1)).getApproxDistance(BWAPI::Position(choke->Pos(choke->end2))) + 15;
		chokeData.width = width;

		// Determine if the choke is blocked by neutrals
		bool blockedByNeutral = false;
		for (const auto staticNeutral : BWAPI::Broodwar->getStaticNeutralUnits())
		{
			if (!blockedByNeutral && staticNeutral->getType() == BWAPI::UnitTypes::Zerg_Egg &&
				staticNeutral->getDistance(chokeCenter) < 100)
			{
				blockedByNeutral = true;
			}
		}
		chokeData.blocked = blockedByNeutral;

		// Determine if the choke is a ramp
		int firstAreaElevation = BWAPI::Broodwar->getGroundHeight(BWAPI::TilePosition(choke->GetAreas().first->Top()));
		int secondAreaElevation = BWAPI::Broodwar->getGroundHeight(BWAPI::TilePosition(choke->GetAreas().second->Top()));
		if (firstAreaElevation != secondAreaElevation)
		{
			chokeData.isRamp = true;

			// For narrow ramps with a difference in elevation, compute a tile at high elevation close to the choke
			// We will use this for pathfinding
			if (chokeData.width < 96)
			{
				// Start by computing the angle of the choke
				BWAPI::Position chokeDelta(choke->Pos(choke->end1) - choke->Pos(choke->end2));
				double chokeAngle = atan2(chokeDelta.y, chokeDelta.x);

				// Now find a tile a bit away from the middle of the choke that is at high elevation
				int highestElevation = std::max(firstAreaElevation, secondAreaElevation);
				BWAPI::Position center(choke->Center());
				BWAPI::TilePosition closestToCenter = BWAPI::TilePositions::Invalid;
				for (int step = 0; step <= 6; step++)
					for (int direction = -1; direction <= 1; direction += 2)
					{
						BWAPI::TilePosition tile(BWAPI::Position(
							center.x - (int)std::round(16 * step * std::cos(chokeAngle + direction * (pi / 2.0))),
							center.y - (int)std::round(16 * step * std::sin(chokeAngle + direction * (pi / 2.0)))));

						if (!tile.isValid()) continue;
						if (!BWEBmap.isWalkable(tile)) continue;

						if (BWAPI::Broodwar->getGroundHeight(tile) == highestElevation)
						{
							chokeData.highElevationTile = tile;
						}
					}
			}
		}
	}

	// On Plasma, we enrich the BWEM chokepoints with data about mineral walking
	if (BWAPI::Broodwar->mapHash() == "6f5295624a7e3887470f3f2e14727b1411321a67")
	{
		// Process each choke
		for (const BWEM::ChokePoint * choke : chokes)
		{
			ChokeData & chokeData = *((ChokeData*)choke->Ext());
			BWAPI::Position chokeCenter(choke->Center());

			// Determine if the choke is blocked by eggs, and grab the close mineral patches
			bool blockedByEggs = false;
			BWAPI::Unit closestMineralPatch = nullptr;
			BWAPI::Unit secondClosestMineralPatch = nullptr;
			int closestMineralPatchDist = INT_MAX;
			int secondClosestMineralPatchDist = INT_MAX;
			for (const auto staticNeutral : BWAPI::Broodwar->getStaticNeutralUnits())
			{
				if (!blockedByEggs && staticNeutral->getType() == BWAPI::UnitTypes::Zerg_Egg &&
					staticNeutral->getDistance(chokeCenter) < 100)
				{
					blockedByEggs = true;
				}

				if (staticNeutral->getType() == BWAPI::UnitTypes::Resource_Mineral_Field &&
					staticNeutral->getResources() == 32)
				{
					int dist = staticNeutral->getDistance(chokeCenter);
					if (dist <= closestMineralPatchDist)
					{
						secondClosestMineralPatchDist = closestMineralPatchDist;
						closestMineralPatchDist = dist;
						secondClosestMineralPatch = closestMineralPatch;
						closestMineralPatch = staticNeutral;
					}
					else if (dist < secondClosestMineralPatchDist)
					{
						secondClosestMineralPatchDist = dist;
						secondClosestMineralPatch = staticNeutral;
					}
				}
			}

			if (!blockedByEggs) continue;

			chokeData.requiresMineralWalk = true;
			chokeData.firstMineralPatch = closestMineralPatch;
			chokeData.secondMineralPatch = secondClosestMineralPatch;
		}
	}

	// TODO testing
	//BWAPI::TilePosition homePosition = BWAPI::Broodwar->self()->getStartLocation();
	//BWAPI::Broodwar->printf("start position %d,%d", homePosition.x, homePosition.y);
}

// Read the map data from BWAPI and remember which 32x32 build tiles are walkable.
// NOTE The game map is walkable at the resolution of 8x8 walk tiles, so this is an approximation.
//      We're asking "Can big units walk here?" Small units may be able to squeeze into more places.
void MapTools::setBWAPIMapData()
{
	// 1. Mark all tiles walkable and buildable at first.
	_terrainWalkable = std::vector< std::vector<bool> >(BWAPI::Broodwar->mapWidth(), std::vector<bool>(BWAPI::Broodwar->mapHeight(), true));
	_walkable = std::vector< std::vector<bool> >(BWAPI::Broodwar->mapWidth(), std::vector<bool>(BWAPI::Broodwar->mapHeight(), true));
	_buildable = std::vector< std::vector<bool> >(BWAPI::Broodwar->mapWidth(), std::vector<bool>(BWAPI::Broodwar->mapHeight(), true));
	_depotBuildable = std::vector< std::vector<bool> >(BWAPI::Broodwar->mapWidth(), std::vector<bool>(BWAPI::Broodwar->mapHeight(), true));

	// 2. Check terrain: Is it buildable? Is it walkable?
	// This sets _walkable and _terrainWalkable identically.
	for (int x = 0; x < BWAPI::Broodwar->mapWidth(); ++x)
	{
		for (int y = 0; y < BWAPI::Broodwar->mapHeight(); ++y)
		{
			// This initializes all cells of _buildable and _depotBuildable.
			bool buildable = BWAPI::Broodwar->isBuildable(BWAPI::TilePosition(x, y), false);
			_buildable[x][y] = buildable;
			_depotBuildable[x][y] = buildable;

			bool walkable = true;

			// Check each 8x8 walk tile within this 32x32 TilePosition.
			int walkableWalkPositions = 0;
			for (int i = 0; i < 4; ++i)
			{
				for (int j = 0; j < 4; ++j)
				{
					if (BWAPI::Broodwar->isWalkable(x * 4 + i, y * 4 + j)) walkableWalkPositions++;
				}
			}

			// On Plasma and ThirdWorld, consider the tile walkable if at least 10 walk positions are walkable
			if (walkableWalkPositions < 10)
			{
				_terrainWalkable[x][y] = false;
				_walkable[x][y] = false;
			}
		}
	}

	// 3. Check neutral units: Do they block walkability?
	// This affects _walkable but not _terrainWalkable. We don't update buildability here.
	for (const auto unit : BWAPI::Broodwar->getStaticNeutralUnits())
	{
		// The neutral units may include moving critters which do not permanently block tiles.
		// Something immobile blocks tiles it occupies until it is destroyed. (Are there exceptions?)
		if (!unit->getType().canMove() && !unit->isFlying())
		{
			BWAPI::TilePosition pos = unit->getTilePosition();
			for (int x = pos.x; x < pos.x + unit->getType().tileWidth(); ++x)
			{
				for (int y = pos.y; y < pos.y + unit->getType().tileHeight(); ++y)
				{
					if (BWAPI::TilePosition(x, y).isValid())   // assume it may be partly off the edge
					{
						_walkable[x][y] = false;
					}
				}
			}
		}
	}

	// 4. Check static resources: Do they block buildability?
	for (const BWAPI::Unit resource : BWAPI::Broodwar->getStaticNeutralUnits())
	{
		if (!resource->getType().isResourceContainer())
		{
			continue;
		}

		int tileX = resource->getTilePosition().x;
		int tileY = resource->getTilePosition().y;

		for (int x = tileX; x<tileX + resource->getType().tileWidth(); ++x)
		{
			for (int y = tileY; y<tileY + resource->getType().tileHeight(); ++y)
			{
				_buildable[x][y] = false;

				// depots can't be built within 3 tiles of any resource
				// TODO rewrite this to be less disgusting
				for (int dx = -3; dx <= 3; dx++)
				{
					for (int dy = -3; dy <= 3; dy++)
					{
						if (!BWAPI::TilePosition(x + dx, y + dy).isValid())
						{
							continue;
						}

						_depotBuildable[x + dx][y + dy] = false;
					}
				}
			}
		}
	}
}
/*
// Read the map data from BWAPI and remember which 32x32 build tiles are walkable.
// NOTE The game map is walkable at the resolution of 8x8 walk tiles, so this is an approximation.
//      We're asking "Can big units walk here?" Small units may be able to squeeze into more places.
void MapTools::setBWAPIMapData()
{
	// 1. Check terrain: Is it walkable?
	for (int r(0); r < BWAPI::Broodwar->mapWidth(); ++r)
	{
		for (int c(0); c < BWAPI::Broodwar->mapHeight(); ++c)
		{
			bool walkable = true;

			// Check each 8x8 walk tile within this 32x32 TilePosition.
			for (int i = 0; i < 4 && walkable; ++i)
			{
				for (int j = 0; j < 4 && walkable; ++j)
				{
					if (!BWAPI::Broodwar->isWalkable(c * 4 + i, r * 4 + j))
					{
						walkable = false;
						_map[getIndex(r, c)] = false;
					}
				}
			}
		}
	}

	// 2. Check static units: Do they block tiles?
	for (const auto unit : BWAPI::Broodwar->getStaticNeutralUnits())
	{
		// The neutral units may include moving critters which do not permanently block tiles.
		// Something immobile blocks tiles it occupies until it is destroyed (exceptions may be possible).
		if (!unit->getType().canMove() && !unit->isFlying())
		{
			BWAPI::TilePosition pos = unit->getTilePosition();
			for (int r = pos.y; r < pos.y + unit->getType().tileHeight(); ++r)
			{
				for (int c = pos.x; c < pos.x + unit->getType().tileWidth(); ++c)
				{
					if (BWAPI::TilePosition(r, c).isValid())   // assume it may be partly off the edge
					{
						_map[getIndex(r, c)] = false;
					}
				}
			}
		}
	}
}
*/
/*
void MapTools::resetFringe()
{
	std::fill(_fringe.begin(), _fringe.end(), 0);
}
*/
// Ground distance in tiles, -1 if no path exists.
// This is Manhattan distance, not walking distance. Still good for finding paths.
int MapTools::getGroundTileDistance(BWAPI::TilePosition origin, BWAPI::TilePosition destination)
{
	// if we have too many maps, reset our stored maps in case we run out of memory
	if (_allMaps.size() > allMapsSize)
	{
		_allMaps.clear();

		if (Config::Debug::DrawMapDistances)
		{
			BWAPI::Broodwar->printf("Cleared distance map cache");
		}
	}

	// Do we have a distance map to the destination?
	auto it = _allMaps.find(destination);
	if (it != _allMaps.end())
	{
		return (*it).second.at(origin);
	}

	// It's symmetrical. A distance map to the origin is just as good.
	it = _allMaps.find(origin);
	if (it != _allMaps.end())
	{
		return (*it).second.at(destination);
	}

	// Make a new map for this destination.
	_allMaps.insert(std::pair<BWAPI::TilePosition, GridDistances>(destination, GridDistances(destination)));
	return _allMaps[destination].at(origin);
}
/*
// Ground distance in tiles, -1 if no path exists.
int MapTools::getGroundTileDistance(BWAPI::Position origin, BWAPI::Position destination)
{
	// if we have too many maps, reset our stored maps in case we run out of memory
	if (_allMaps.size() > allMapsSize)
	{
		_allMaps.clear();

		if (Config::Debug::DrawMapDistances)
		{
			BWAPI::Broodwar->printf("Cleared distance map cache");
		}
	}

	// if we have already computed the distance map to the destination
	if (_allMaps.find(destination) != _allMaps.end())
	{
		return _allMaps[destination][origin];
	}

	// if we have computed the opposite direction, we can use that too
	if (_allMaps.find(origin) != _allMaps.end())
	{
		return _allMaps[origin][destination];
	}

	// add the map and compute it
	_allMaps.insert(std::pair<BWAPI::Position, DistanceMap>(destination, DistanceMap()));
	computeDistance(_allMaps[destination], destination);
	return _allMaps[destination][origin];
}
*/

int MapTools::getGroundTileDistance(BWAPI::Position origin, BWAPI::Position destination)
{
	return getGroundTileDistance(BWAPI::TilePosition(origin), BWAPI::TilePosition(destination));
}

int MapTools::getGroundDistance(BWAPI::TilePosition origin, BWAPI::TilePosition destination)
{
	return getGroundDistance(BWAPI::Position(origin), BWAPI::Position(destination));
}

// Ground distance in pixels (with build tile granularity), -1 if no path exists.
// Build tile granularity means that the distance is a multiple of 32 pixels.
int MapTools::getGroundDistance(BWAPI::Position origin, BWAPI::Position destination)
{
	int tiles = getGroundTileDistance(origin, destination);
	if (tiles > 0)
	{
		return 32 * tiles;
	}
	return tiles;
}
/*
// computes walk distance from Position P to all other points on the map
void MapTools::computeDistance(DistanceMap & dmap, const BWAPI::Position p)
{
	search(dmap, p.y / 32, p.x / 32);
}

// does the dynamic programming search
void MapTools::search(DistanceMap & dmap, const int sR, const int sC)
{
	// reset the internal variables
	resetFringe();

	// set the starting position for this search
	// dmap.setStartPosition(sR,sC);

	// set the distance of the start cell to zero
	dmap[getIndex(sR, sC)] = 0;

	// set the fringe variables accordingly
	int fringeSize(1);
	int fringeIndex(0);
	_fringe[0] = getIndex(sR, sC);
	dmap.addSorted(getTilePosition(_fringe[0]));

	// temporary variables used in search loop
	int currentIndex, nextIndex;
	int newDist;

	// the size of the map
	int size = _rows*_cols;

	// while we still have things left to expand
	while (fringeIndex < fringeSize)
	{
		// grab the current index to expand from the fringe
		currentIndex = _fringe[fringeIndex++];
		newDist = dmap[currentIndex] + 1;

		// search up
		nextIndex = (currentIndex > _cols) ? (currentIndex - _cols) : -1;
		if (unexplored(dmap, nextIndex))
		{
			// set the distance based on distance to current cell
			dmap.setDistance(nextIndex, newDist);
			dmap.addSorted(getTilePosition(nextIndex));

			// put it in the fringe
			_fringe[fringeSize++] = nextIndex;
		}

		// search down
		nextIndex = (currentIndex + _cols < size) ? (currentIndex + _cols) : -1;
		if (unexplored(dmap, nextIndex))
		{
			// set the distance based on distance to current cell
			dmap.setDistance(nextIndex, newDist);
			dmap.addSorted(getTilePosition(nextIndex));

			// put it in the fringe
			_fringe[fringeSize++] = nextIndex;
		}

		// search left
		nextIndex = (currentIndex % _cols > 0) ? (currentIndex - 1) : -1;
		if (unexplored(dmap, nextIndex))
		{
			// set the distance based on distance to current cell
			dmap.setDistance(nextIndex, newDist);
			dmap.addSorted(getTilePosition(nextIndex));

			// put it in the fringe
			_fringe[fringeSize++] = nextIndex;
		}

		// search right
		nextIndex = (currentIndex % _cols < _cols - 1) ? (currentIndex + 1) : -1;
		if (unexplored(dmap, nextIndex))
		{
			// set the distance based on distance to current cell
			dmap.setDistance(nextIndex, newDist);
			dmap.addSorted(getTilePosition(nextIndex));

			// put it in the fringe
			_fringe[fringeSize++] = nextIndex;
		}
	}
}
*/
const std::vector<BWAPI::TilePosition> & MapTools::getClosestTilesTo(BWAPI::TilePosition pos)
{
	// make sure the distance map is calculated with pos as a destination
	int a = getGroundTileDistance(pos, pos);

	return _allMaps[pos].getSortedTiles();
}
/*
const std::vector<BWAPI::TilePosition> & MapTools::getClosestTilesTo(BWAPI::Position pos)
{
	// make sure the distance map is calculated with pos as a destination
	int a = getGroundTileDistance(pos, pos);

	return _allMaps[pos].getSortedTiles();
}
*/
const std::vector<BWAPI::TilePosition> & MapTools::getClosestTilesTo(BWAPI::Position pos)
{
	return getClosestTilesTo(BWAPI::TilePosition(pos));
}

/*
BWAPI::TilePosition MapTools::getTilePosition(int index)
{
	return BWAPI::TilePosition(index % _cols, index / _cols);
}
*/

bool MapTools::isBuildable(BWAPI::TilePosition tile, BWAPI::UnitType type) const
{
	if (!tile.isValid())
	{
		return false;
	}

	int startX = tile.x;
	int endX = tile.x + type.tileWidth();
	int startY = tile.y;
	int endY = tile.y + type.tileHeight();

	for (int x = startX; x<endX; ++x)
	{
		for (int y = startY; y<endY; ++y)
		{
			BWAPI::TilePosition tile(x, y);

			if (!tile.isValid() || !isBuildable(tile) || type.isResourceDepot() && !isDepotBuildable(tile))
			{
				return false;
			}
		}
	}

	return true;
}

void MapTools::drawHomeDistanceMap()
{
	if (!Config::Debug::DrawMapDistances)
	{
		return;
	}

	BWAPI::Position homePosition = BWAPI::Position(BWAPI::Broodwar->self()->getStartLocation());

	for (int x = 0; x < BWAPI::Broodwar->mapWidth(); ++x)
	{
		for (int y = 0; y < BWAPI::Broodwar->mapHeight(); ++y)
		{
			BWAPI::Position pos(x * 32, y * 32);

			int dist = getGroundTileDistance(pos, homePosition);

			BWAPI::Broodwar->drawTextMap(pos + BWAPI::Position(16, 16), "%d", dist);
		}
	}
}

const BWEM::Base * MapTools::nextExpansion(bool hidden, bool minOnlyOK)
{
	// Abbreviations.
	BWAPI::Player player = BWAPI::Broodwar->self();
	BWAPI::Player enemy = BWAPI::Broodwar->enemy();

	// We'll go through the bases and pick the one with the best score.
	const BWEM::Base * bestBase = nullptr;
	double bestScore = 0.0;

	BWAPI::TilePosition homeTile = InformationManager::Instance().getMyMainBaseLocation()->Location();
	BWAPI::Position myBasePosition(homeTile);
	const BWEM::Base * enemyBase = InformationManager::Instance().getEnemyMainBaseLocation();  // may be null

	for (auto& area : BWEMmap.Areas())
	{
		for (const auto& base : area.Bases())
		{
			double score = 0.0;

			// for now, don't expand to unconnected expansions
			if (base.Location() != BWAPI::Broodwar->self()->getStartLocation() &&
				BWEMmap.GetPath(BWAPI::Position(BWAPI::Broodwar->self()->getStartLocation()), BWAPI::Position(base.Location())).empty())
			{
				continue;
			}

			// Do we demand a gas base (!minOnlyOK)?
			if (!minOnlyOK && base.Geysers().empty())
			{
				continue;
			}

			// Don't expand to an existing base.
			if (InformationManager::Instance().getBaseOwner(&base) != BWAPI::Broodwar->neutral())
			{
				continue;
			}

			// Don't expand to a base already reserved for another expansion.
			if (InformationManager::Instance().isBaseReserved(&base))
			{
				continue;
			}

			BWAPI::TilePosition tile = base.Location();
			bool buildingInTheWay = false;
		
			for (int x = 0; x < player->getRace().getCenter().tileWidth(); ++x)
			{
				for (int y = 0; y < player->getRace().getCenter().tileHeight(); ++y)
				{
					if (BuildingPlacer::Instance().isReserved(tile.x + x, tile.y + y))
					{
						// This happens if we were already planning to expand here. Try somewhere else.
						buildingInTheWay = true;
						break;
					}

					// TODO bug: this doesn't include enemy buildings which are known but out of sight
					for (const auto unit : BWAPI::Broodwar->getUnitsOnTile(BWAPI::TilePosition(tile.x + x, tile.y + y)))
					{
						if (unit->getType().isBuilding() && !unit->isLifted())
						{
							buildingInTheWay = true;
							break;
						}
					}
				}
			}
		
			if (buildingInTheWay)
			{
				continue;
			}

			// Want to be close to our own base.
			double distanceFromUs = getGroundTileDistance(homeTile, tile);
			if (BWEMmap.GetPath(BWAPI::Position(homeTile), BWAPI::Position(tile)).empty() || distanceFromUs < 0)
			{
				//BWAPI::Broodwar->printf("Base not connected");
				continue;
			}

			// Want to be far from the enemy base.
			double distanceFromEnemy = 0.0;
			if (enemyBase) 
			{
				BWAPI::TilePosition enemyTile = enemyBase->Location();
				distanceFromEnemy = getGroundTileDistance(enemyTile, tile);
				if (BWEMmap.GetPath(BWAPI::Position(enemyTile), BWAPI::Position(tile)).empty() || distanceFromEnemy < 0)
				{
					//BWAPI::Broodwar->printf("Base not connected");
					distanceFromEnemy = 0.0;
				}
			}

			// Add up the score.
			score = hidden ? (distanceFromEnemy + distanceFromUs / 2.0) : (distanceFromEnemy - 2.0 * distanceFromUs);

			// More resources -> better.
			int mineralSize = 0;
			for (const auto &m : base.Minerals())
			{
				mineralSize += m->Amount();
			}

			int gasSize = 0;
			for (const auto &g : base.Geysers())
			{
				gasSize += g->Amount();
			}

			score += 0.01 * mineralSize;
			if (!minOnlyOK)
			{
				score += 0.02 * gasSize;
			}

			// Big penalty for enemy buildings in the same region.
			if (InformationManager::Instance().isEnemyBuildingInRegion(base.GetArea()))
			{
				score -= 100.0;
			}

			//BWAPI::Broodwar->printf("base score %d, %d -> %f",  tile.x, tile.y, score);
			if (!bestBase || score > bestScore)
			{
				bestBase = &base;
				bestScore = score;
			}
		}
	}

	if (bestBase)
	{
		return bestBase;
	}
	if (!minOnlyOK)
	{
		// We wanted a gas base and there isn't one. Try for a mineral-only base.
		return nextExpansion(hidden, true);
	}
	return nullptr;
}

BWAPI::TilePosition MapTools::getNextExpansion(bool hidden, bool minOnlyOK)
{
	const BWEM::Base * base = nextExpansion(hidden, minOnlyOK);
	if (base)
	{
		// BWAPI::Broodwar->printf("foresee base @ %d, %d", base->getTilePosition().x, base->getTilePosition().y);
		return base->Location();
	}
	return BWAPI::TilePositions::None;
}

BWAPI::TilePosition MapTools::reserveNextExpansion(bool hidden, bool minOnlyOK)
{
	const BWEM::Base * base = nextExpansion(hidden, minOnlyOK);
	if (base)
	{
		// BWAPI::Broodwar->printf("reserve base @ %d, %d", base->getTilePosition().x, base->getTilePosition().y);
		InformationManager::Instance().reserveBase(base);
		return base->Location();
	}
	return BWAPI::TilePositions::None;
}
