JNIBWAPI Tutorial
This guide should teach you the basics of StarCraft bot programming in JAVA
with JNIBWAPI. We will be using a JNIBWAPI Starter Pack,
that has been specifically prepared for the SSCAI tournament (it's compatible
with BWAPI 3.7.4).
Installation
Download the JNIBWAPI Starter Pack, extract it, and follow
the instructions in the README.txt file. Once everything works, we
will be modifying the example bot implemented in JavaBot.java file.
You should study its source code along with this text.
Units in a StarCraft match
Almost everything we see in the game is an object of a type Unit.
Our bot can access those objects and call their methods.
Note, that buildings, mineral patches, and geysers are units too in BWAPI.
bwapi.getMyUnits()retrieves the ArrayList of all my own units and buildings.bwapi.getEnemyUnits()retrieves the ArrayList of all currently visible enemy units and buildings.bwapi.getNeutralUnits()retrieves the ArrayList of all currently visible mineral patches, vespene geysers, critters, or any other neutral units/buildings.
for (Unit unit : bwapi.getMyUnits()) {
// do something with this unit
}
Note: Take a look at the act() function in your JavaBot.java
file. You can see, that we iterate over the ArrayList of our own units twice.
We do this to issue some orders to our units.
Unit Types
Every unit has a certain type. For example, a Terran Marine has unit
type
UnitTypes.Terran_Marine. Imagine, that we want to
send all our workers (Terran SCVs) to mine minerals. We will iterate over
all our units, and compare their type to UnitTypes.Terran_SCV
like this:
for (Unit unit : bwapi.getMyUnits()) {
if (unit.getTypeID() == UnitTypes.Terran_SCV.ordinal()) {
// some code, that will send them to gather minerals
}
}
Note: There is a wide variety of orders we can issue to our units,
and we will get to them soon. But first imagine, that you want to send your
unit to move to a certain place. You need to know how to specify this
place within the map.
Map
The position within a map in SC can be specified in three ways (with different precision):
- Pixel Position (most precise)
We use the pixel position for example when issuing move order to our units. Thebwapi.move(unit.getID(), 2896, 4289)call sends our unit to exact pixel position (2896,4289). Every unit also has two functions:unit.getX()andunit.getY(), which return current pixel coordinates of this unit. - Build Tile (used for building placement)
One build tile is a square of 32x32 pixels. Build tiles are used for specifying where to build our buildings. If we want to order one of our workers to build a Supply Depot at a build tile position (79,122), we call thebwapi.build(unit.getID(), 79, 122, UnitTypes_Terran_Supply_Depot)function.
To get the build tile coordinates of a certain unit, we can either call theunit.getTileX()andunit.getTileY()functions, or simply divide their pixel coordinates by 32. - Region (advanced)
Region is the partition of a SC map that represents for example one base area (the map is divided into several polygons - Regions). It has its own class called Region. To retrieve the ArrayList of all the regions, we can callbwapi.getMap().getRegions(). However, for most basic stuff you don't need to use them.
Note: Basically, we use pixel positions for attacking and unit
movement, and build tiles for building placement. You can ignore regions
for now.
Note 2: Take a look at the second for loop in the act()
function. Notice, how we compute the euclidean distance between pixel positions
of two units, in order to find the mineral patch that is closest to our worker.
Orders
There is a collection of orders, that you can issue to your units. For example, if you want to order your
unit to attack a position (3421,1290), you will call
bwapi.attack(unit.getID(),3421,1290).
The most used orders are:
- rightclick( unitID, pixelPositionX, pixelPositionY ): Does the same thing, as if we had this unit selected, and right-clicked on some position in the game.
- rightclick( unitID, targetUnitID ): Does the same thing, as if we had this unit selected, and right-clicked on target unit.
- move( unitID, pixelPositionX, pixelPositionY )
- build( unitID, tilePositionX, tilePositionY, unitTypeToBuild ): If our unit is a worker, this sends it to construct a building on a given build tile.
- gather( unitID, targetUnitID ): Sends our unit to gather resources from target mineral patch or Refinery/Assimilator/Extractor.
- attack( unitID, pixelPositionX, pixelPositionY ): Makes our unit to move to a specified location, while attacking all the enemies in its path.
- attack( unitID, targetUnitID ): Makes our unit chase and attack target unit (ignoring everything else).
- train( buildingID, unitTypeID ): Trains a unit of a specified type in our building.
- upgrade( buildingID, upgradeID ): Starts the upgrade of a specified type in our building (e.g. damage or armor). Use
UpgradeTypes.somethingto specify upgrades. - research( buildingID, technologyID ): Starts researching a specified technology/ability in our building (e.g. Stimpacks or Parasite). Use
TechTypes.somethingto specify technology.
Resources
Before building new buildings or training new units, you should check, if you have enough
resources. To do that, use:
bwapi.getSelf().getMinerals()bwapi.getSelf().getGas()(bwapi.getSelf().getSupplyTotal() - bwapi.getSelf().getSupplyUsed())/2: To get the actual free supply, we need to substract the used supply from our total supply. Finally, we divide the number by 2, because these two functions return double the value, that is displayed in the game (the value is mutiplied by 2 in order to avoid using foat type, since Zerglings take up 0.5 supply).
// check if we have at least 50 minerals and 1 free supply
if ( (bwapi.getSelf().getMinerals() >= 50) &&
( ((bwapi.getSelf().getSupplyTotal() - bwapi.getSelf().getSupplyUsed())/2) >= 1) ) {
for (Unit unit : bwapi.getMyUnits()) {
if (unit.getTypeID() == UnitTypes.Terran_Barracks.ordinal()) {
// and train a new marine in this Barracks (if we aren't already training some other unit here)
if (unit.getTrainingQueueSize() == 0) bwapi.train(unit.getID(), UnitTypes.Terran_Marine.ordinal());
}
}
}
Note: In the last part of our act() function, we build a new
Supply Depot whenever our free supply is less than 3, and we have 100
minerals. Try changing these values and see what happens.
Speed
For debugging purposes, it is possible to change the game speed to a certain integer value.
Game speed value 30 approximately corresponds to a typical speed of human-played matches.
Speed value of 0 is the fastest possible setting. Values higher than 30 slow the
game down (SSCAI tournament is played at speed 20). There are two ways to change the speed:
- Call the
bwapi.setGameSpeed(10)function from your bot's source. - Type the
/speed 10command into the in-game chat.
Debugging
There are several functions, that let you display some debug information
during the game. Most used are:
bwapi.printText(message), which displays a string message for a couple of seconds in the left part of the screen.bwapi.drawText(x, y, msg, screenCoords)displays a string msg in the specified portion of the screen or on specific position on the map (according to the value of boolean parameter screenCoords). This message is only displayed in the current frame, so we need to call it repeatedly on every frame.bwapi.drawCircle(...)andbwapi.drawLine(...)functions work similarly, but instead of text, they can draw simple geometric shapes.System.out.println(arg0)can also be used to print anything into the console.
Note: Take a look at the drawDebugInfo() function, that we prepared for you.
This is called on every frame and displays the text in the top-left part of the
screen, as well as some circles over your workers.
You should now be able to modify it. Try adding the current number of your
workers somewhere on the screen (hint: you'll need to iterate over bwapi.getMyUnits() and count them).
Important functions in your agent's code
In the source code of your example agent (JavaBot.java), you will find two
functions, that are the most important. You will implement the majority of
bot's behaviour inside them:
public void gameStarted(): This is called at the beginning of every match (even before the first frame is displayed). Here, you can do all the preparations (for example initialize some data structures, that you will use. You can for example initialize an array of seen enemy building locations, so that you know where to attack later).public void act(): This function is called approximately once a second. Almost everything that your agent does, can be implemented here. However, if you want to execute some code more often, take a look at thegameUpdate()function - it's called on every frame.
getNearestUnit(int unitTypeID, int x, int y): This will find my unit of a given type, that is closest to a pixel position (x,y). Returns the ID of that unit, or -1 if I don't have any units of that type.getBuildTile(int builderID, int buildingTypeID, int x, int y): This function finds an appropriate build tile near pixel position (x,y) where we can place a building. We need to provide the ID of our worker and a type of the building we want to build. It returns a Point object, or Point(-1,-1) if suitable position is not found.weAreBuilding(int buildingTypeID): Returns true if we are currently constructing the building of a given type.
unitDestroy(int unitID) is called
whenever we see a unit being destroyed. These functions are:
gameEnded()matchEnded(boolean winner)nukeDetect(int x, int y)nukeDetect()playerLeft(int id)unitCreate(int unitID)unitDestroy(int unitID)unitEvade(int unitID)unitMorph(int unitID)unitShow(int unitID)keyPressed(int keyCode)
Note: Try printing out some message, whenever new unit is created, to test
how this works. Hint: You can get the string representation of unit's type
by calling bwapi.getUnitType(bwapi.getUnit(unitID).getTypeID()).getName().
Finding the enemy base
On every map, there are several places where it makes sense to build a
base (because there are resources). The list of all these places can be easily
accessed by the
bwapi.getMap().getBaseLocations()
function. Some of these base locations are also a start locations (there are 2-8 start
locations, depending on a map). This means, that enemy player's base can
be there at the beginning of the game. We can easily iterate over all the
base locations, and check which of them are possible start locations with the following code:
for (BaseLocation b : bwapi.getMap().getBaseLocations()) {
// If this is a possible start location,
if (b.isStartLocation()) {
// do something. For example send some unit to a position
// b.getX(),b.getY() to see, if the enemy is there.
}
}
Note: There comes a time in the game, when we want to attack our opponent.
When we don't know where his base is, we should send a scout (basically any cheap unit)
to every start location. With the code above, you should be able to do that, and find the enemy. When you discover some enemy buildings, you should remember their location, so that you don't need to
look for them in future.
Note 2: If later in the game comes a time when you've destroyed every
known building, you need to look for other remaining buildings. Some of them
may not be close to start locations. In that case, you should scout all the
base locations on the map.
Submitting your bot
To submit your bot to SSCAI Tournament, you need to go to the
Log In & Submit Bots
subpage. You should upload a zip archive containing these 3 things:
- README file (TXT or PDF) with the description of your bot.
- Compiled bot. Either a .JAR file if it's coded in JNIBWAPI, or .dll file if you used C++.
- Source code of your bot.
