BWMirror API is a Java wrapper for BWAPI. It wraps all classes, constants and enums inside Java objects,
while providing the exact same interface as the original C++ BWAPI. This is achieved by heavily utilising JNI.
BWMirror API itself was creating using a code generation pipeline,
extracting the library structure from BWAPI headers. It currently uses the last revision of BWAPI3 and the BWAPI4 support is planned as well.
It takes three simple steps to connect your bot to BWMirror API and Broodwar:
The Game object, received by calling Mirror.getGame(), gives you access to players, units as well as general information about the current game. It also provides you with the ability to print draw text or simple geometry on the game screen/map. This greatly helps with debugging your bot. See the collection of methods: draw___Screen to draw using screen coordinates (0,0 is top left corner of the screen) and draw___Map if you wish to use map coordinates (0,0 is top left corner of the map)
Player object mainly gives you access to your units and resources.
Player self = game.self(); game.drawTextScreen(10, 10, "Playing as " + self.getName() + " - " + self.getRace()); game.drawTextScreen(10, 20, "Resources: " + self.minerals() + " minerals, " + self.gas() + " gas");
Supply - because zerglings take 0.5 supply, the supply values in the API are doubled compared to what you would expect. Thus if self.supplyTotal() - self.supplyUsed() == 2, we can only build one Marrine.
BWMirror API represents almost all in game objects as units. This means not only Zerglings and Overlords count, but buildins such as Barracks, thier addons or evene Mineral Fields are units.
To access your units, just call Player.units(). The same works enemy units, just call Game.enemy.getUnits().
To give a unit an order, simply call the appropriate method for example: move, gather, train, research or attack
for (Unit myUnit : self.getUnits()) { //issue orders }
UnitType gives you a lot of additional information about the unit, such as its max health, cost, weapon type, or even build time. To get unit's type, use unit.getType(). BWMirror API comes with predefined constants for all unit types, you can find them as public static class fields in UnitType class. To test, whether a unit is of a particular type, you can simply compare it's type with one of the predefined constants:
if (myUnit.getType() == UnitType.Zerg_Larva && self.minerals() >= 50) { myUnit.morph(UnitType.Zerg_Drone); }
Hint: Yes, you can use == to compare UnitTypes ( and WeaponTypes, DamageTypes and other __Types too) as there's exactly one instance per every UnitType.
BWMirror API uses two basic positioning concepts:
You can use Game class, to get basic information such as map size, or testing whether a particular tile is buildable or walkable. If you want more complex things, such as getting the locations of all bases, we recommend using BWTA (see next section).
BWTA is terrain analyzer for BWAPI. This library was also mirrored in BWMirror API.
BWTA.readMap(); BWTA.analyze();Usually, you would do this in BWEventListener.onStart().
Hint - BWTA takes some to analyze the map, thus your bot will appear "frozen", while BWTA analyzes the map. This process usually takes up to three minutes, however the result is stored in bwapi-data/BWTA/, so next time the analyzed map is played, BWTA fires up instantly. We've included BWTA files for the maps from SSCAITournament map pool.
This is a simple drone building, and mineral mining bot. It also displays some debug information on the screen. The core of it's logic is in onFrame method, where we simply train SCVs if we have enough minerals, and order idle SCVs to gather closest mineral patches.
import bwapi.*; import bwta.BWTA; public class TestBot1 extends DefaultBWListener { private Mirror mirror = new Mirror(); private Game game; private Player self; public void run() { mirror.getModule().setEventListener(this); mirror.startGame(); } @Override public void onUnitCreate(Unit unit) { System.out.println("New unit " + unit.getType()); } @Override public void onStart() { game = mirror.getGame(); self = game.self(); //Use BWTA to analyze map //This may take a few minutes if the map is processed first time! System.out.println("Analyzing map..."); BWTA.readMap(); BWTA.analyze(); System.out.println("Map data ready"); } @Override public void onFrame() { game.setTextSize(10); game.drawTextScreen(10, 10, "Playing as " + self.getName() + " - " + self.getRace()); StringBuilder units = new StringBuilder("My units:\n"); //iterate through my units for (Unit myUnit : self.getUnits()) { units.append(myUnit.getType()).append(" ").append(myUnit.getTilePosition()).append("\n"); //if there's enough minerals, train an SCV if (myUnit.getType() == UnitType.Terran_Command_Center && self.minerals() >= 50) { myUnit.train(UnitType.Terran_SCV); } //if it's a drone and it's idle, send it to the closest mineral patch if (myUnit.getType().isWorker() && myUnit.isIdle()) { Unit closestMineral = null; //find the closest mineral for (Unit neutralUnit : game.neutral().getUnits()) { if (neutralUnit.getType().isMineralField()) { if (closestMineral == null || myUnit.getDistance(neutralUnit) < myUnit.getDistance(closestMineral)) { closestMineral = neutralUnit; } } } //if a mineral patch was found, send the drone to gather it if (closestMineral != null) { myUnit.gather(closestMineral, false); } } } //draw my units on screen game.drawTextScreen(10, 25, units.toString()); } public static void main(String[] args) { new TestBot1().run(); } }
For more information, see BWAPI's StarCraft guide.
Install a 32-bit version of JRE and point your IDE to it. You can find the current 32-bit versions here.
Note: While it's possible to build x64 version of BWMirror API, it fails to cooperate with StarCraft at runtime, as StarCraft is an x86 application. Therefore we only provide the x86 version.
The first time BWTA analyses a map, it can take a few minutes. The results are saved to bwapi-data/BWTA/ folder for later use. We have prepared BWTA data for some maps (the map pool from SSCAITournament).
In Eclipse, export your bot as JAR file and select "Extract required libraries into generated JAR file". The bwmirror archive contains all the required DLLs and will extract them at runtime if needed.
At the time I started creating BWMirror API, JNIBWAPI was heavily using ids instead of objects
and missing a large number of methods. My aim was to provide an alternative OO API, that would contain the same functions as the C++ API.
The interface of JNIBWAPI changed recently quite a bit, it's now much more object oriented.
From what I've seen it was a huge improvement.
It is available on github, but parts of the code are still quite ugly, and it may take a while before it will be properly refactored. However it is my primary concern to make the generator well structured before adding the support for BWAPI4.
The BWAPI header files are parsed and an abstract representation of the API is created. Namespaces, classes, enums and constants are extracted.
The abstract representation is now used to copy the API interface 1:1 into Java classes and enums. Additional classes are added to the API such as the initialisation class.
The Java code is now compiled and header files for native functions are generated by Javah tool.
The methods from headers are paired with their Java native counterparts and the C++ method implementation is created. This is the key part of the pipeline, as C++ object are converted to Java objects and vice versa.
//Example of generated Java method public boolean hasPowerPrecise(int x, int y, UnitType unitType) { return hasPowerPrecise_native(pointer, x, y, unitType); } private native boolean hasPowerPrecise_native(long pointer, int x, int y, UnitType unitType);
//Example of generated C++ function JNIEXPORT jboolean JNICALL Java_bwapi_Game_hasPowerPrecise_1native__JIILbwapi_UnitType_2(JNIEnv * env, jobject obj, jlong pointer, int x, int y, jobject p_unitType){ Game* x_game = (Game*)pointer; UnitType* unitType = (UnitType*)env->GetLongField(p_unitType, env->GetFieldID(env->GetObjectClass(p_unitType), "pointer", "J")); return x_game->hasPowerPrecise(x, y, *unitType);
An initialisation function is created which maps all of BWAPI global namespace constants such as UnitTypes to Java class constants.
//example of generated constant binding jclass cls = env->FindClass("Lbwapi/UnitType"); jmethodID getId = env->GetStaticMethodID(cls, "get", "(J)Lbwapi/UnitType;"); env->SetObjectField(obj, env->GetStaticFieldID(cls, "Protoss_Zealot", "Lbwapi/UnitType"), env->CallStaticObjectMethod(cls, getId, (jlong)&UnitTypes::Protoss_Zealot));
First thing we need to do is, that for a particular C++ object we need to resolve, wheter its Java counter part has already been instantiated and if so, we need to find this particular instance. Because of this a pairing mechanism needs to be in place. For each class a static instance map is created, which maps raw C++ pointers to Java objects.
For this purpose, each Java object stores the pointer to the C++ object it represents.
As both types of conversion need to access both C++ and Java objects, they take place inside the native methods at the C++ side.
Implemented some ideas by Rafał Poniatowski.
You can now print colored text! Use Utils.formatText and pick any of the options specified in Utils.java.
game.drawTextScreen(10, 25, Utils.formatText("Hello world", Utils.Blue));
Common ancestors were added for Position based classes, so you can use commands such as:
unit.distanceTo(chokepoint); unit.distanceTo(firstBase);
Fixed an issue when some functions returned invalid object's instead of null as response, resulting in a crash when accessing their fields in native code.
The first official release covering the BWAPI's functionality.