Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • stebr364/pycommandcenter
  • starcraft-ai-course/pycommandcenter
  • eriah592/pycommandcenter
  • edvbe696/pycommandcenter
  • dawab699/pycommandcenter
  • hanja189/pycommandcenter
  • teoga849/pycommandcenter
  • musab250/pycommandcenter
  • emibr898/pycommandcenter
  • chrgu102/pycommandcenter
  • axega544/pycommandcenter
  • edvth289/pycommandcenter
  • jonbo278/py-command-center-v-2
13 results
Show changes
Showing with 1130 additions and 1542 deletions
#pragma once
#include "Common.h"
#include "MetaType.h"
class BuildOrder
{
std::vector<MetaType> m_buildOrder;
public:
BuildOrder();
BuildOrder(const std::vector<MetaType> & buildVector);
void add(const MetaType & type);
size_t size() const;
const MetaType & operator [] (const size_t & index) const;
MetaType & operator [] (const size_t & index);
};
#include "BuildOrderQueue.h"
#include "IDABot.h"
BuildOrderQueue::BuildOrderQueue(IDABot & bot)
: m_bot(bot)
, m_highestPriority(0)
, m_lowestPriority(0)
, m_defaultPrioritySpacing(10)
, m_numSkippedItems(0)
{
}
void BuildOrderQueue::clearAll()
{
// clear the queue
m_queue.clear();
// reset the priorities
m_highestPriority = 0;
m_lowestPriority = 0;
}
BuildOrderItem & BuildOrderQueue::getHighestPriorityItem()
{
// reset the number of skipped items to zero
m_numSkippedItems = 0;
// the queue will be sorted with the highest priority at the back
return m_queue.back();
}
BuildOrderItem & BuildOrderQueue::getNextHighestPriorityItem()
{
assert(m_queue.size() - 1 - m_numSkippedItems >= 0);
// the queue will be sorted with the highest priority at the back
return m_queue[m_queue.size() - 1 - m_numSkippedItems];
}
void BuildOrderQueue::skipItem()
{
// make sure we can skip
assert(canSkipItem());
// skip it
m_numSkippedItems++;
}
bool BuildOrderQueue::canSkipItem()
{
// does the queue have more elements
bool bigEnough = m_queue.size() > (size_t)(1 + m_numSkippedItems);
if (!bigEnough)
{
return false;
}
// is the current highest priority item not blocking a skip
bool highestNotBlocking = !m_queue[m_queue.size() - 1 - m_numSkippedItems].blocking;
// this tells us if we can skip
return highestNotBlocking;
}
void BuildOrderQueue::queueItem(const BuildOrderItem & b)
{
// if the queue is empty, set the highest and lowest priorities
if (m_queue.empty())
{
m_highestPriority = b.priority;
m_lowestPriority = b.priority;
}
// push the item into the queue
if (b.priority <= m_lowestPriority)
{
m_queue.push_front(b);
}
else
{
m_queue.push_back(b);
}
// if the item is somewhere in the middle, we have to sort again
if ((m_queue.size() > 1) && (b.priority < m_highestPriority) && (b.priority > m_lowestPriority))
{
// sort the list in ascending order, putting highest priority at the top
std::sort(m_queue.begin(), m_queue.end());
}
// update the highest or lowest if it is beaten
m_highestPriority = (b.priority > m_highestPriority) ? b.priority : m_highestPriority;
m_lowestPriority = (b.priority < m_lowestPriority) ? b.priority : m_lowestPriority;
}
void BuildOrderQueue::queueAsHighestPriority(const MetaType & type, bool blocking)
{
// the new priority will be higher
int newPriority = m_highestPriority + m_defaultPrioritySpacing;
// queue the item
queueItem(BuildOrderItem(type, newPriority, blocking));
}
void BuildOrderQueue::queueAsLowestPriority(const MetaType & type, bool blocking)
{
// the new priority will be higher
int newPriority = m_lowestPriority - m_defaultPrioritySpacing;
// queue the item
queueItem(BuildOrderItem(type, newPriority, blocking));
}
void BuildOrderQueue::removeHighestPriorityItem()
{
// remove the back element of the vector
m_queue.pop_back();
// if the list is not empty, set the highest accordingly
m_highestPriority = m_queue.empty() ? 0 : m_queue.back().priority;
m_lowestPriority = m_queue.empty() ? 0 : m_lowestPriority;
}
void BuildOrderQueue::removeCurrentHighestPriorityItem()
{
// remove the back element of the vector
m_queue.erase(m_queue.begin() + m_queue.size() - 1 - m_numSkippedItems);
//assert((int)(queue.size()) < size);
// if the list is not empty, set the highest accordingly
m_highestPriority = m_queue.empty() ? 0 : m_queue.back().priority;
m_lowestPriority = m_queue.empty() ? 0 : m_lowestPriority;
}
size_t BuildOrderQueue::size()
{
return m_queue.size();
}
bool BuildOrderQueue::isEmpty()
{
return (m_queue.size() == 0);
}
BuildOrderItem BuildOrderQueue::operator [] (int i)
{
return m_queue[i];
}
std::string BuildOrderQueue::getQueueInformation() const
{
size_t reps = m_queue.size() < 30 ? m_queue.size() : 30;
std::stringstream ss;
// for each unit in the queue
for (size_t i(0); i<reps; i++)
{
const MetaType & type = m_queue[m_queue.size() - 1 - i].type;
ss << type.getName() << "\n";
}
return ss.str();
}
BuildOrderItem::BuildOrderItem(const MetaType & t, int p, bool b)
: type(t)
, priority(p)
, blocking(b)
{
}
bool BuildOrderItem::operator < (const BuildOrderItem & x) const
{
return priority < x.priority;
}
\ No newline at end of file
#pragma once
#include "Common.h"
#include "MetaType.h"
class IDABot;
struct BuildOrderItem
{
MetaType type; // the thing we want to 'build'
int priority; // the priority at which to place it in the queue
bool blocking; // whether or not we block further items
BuildOrderItem(const MetaType & t, int p, bool b);
bool operator<(const BuildOrderItem & x) const;
};
class BuildOrderQueue
{
IDABot & m_bot;
std::deque<BuildOrderItem> m_queue;
int m_lowestPriority;
int m_highestPriority;
int m_defaultPrioritySpacing;
int m_numSkippedItems;
public:
BuildOrderQueue(IDABot & bot);
void clearAll(); // clears the entire build order queue
void skipItem(); // increments skippedItems
void queueAsHighestPriority(const MetaType & type, bool blocking); // queues something at the highest priority
void queueAsLowestPriority(const MetaType & type, bool blocking); // queues something at the lowest priority
void queueItem(const BuildOrderItem & b); // queues something with a given priority
void removeHighestPriorityItem(); // removes the highest priority item
void removeCurrentHighestPriorityItem();
size_t size(); // returns the size of the queue
bool isEmpty();
BuildOrderItem & getHighestPriorityItem(); // returns the highest priority item
BuildOrderItem & getNextHighestPriorityItem(); // returns the highest priority item
bool canSkipItem();
std::string getQueueInformation() const;
// overload the bracket operator for ease of use
BuildOrderItem operator [] (int i);
};
#include "Building.h"
Building::Building()
: desiredPosition (0,0)
, finalPosition (0,0)
, position (0,0)
, type ()
, buildingUnit ()
, builderUnit ()
, lastOrderFrame (0)
, status (BuildingStatus::Unassigned)
, buildCommandGiven (false)
, underConstruction (false)
{}
// constructor we use most often
Building::Building(UnitType t, CCTilePosition desired)
: desiredPosition (desired)
, finalPosition (0,0)
, position (0,0)
, type (t)
, buildingUnit ()
, builderUnit ()
, lastOrderFrame (0)
, status (BuildingStatus::Unassigned)
, buildCommandGiven (false)
, underConstruction (false)
{}
// equals operator
bool Building::operator == (const Building & b)
{
// buildings are equal if their worker unit and building unit are equal
return (b.buildingUnit == buildingUnit)
&& (b.builderUnit == builderUnit)
&& (b.finalPosition.x == finalPosition.x)
&& (b.finalPosition.y == finalPosition.y);
}
\ No newline at end of file
#pragma once
#include "Common.h"
#include "Unit.h"
#include "UnitType.h"
namespace BuildingStatus
{
enum { Unassigned = 0, Assigned = 1, UnderConstruction = 2, Size = 3 };
}
class Building
{
public:
CCTilePosition desiredPosition;
CCTilePosition finalPosition;
CCTilePosition position;
UnitType type;
Unit buildingUnit;
Unit builderUnit;
size_t status;
int lastOrderFrame;
bool buildCommandGiven;
bool underConstruction;
Building();
// constructor we use most often
Building(UnitType t, CCTilePosition desired);
// equals operator
bool operator == (const Building & b);
};
#include "BuildingData.h"
BuildingData::BuildingData()
{
}
void BuildingData::removeBuilding(const Building & b)
{
const auto & building = std::find(_buildings.begin(), _buildings.end(), b);
if (building != _buildings.end())
{
_buildings.erase(building);
}
}
std::vector<Building> & BuildingData::getBuildings()
{
return _buildings;
}
void BuildingData::addBuilding(const Building & b)
{
_buildings.push_back(b);
}
bool BuildingData::isBeingBuilt(UnitType type)
{
for (auto & b : _buildings)
{
if (b.type == type)
{
return true;
}
}
return false;
}
void BuildingData::removeBuildings(const std::vector<Building> & buildings)
{
for (const auto & b : buildings)
{
removeBuilding(b);
}
}
\ No newline at end of file
#pragma once
#include "Common.h"
#include "Building.h"
class BuildingData
{
std::vector<Building> _buildings;
public:
BuildingData();
std::vector<Building> & getBuildings();
void addBuilding(const Building & b);
void removeBuilding(const Building & b);
void removeBuildings(const std::vector<Building> & buildings);
bool isBeingBuilt(UnitType type);
};
\ No newline at end of file
#include "Common.h"
#include "BuildingManager.h"
#include "IDABot.h"
#include "Util.h"
BuildingManager::BuildingManager(IDABot & bot)
: m_bot(bot)
, m_buildingPlacer(bot)
, m_debugMode(false)
, m_reservedMinerals(0)
, m_reservedGas(0)
{
}
void BuildingManager::onStart()
{
m_buildingPlacer.onStart();
}
// gets called every frame from GameCommander
void BuildingManager::onFrame()
{
for (auto unit : m_bot.UnitInfo().getUnits(Players::Self))
{
// filter out units which aren't buildings under construction
if (m_bot.Data(unit).isBuilding)
{
std::stringstream ss;
ss << unit.getID();
m_bot.Map().drawText(unit.getPosition(), ss.str());
}
}
validateWorkersAndBuildings(); // check to see if assigned workers have died en route or while constructing
assignWorkersToUnassignedBuildings(); // assign workers to the unassigned buildings and label them 'planned'
constructAssignedBuildings(); // for each planned building, if the worker isn't constructing, send the command
checkForStartedConstruction(); // check to see if any buildings have started construction and update data structures
checkForDeadTerranBuilders(); // if we are terran and a building is under construction without a worker, assign a new one
checkForCompletedBuildings(); // check to see if any buildings have completed and update data structures
drawBuildingInformation();
}
bool BuildingManager::isBeingBuilt(UnitType type)
{
for (auto & b : m_buildings)
{
if (b.type == type)
{
return true;
}
}
return false;
}
// STEP 1: DO BOOK KEEPING ON WORKERS WHICH MAY HAVE DIED
void BuildingManager::validateWorkersAndBuildings()
{
// TODO: if a terran worker dies while constructing and its building
// is under construction, place unit back into buildingsNeedingBuilders
std::vector<Building> toRemove;
// find any buildings which have become obsolete
for (auto & b : m_buildings)
{
if (b.status != BuildingStatus::UnderConstruction)
{
continue;
}
auto buildingUnit = b.buildingUnit;
// TODO: || !b.buildingUnit->getType().isBuilding()
if (!buildingUnit.isValid())
{
toRemove.push_back(b);
}
}
removeBuildings(toRemove);
}
// STEP 2: ASSIGN WORKERS TO BUILDINGS WITHOUT THEM
void BuildingManager::assignWorkersToUnassignedBuildings()
{
// for each building that doesn't have a builder, assign one
for (Building & b : m_buildings)
{
if (b.status != BuildingStatus::Unassigned)
{
continue;
}
BOT_ASSERT(!b.builderUnit.isValid(), "Error: Tried to assign a builder to a building that already had one ");
if (m_debugMode) { printf("Assigning Worker To: %s", b.type.getName().c_str()); }
// grab a worker unit from WorkerManager which is closest to this final position
CCTilePosition testLocation = getBuildingLocation(b);
if (!m_bot.Map().isValidTile(testLocation) || (testLocation.x == 0 && testLocation.y == 0))
{
continue;
}
b.finalPosition = testLocation;
// grab the worker unit from WorkerManager which is closest to this final position
Unit builderUnit = m_bot.Workers().getBuilder(b);
b.builderUnit = builderUnit;
if (!b.builderUnit.isValid())
{
continue;
}
// reserve this building's space
m_buildingPlacer.reserveTiles((int)b.finalPosition.x, (int)b.finalPosition.y, b.type.tileWidth(), b.type.tileHeight());
b.status = BuildingStatus::Assigned;
}
}
// STEP 3: ISSUE CONSTRUCTION ORDERS TO ASSIGN BUILDINGS AS NEEDED
void BuildingManager::constructAssignedBuildings()
{
for (auto & b : m_buildings)
{
if (b.status != BuildingStatus::Assigned)
{
continue;
}
// TODO: not sure if this is the correct way to tell if the building is constructing
//sc2::AbilityID buildAbility = m_bot.Data(b.type).buildAbility;
Unit builderUnit = b.builderUnit;
bool isConstructing = false;
// if we're zerg and the builder unit is null, we assume it morphed into the building
if (Util::IsZerg(m_bot.GetPlayerRace(Players::Self)))
{
if (!builderUnit.isValid())
{
isConstructing = true;
}
}
else
{
BOT_ASSERT(builderUnit.isValid(), "null builder unit");
isConstructing = builderUnit.isConstructing(b.type);
}
// if that worker is not currently constructing
if (!isConstructing)
{
// if we haven't explored the build position, go there
if (!isBuildingPositionExplored(b))
{
builderUnit.move(b.finalPosition);
}
// if this is not the first time we've sent this guy to build this
// it must be the case that something was in the way of building
else if (b.buildCommandGiven)
{
// TODO: in here is where we would check to see if the builder died on the way
// or if things are taking too long, or the build location is no longer valid
}
else
{
// if it's a refinery, the build command has to be on the geyser unit tag
if (b.type.isRefinery())
{
// first we find the geyser at the desired location
Unit geyser;
for (auto unit : m_bot.GetAllUnits())
{
if (unit.getType().isGeyser() && Util::Dist(Util::GetPosition(b.finalPosition), unit.getPosition()) < 3)
{
geyser = unit;
break;
}
}
if (geyser.isValid())
{
b.builderUnit.buildTarget(b.type, geyser);
}
else
{
std::cout << "WARNING: NO VALID GEYSER UNIT FOUND TO BUILD ON, SKIPPING REFINERY\n";
}
}
// if it's not a refinery, we build right on the position
else
{
b.builderUnit.build(b.type, b.finalPosition);
}
// set the flag to true
b.buildCommandGiven = true;
}
}
}
}
// STEP 4: UPDATE DATA STRUCTURES FOR BUILDINGS STARTING CONSTRUCTION
void BuildingManager::checkForStartedConstruction()
{
// for each building unit which is being constructed
for (auto buildingStarted : m_bot.UnitInfo().getUnits(Players::Self))
{
// filter out units which aren't buildings under construction
if (!buildingStarted.getType().isBuilding() || !buildingStarted.isBeingConstructed())
{
continue;
}
// check all our building status objects to see if we have a match and if we do, update it
for (auto & b : m_buildings)
{
if (b.status != BuildingStatus::Assigned)
{
continue;
}
// check if the positions match
int dx = b.finalPosition.x - buildingStarted.getTilePosition().x;
int dy = b.finalPosition.y - buildingStarted.getTilePosition().y;
if (dx*dx + dy*dy < Util::TileToPosition(1.0f))
{
if (b.buildingUnit.isValid())
{
std::cout << "Building mis-match somehow\n";
}
// the resources should now be spent, so unreserve them
m_reservedMinerals -= buildingStarted.getType().mineralPrice();
m_reservedGas -= buildingStarted.getType().gasPrice();
// flag it as started and set the buildingUnit
b.underConstruction = true;
b.buildingUnit = buildingStarted;
// if we are zerg, the buildingUnit now becomes nullptr since it's destroyed
if (Util::IsZerg(m_bot.GetPlayerRace(Players::Self)))
{
b.builderUnit = Unit();
}
else if (Util::IsProtoss(m_bot.GetPlayerRace(Players::Self)))
{
m_bot.Workers().finishedWithWorker(b.builderUnit);
b.builderUnit = Unit();
}
// put it in the under construction vector
b.status = BuildingStatus::UnderConstruction;
// free this space
m_buildingPlacer.freeTiles((int)b.finalPosition.x, (int)b.finalPosition.y, b.type.tileWidth(), b.type.tileHeight());
// only one building will match
break;
}
}
}
}
// STEP 5: IF WE ARE TERRAN, THIS MATTERS, SO: LOL
void BuildingManager::checkForDeadTerranBuilders() {}
// STEP 6: CHECK FOR COMPLETED BUILDINGS
void BuildingManager::checkForCompletedBuildings()
{
std::vector<Building> toRemove;
// for each of our buildings under construction
for (auto & b : m_buildings)
{
if (b.status != BuildingStatus::UnderConstruction)
{
continue;
}
// if the unit has completed
if (b.buildingUnit.isCompleted())
{
// if we are terran, give the worker back to worker manager
if (Util::IsTerran(m_bot.GetPlayerRace(Players::Self)))
{
m_bot.Workers().finishedWithWorker(b.builderUnit);
}
// remove this unit from the under construction vector
toRemove.push_back(b);
}
}
removeBuildings(toRemove);
}
// add a new building to be constructed
void BuildingManager::addBuildingTask(const UnitType & type, const CCTilePosition & desiredPosition)
{
m_reservedMinerals += m_bot.Data(type).mineralCost;
m_reservedGas += m_bot.Data(type).gasCost;
Building b(type, desiredPosition);
b.status = BuildingStatus::Unassigned;
m_buildings.push_back(b);
}
// TODO: may need to iterate over all tiles of the building footprint
bool BuildingManager::isBuildingPositionExplored(const Building & b) const
{
return m_bot.Map().isExplored(b.finalPosition);
}
char BuildingManager::getBuildingWorkerCode(const Building & b) const
{
return b.builderUnit.isValid() ? 'W' : 'X';
}
int BuildingManager::getReservedMinerals()
{
return m_reservedMinerals;
}
int BuildingManager::getReservedGas()
{
return m_reservedGas;
}
void BuildingManager::drawBuildingInformation()
{
m_buildingPlacer.drawReservedTiles();
std::stringstream ss;
ss << "Building Information " << m_buildings.size() << "\n\n\n";
int yspace = 0;
for (const auto & b : m_buildings)
{
std::stringstream dss;
if (b.builderUnit.isValid())
{
dss << "\n\nBuilder: " << b.builderUnit.getID() << "\n";
}
if (b.buildingUnit.isValid())
{
dss << "Building: " << b.buildingUnit.getID() << "\n" << b.buildingUnit.getBuildPercentage();
m_bot.Map().drawText(b.buildingUnit.getPosition(), dss.str());
}
if (b.status == BuildingStatus::Unassigned)
{
ss << "Unassigned " << b.type.getName() << " " << getBuildingWorkerCode(b) << "\n";
}
else if (b.status == BuildingStatus::Assigned)
{
ss << "Assigned " << b.type.getName() << " " << b.builderUnit.getID() << " " << getBuildingWorkerCode(b) << " (" << b.finalPosition.x << "," << b.finalPosition.y << ")\n";
int x1 = b.finalPosition.x;
int y1 = b.finalPosition.y;
int x2 = b.finalPosition.x + b.type.tileWidth();
int y2 = b.finalPosition.y + b.type.tileHeight();
m_bot.Map().drawBox((CCPositionType)x1, (CCPositionType)y1, (CCPositionType)x2, (CCPositionType)y2, CCColor(255, 0, 0));
//m_bot.Map().drawLine(b.finalPosition, m_bot.GetUnit(b.builderUnitTag)->pos, CCColors::Yellow);
}
else if (b.status == BuildingStatus::UnderConstruction)
{
ss << "Constructing " << b.type.getName() << " " << getBuildingWorkerCode(b) << "\n";
}
}
m_bot.Map().drawTextScreen(0.3f, 0.05f, ss.str());
}
std::vector<UnitType> BuildingManager::buildingsQueued() const
{
std::vector<UnitType> buildingsQueued;
for (const auto & b : m_buildings)
{
if (b.status == BuildingStatus::Unassigned || b.status == BuildingStatus::Assigned)
{
buildingsQueued.push_back(b.type);
}
}
return buildingsQueued;
}
CCTilePosition BuildingManager::getBuildingLocation(const Building & b)
{
size_t numPylons = m_bot.UnitInfo().getUnitTypeCount(Players::Self, Util::GetSupplyProvider(m_bot.GetPlayerRace(Players::Self), m_bot), true);
// TODO: if requires psi and we have no pylons return 0
if (b.type.isRefinery())
{
return m_buildingPlacer.getRefineryPosition();
}
if (b.type.isResourceDepot())
{
return m_bot.Bases().getNextExpansion(Players::Self)->getDepotPosition();
}
// get a position within our region
// TODO: put back in special pylon / cannon spacing
return m_buildingPlacer.getBuildLocationNear(b, 1);
}
void BuildingManager::removeBuildings(const std::vector<Building> & toRemove)
{
for (auto & b : toRemove)
{
const auto & it = std::find(m_buildings.begin(), m_buildings.end(), b);
if (it != m_buildings.end())
{
m_buildings.erase(it);
}
}
}
\ No newline at end of file
#pragma once
#include "Common.h"
#include "BuildingPlacer.h"
class IDABot;
class BuildingManager
{
IDABot & m_bot;
BuildingPlacer m_buildingPlacer;
std::vector<Building> m_buildings;
bool m_debugMode;
int m_reservedMinerals; // minerals reserved for planned buildings
int m_reservedGas; // gas reserved for planned buildings
bool isBuildingPositionExplored(const Building & b) const;
void removeBuildings(const std::vector<Building> & toRemove);
void validateWorkersAndBuildings(); // STEP 1
void assignWorkersToUnassignedBuildings(); // STEP 2
void constructAssignedBuildings(); // STEP 3
void checkForStartedConstruction(); // STEP 4
void checkForDeadTerranBuilders(); // STEP 5
void checkForCompletedBuildings(); // STEP 6
char getBuildingWorkerCode(const Building & b) const;
public:
BuildingManager(IDABot & bot);
void onStart();
void onFrame();
void addBuildingTask(const UnitType & type, const CCTilePosition & desiredPosition);
void drawBuildingInformation();
CCTilePosition getBuildingLocation(const Building & b);
int getReservedMinerals();
int getReservedGas();
bool isBeingBuilt(UnitType type);
std::vector<UnitType> buildingsQueued() const;
};
#include "Common.h"
#include "BuildingPlacer.h"
#include "IDABot.h"
#include "Building.h"
#include "Util.h"
BuildingPlacer::BuildingPlacer(IDABot & bot)
......@@ -15,25 +14,47 @@ void BuildingPlacer::onStart()
m_reserveMap = std::vector< std::vector<bool> >(m_bot.Map().width(), std::vector<bool>(m_bot.Map().height(), false));
}
void BuildingPlacer::updateReserved(const std::vector<Unit> & units)
{
freeAllTiles();
for (Unit unit : units) {
reserveTiles(unit.getTilePosition().x, unit.getTilePosition().y, unit.getType().tileWidth(), unit.getType().tileHeight());
}
}
void BuildingPlacer::freeAllTiles() {
for (int x = 0; x < m_reserveMap.size(); x++) {
for (int y = 0; y < m_reserveMap[x].size(); y++) {
m_reserveMap[x][y] = false;
}
}
}
bool BuildingPlacer::isInResourceBox(int tileX, int tileY) const
{
return m_bot.Bases().getPlayerStartingBaseLocation(Players::Self)->isInResourceBox(tileX, tileY);
}
// makes final checks to see if a building can be built at a certain location
bool BuildingPlacer::canBuildHere(int bx, int by, const Building & b) const
bool BuildingPlacer::canBuildHere(int bx, int by, const UnitType & type) const
{
if (isInResourceBox(bx, by))
{
return false;
}
// check the reserve map
for (int x = bx; x < bx + b.type.tileWidth(); x++)
int width = type.tileWidth();
int height = type.tileHeight();
int xdelta = (int)std::ceil((width - 1.0) / 2);
int ydelta = (int)std::ceil((height - 1.0) / 2);
// check the reserve map, that it is a valid tile and not a wall
for (int x = bx - xdelta; x < bx + width - xdelta; x++)
{
for (int y = by; y < by + b.type.tileHeight(); y++)
for (int y = by - ydelta; y < by + height - ydelta; y++)
{
if (!m_bot.Map().isValidTile(x, y) || m_reserveMap[x][y])
if (!m_bot.Map().isValidTile(x, y) || isReserved(x, y) || !m_bot.Map().isWalkable(x, y))
{
return false;
}
......@@ -41,7 +62,7 @@ bool BuildingPlacer::canBuildHere(int bx, int by, const Building & b) const
}
// if it overlaps a base location return false
if (tileOverlapsBaseLocation(bx, by, b.type))
if (tileOverlapsBaseLocation(bx, by, type))
{
return false;
}
......@@ -49,45 +70,105 @@ bool BuildingPlacer::canBuildHere(int bx, int by, const Building & b) const
return true;
}
//returns true if we can build this type of unit here with the specified amount of space.
bool BuildingPlacer::canBuildHereWithSpace(int bx, int by, const Building & b, int buildDist) const
bool BuildingPlacer::canBuildHereWithSize(int bx, int by, int width, int height)
{
UnitType type = b.type;
if (isInResourceBox(bx, by))
{
return false;
}
int xdelta = (int)std::ceil((width - 1.0) / 2);
int ydelta = (int)std::ceil((height - 1.0) / 2);
// check the reserve map, that it is a valid tile and not a wall
for (int x = bx - xdelta; x < bx + width - xdelta; x++)
{
for (int y = by - ydelta; y < by + height - ydelta; y++)
{
if (!m_bot.Map().isValidTile(x, y) || isReserved(x, y) || !m_bot.Map().isWalkable(x, y))
{
return false;
}
}
}
// Check so it doesn't overlap with a baselocation
// dimensions of the proposed location
int tx1 = bx - xdelta;
int ty1 = by - ydelta;
int tx2 = tx1 + width - xdelta;
int ty2 = ty1 + height - ydelta;
// for each base location
for (const BaseLocation * base : m_bot.Bases().getBaseLocations())
{
// dimensions of the base location
int bx1 = (int)base->getDepotPosition().x;
int by1 = (int)base->getDepotPosition().y;
int bx2 = bx1 + Util::GetTownHall(m_bot.GetPlayerRace(Players::Self), m_bot).tileWidth();
int by2 = by1 + Util::GetTownHall(m_bot.GetPlayerRace(Players::Self), m_bot).tileHeight();
// conditions for non-overlap are easy
bool noOverlap = (tx2 < bx1) || (tx1 > bx2) || (ty2 < by1) || (ty1 > by2);
// If there is overlap, return false
if (!noOverlap)
{
return false;
}
}
// otherwise there is no overlap
return true;
}
//returns true if we can build this type of unit here with the specified amount of space.
bool BuildingPlacer::canBuildHereWithSpace(int bx, int by, const UnitType & type, int buildDist) const
{
//if we can't build here, we of course can't build here with space
if (!canBuildHere(bx, by, b))
if (!canBuildHere(bx, by, type))
{
return false;
}
// height and width of the building
int width = b.type.tileWidth();
int height = b.type.tileHeight();
int width = type.tileWidth();
int height = type.tileHeight();
int xdelta = (int)std::ceil((width - 1.0) / 2);
int ydelta = (int)std::ceil((height - 1.0) / 2);
// TODO: make sure we leave space for add-ons. These types of units can have addons:
// define the rectangle of the building spot
int startx = bx - buildDist;
int starty = by - buildDist;
int endx = bx + width + buildDist;
int endy = by + height + buildDist;
int startx = bx - buildDist - xdelta;
int starty = by - buildDist - ydelta;
int endx = bx + width + buildDist - xdelta;
int endy = by + height + buildDist - ydelta;
// TODO: recalculate start and end positions for addons
// if this rectangle doesn't fit on the map we can't build here
if (startx < 0 || starty < 0 || endx > m_bot.Map().width() || endx < bx + width || endy > m_bot.Map().height())
if (startx < 0 || starty < 0 || endx > m_bot.Map().width() || endx < bx + width - xdelta || endy > m_bot.Map().height() || endy < by + height - ydelta)
{
return false;
}
if (!m_bot.Map().canBuildTypeAtPosition(bx, by, type))
{
return false;
}
// if we can't build here, or space is reserved, or it's in the resource box, we can't build here
for (int x = startx; x < endx; x++)
{
for (int y = starty; y < endy; y++)
{
if (!b.type.isRefinery())
if (!type.isRefinery())
{
if (!buildable(b, x, y) || m_reserveMap[x][y])
if (!buildable(type, x, y) || isReserved(x, y) || !m_bot.Map().isWalkable(x, y)) // added isWalkabale in order to check towards walls
{
return false;
}
......@@ -98,22 +179,24 @@ bool BuildingPlacer::canBuildHereWithSpace(int bx, int by, const Building & b, i
return true;
}
CCTilePosition BuildingPlacer::getBuildLocationNear(const Building & b, int buildDist) const
// BuildDist is the distance from the position where the building is gonna be placed.
CCTilePosition BuildingPlacer::getBuildLocationNear(const CCTilePosition & p, const UnitType & t, int buildDist, size_t search_count) const
{
//Timer t;
//t.start();
// get the precomputed vector of tile positions which are sorted closes to this location
auto & closestToBuilding = m_bot.Map().getClosestTilesTo(b.desiredPosition);
auto & closestToBuilding = m_bot.Map().getClosestTilesTo(p);
//double ms1 = t.getElapsedTimeInMilliSec();
// iterate through the list until we've found a suitable location
for (size_t i(0); i < closestToBuilding.size() && i < 1000; ++i)
for (size_t i(0); i < closestToBuilding.size() && (search_count == 0 || i < search_count); ++i)
{
auto & pos = closestToBuilding[i];
if (canBuildHereWithSpace(pos.x, pos.y, b, buildDist))
if (canBuildHereWithSpace(pos.x, pos.y, t, buildDist))
{
//double ms = t.getElapsedTimeInMilliSec();
//printf("Building Placer Took %d iterations, lasting %lf ms @ %lf iterations/ms, %lf setup ms\n", (int)i, ms, (i / ms), ms1);
......@@ -124,6 +207,7 @@ CCTilePosition BuildingPlacer::getBuildLocationNear(const Building & b, int buil
//double ms = t.getElapsedTimeInMilliSec();
//printf("Building Placer Failure: %s - Took %lf ms\n", b.type.getName().c_str(), ms);
std::cout << "Warning! Could not find valid placement for " << t.getName() << " near (" << p.x << ", " << p.y << "). Returning (0, 0) instead.";
return CCTilePosition(0, 0);
}
......@@ -137,10 +221,14 @@ bool BuildingPlacer::tileOverlapsBaseLocation(int x, int y, UnitType type) const
}
// dimensions of the proposed location
int tx1 = x;
int ty1 = y;
int tx2 = tx1 + type.tileWidth();
int ty2 = ty1 + type.tileHeight();
int xdelta = (int)std::ceil((type.tileWidth() - 1.0) / 2);
int ydelta = (int)std::ceil((type.tileHeight() - 1.0) / 2);
int tx1 = x - xdelta;
int ty1 = y - ydelta;
int tx2 = tx1 + type.tileWidth() - xdelta;
int ty2 = ty1 + type.tileHeight() - ydelta;
// for each base location
for (const BaseLocation * base : m_bot.Bases().getBaseLocations())
......@@ -165,10 +253,10 @@ bool BuildingPlacer::tileOverlapsBaseLocation(int x, int y, UnitType type) const
return false;
}
bool BuildingPlacer::buildable(const Building & b, int x, int y) const
bool BuildingPlacer::buildable(const UnitType & type, int x, int y) const
{
// TODO: does this take units on the map into account?
if (!m_bot.Map().isValidTile(x, y) || !m_bot.Map().canBuildTypeAtPosition(x, y, b.type))
if (!m_bot.Map().isValidTile(x, y))
{
return false;
}
......@@ -180,11 +268,16 @@ bool BuildingPlacer::buildable(const Building & b, int x, int y) const
void BuildingPlacer::reserveTiles(int bx, int by, int width, int height)
{
// THIS is never called, that's why spacing doesnt work correctly
int rwidth = (int)m_reserveMap.size();
int rheight = (int)m_reserveMap[0].size();
for (int x = bx; x < bx + width && x < rwidth; x++)
int xdelta = (int)std::ceil((width - 1.0) / 2);
int ydelta = (int)std::ceil((height - 1.0) / 2);
for (int x = bx - xdelta; x < bx + width - xdelta && x < rwidth; x++)
{
for (int y = by; y < by + height && y < rheight; y++)
for (int y = by - ydelta; y < by + height - ydelta && y < rheight; y++)
{
m_reserveMap[x][y] = true;
}
......@@ -193,6 +286,7 @@ void BuildingPlacer::reserveTiles(int bx, int by, int width, int height)
void BuildingPlacer::drawReservedTiles()
{
// Why is there a return here? Should we not use the function? /Hannes Jmtner
return;
int rwidth = (int)m_reserveMap.size();
int rheight = (int)m_reserveMap[0].size();
......@@ -214,9 +308,12 @@ void BuildingPlacer::freeTiles(int bx, int by, int width, int height)
int rwidth = (int)m_reserveMap.size();
int rheight = (int)m_reserveMap[0].size();
for (int x = bx; x < bx + width && x < rwidth; x++)
int xdelta = (int)std::ceil((width - 1.0) / 2);
int ydelta = (int)std::ceil((height - 1.0) / 2);
for (int x = bx - xdelta; x < bx + width - xdelta && x < rwidth; x++)
{
for (int y = by; y < by + height && y < rheight; y++)
for (int y = by - ydelta; y < by + height - ydelta && y < rheight; y++)
{
m_reserveMap[x][y] = false;
}
......@@ -229,8 +326,12 @@ CCTilePosition BuildingPlacer::getRefineryPosition()
double minGeyserDistanceFromHome = std::numeric_limits<double>::max();
CCPosition homePosition = m_bot.GetStartLocation();
for (auto & unit : m_bot.GetAllUnits())
{
UnitType refinery = Util::GetRefinery(m_bot.GetPlayerRace(Players::Self), m_bot);
if (!unit.getType().isGeyser())
{
continue;
......@@ -238,6 +339,13 @@ CCTilePosition BuildingPlacer::getRefineryPosition()
CCPosition geyserPos(unit.getPosition());
// can't build a refinery on top of another
if (!m_bot.Map().canBuildTypeAtPosition((int)geyserPos.x, (int)geyserPos.y, refinery))
{
continue;
}
// check to see if it's next to one of our depots
bool nearDepot = false;
for (auto & unit : m_bot.UnitInfo().getUnits(Players::Self))
......@@ -259,12 +367,7 @@ CCTilePosition BuildingPlacer::getRefineryPosition()
}
}
}
#ifdef SC2API
return CCTilePosition((int)closestGeyser.x, (int)closestGeyser.y);
#else
return CCTilePosition(closestGeyser);
#endif
}
bool BuildingPlacer::isReserved(int x, int y) const
......
#pragma once
#include "Common.h"
#include "BuildingData.h"
class IDABot;
class BaseLocation;
class UnitType;
class Unit;
class BuildingPlacer
{
......@@ -13,24 +14,26 @@ class BuildingPlacer
std::vector< std::vector<bool> > m_reserveMap;
// queries for various BuildingPlacer data
bool buildable(const Building & b, int x, int y) const;
bool buildable(const UnitType & type, int x, int y) const;
bool isReserved(int x, int y) const;
bool isInResourceBox(int x, int y) const;
bool tileOverlapsBaseLocation(int x, int y, UnitType type) const;
public:
BuildingPlacer(IDABot & bot);
void onStart();
void updateReserved(const std::vector<Unit> & units);
void freeAllTiles();
// determines whether we can build at a given location
bool canBuildHere(int bx, int by, const Building & b) const;
bool canBuildHereWithSpace(int bx, int by, const Building & b, int buildDist) const;
bool canBuildHere(int bx, int by, const UnitType & type) const;
bool canBuildHereWithSize(int bx, int by, int width, int height);
bool canBuildHereWithSpace(int bx, int by, const UnitType & type, int buildDist) const;
// returns a build location near a building's desired location
CCTilePosition getBuildLocationNear(const Building & b, int buildDist) const;
CCTilePosition getBuildLocationNear(const CCTilePosition & p, const UnitType & type, int buildDist, size_t search_count = 1000) const;
void drawReservedTiles();
......
......@@ -2,12 +2,13 @@
file(GLOB BOT_SOURCES "*.cpp" "*.h" "*.hpp")
include_directories(SYSTEM
${PROJECT_SOURCE_DIR}/lib/s2client-api/include
${PROJECT_SOURCE_DIR}/lib/s2client-api/contrib/protobuf/src
${PROJECT_BINARY_DIR}/lib/s2client-api/generated
${PROJECT_SOURCE_DIR}/lib/cpp-sc2/include
${PROJECT_SOURCE_DIR}/lib/cpp-sc2/contrib/protobuf/src
${PROJECT_BINARY_DIR}/lib/cpp-sc2/generated
${PROJECT_SOURCE_DIR}/lib/json/include
)
link_directories(${PROJECT_BINARY_DIR}/s2client-api/bin)
link_directories(${PROJECT_BINARY_DIR}/cpp-sc2/bin)
# Enable compilation of the SC2 version of the bot.
add_definitions(-DSC2API)
......
......@@ -24,10 +24,11 @@ typedef sc2::Tag CCUnitID;
typedef sc2::Race CCRace;
typedef float CCHealth;
typedef float CCPositionType;
typedef sc2::BuffID CCBuff;
typedef size_t CCPlayer;
namespace Players
{
enum {Self = 0u, Enemy = 1u, Neutral = 2u, Ally = 3u, Size = 4u, None = 5u};
}
\ No newline at end of file
}
#include "IDABot.h"
#include "Util.h"
#include "sc2api/sc2_score.h"
void IDABot::OnStep_UpdateIDABot()
{
// This is the entry point of the bot.
// This function is called every time the game loop is run.
//if (!Bases().getPlayerStartingBaseLocation(Players::Enemy)->isExplored())
//{
// assignScout();
//}
std::vector<UnitType> build_plan = CreateBuildPlan();
manageWorkers(build_plan);
manageBuilding(build_plan);
}
void IDABot::manageBuilding(std::vector<UnitType> & build_plan)
{
std::map<UnitType, int> currently_being_built = numberOfTypeBeingBuilt();
for (auto & pair : currently_being_built)
std::cout << "Building " << pair.second << " of " << pair.first.getName() << std::endl;
int minerals = GetMinerals();
int gas = GetGas();
// TODO: Supply
for (UnitType type : build_plan)
{
if (currently_being_built[type] > 0)
{
currently_being_built[type]--;
std::cout << "Already building " << type.getName() << std::endl;
continue;
}
// If we cannot afford the next thing, we don't want to build at all
if (type.mineralPrice() > minerals || type.gasPrice() > gas)
{
break;
}
std::vector<Unit> producers = getProducer(MetaType(type, *this));
// Reserve some resources
// TODO: Only reserve resources if the corresponding worker is in BuildingWalking
minerals -= type.mineralPrice();
gas -= type.gasPrice();
if (!producers.empty())
{
if (type.isBuilding())
{
for (Unit & worker : getWorkers())
{
if (isFree(worker))
{
CCTilePosition position = getBuildPosition(type);
BuildStatus status{ type, position };
currently_building[worker] = status;
// Reserve the location
m_buildingPlacer.reserveTiles(position.x, position.y, type.tileWidth(), type.tileHeight());
// Update economy book-keeping
if (type.isRefinery() || type.isResourceDepot())
{
economy_spending.minerals += type.mineralPrice();
economy_spending.gas += type.gasPrice();
}
else if (type.supplyProvided() == 0)
{
military_spending.minerals += type.mineralPrice();
military_spending.gas += type.gasPrice();
}
assignWork(worker, Assignment::BuildWalking);
return;
}
}
}
else if (type.isCombatUnit() || type.isWorker())
{
// TODO: Remove code-duplication
// Update economy book-keeping
if (type.isWorker())
{
economy_spending.minerals += type.mineralPrice();
economy_spending.gas += type.gasPrice();
}
else
{
military_spending.minerals += type.mineralPrice();
military_spending.gas += type.gasPrice();
}
std::cout << "Training unit " << type.getName() << std::endl;
producers.front().train(type);
return;
}
}
}
}
bool IDABot::isFree(Unit & worker)
{
if (workerAssignment.count(worker) > 0)
{
return workerAssignment[worker] == Assignment::Mineral;
}
else
{
return true;
}
}
std::vector<UnitType> IDABot::CreateBuildPlan()
{
// Count the total number of minerals, including all bases
size_t number_of_minerals{ 0 };
for (const BaseLocation * base : Bases().getOccupiedBaseLocations(Players::Self))
{
number_of_minerals += base->getMinerals().size();
}
size_t number_of_workers = getWorkers().size();
size_t available_food = GetMaxSupply() - GetCurrentSupply();
std::vector<UnitType> build_plan;
std::cout << "Military spending (minerals) " << military_spending.minerals << std::endl;
std::cout << "Economy spending (minerals) " << economy_spending.minerals << std::endl;
// TODO: Make a better decision here
if ((500 + military_spending.minerals) < economy_spending.minerals)
{
CreateMaximizeMilitaryBuildPlan(build_plan, available_food);
CreateMaximizeEconomyBuildPlan(number_of_minerals, number_of_workers, available_food, build_plan);
}
else
{
CreateMaximizeEconomyBuildPlan(number_of_minerals, number_of_workers, available_food, build_plan);
CreateMaximizeMilitaryBuildPlan(build_plan, available_food);
}
for (size_t i{}; i < build_plan.size() && i < 10; ++i)
{
std::cout << build_plan[i].getName() << ", ";
}
std::cout << std::endl;
return build_plan;
}
void IDABot::CreateMaximizeMilitaryBuildPlan(std::vector<UnitType> &build_plan, size_t &available_food)
{
for (auto & pair : military_goal)
{
// How many do we already have?
size_t units_of_type = GetUnits(pair.first).size();
// First, do we meet the military goal for this UnitType?
if (units_of_type >= pair.second) {
continue;
}
// Second, can we produce anything of UnitType?
std::vector<Unit> producers = getProducer(MetaType(pair.first, *this), true);
// TODO: Don't build too many Barracks if we don't need to
if (producers.empty())
{
// TODO: Calculate what we need to build using the TechTree
// For now, let's build a barrack
build_plan.push_back(UnitType(sc2::UNIT_TYPEID::TERRAN_BARRACKS, *this));
}
else
{
// TODO: This is never run, or is it??
int units_needed = pair.second - units_of_type;
int food_required = pair.first.supplyRequired();
for (int i{}; i < units_needed; ++i)
{
if (available_food >= food_required)
{
return;
}
else
{
available_food -= food_required;
build_plan.push_back(pair.first);
}
}
}
}
}
void IDABot::CreateMaximizeEconomyBuildPlan(size_t &number_of_minerals, size_t &number_of_workers, size_t &available_food, std::vector<UnitType> &build_plan)
{
const int WORKER_PER_MINERAL_DEPOSIT = 3;
const int FOOD_THRESHOLD_BUILD_DEPOT = 1;
// Plan for the two next base expansions
for (int i = 0; i < 2; i++)
{
while (WORKER_PER_MINERAL_DEPOSIT * number_of_minerals > number_of_workers)
{
if (available_food == FOOD_THRESHOLD_BUILD_DEPOT)
{
UnitType type{ sc2::UNIT_TYPEID::TERRAN_SUPPLYDEPOT, *this };
build_plan.push_back(type);
available_food += type.supplyProvided(); // TODO: Maybe 16 sometimes? (+16 with Calldown: Supplies)
}
else
{
build_plan.push_back(UnitType(sc2::UNIT_TYPEID::TERRAN_SCV, *this));
available_food--;
number_of_workers++;
}
}
UnitType type{ sc2::UNIT_TYPEID::TERRAN_COMMANDCENTER, *this };
build_plan.push_back(type);
available_food += type.supplyProvided();
const BaseLocation * next_expansion = Bases().getNextExpansion(Players::Self);
if (next_expansion)
{
number_of_minerals += next_expansion->getMinerals().size();
}
}
}
void IDABot::assignScout()
{
// Assumes that at least one worker is unassigned
for (Unit worker : getWorkers())
{
if (workerAssignment.count(worker) == 0)
{
assignWork(worker, Assignment::Scout);
std::cout << "Assigned worker to Scout" << std::endl;
break;
}
}
}
void IDABot::manageWorkers(std::vector<UnitType> & build_plan)
{
std::vector<Unit> workers = getWorkers();
for (Unit worker : workers)
{
// If the worker has does not have an assignment
if (workerAssignment.count(worker) == 0)
{
assignWork(worker, Assignment::Mineral);
}
else
{
CCTilePosition position;
switch (workerAssignment[worker]) {
case Assignment::Mineral:
case Assignment::Gas:
// Never change let the gas/mineral workers rest
break;
case Assignment::Scout:
if (Bases().getPlayerStartingBaseLocation(Players::Enemy)->isExplored()) {
assignWork(worker, Assignment::Mineral);
}
break;
case Assignment::BuildWalking:
position = currently_building[worker].position;
if (position == Util::GetTilePosition(worker.getPosition()))
{
assignWork(worker, Assignment::BuildBuilding);
}
break;
case Assignment::BuildBuilding:
// Probably done,
if (worker.isIdle())
{
BuildStatus status = currently_building[worker];
m_buildingPlacer.freeTiles(status.position.x, status.position.y, status.type.tileWidth(), status.type.tileHeight());
currently_building.erase(worker);
assignWork(worker, Assignment::Mineral);
}
break;
}
}
}
}
void IDABot::assignWork(Unit & worker, Assignment assignment)
{
// Assigns worker to assignment
workerAssignment[worker] = assignment;
Unit mineral;
const BaseLocation * enemyBaseLocation;
CCTilePosition position;
AssignmentData data;
const BaseLocation * assigned_base;
switch (assignment) {
case Assignment::Mineral:
// Select which base to mine for
assigned_base = AssignBase(worker);
mineral = getClosestMineral(assigned_base->getPosition());
worker.rightClick(mineral);
break;
case Assignment::Scout:
enemyBaseLocation = Bases().getPlayerStartingBaseLocation(Players::Enemy);
if (enemyBaseLocation)
{
std::cout << "Enemy base location known!" << std::endl;
worker.move(enemyBaseLocation->getPosition());
}
else
{
std::cout << "Enemy base location unknown!" << std::endl;
for (const BaseLocation * location : Bases().getStartingBaseLocations())
{
if (!Map().isExplored(location->getPosition()))
{
worker.move(location->getPosition());
break;
}
}
}
break;
case Assignment::BuildWalking:
worker.move(currently_building[worker].position);
break;
case Assignment::BuildBuilding:
worker.build(currently_building[worker].type, currently_building[worker].position);
break;
}
}
std::map<UnitType, int> IDABot::numberOfTypeBeingBuilt() const
{
std::map<UnitType, int> being_built;
for (auto & pair : currently_building)
{
being_built[pair.second.type]++;
}
return being_built;
}
const BaseLocation * IDABot::AssignBase(Unit & worker)
{
std::set<const BaseLocation *> base_locations = Bases().getOccupiedBaseLocations(Players::Self);
int most_needed_workers_value{std::numeric_limits<int>().min()};
const BaseLocation * most_needed_workers_pointer{};
for (const BaseLocation * base_location : base_locations)
{
int workers_needed = 3 * base_location->getMinerals().size();
int number_of_workers;
if (base_assignment.count(base_location) > 0)
{
std::vector<Unit> & workers = base_assignment[base_location];
number_of_workers = workers.size();
}
else
{
number_of_workers = 0;
std::vector<Unit> workers;
base_assignment[base_location] = workers;
}
int needed_workers = workers_needed - number_of_workers;
if (needed_workers > most_needed_workers_value)
{
most_needed_workers_value = needed_workers;
most_needed_workers_pointer = base_location;
}
}
std::vector<Unit> & workers = base_assignment[most_needed_workers_pointer];
workers.push_back(worker);
return most_needed_workers_pointer;
}
CCTilePosition IDABot::getBuildPosition(UnitType & building)
{
CCTilePosition tile_position;
if (Util::GetTownHall(GetPlayerRace((int) Players::Self), *this) == building)
{
tile_position = Bases().getNextExpansion((int)Players::Self)->getDepotPosition();
}
else
{
CCPosition position = Bases().getPlayerStartingBaseLocation(Players::Self)->getPosition();
Building b{ building, Util::GetTilePosition(position)};
tile_position = m_buildingPlacer.getBuildLocationNear(b, 1);
}
return tile_position;
}
Unit IDABot::getClosestMineral(const CCPosition & position) const
{
Unit bestMineral;
double bestDist = 100000;
for (auto & mineral : GetAllUnits())
{
if (!mineral.getType().isMineral()) continue;
double dist = Util::Dist(mineral, position);
if (dist < bestDist)
{
bestMineral = mineral;
bestDist = dist;
}
}
return bestMineral;
}
std::vector<Unit> IDABot::getWorkers()
{
std::vector<Unit> workers;
for (auto & unit : GetMyUnits())
{
if (unit.getType().isWorker()) {
workers.push_back(unit);
}
}
return workers;
}
std::vector<Unit> IDABot::getProducer(const MetaType & type, bool includeBusy, bool includeIncomplete)
{
// get all the types of units that cna build this type
auto & producerTypes = Data(type).whatBuilds;
// make a set of all candidate producers
std::vector<Unit> candidateProducers;
for (auto unit : UnitInfo().getUnits(Players::Self))
{
// reasons a unit can not train the desired type
if (std::find(producerTypes.begin(), producerTypes.end(), unit.getType()) == producerTypes.end()) { continue; }
if (!includeIncomplete && !unit.isCompleted()) { continue; }
if (!includeBusy && Data(unit).isBuilding && unit.isTraining()) { continue; }
if (unit.isFlying()) { continue; }
// TODO: if unit is not powered continue
// TODO: if the type is an addon, some special cases
// TODO: if the type requires an addon and the producer doesn't have one
// if we haven't cut it, add it to the set of candidates
candidateProducers.push_back(unit);
}
return candidateProducers;
}
IDABot::IDABot()
: m_map(*this)
, m_bases(*this)
, m_unitInfo(*this)
, m_workers(*this)
, m_techTree(*this)
, m_buildingPlacer(*this)
{
military_goal[UnitType(sc2::UNIT_TYPEID::TERRAN_MARINE, *this)] = 30;
}
void IDABot::OnGameStart()
......@@ -488,9 +31,9 @@ void IDABot::OnGameStart()
m_map.onStart();
m_unitInfo.onStart();
m_bases.onStart();
// TODO: Maybe not include this for the students to use??
m_buildingPlacer.onStart();
/*
UnitType target { sc2::UNIT_TYPEID::TERRAN_FUSIONCORE, *this };
std::vector<UnitType> needed = { target };
build_order.clear();
......@@ -518,6 +61,7 @@ void IDABot::OnGameStart()
std::cout << "." << std::endl;
std::cout << "Created build plan of " << build_order.size() << " steps." << std::endl;
*/
}
void IDABot::OnStep()
......@@ -530,16 +74,21 @@ void IDABot::OnStep()
m_unitInfo.onFrame();
m_bases.onFrame();
// -----------------------------------------------------------------
// Run the actual bot.
// -----------------------------------------------------------------
OnStep_UpdateIDABot();
// suppress warnings while we update the tiles occupied by units
bool old_suppress = m_techTree.getSuppressWarnings();
m_techTree.setSuppressWarnings(true);
m_buildingPlacer.updateReserved(GetAllUnits());
m_techTree.setSuppressWarnings(old_suppress);
// -----------------------------------------------------------------
// Draw debug interface, and send debug interface to the Sc2 client.
// -----------------------------------------------------------------
Debug()->SendDebug();
m_buildingPlacer.drawReservedTiles();
m_buildingPlacer.drawReservedTiles();
}
void IDABot::OnGameEnd()
{
}
void IDABot::setUnits()
......@@ -587,9 +136,9 @@ int IDABot::GetCurrentFrame() const
return (int)Observation()->GetGameLoop();
}
WorkerManager & IDABot::Workers()
const TechTree & IDABot::GetTechTree() const
{
return m_workers;
return m_techTree;
}
int IDABot::GetCurrentSupply() const
......@@ -614,6 +163,10 @@ int IDABot::GetGas() const
Unit IDABot::GetUnit(const CCUnitID & tag) const
{
if (Observation()->GetUnit(tag) == nullptr) {
return Unit();
}
return Unit(Observation()->GetUnit(tag), *(IDABot *)this);
}
......@@ -657,6 +210,17 @@ void IDABot::OnError(const std::vector<sc2::ClientError> & client_errors, const
// This is called when the sc2api (Google's API) has an error.
}
BuildingPlacer & IDABot::GetBuildingPlacer()
{
return m_buildingPlacer;
}
void IDABot::SendChat(const std::string & message)
{
Actions()->SendChat(message);
}
const TypeData & IDABot::Data(const UnitType & type) const
{
return m_techTree.getData(type);
......@@ -677,3 +241,176 @@ const TypeData & IDABot::Data(const MetaType & type) const
return m_techTree.getData(type);
}
/*
API extended summer 2020
*/
void IDABot::DebugCreateUnit(UnitTypeID unit_type, const CCPosition& p, uint32_t player_id, uint32_t count)
{
switch (player_id) // playerconstants for the IDAbot is not the same as for the sc2 API
{
case 0:
Debug()->DebugCreateUnit(unit_type, p, 1, count);
break;
case 1:
Debug()->DebugCreateUnit(unit_type, p, 2, count);
break;
case 2:
Debug()->DebugCreateUnit(unit_type, p, 0, count);
break;
case 3:
Debug()->DebugCreateUnit(unit_type, p, 3, count);
default:
break;
}
}
void IDABot::DebugKillUnit(const Unit unit)
{
Debug()->DebugKillUnit(unit.getUnitPtr());
}
void IDABot::DebugShowMap()
{
Debug()->DebugShowMap();
}
void IDABot::DebugFastBuild()
{
Debug()->DebugFastBuild();
}
void IDABot::DebugEnemyControl()
{
Debug()->DebugEnemyControl();
}
void IDABot::DebugIgnoreFood()
{
Debug()->DebugIgnoreFood();
}
void IDABot::DebugIgnoreResourceCost()
{
Debug()->DebugIgnoreResourceCost();
}
void IDABot::DebugGiveAllResources()
{
Debug()->DebugGiveAllResources();
}
void IDABot::DebugGodMode()
{
Debug()->DebugGodMode();
}
void IDABot::DebugIgnoreMineral()
{
Debug()->DebugIgnoreMineral();
}
void IDABot::DebugNoCooldowns()
{
Debug()->DebugIgnoreMineral();
}
void IDABot::DebugGiveAllTech()
{
Debug()->DebugGiveAllTech();
}
void IDABot::DebugGiveAllUpgrades()
{
Debug()->DebugGiveAllUpgrades();
}
void IDABot::DebugSetScore(float score)
{
Debug()->DebugSetScore(score);
}
void IDABot::DebugEndGame(bool victory)
{
Debug()->DebugEndGame(victory);
}
void IDABot::DebugSetEnergy(float value, const Unit unit)
{
Debug()->DebugSetEnergy(value, unit.getUnitPtr());
}
void IDABot::DebugSetLife(float value, const Unit unit)
{
Debug()->DebugSetLife(value, unit.getUnitPtr());
}
void IDABot::DebugSetShields(float value, const Unit unit)
{
Debug()->DebugSetShields(value, unit.getUnitPtr());
}
// There is a bug in the latest SC2 (if using Blizzard API with game >=4.10)
// This a function to get the enemy base instead of using build location manager
// Switched over to other API where this is solved
// Leaving function incase of it breaking
const std::vector<Point2D> IDABot::GetEnemyBaseLocations()
{
return Observation()->GetGameInfo().enemy_start_locations;
}
bool IDABot::HasCreep(Point2D p) const
{
return Observation()->HasCreep(p);
}
void IDABot::CameraMove(Point2DI p)
{
ActionsFeatureLayer()->CameraMove(p);
}
sc2::ABILITY_ID IDABot::abilityForUpgrade(sc2::UpgradeID upgrade_id) const
{
return (sc2::ABILITY_ID)Observation()->GetUpgradeData()[upgrade_id].ability_id;
}
uint32_t IDABot::UpgradeMineralCost(sc2::UpgradeID upgrade_id) const
{
return Observation()->GetUpgradeData()[upgrade_id].mineral_cost;
}
uint32_t IDABot::UpgradeGasCost(sc2::UpgradeID upgrade_id) const
{
return Observation()->GetUpgradeData()[upgrade_id].vespene_cost;
}
float IDABot::UpgradeResearchTime(sc2::UpgradeID upgrade_id) const
{
return Observation()->GetUpgradeData()[upgrade_id].research_time;
}
float IDABot::RadiusEffect(sc2::EffectID effect_id) const
{
return Observation()->GetEffectData()[effect_id].radius;
}
float IDABot::GetScore() const
{
return Observation()->GetScore().score;
}
const sc2::GameResult IDABot::GetPlayerResults() const
{
auto res = Observation()->GetResults();
for(int i = 0; i < res.size(); i++)
{
if (res.at(i).player_id == Observation()->GetPlayerID()) return res.at(i).result;
}
std::cout << "The player can not be found" << std::endl;
return sc2::GameResult::Undecided;
}
bool IDABot::SaveReplay(const std::string& path)
{
return this->Control()->SaveReplay(path);
}
\ No newline at end of file
......@@ -8,20 +8,22 @@
#include "MapTools.h"
#include "BaseLocationManager.h"
#include "UnitInfoManager.h"
#include "WorkerManager.h"
#include "BuildingPlacer.h"
#include "TechTree.h"
#include "TechTreeImproved.h"
#include "MetaType.h"
#include "Unit.h"
using sc2::UnitTypeID;
using sc2::Point2D;
using sc2::Point2DI;
class IDABot : public sc2::Agent
{
MapTools m_map;
BaseLocationManager m_bases;
UnitInfoManager m_unitInfo;
WorkerManager m_workers;
TechTree m_techTree;
// TODO: This should not be exported for student use
BuildingPlacer m_buildingPlacer;
std::vector<Unit> m_allUnits;
......@@ -36,85 +38,20 @@ public:
void OnGameStart() override;
void OnStep() override;
virtual void OnStep_UpdateIDABot();
/*
My stuff
*/
// Worker management
enum Assignment {
Scout,
Mineral,
Gas,
BuildWalking,
BuildBuilding
};
struct AssignmentData {
// When assigned to building, this corresponds to the location for the building
CCTilePosition buildGoal;
};
// Building management
// Worker assignment book-keeping
std::map<Unit, Assignment> workerAssignment;
// When workers are assigned to either minerals or gas, they get assigned a base
std::map<const BaseLocation *, std::vector<Unit>> base_assignment;
std::vector<UnitType> build_order; // TODO: Not used
std::vector<UnitType> CreateBuildPlan();
void CreateMaximizeMilitaryBuildPlan(std::vector<UnitType> &build_plan, size_t &available_food);
void CreateMaximizeEconomyBuildPlan(size_t &number_of_minerals, size_t &number_of_workers, size_t &available_food, std::vector<UnitType> &build_plan);
void assignScout();
void manageWorkers(std::vector<UnitType> & build_plan);
void manageBuilding(std::vector<UnitType> & build_plan);
bool isFree(Unit & worker);
void assignWork(Unit & worker, Assignment assignment);
std::map<UnitType, int> numberOfTypeBeingBuilt() const;
const BaseLocation * AssignBase(Unit & worker);
CCTilePosition getBuildPosition(UnitType & building);
Unit getClosestMineral(const CCPosition & unit) const;
struct BuildStatus
{
UnitType type;
CCTilePosition position;
int idle;
};
std::map<Unit, BuildStatus> currently_building;
// Military management
std::map<UnitType, int> military_goal;
// Economy
struct Resources
{
int minerals;
int gas;
};
Resources military_spending{};
Resources economy_spending{};
// Getters
std::vector<Unit> getWorkers();
// Maybe
std::vector<Unit> getProducer(const MetaType & type, bool includeBusy = false, bool include_incomplete = false);
void OnGameEnd() override;
/*
API for students
*/
WorkerManager & Workers();
const TechTree & GetTechTree() const;
const BaseLocationManager & Bases() const;
const MapTools & Map() const;
const UnitInfoManager & UnitInfo() const;
CCRace GetPlayerRace(int player) const;
CCPosition GetStartLocation() const;
BuildingPlacer & GetBuildingPlacer();
void SendChat(const std::string & message);
int GetCurrentFrame() const;
int GetMinerals() const;
......@@ -127,9 +64,46 @@ public:
const std::vector<Unit> GetUnits(const UnitType & type, int player = Players::Self) const;
const std::vector<CCPosition> & GetStartLocations() const;
/*
API extended summer 2020
*/
void DebugCreateUnit(UnitTypeID unit_type, const CCPosition& p, uint32_t player_id = 1, uint32_t count = 1);
void DebugKillUnit(const Unit unit);
void DebugShowMap();
void DebugFastBuild();
void DebugEnemyControl();
void DebugIgnoreFood();
void DebugIgnoreResourceCost();
void DebugGiveAllResources();
void DebugGodMode();
void DebugIgnoreMineral();
void DebugNoCooldowns();
void DebugGiveAllTech();
void DebugGiveAllUpgrades();
void DebugSetScore(float score);
void DebugEndGame(bool victory);
void DebugSetEnergy(float value, const Unit unit);
void DebugSetLife(float value, const Unit unit);
void DebugSetShields(float value, const Unit unit);
const std::vector<Point2D> GetEnemyBaseLocations();
bool HasCreep(Point2D p) const;
void CameraMove(Point2DI p);
sc2::ABILITY_ID abilityForUpgrade(sc2::UpgradeID upgrade_id) const;
uint32_t UpgradeMineralCost(sc2::UpgradeID upgrade_id) const;
uint32_t UpgradeGasCost(sc2::UpgradeID upgrade_id) const;
float UpgradeResearchTime(sc2::UpgradeID upgrade_id) const;
float RadiusEffect(sc2::EffectID effect_id) const;
float GetScore() const;
const sc2::GameResult GetPlayerResults() const;
bool SaveReplay(const std::string & path);
// Not needed, just convenience functions
const TypeData & Data(const UnitType & type) const;
const TypeData & Data(const CCUpgrade & type) const;
const TypeData & Data(const MetaType & type) const;
const TypeData & Data(const Unit & unit) const;
};
\ No newline at end of file
};
#include "IDAReplayObserver.h"
#include "Util.h"
void IDAReplayObserver::setUnits()
{
m_allUnits.clear();
m_allUnitsID.clear();
for (auto & unit : Observation()->GetUnits())
{
ReplayUnit replayUnit = ReplayUnit(unit, *this);
m_allUnits.push_back(replayUnit);
m_allUnitsID.insert(replayUnit.getID());
}
}
IDAReplayObserver::IDAReplayObserver():
sc2::ReplayObserver(),
m_techTree(*this)
{
}
void IDAReplayObserver::OnGameStart()
{
setUnits();
m_techTree.onStart();
}
void IDAReplayObserver::OnStep()
{
setUnits();
}
void IDAReplayObserver::OnGameEnd()
{
}
void IDAReplayObserver::OnUnitDestroyed(const sc2::Unit* unit)
{
ReplayUnit unitInformation = ReplayUnit(unit, *this);
OnReplayUnitDestroyed(&unitInformation);
}
void IDAReplayObserver::OnReplayUnitDestroyed(const ReplayUnit *)
{
}
void IDAReplayObserver::OnUnitCreated(const sc2::Unit * unit)
{
ReplayUnit unitInformation = ReplayUnit(unit, *this);
OnReplayUnitCreated(&unitInformation);
}
void IDAReplayObserver::OnReplayUnitCreated(const ReplayUnit *)
{
}
void IDAReplayObserver::OnBuildingConstructionComplete(const sc2::Unit *unit)
{
ReplayUnit unitInformation = ReplayUnit(unit, *this);
OnReplayUnitCreated(&unitInformation);
}
ReplayUnit IDAReplayObserver::GetUnit(const CCUnitID tag) const
{
return ReplayUnit(Observation()->GetUnit(tag), *(IDAReplayObserver *)this);
}
bool IDAReplayObserver::UnitExists(const CCUnitID tag) const
{
return m_allUnitsID.find(tag) != m_allUnitsID.end();
}
const std::vector<ReplayUnit>& IDAReplayObserver::GetAllUnits() const
{
return m_allUnits;
}
CCRace IDAReplayObserver::GetPlayerRace(int player)
{
return ReplayControl()->GetReplayInfo().players[player].race;
}
std::string IDAReplayObserver::GetReplayPath()
{
return ReplayControl()->GetReplayInfo().replay_path;
}
sc2::GameResult IDAReplayObserver::GetResultForPlayer(int player)
{
return ReplayControl()->GetReplayInfo().players[player].game_result;
}
const TechTree & IDAReplayObserver::GetTechTree() const
{
return m_techTree;
}
const TypeData & IDAReplayObserver::Data(const UnitType & type) const
{
return m_techTree.getData(type);
}
const TypeData & IDAReplayObserver::Data(const CCUpgrade & type) const
{
return m_techTree.getData(type);
}
const TypeData & IDAReplayObserver::Data(const MetaType & type) const
{
return m_techTree.getData(type);
}
const TypeData & IDAReplayObserver::Data(const Unit & unit) const
{
return m_techTree.getData(unit.getType());
}
#pragma once
#include <deque>
#include <limits>
#include "Common.h"
#include "ReplayUnit.h"
#include "TechTree.h"
class ReplayUnit;
class IDAReplayObserver : public sc2::ReplayObserver
{
void setUnits();
std::vector<ReplayUnit> m_allUnits;
std::set<CCUnitID> m_allUnitsID;
TechTree m_techTree;
public:
IDAReplayObserver();
void OnGameStart() override;
void OnStep() override;
void OnGameEnd() override;
void OnUnitDestroyed(const sc2::Unit*) override;
virtual void OnReplayUnitDestroyed(const ReplayUnit*);
void OnUnitCreated(const sc2::Unit*);
virtual void OnReplayUnitCreated(const ReplayUnit*);
void OnBuildingConstructionComplete(const sc2::Unit*);
ReplayUnit GetUnit(const CCUnitID tag) const;
bool UnitExists(const CCUnitID tag) const;
const std::vector<ReplayUnit> & GetAllUnits() const;
CCRace GetPlayerRace(int player);
std::string GetReplayPath();
sc2::GameResult GetResultForPlayer(int player);
const TechTree & GetTechTree() const;
const TypeData & Data(const UnitType & type) const;
const TypeData & Data(const CCUpgrade & type) const;
const TypeData & Data(const MetaType & type) const;
const TypeData & Data(const Unit & unit) const;
};
......@@ -7,6 +7,23 @@
#include <fstream>
#include <array>
namespace {
bool getBit(const sc2::ImageData& grid, int tileX, int tileY) {
assert(grid.bits_per_pixel == 1);
sc2::Point2DI pointI(tileX, tileY);
if (pointI.x < 0 || pointI.x >= grid.width || pointI.y < 0 || pointI.y >= grid.height)
{
return false;
}
div_t idx = div(pointI.x + pointI.y * grid.width, 8);
return (grid.data[idx.quot] >> (7 - idx.rem)) & 1;
}
} // namespace
const size_t LegalActions = 4;
const int actionX[LegalActions] ={1, -1, 0, 0};
const int actionY[LegalActions] ={0, 0, 1, -1};
......@@ -23,7 +40,8 @@ typedef std::vector<std::vector<float>> vvf;
// constructor for MapTools
MapTools::MapTools(IDABot & bot)
: m_bot (bot)
: m_bot(bot)
, m_mapName ("")
, m_width (0)
, m_height (0)
, m_maxZ (0.0f)
......@@ -37,6 +55,7 @@ void MapTools::onStart()
#ifdef SC2API
m_width = m_bot.Observation()->GetGameInfo().width;
m_height = m_bot.Observation()->GetGameInfo().height;
m_mapName= m_bot.Observation()->GetGameInfo().map_name;
#else
m_width = BWAPI::Broodwar->mapWidth();
m_height = BWAPI::Broodwar->mapHeight();
......@@ -160,8 +179,6 @@ void MapTools::onFrame()
}
}
}
draw();
}
void MapTools::computeConnectivity()
......@@ -263,7 +280,7 @@ bool MapTools::isPowered(int tileX, int tileY) const
#endif
}
float MapTools::terrainHeight(float x, float y) const
float MapTools::terrainHeightFromCoord(float x, float y) const
{
return m_terrainHeight[(int)x][(int)y];
}
......@@ -329,7 +346,8 @@ bool MapTools::isValidPosition(const CCPosition & pos) const
void MapTools::drawLine(CCPositionType x1, CCPositionType y1, CCPositionType x2, CCPositionType y2, const CCColor & color) const
{
#ifdef SC2API
m_bot.Debug()->DebugLineOut(sc2::Point3D(x1, y1, terrainHeight(x1, y1) + 0.2f), sc2::Point3D(x2, y2, terrainHeight(x2, y2) + 0.2f), color);
//m_bot.Debug()->DebugLineOut(sc2::Point3D(x1, y1, terrainHeight(x1, y1) + 0.2f), sc2::Point3D(x2, y2, terrainHeight(x2, y2) + 0.2f), color);
m_bot.Debug()->DebugLineOut(sc2::Point3D(x1, y1, m_maxZ), sc2::Point3D(x2, y2, m_maxZ), color); // changed to m_maxZ instead fo terrainHeight since it doesnot work correctly
#else
BWAPI::Broodwar->drawLineMap(BWAPI::Position(x1, y1), BWAPI::Position(x2, y2), color);
#endif
......@@ -356,6 +374,10 @@ void MapTools::drawTile(int tileX, int tileY, const CCColor & color) const
drawLine(px, py + d, px, py, color);
}
void MapTools::drawTile(const CCTilePosition & pos, const CCColor & color) const {
drawTile(pos.x, pos.y, color);
}
void MapTools::drawBox(CCPositionType x1, CCPositionType y1, CCPositionType x2, CCPositionType y2, const CCColor & color) const
{
#ifdef SC2API
......@@ -386,6 +408,15 @@ void MapTools::drawCircle(const CCPosition & pos, CCPositionType radius, const C
#endif
}
void MapTools::drawResourceSphere(const sc2::Point3D & pos, CCPositionType radius, const CCColor & color) const
{
#ifdef SC2API
m_bot.Debug()->DebugSphereOut(pos, radius, color);
#else
BWAPI::Broodwar->drawCircleMap(pos, radius, color);
#endif
}
void MapTools::drawCircle(CCPositionType x, CCPositionType y, CCPositionType radius, const CCColor & color) const
{
#ifdef SC2API
......@@ -514,6 +545,11 @@ int MapTools::height() const
return m_height;
}
std::string MapTools::name() const
{
return m_mapName;
}
const std::vector<CCTilePosition> & MapTools::getClosestTilesTo(const CCTilePosition & pos) const
{
return getDistanceMap(pos).getSortedTiles();
......@@ -543,17 +579,7 @@ CCTilePosition MapTools::getLeastRecentlySeenTile() const
bool MapTools::canWalk(int tileX, int tileY)
{
#ifdef SC2API
auto & info = m_bot.Observation()->GetGameInfo();
sc2::Point2DI pointI(tileX, tileY);
if (pointI.x < 0 || pointI.x >= info.width || pointI.y < 0 || pointI.y >= info.width)
{
return false;
}
assert(info.pathing_grid.data.size() == info.width * info.height);
unsigned char encodedPlacement = info.pathing_grid.data[pointI.x + ((info.height - 1) - pointI.y) * info.width];
bool decodedPlacement = encodedPlacement == 255 ? false : true;
return decodedPlacement;
return getBit(m_bot.Observation()->GetGameInfo().pathing_grid, tileX, tileY);
#else
for (int i=0; i<4; ++i)
{
......@@ -573,17 +599,7 @@ bool MapTools::canWalk(int tileX, int tileY)
bool MapTools::canBuild(int tileX, int tileY)
{
#ifdef SC2API
auto & info = m_bot.Observation()->GetGameInfo();
sc2::Point2DI pointI(tileX, tileY);
if (pointI.x < 0 || pointI.x >= info.width || pointI.y < 0 || pointI.y >= info.width)
{
return false;
}
assert(info.placement_grid.data.size() == info.width * info.height);
unsigned char encodedPlacement = info.placement_grid.data[pointI.x + ((info.height - 1) - pointI.y) * info.width];
bool decodedPlacement = encodedPlacement == 255 ? true : false;
return decodedPlacement;
return getBit(m_bot.Observation()->GetGameInfo().placement_grid, tileX, tileY);
#else
return BWAPI::Broodwar->isBuildable(BWAPI::TilePosition(tileX, tileY));
#endif
......@@ -608,48 +624,3 @@ float MapTools::terrainHeight(const CCPosition & point) const
#endif
}
void MapTools::draw() const
{
#ifdef SC2API
CCPosition camera = m_bot.Observation()->GetCameraPos();
int sx = (int)(camera.x - 12.0f);
int sy = (int)(camera.y - 8);
int ex = sx + 24;
int ey = sy + 20;
#else
BWAPI::TilePosition screen(BWAPI::Broodwar->getScreenPosition());
int sx = screen.x;
int sy = screen.y;
int ex = sx + 20;
int ey = sy + 15;
#endif
for (int x = sx; x < ex; ++x)
{
for (int y = sy; y < ey; y++)
{
if (!isValidTile((int)x, (int)y))
{
continue;
}
// TODO: This maybe should be restored somehow? (Check git blame)
if (false)
{
std::stringstream ss;
ss << getSectorNumber(x, y);
drawText(CCPosition(Util::TileToPosition(x + 0.5f), Util::TileToPosition(y + 0.5f)), ss.str());
}
if (false)
{
CCColor color = isWalkable(x, y) ? CCColor(0, 255, 0) : CCColor(255, 0, 0);
if (isWalkable(x, y) && !isBuildable(x, y)) { color = CCColor(255, 255, 0); }
if (isBuildable(x, y) && !isDepotBuildableTile(x, y)) { color = CCColor(127, 255, 255); }
drawTile(x, y, color);
}
}
}
}
......@@ -8,11 +8,12 @@ class IDABot;
class MapTools
{
IDABot & m_bot;
int m_width;
int m_height;
float m_maxZ;
int m_frame;
IDABot & m_bot;
std::string m_mapName;
int m_width;
int m_height;
float m_maxZ;
int m_frame;
// a cache of already computed distance maps, which is mutable since it only acts as a cache
......@@ -41,19 +42,22 @@ public:
void onStart();
void onFrame();
void draw() const;
int width() const;
int height() const;
float terrainHeight(float x, float y) const;
std::string name() const;
float terrainHeightFromCoord(float x, float y) const;
void drawLine(CCPositionType x1, CCPositionType y1, CCPositionType x2, CCPositionType y2, const CCColor & color = CCColor(255, 255, 255)) const;
void drawLine(const CCPosition & p1, const CCPosition & p2, const CCColor & color = CCColor(255, 255, 255)) const;
void drawTile(int tileX, int tileY, const CCColor & color = CCColor(255, 255, 255)) const;
void drawTile(const CCTilePosition & pos, const CCColor & color) const;
void drawBox(CCPositionType x1, CCPositionType y1, CCPositionType x2, CCPositionType y2, const CCColor & color = CCColor(255, 255, 255)) const;
void drawBox(const CCPosition & tl, const CCPosition & br, const CCColor & color = CCColor(255, 255, 255)) const;
void drawCircle(CCPositionType x1, CCPositionType x2, CCPositionType radius, const CCColor & color = CCColor(255, 255, 255)) const;
void drawCircle(const CCPosition & pos, CCPositionType radius, const CCColor & color = CCColor(255, 255, 255)) const;
void drawResourceSphere(const sc2::Point3D & pos, CCPositionType radius, const CCColor & color = CCColor(255, 255, 255)) const;
void drawText(const CCPosition & pos, const std::string & str, const CCColor & color = CCColor(255, 255, 255)) const;
void drawTextScreen(float xPerc, float yPerc, const std::string & str, const CCColor & color = CCColor(255, 255, 255)) const;
......
#include "MyAgent.h"
MyAgent::MyAgent()
{
military_goal[UnitType(sc2::UNIT_TYPEID::TERRAN_MARINE, *this)] = 30;
}
void MyAgent::OnGameStart()
{
IDABot::OnGameStart();
sc2::UnitTypeID wanted_type = sc2::UNIT_TYPEID::TERRAN_GHOST;
sc2::UnitTypeData wanted_data = Observation()->GetUnitTypeData()[wanted_type];
std::cout << "Looking up: " << wanted_data.name << std::endl;
sc2::UnitTypeData requirement = Observation()->GetUnitTypeData()[wanted_data.tech_requirement];
std::cout << "Found tech requirement: " << requirement.name << "(" << wanted_data.tech_requirement << ")" << std::endl;
std::cout << std::boolalpha;
std::cout << "Require attached (Is addon?): " << wanted_data.require_attached << std::endl;
std::cout << "Found ability_id" << wanted_data.ability_id << std::endl;
// TODO: What/who can use this ability??
sc2::AbilityData ability_to_create = Observation()->GetAbilityData()[wanted_data.ability_id];
std::cout << "Button name for ability " << ability_to_create.button_name << std::endl;
}
void MyAgent::OnStep()
{
IDABot::OnStep();
// This is the entry point of the bot.
// This function is called every time the game loop is run.
//if (!Bases().getPlayerStartingBaseLocation(Players::Enemy)->isExplored())
//{
// assignScout();
//}
std::vector<UnitType> build_plan = CreateBuildPlan();
manageWorkers(build_plan);
manageBuilding(build_plan);
}
void MyAgent::manageBuilding(std::vector<UnitType> & build_plan)
{
std::map<UnitType, int> currently_being_built = numberOfTypeBeingBuilt();
for (auto & pair : currently_being_built)
std::cout << "Building " << pair.second << " of " << pair.first.getName() << std::endl;
int minerals = GetMinerals();
int gas = GetGas();
// TODO: Supply
for (UnitType type : build_plan)
{
if (currently_being_built[type] > 0)
{
currently_being_built[type]--;
std::cout << "Already building " << type.getName() << std::endl;
continue;
}
// If we cannot afford the next thing, we don't want to build at all
if (type.mineralPrice() > minerals || type.gasPrice() > gas)
{
break;
}
std::vector<Unit> producers = getProducer(MetaType(type, *this));
// Reserve some resources
// TODO: Only reserve resources if the corresponding worker is in BuildingWalking
minerals -= type.mineralPrice();
gas -= type.gasPrice();
if (!producers.empty())
{
if (type.isBuilding())
{
for (Unit & worker : getWorkers())
{
if (isFree(worker))
{
CCTilePosition position = getBuildPosition(type);
BuildStatus status{ type, position };
currently_building[worker] = status;
// Reserve the location
GetBuildingPlacer().reserveTiles(position.x, position.y, type.tileWidth(), type.tileHeight());
// Update economy book-keeping
if (type.isRefinery() || type.isResourceDepot())
{
economy_spending.minerals += type.mineralPrice();
economy_spending.gas += type.gasPrice();
}
else if (type.supplyProvided() == 0)
{
military_spending.minerals += type.mineralPrice();
military_spending.gas += type.gasPrice();
}
assignWork(worker, Assignment::BuildWalking);
return;
}
}
}
else if (type.isCombatUnit() || type.isWorker())
{
// TODO: Remove code-duplication
// Update economy book-keeping
if (type.isWorker())
{
economy_spending.minerals += type.mineralPrice();
economy_spending.gas += type.gasPrice();
}
else
{
military_spending.minerals += type.mineralPrice();
military_spending.gas += type.gasPrice();
}
std::cout << "Training unit " << type.getName() << std::endl;
producers.front().train(type);
return;
}
}
}
}
bool MyAgent::isFree(Unit & worker)
{
if (workerAssignment.count(worker) > 0)
{
return workerAssignment[worker] == Assignment::Mineral;
}
else
{
return true;
}
}
std::vector<UnitType> MyAgent::CreateBuildPlan()
{
// Count the total number of minerals, including all bases
size_t number_of_minerals{ 0 };
for (const BaseLocation * base : Bases().getOccupiedBaseLocations(Players::Self))
{
number_of_minerals += base->getMinerals().size();
}
size_t number_of_workers = getWorkers().size();
size_t available_food = GetMaxSupply() - GetCurrentSupply();
std::vector<UnitType> build_plan;
std::cout << "Military spending (minerals) " << military_spending.minerals << std::endl;
std::cout << "Economy spending (minerals) " << economy_spending.minerals << std::endl;
// TODO: Make a better decision here
if ((500 + military_spending.minerals) < economy_spending.minerals)
{
CreateMaximizeMilitaryBuildPlan(build_plan, available_food);
CreateMaximizeEconomyBuildPlan(number_of_minerals, number_of_workers, available_food, build_plan);
}
else
{
CreateMaximizeEconomyBuildPlan(number_of_minerals, number_of_workers, available_food, build_plan);
CreateMaximizeMilitaryBuildPlan(build_plan, available_food);
}
for (size_t i{}; i < build_plan.size() && i < 10; ++i)
{
std::cout << build_plan[i].getName() << ", ";
}
std::cout << std::endl;
return build_plan;
}
void MyAgent::CreateMaximizeMilitaryBuildPlan(std::vector<UnitType> &build_plan, size_t &available_food)
{
for (auto & pair : military_goal)
{
// How many do we already have?
size_t units_of_type = GetUnits(pair.first).size();
// First, do we meet the military goal for this UnitType?
if (units_of_type >= pair.second) {
continue;
}
// Second, can we produce anything of UnitType?
std::vector<Unit> producers = getProducer(MetaType(pair.first, *this), true);
// TODO: Don't build too many Barracks if we don't need to
if (producers.empty())
{
// TODO: Calculate what we need to build using the TechTree
// For now, let's build a barrack
build_plan.push_back(UnitType(sc2::UNIT_TYPEID::TERRAN_BARRACKS, *this));
}
else
{
// TODO: This is never run, or is it??
int units_needed = pair.second - units_of_type;
int food_required = pair.first.supplyRequired();
for (int i{}; i < units_needed; ++i)
{
if (available_food >= food_required)
{
return;
}
else
{
available_food -= food_required;
build_plan.push_back(pair.first);
}
}
}
}
}
void MyAgent::CreateMaximizeEconomyBuildPlan(size_t &number_of_minerals, size_t &number_of_workers, size_t &available_food, std::vector<UnitType> &build_plan)
{
const int WORKER_PER_MINERAL_DEPOSIT = 3;
const int FOOD_THRESHOLD_BUILD_DEPOT = 1;
// Plan for the two next base expansions
for (int i = 0; i < 2; i++)
{
while (WORKER_PER_MINERAL_DEPOSIT * number_of_minerals > number_of_workers)
{
if (available_food == FOOD_THRESHOLD_BUILD_DEPOT)
{
UnitType type{ sc2::UNIT_TYPEID::TERRAN_SUPPLYDEPOT, *this };
build_plan.push_back(type);
available_food += type.supplyProvided(); // TODO: Maybe 16 sometimes? (+16 with Calldown: Supplies)
}
else
{
build_plan.push_back(UnitType(sc2::UNIT_TYPEID::TERRAN_SCV, *this));
available_food--;
number_of_workers++;
}
}
UnitType type{ sc2::UNIT_TYPEID::TERRAN_COMMANDCENTER, *this };
build_plan.push_back(type);
available_food += type.supplyProvided();
const BaseLocation * next_expansion = Bases().getNextExpansion(Players::Self);
if (next_expansion)
{
number_of_minerals += next_expansion->getMinerals().size();
}
}
}
void MyAgent::assignScout()
{
// Assumes that at least one worker is unassigned
for (Unit worker : getWorkers())
{
if (workerAssignment.count(worker) == 0)
{
assignWork(worker, Assignment::Scout);
std::cout << "Assigned worker to Scout" << std::endl;
break;
}
}
}
void MyAgent::manageWorkers(std::vector<UnitType> & build_plan)
{
std::vector<Unit> workers = getWorkers();
for (Unit worker : workers)
{
// If the worker has does not have an assignment
if (workerAssignment.count(worker) == 0)
{
assignWork(worker, Assignment::Mineral);
}
else
{
CCTilePosition position;
switch (workerAssignment[worker]) {
case Assignment::Mineral:
case Assignment::Gas:
// Never change let the gas/mineral workers rest
break;
case Assignment::Scout:
if (Bases().getPlayerStartingBaseLocation(Players::Enemy)->isExplored()) {
assignWork(worker, Assignment::Mineral);
}
break;
case Assignment::BuildWalking:
position = currently_building[worker].position;
if (position == Util::GetTilePosition(worker.getPosition()))
{
assignWork(worker, Assignment::BuildBuilding);
}
break;
case Assignment::BuildBuilding:
// Probably done,
if (worker.isIdle())
{
BuildStatus status = currently_building[worker];
GetBuildingPlacer().freeTiles(status.position.x, status.position.y, status.type.tileWidth(), status.type.tileHeight());
currently_building.erase(worker);
assignWork(worker, Assignment::Mineral);
}
break;
}
}
}
}
void MyAgent::assignWork(Unit & worker, Assignment assignment)
{
// Assigns worker to assignment
workerAssignment[worker] = assignment;
Unit mineral;
const BaseLocation * enemyBaseLocation;
CCTilePosition position;
AssignmentData data;
const BaseLocation * assigned_base;
switch (assignment) {
case Assignment::Mineral:
// Select which base to mine for
assigned_base = AssignBase(worker);
mineral = getClosestMineral(assigned_base->getPosition());
worker.rightClick(mineral);
break;
case Assignment::Scout:
enemyBaseLocation = Bases().getPlayerStartingBaseLocation(Players::Enemy);
if (enemyBaseLocation)
{
std::cout << "Enemy base location known!" << std::endl;
worker.move(enemyBaseLocation->getPosition());
}
else
{
std::cout << "Enemy base location unknown!" << std::endl;
for (const BaseLocation * location : Bases().getStartingBaseLocations())
{
if (!Map().isExplored(location->getPosition()))
{
worker.move(location->getPosition());
break;
}
}
}
break;
case Assignment::BuildWalking:
worker.move(currently_building[worker].position);
break;
case Assignment::BuildBuilding:
worker.build(currently_building[worker].type, currently_building[worker].position);
break;
}
}
std::map<UnitType, int> MyAgent::numberOfTypeBeingBuilt() const
{
std::map<UnitType, int> being_built;
for (auto & pair : currently_building)
{
being_built[pair.second.type]++;
}
return being_built;
}
const BaseLocation * MyAgent::AssignBase(Unit & worker)
{
std::set<const BaseLocation *> base_locations = Bases().getOccupiedBaseLocations(Players::Self);
int most_needed_workers_value{std::numeric_limits<int>().min()};
const BaseLocation * most_needed_workers_pointer{};
for (const BaseLocation * base_location : base_locations)
{
int workers_needed = 3 * base_location->getMinerals().size();
int number_of_workers;
if (base_assignment.count(base_location) > 0)
{
std::vector<Unit> & workers = base_assignment[base_location];
number_of_workers = workers.size();
}
else
{
number_of_workers = 0;
std::vector<Unit> workers;
base_assignment[base_location] = workers;
}
int needed_workers = workers_needed - number_of_workers;
if (needed_workers > most_needed_workers_value)
{
most_needed_workers_value = needed_workers;
most_needed_workers_pointer = base_location;
}
}
std::vector<Unit> & workers = base_assignment[most_needed_workers_pointer];
workers.push_back(worker);
return most_needed_workers_pointer;
}
CCTilePosition MyAgent::getBuildPosition(UnitType & building)
{
CCTilePosition tile_position;
if (Util::GetTownHall(GetPlayerRace((int) Players::Self), *this) == building)
{
tile_position = Bases().getNextExpansion((int)Players::Self)->getDepotPosition();
}
else
{
CCPosition position = Bases().getPlayerStartingBaseLocation(Players::Self)->getPosition();
tile_position = GetBuildingPlacer().getBuildLocationNear(Util::GetTilePosition(position), building, 1);
}
return tile_position;
}
Unit MyAgent::getClosestMineral(const CCPosition & position) const
{
Unit bestMineral;
double bestDist = 100000;
for (auto & mineral : GetAllUnits())
{
if (!mineral.getType().isMineral()) continue;
double dist = Util::Dist(mineral, position);
if (dist < bestDist)
{
bestMineral = mineral;
bestDist = dist;
}
}
return bestMineral;
}
std::vector<Unit> MyAgent::getWorkers()
{
std::vector<Unit> workers;
for (auto & unit : GetMyUnits())
{
if (unit.getType().isWorker())
{
workers.push_back(unit);
}
}
return workers;
}
std::vector<Unit> MyAgent::getProducer(const MetaType & type, bool includeBusy, bool includeIncomplete)
{
// get all the types of units that cna build this type
auto & producerTypes = Data(type).whatBuilds;
// make a set of all candidate producers
std::vector<Unit> candidateProducers;
for (auto unit : UnitInfo().getUnits(Players::Self))
{
// reasons a unit can not train the desired type
if (std::find(producerTypes.begin(), producerTypes.end(), unit.getType()) == producerTypes.end()) { continue; }
if (!includeIncomplete && !unit.isCompleted()) { continue; }
if (!includeBusy && Data(unit).isBuilding && unit.isTraining()) { continue; }
if (unit.isFlying()) { continue; }
// TODO: if unit is not powered continue
// TODO: if the type is an addon, some special cases
// TODO: if the type requires an addon and the producer doesn't have one
// if we haven't cut it, add it to the set of candidates
candidateProducers.push_back(unit);
}
return candidateProducers;
}