BWMirror API - Documentation

Getting Started

Overview

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.

Setup

  • Download and extract BWAPI
  • Run install.exe to install BWAPI
  • Download Example Bot - Eclipse
  • Run ChaosLauncher with BWAPI enabled
  • Run your bot using 32-bit JRE

Initialization

It takes three simple steps to connect your bot to BWMirror API and Broodwar:

  • Create an implementation of BWEventListener. You can either implement the interface directly, or extend the stub class DefaultBWListener. The most important method to implement is onFrame(), which is called by the BWMirror API once every logical game frame. Your bots logic should be called from this function.
  • Create a new Mirror object and call getModule().setEventListener(yourListener), to register your listener
  • Your bot is now ready, just call Mirror.startGame(), and launch Broodwar from ChaosLauncher !

Game

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

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.

Unit

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

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.

Position

BWMirror API uses two basic positioning concepts:

  • Position - represents pixel precise position in the game. Use unit.getPosition().
  • TilePosition - represents tile position in game. A tile is a 32x32 pixels square, used primarily for building placement. Use unit.getTilePosition() to access this one.

Map

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

BWTA is terrain analyzer for BWAPI. This library was also mirrored in BWMirror API.

  • To enable BWTA, just call these two static methods:
    BWTA.readMap();
    BWTA.analyze(); 
    Usually, you would do this in BWEventListener.onStart().
  • Once the analysis is complete, you can browse the BaseLocations, Regions, ChokePoints simply by calling the appropriate static methods of BWTA class.

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.

Example bot

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();
    }
}
            

This bot in action!

More info

For more information, see BWAPI's StarCraft guide.


FAQ

My bot prints the message "BWMirror API supports only x86 architecture." and fails to load. What's the problem?

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.

My bot freezes at startup when I enable BWTA.

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).

How can I build a standalone version of my bot as a single jar file?

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.

How is BWMirror API different from JNIBWAPI?

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.

Will you provide source code to the code generation pipeline?

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.

About BWMirror API - Code generation pipeline

#1 Parsing

The BWAPI header files are parsed and an abstract representation of the API is created. Namespaces, classes, enums and constants are extracted.

#2 Mirroring

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.

#3 Compilation and header generation

The Java code is now compiled and header files for native functions are generated by Javah tool.

#4 Method body implementation

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);
            

#5 Binding constants

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));
            

Converting objects between C++ and Java and vice versa

Converting C++ objects to Java objects

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.

Converting Java objects to C++

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.


Version 1.2 (Current)

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);

Version 1.1

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.

Version 1.0

The first official release covering the BWAPI's functionality.