Compare commits

...

23 commits
2.6 ... master

Author SHA1 Message Date
Gered 0702faf9b0 version 2.7.1 2017-04-19 22:57:31 -04:00
gered ec71d44c86 update initial console output again... decided i like less afterall 2017-04-19 22:21:37 -04:00
gered e450e25e99 fix Unit.getUnitsInWeaponRange crashing due to bwapi bug
bwapi's implementation of getUnitsInWeaponRange does not check for a
null filter function, so it crashes if one is not passed in. we were
never passing one in here. to work around this for now, we'll just pass
in a dummy filter function that always returns true (so returns all
units in range always).
2017-04-19 21:52:06 -04:00
gered 688c611e91 add missing bwapi classes to jni generator constant types list 2017-04-19 07:18:43 -04:00
gered 544e825362 should be casting pointers passed to Java via JNI as jlong, not long
this was the only remaining place where this wasn't being done this way
2017-04-18 21:54:36 -04:00
gered 3737998f60 minor initTables adjustments 2017-04-18 21:49:28 -04:00
Gered b3473776e6 version 2.7 2017-04-14 18:44:41 -04:00
Gered 035b9beaa3 update README.md 2017-04-14 18:43:44 -04:00
Gered 38ab003dca ensure game loop won't crash if broodwar isn't running when interrupted 2017-04-14 18:43:19 -04:00
Gered 564133f95a javadoc updates 2017-04-14 18:07:46 -04:00
Gered 794678d46e prefix BWMirror console output so it stands out from BWAPI's output 2017-04-14 17:51:38 -04:00
Gered 4ab3980d24 fix up game loop to _actually_ be interruptable 2017-04-14 17:37:48 -04:00
Gered 57608ad74a fix searching of the classpath to not fail on non-existant entries
e.g. things like "test" directories that may have been added to the
classpath by IDE's / build tools but that may or may not actually exist
2017-04-14 17:05:41 -04:00
Gered ad647d1dd7 update README.md 2017-04-14 13:28:46 -04:00
Gered 1f68039507 minor tweak to startup file extracting console output 2017-04-14 12:43:30 -04:00
Gered c0a65e037c bit more of a robust method of checking if running on a 32-bit jvm
os.arch can have a wide variety of values (e.g. not just "x86" for
32 bit)
2017-04-14 12:40:48 -04:00
Gered e5721392c6 add hardcoded equals/hashCode impls to certain generated classes 2017-04-14 12:01:15 -04:00
Gered 6b810bcd2e update README.md 2017-04-12 22:18:10 -04:00
Gered c3053f5c7b Merge pull request #2 from gered/startgame_redo
Convert Mirror.startGame to Java code
2017-04-12 22:01:31 -04:00
Gered 201af45caf update example code in README.md 2017-04-12 21:51:38 -04:00
gered ffb7cecc3a update bwmirror version for next release 2017-04-12 21:46:32 -04:00
gered d795f83181 update TestBot 2017-04-12 21:45:26 -04:00
gered 927ac86c9d convert majority of Mirror.startGame to Java code 2017-04-12 21:45:14 -04:00
10 changed files with 405 additions and 202 deletions

View file

@ -1,6 +1,8 @@
# BWMirror API
This is the BWMirror Generator which is used to generate the Java classes that will mirror the ones found in BWAPI and automatically generate the C++ JNI glue that makes it all work.
A Java API for [BWAPI](http://bwapi.github.io/) and [BWTA2](https://bitbucket.org/auriarte/bwta2) allowing you to create Starcraft: Broodwar AIs using Java.
This repository contains a "generator" project which is used to programmatically generate the Java classes that will mirror the ones found in BWAPI/BWTA2 and automatically generate the C++ JNI glue that makes it all work together. It's fairly involved, but if you're just interested in writing a Starcraft AI in Java, then you don't need to worry about the details too much!
## Usage
@ -12,7 +14,7 @@ If your project uses Maven, you can add a dependency to BWMirror:
<dependency>
<groupId>com.github.gered</groupId>
<artifactId>bwmirror</artifactId>
<version>2.6</version>
<version>2.7.1</version>
</dependency>
```
@ -34,12 +36,15 @@ public class ExampleBot {
}
@Override
public void onEnd(boolean b) {
public void onEnd(boolean isWinner) {
System.out.println("Game has ended");
}
@Override
public void onFrame() {
// this object is ONLY valid while Mirror.startGame is running
// (which is fine 99% of the time, it just means you should only
// use it within code that is invoked from these listener methods)
Game game = mirror.getGame();
Player self = game.self();
@ -47,19 +52,42 @@ public class ExampleBot {
}
});
// blocks indefinitely
mirror.startGame();
// if you pass false, blocks indefinitely and keeps a Broodwar
// instance connection to keep playing subsequent matches.
// if you pass true, startGame will return after a single match and
// disconnects from the Broodwar instance. you can call startGame as
// many times as you wish in this case.
mirror.startGame(true);
}
public static void main(String... args) {
new TestBot().run();
new ExampleBot().run();
}
}
```
## Building BWMirror
This is a fair bit involved due to the nature of the project. If you want to contribute to BWMirror, read on.
This is a fair bit involved due to the nature of the project. If you want to contribute to BWMirror, read on. If you are only interested in writing a Starcraft AI, then you can stop here.
### Introduction: How This All Works
Basically, in this repository the `/bwmirror` directory contains the final BWMirror Java library. However, you will find it's missing basically all of the code!
This is because BWMirror consists largely of code that is _automatically generated_ from BWAPI and BWTA2 C++ header files. The generator project under `/generator` is where all of this code generation occurs.
The generator project works by parsing the C++ header files and building a list of classes and methods that exist on the C++ side of things. Then these are translated into equivalent "mirror" Java classes. These mirror classes will contain public Java methods that bots will use. As well, there will be an equivalent number of private `native` methods which is where JNI code will be used to call the actual BWAPI/BWTA2 libraries. Note that there are a small number of Java classes that were manually developed and do not contain automatically generated code. These classes are simply copied into the BWMirror project as-is and their source is located under `/manual-bwapi-src`.
`javah` is used to generate JNI function declarations for all of these `native` Java class methods, and then a JNI C++ implementation file, `impl.cpp` is generated with almost all automatically generated function implementations (since the vast majority of these are really basic and simply call BWAPI/BWTA2 functions with the same number of parameters, etc). A very small number of functions in `impl.cpp` are manually implemented using specially-crafted code.
A Visual Studio 2013 project is also included in `/bwapi_bridge` which needs to be used to compile the automatically generated JNI C++ code into a DLL.
All of the automatically generated Java code and the JNI DLL are automatically placed into the appropriate places in the `/bwmirror` directory. Once all of this is done, the BWMirror library simply needs to be built.
> **NOTE:** Generally speaking, you should not be directly editing code under `/bwmirror` or `/output` unless you're just testing/debugging quick changes. This is due to the fact that all of the code in there is automatically being generated/copied.
>
> The "proper" way to make changes to BWMirror is to modify the generator project and/or edit the Java classes under `/manual-bwapi-src`.
### Installing Pre-Requisites
@ -97,7 +125,6 @@ https://bitbucket.org/auriarte/bwta2/downloads/
Download and extract the BWTA files somewhere and take note of the location you extracted them to, creating a `BWTA_HOME` environment variable that points to this same location. e.g. "C:\dev\BWTAlib_2.2"
### Running the Generator
If you've not already done so, clone the BWMirror-Generator repository.

View file

@ -4,7 +4,7 @@
<groupId>com.github.gered</groupId>
<artifactId>bwmirror</artifactId>
<version>2.6</version>
<version>2.7.1</version>
<packaging>jar</packaging>
<name>BWMirror</name>

View file

@ -138,7 +138,7 @@ public class TestBot {
}
});
*/
mirror.startGame();
mirror.startGame(false);
System.out.println("It's over");
}

View file

@ -1,11 +1,13 @@
package bwmirror.generator;
import bwmirror.c.Param;
import bwmirror.util.Generic;
import bwmirror.util.PointerTest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created with IntelliJ IDEA.
@ -19,7 +21,7 @@ public class JavaContext {
private List<String> valueTypes = Arrays.asList("Position", "TilePosition", "WalkPosition", "Color", "BWTA::RectangleArray<double>", "PositionOrUnit", "Point", "UnitCommand", "Integer", "Long", "Double", "Boolean", "int", "void", "double", "long", "Pair");
private List<String> constantTypes = Arrays.asList("UnitType", "TechType", "UpgradeType", "Race", "UnitCommand", "WeaponType", "Order", "GameType", "Error", "PlayerType", "UnitCommandType");
private List<String> constantTypes = Arrays.asList("UnitType", "TechType", "UpgradeType", "Race", "UnitCommand", "WeaponType", "Order", "GameType", "Error", "PlayerType", "UnitCommandType", "UnitSizeType", "DamageType", "ExplosionType");
private List<String> enumTypes = Arrays.asList("MouseButton", "Key", "bwapi.Text.Size.Enum", "bwapi.CoordinateType.Enum", "Text::Size::Enum", "CoordinateType::Enum");
@ -29,6 +31,8 @@ public class JavaContext {
private List<String> selfReturnTypes = Arrays.asList("BWTA::Polygon");
private Map<String, Param> requiresExplicitDummyPredicate = new HashMap<>();
private String packageName = "bwapi";
@ -39,6 +43,8 @@ public class JavaContext {
javaToCType.put("void", "void");
javaToCType.put("boolean", "jboolean");
javaToCType.put("String", "jstring");
requiresExplicitDummyPredicate.put("getUnitsInWeaponRange", new Param("", "IdentityUnitFilter"));
}
public boolean isSelfReturnType(String clsName, String methodName) {
@ -93,6 +99,10 @@ public class JavaContext {
return constantTypes.contains(javaType);
}
public boolean needsExtraFilterParameter(String methodName) { return requiresExplicitDummyPredicate.containsKey(methodName); }
public Param getExtraFilterParameter(String methodName) { return requiresExplicitDummyPredicate.get(methodName); }
public String ptrCType() {
return "jobject";
}

View file

@ -30,165 +30,154 @@ public class Bind {
this.context = context;
}
private void implementConnectionRoutine() {
out.println("\t\t\t\tprintln(\"Waiting...\");\n" +
"while ( !Broodwar->isInGame() )\n" +
" {\n" +
" BWAPI::BWAPIClient.update();\n" +
" if (!BWAPI::BWAPIClient.isConnected())\n" +
" {\n" +
" println(\"Reconnecting...\");\n" +
" reconnect();\n" +
" }\n" +
" }");
}
private void implementGameStart() {
out.println("println(\"Connecting to Broodwar...\");\n" +
"\t\treconnect();\n" +
"\t\tprintln(\"Connection successful, starting match...\");\n" +
"\n" +
"\t\tcls = env->GetObjectClass(obj);\n" +
"\t\tjclass gamecls = env->FindClass(\"L" + context.getPackageName() + "/Game;\");\n" +
"\t\tjclass unitCls = env->FindClass(\"L" + context.getPackageName() + "/Unit;\");\n" +
"\t\tjclass playerCls = env->FindClass(\"L" + context.getPackageName() + "/Player;\");\n" +
"\t\tjclass posCls = env->FindClass(\"L" + context.getPackageName() + "/Position;\");\n" +
"\t\tjobject moduleObj = env->GetObjectField(obj, env->GetFieldID(cls, \"module\", \"L" + context.getPackageName() + "/AIModule;\"));\n" +
"\t\tjclass moduleCls = env->GetObjectClass(moduleObj);\n" +
"\t\tenv->SetObjectField(obj, env->GetFieldID(cls, \"game\", \"L" + context.getPackageName() + "/Game;\"), " +
"env->CallStaticObjectMethod(gamecls, env->GetStaticMethodID(gamecls, \"get\", \"(J)L" + context.getPackageName() + "/Game;\"), (long)BroodwarPtr));\n" +
"\n" +
"\t\tjmethodID updateMethodID = env->GetMethodID(env->GetObjectClass(obj), \"update\", \"()V\");");
out.println("\t\tjmethodID matchStartCallback = env->GetMethodID(moduleCls, \"onStart\", \"()V\");\n" +
"\t\tjmethodID matchEndCallback = env->GetMethodID(moduleCls, \"onEnd\", \"(Z)V\");\n" +
"\t\tjmethodID matchFrameCallback = env->GetMethodID(moduleCls, \"onFrame\", \"()V\");\n" +
"\t\tjmethodID sendTextCallback = env->GetMethodID(moduleCls, \"onSendText\", \"(Ljava/lang/String;)V\");\n" +
"\t\tjmethodID receiveTextCallback = env->GetMethodID(moduleCls, \"onReceiveText\", \"(L" + context.getPackageName() + "/Player;Ljava/lang/String;)V\");\n" +
"\t\tjmethodID playerLeftCallback = env->GetMethodID(moduleCls, \"onPlayerLeft\", \"(L" + context.getPackageName() + "/Player;)V\");\n" +
"\t\tjmethodID nukeDetectCallback = env->GetMethodID(moduleCls, \"onNukeDetect\", \"(L" + context.getPackageName() + "/Position;)V\");\n" +
"\t\tjmethodID unitDiscoverCallback = env->GetMethodID(moduleCls, \"onUnitDiscover\", \"(L" + context.getPackageName() + "/Unit;)V\");\n" +
"\t\tjmethodID unitEvadeCallback = env->GetMethodID(moduleCls, \"onUnitEvade\", \"(L" + context.getPackageName() + "/Unit;)V\");\n" +
"\t\tjmethodID unitShowCallback = env->GetMethodID(moduleCls, \"onUnitShow\", \"(L" + context.getPackageName() + "/Unit;)V\");\n" +
"\t\tjmethodID unitHideCallback = env->GetMethodID(moduleCls, \"onUnitHide\", \"(L" + context.getPackageName() + "/Unit;)V\");\n" +
"\t\tjmethodID unitCreateCallback = env->GetMethodID(moduleCls, \"onUnitCreate\", \"(L" + context.getPackageName() + "/Unit;)V\");\n" +
"\t\tjmethodID unitDestroyCallback = env->GetMethodID(moduleCls, \"onUnitDestroy\", \"(L" + context.getPackageName() + "/Unit;)V\");\n" +
"\t\tjmethodID unitMorphCallback = env->GetMethodID(moduleCls, \"onUnitMorph\", \"(L" + context.getPackageName() + "/Unit;)V\");\n" +
"\t\tjmethodID unitRenegadeCallback = env->GetMethodID(moduleCls, \"onUnitRenegade\", \"(L" + context.getPackageName() + "/Unit;)V\");\n" +
"\t\tjmethodID saveGameCallback = env->GetMethodID(moduleCls, \"onSaveGame\", \"(Ljava/lang/String;)V\");\n" +
"\t\tjmethodID unitCompleteCallback = env->GetMethodID(moduleCls, \"onUnitComplete\", \"(L" + context.getPackageName() + "/Unit;)V\");\n" +
"\t\tjmethodID playerDroppedCallback = env->GetMethodID(moduleCls, \"onPlayerDropped\", \"(L" + context.getPackageName() + "/Player;)V\");");
out.println("\t\twhile (true) {\n");
implementConnectionRoutine();
out.println("\t\t\tprintln(\"Game ready!!!\");\n" +
"\n" +
"\t\t\twhile (Broodwar->isInGame()) {\n" +
"\t\t\t\t\n" +
"\t\t\t\tenv->CallVoidMethod(obj, updateMethodID);\n");
out.println("\n" +
"\t\t\t\tfor(std::list<Event>::const_iterator it = Broodwar->getEvents().begin(); it!=Broodwar->getEvents().end(); it++)\n" +
"\t\t\t\t {\n" +
"\t\t\t\t\t switch (it->getType()) {\n" +
"\t\t\t\t\t\t case EventType::MatchStart:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, matchStartCallback);\n" +
"\t\t\t\t\t\t break;\n" +
"\t\t\t\t\t\t case EventType::MatchEnd:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, matchEndCallback, it->isWinner());\n" +
"\t\t\t\t\t\t break;\n" +
"\t\t\t\t\t\t case EventType::MatchFrame:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, matchFrameCallback);\n" +
"\t\t\t\t\t\t break;\n" +
"\t\t\t\t\t\t case EventType::SendText:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, sendTextCallback, env->NewStringUTF(it->getText().c_str()));\n" +
"\t\t\t\t\t\t break;\n" +
"\t\t\t\t\t\t case EventType::ReceiveText:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, receiveTextCallback, env->CallStaticObjectMethod(playerCls, env->GetStaticMethodID(playerCls, \"get\", \"(J)L" + context.getPackageName() + "/Player;\"), (jlong)it->getPlayer()), env->NewStringUTF(it->getText().c_str()));\n" +
"\t\t\t\t\t\t break;\n" +
"\t\t\t\t\t\t case EventType::PlayerLeft:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, playerLeftCallback, env->CallStaticObjectMethod(playerCls, env->GetStaticMethodID(playerCls, \"get\", \"(J)L" + context.getPackageName() + "/Player;\"), (jlong)it->getPlayer()));\n" +
"\t\t\t\t\t\t break;\n" +
"\t\t\t\t\t\t case EventType::NukeDetect:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, nukeDetectCallback, env->NewObject(posCls, env->GetMethodID(posCls,\"<init>\", \"(II)V\"), it->getPosition().x, it->getPosition().y));\n" +
"\t\t\t\t\t\t break;\n" +
"\t\t\t\t\t\t case EventType::UnitDiscover:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, unitDiscoverCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)L" + context.getPackageName() + "/Unit;\"), (jlong)it->getUnit()));\n" +
"\t\t\t\t\t\t break;\n" +
"\t\t\t\t\t\t case EventType::UnitEvade:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, unitEvadeCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)L" + context.getPackageName() + "/Unit;\"), (jlong)it->getUnit()));\n" +
"\t\t\t\t\t\t break;\n" +
"\t\t\t\t\t\t case EventType::UnitShow:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, unitShowCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)L" + context.getPackageName() + "/Unit;\"), (jlong)it->getUnit()));\n" +
"\t\t\t\t\t\t break;\n" +
"\t\t\t\t\t\t case EventType::UnitHide:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, unitHideCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)L" + context.getPackageName() + "/Unit;\"), (jlong)it->getUnit()));\n" +
"\t\t\t\t\t\t break;\n" +
"\t\t\t\t\t\t case EventType::UnitCreate:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, unitCreateCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)L" + context.getPackageName() + "/Unit;\"), (jlong)it->getUnit()));\n" +
"\t\t\t\t\t\t break;\n" +
"\t\t\t\t\t\t case EventType::UnitDestroy:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, unitDestroyCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)L" + context.getPackageName() + "/Unit;\"), (jlong)it->getUnit()));\n" +
"\t\t\t\t\t\t break;\n" +
"\t\t\t\t\t\t case EventType::UnitMorph:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, unitMorphCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)L" + context.getPackageName() + "/Unit;\"), (jlong)it->getUnit()));\n" +
"\t\t\t\t\t\t break;\n" +
"\t\t\t\t\t\t case EventType::UnitRenegade:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, unitRenegadeCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)L" + context.getPackageName() + "/Unit;\"), (jlong)it->getUnit()));\n" +
"\t\t\t\t\t\t break;\n" +
"\t\t\t\t\t\t case EventType::SaveGame:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, saveGameCallback, env->NewStringUTF(it->getText().c_str()));\n" +
"\t\t\t\t\t\t break;\n" +
"\t\t\t\t\t\t case EventType::UnitComplete:\n" +
"\t\t\t\t\t\t\t env->CallVoidMethod(moduleObj, unitCompleteCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)L" + context.getPackageName() + "/Unit;\"), (jlong)it->getUnit()));\n" +
"\t\t\t\t\t\t break;\n" +
"\n" +
"\t\t\t\t\t }\n" +
"\t\t\t\t }");
out.println(
"\t\t\t\tBWAPIClient.update();\n" +
"\t\t\t\tif (!BWAPI::BWAPIClient.isConnected()) {\n" +
"\t\t\t\t\t\tprintln(\"Reconnecting...\");\n" +
"\t\t\t\t\t\treconnect();\n" +
"\t\t\t\t}\n" +
"\t\t\t}\n" +
"println(\"Match ended.\");" +
"\t\t}");
}
private void implementHelpers() {
out.println("void reconnect()\n" +
"{\n" +
"\twhile (!BWAPIClient.connect()) {\n" +
" std::this_thread::sleep_for(std::chrono::milliseconds{ 1000 });\n" +
" }\n" +
"}\n" +
"\n" +
"\n");
out.println(
"void flushPrint(const char * text){\n" +
"\tprintf(text);\n" +
"\tfflush(stdout); \n" +
"}\n" +
"\n" +
"void println(const char * text){\n" +
"\tprintf(text);\n" +
"\tflushPrint(\"\\n\");\n" +
"}\n");
" printf(text);\n" +
" fflush(stdout); \n" +
"}\n" +
"\n" +
"void println(const char * text){\n" +
" printf(text);\n" +
" flushPrint(\"\\n\");\n" +
"}\n"
);
out.println();
}
private void implementMirrorInit(List<CDeclaration> declarationList) {
implementHelpers();
out.println("JNIEXPORT void JNICALL Java_" + context.getPackageName() + "_Mirror_startGame(JNIEnv * env, jobject obj){");
private void implementMirror_initTables(List<CDeclaration> declarationList) {
out.println("JNIEXPORT void JNICALL Java_" + context.getPackageName() + "_Mirror_initTables(JNIEnv * env, jclass jclz){");
out.println("if (areTypeTablesInitialized) return;");
implementVariablesBind(declarationList);
implementGameStart();
out.println("areTypeTablesInitialized = true;");
out.println("println(\"BWMirror C++ lookup tables are now initialized.\");");
out.println("}");
out.println();
}
private void implementMirror_getInternalGame() {
out.println(
"JNIEXPORT jobject JNICALL Java_" + context.getPackageName() + "_Mirror_getInternalGame(JNIEnv * env, jobject obj){\n" +
" jclass gamecls = env->FindClass(\"Lbwapi/Game;\");\n" +
" jmethodID getMethodID = env->GetStaticMethodID(gamecls, \"get\", \"(J)Lbwapi/Game;\");\n" +
" return env->CallStaticObjectMethod(gamecls, getMethodID, (jlong)BroodwarPtr);\n" +
"}\n"
);
out.println();
}
private void implementMirror_processGameEvents() {
out.println(
"JNIEXPORT void JNICALL Java_" + context.getPackageName() + "_Mirror_processGameEvents(JNIEnv * env, jobject obj){\n" +
" jclass cls = env->GetObjectClass(obj);\n" +
" jobject moduleObj = env->GetObjectField(obj, env->GetFieldID(cls, \"module\", \"Lbwapi/AIModule;\"));\n" +
" jclass moduleCls = env->GetObjectClass(moduleObj);\n" +
"\n" +
" jclass unitCls = env->FindClass(\"Lbwapi/Unit;\");\n" +
" jclass playerCls = env->FindClass(\"Lbwapi/Player;\");\n" +
" jclass posCls = env->FindClass(\"Lbwapi/Position;\");\n" +
"\n" +
" jmethodID matchStartCallback = env->GetMethodID(moduleCls, \"onStart\", \"()V\");\n" +
" jmethodID matchEndCallback = env->GetMethodID(moduleCls, \"onEnd\", \"(Z)V\");\n" +
" jmethodID matchFrameCallback = env->GetMethodID(moduleCls, \"onFrame\", \"()V\");\n" +
" jmethodID sendTextCallback = env->GetMethodID(moduleCls, \"onSendText\", \"(Ljava/lang/String;)V\");\n" +
" jmethodID receiveTextCallback = env->GetMethodID(moduleCls, \"onReceiveText\", \"(Lbwapi/Player;Ljava/lang/String;)V\");\n" +
" jmethodID playerLeftCallback = env->GetMethodID(moduleCls, \"onPlayerLeft\", \"(Lbwapi/Player;)V\");\n" +
" jmethodID nukeDetectCallback = env->GetMethodID(moduleCls, \"onNukeDetect\", \"(Lbwapi/Position;)V\");\n" +
" jmethodID unitDiscoverCallback = env->GetMethodID(moduleCls, \"onUnitDiscover\", \"(Lbwapi/Unit;)V\");\n" +
" jmethodID unitEvadeCallback = env->GetMethodID(moduleCls, \"onUnitEvade\", \"(Lbwapi/Unit;)V\");\n" +
" jmethodID unitShowCallback = env->GetMethodID(moduleCls, \"onUnitShow\", \"(Lbwapi/Unit;)V\");\n" +
" jmethodID unitHideCallback = env->GetMethodID(moduleCls, \"onUnitHide\", \"(Lbwapi/Unit;)V\");\n" +
" jmethodID unitCreateCallback = env->GetMethodID(moduleCls, \"onUnitCreate\", \"(Lbwapi/Unit;)V\");\n" +
" jmethodID unitDestroyCallback = env->GetMethodID(moduleCls, \"onUnitDestroy\", \"(Lbwapi/Unit;)V\");\n" +
" jmethodID unitMorphCallback = env->GetMethodID(moduleCls, \"onUnitMorph\", \"(Lbwapi/Unit;)V\");\n" +
" jmethodID unitRenegadeCallback = env->GetMethodID(moduleCls, \"onUnitRenegade\", \"(Lbwapi/Unit;)V\");\n" +
" jmethodID saveGameCallback = env->GetMethodID(moduleCls, \"onSaveGame\", \"(Ljava/lang/String;)V\");\n" +
" jmethodID unitCompleteCallback = env->GetMethodID(moduleCls, \"onUnitComplete\", \"(Lbwapi/Unit;)V\");\n" +
"\n" +
" for (std::list<Event>::const_iterator it = Broodwar->getEvents().begin(); it != Broodwar->getEvents().end(); it++)\n" +
" {\n" +
" switch (it->getType()) {\n" +
" case EventType::MatchStart:\n" +
" env->CallVoidMethod(moduleObj, matchStartCallback);\n" +
" break;\n" +
" case EventType::MatchEnd:\n" +
" env->CallVoidMethod(moduleObj, matchEndCallback, it->isWinner());\n" +
" break;\n" +
" case EventType::MatchFrame:\n" +
" env->CallVoidMethod(moduleObj, matchFrameCallback);\n" +
" break;\n" +
" case EventType::SendText:\n" +
" env->CallVoidMethod(moduleObj, sendTextCallback, env->NewStringUTF(it->getText().c_str()));\n" +
" break;\n" +
" case EventType::ReceiveText:\n" +
" env->CallVoidMethod(moduleObj, receiveTextCallback, env->CallStaticObjectMethod(playerCls, env->GetStaticMethodID(playerCls, \"get\", \"(J)Lbwapi/Player;\"), (jlong)it->getPlayer()), env->NewStringUTF(it->getText().c_str()));\n" +
" break;\n" +
" case EventType::PlayerLeft:\n" +
" env->CallVoidMethod(moduleObj, playerLeftCallback, env->CallStaticObjectMethod(playerCls, env->GetStaticMethodID(playerCls, \"get\", \"(J)Lbwapi/Player;\"), (jlong)it->getPlayer()));\n" +
" break;\n" +
" case EventType::NukeDetect:\n" +
" env->CallVoidMethod(moduleObj, nukeDetectCallback, env->NewObject(posCls, env->GetMethodID(posCls, \"<init>\", \"(II)V\"), it->getPosition().x, it->getPosition().y));\n" +
" break;\n" +
" case EventType::UnitDiscover:\n" +
" env->CallVoidMethod(moduleObj, unitDiscoverCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)Lbwapi/Unit;\"), (jlong)it->getUnit()));\n" +
" break;\n" +
" case EventType::UnitEvade:\n" +
" env->CallVoidMethod(moduleObj, unitEvadeCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)Lbwapi/Unit;\"), (jlong)it->getUnit()));\n" +
" break;\n" +
" case EventType::UnitShow:\n" +
" env->CallVoidMethod(moduleObj, unitShowCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)Lbwapi/Unit;\"), (jlong)it->getUnit()));\n" +
" break;\n" +
" case EventType::UnitHide:\n" +
" env->CallVoidMethod(moduleObj, unitHideCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)Lbwapi/Unit;\"), (jlong)it->getUnit()));\n" +
" break;\n" +
" case EventType::UnitCreate:\n" +
" env->CallVoidMethod(moduleObj, unitCreateCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)Lbwapi/Unit;\"), (jlong)it->getUnit()));\n" +
" break;\n" +
" case EventType::UnitDestroy:\n" +
" env->CallVoidMethod(moduleObj, unitDestroyCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)Lbwapi/Unit;\"), (jlong)it->getUnit()));\n" +
" break;\n" +
" case EventType::UnitMorph:\n" +
" env->CallVoidMethod(moduleObj, unitMorphCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)Lbwapi/Unit;\"), (jlong)it->getUnit()));\n" +
" break;\n" +
" case EventType::UnitRenegade:\n" +
" env->CallVoidMethod(moduleObj, unitRenegadeCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)Lbwapi/Unit;\"), (jlong)it->getUnit()));\n" +
" break;\n" +
" case EventType::SaveGame:\n" +
" env->CallVoidMethod(moduleObj, saveGameCallback, env->NewStringUTF(it->getText().c_str()));\n" +
" break;\n" +
" case EventType::UnitComplete:\n" +
" env->CallVoidMethod(moduleObj, unitCompleteCallback, env->CallStaticObjectMethod(unitCls, env->GetStaticMethodID(unitCls, \"get\", \"(J)Lbwapi/Unit;\"), (jlong)it->getUnit()));\n" +
" break;\n" +
" }\n" +
" }\n" +
"}\n"
);
out.println();
}
private void implementMirror_BWAPIClientGetters() {
out.println(
"JNIEXPORT jboolean JNICALL Java_bwapi_Mirror_isConnected(JNIEnv * env, jclass jclz){\n" +
" return BWAPI::BWAPIClient.isConnected();\n" +
"}\n" +
"\n" +
"JNIEXPORT jboolean JNICALL Java_bwapi_Mirror_connect(JNIEnv * env, jclass jclz){\n" +
" return BWAPI::BWAPIClient.connect();\n" +
"}\n" +
"\n" +
"JNIEXPORT void JNICALL Java_bwapi_Mirror_disconnect(JNIEnv * env, jclass jclz){\n" +
" BWAPI::BWAPIClient.disconnect();\n" +
"}\n" +
"\n" +
"JNIEXPORT void JNICALL Java_bwapi_Mirror_update(JNIEnv * env, jclass jclz){\n" +
" BWAPI::BWAPIClient.update();\n" +
"}\n"
);
out.println();
}
private void implementVariablesBind(List<CDeclaration> declarationList) {
out.println("jclass cls;");
out.println("jmethodID getId;");
@ -210,6 +199,7 @@ public class Bind {
out.println("getId = env->GetMethodID(cls,\"<init>\", \"(III)V\");");
} else {
out.println("getId = env->GetStaticMethodID(cls, \"get\", \"(J)L" + context.getPackageName() + "/" + cClass.getName() + ";\");");
out.println("table" + cClass.getName() + ".clear();");
}
printedIntro = true;
}
@ -251,7 +241,12 @@ public class Bind {
}
public void implementBind(List<CDeclaration> declarationList) {
implementMirrorInit(declarationList);
implementHelpers();
implementMirror_initTables(declarationList);
implementMirror_getInternalGame();
implementMirror_processGameEvents();
implementMirror_BWAPIClientGetters();
}
}

View file

@ -28,6 +28,8 @@ public class TypeTable {
}
}
out.println();
out.println("bool areTypeTablesInitialized = false;");
out.println();
}
private void checkTypeTable(CClass cClass) {

View file

@ -60,7 +60,12 @@ public class CallImplementer {
"#include <jni.h>\n" +
"#include <cstring>\n" +
"\n" +
"using namespace BWAPI;\n\n");
"using namespace BWAPI;\n\n" +
"\n" +
"bool IdentityUnitFilter(Unit u) {\n" +
"\treturn true;\n" +
"}\n" +
"\n");
}
private void implementAccessObject(String clsName, String objName) {
@ -382,7 +387,7 @@ public class CallImplementer {
out.println("*it;");
}
if (!javaContext.isValueType(genericType)) {
out.println("jobject elem = env->CallStaticObjectMethod(elemClass, getMethodID, (long)elem_ptr) ;");
out.println("jobject elem = env->CallStaticObjectMethod(elemClass, getMethodID, (jlong)elem_ptr) ;");
} else {
out.println("jobject elem = env->NewObject(elemClass, elemConsID" + javaContext.implementCopyReturn(genericType, "elem_ptr") + ")" + SEMICOLON);
}
@ -505,6 +510,8 @@ public class CallImplementer {
String genericType = convertToBWTA(Generic.extractGeneric(javaRetType));
if (javaPackageName.equals("bwta") || (!genericType.equals("UnitType") && !genericType.equals("Position") && !genericType.equals("TilePosition"))){
out.print(wrapInCCollection(genericType, javaMethodName) + " cresult = " + objectAccessor + (javaContext.isSelfReturnType(clsName, javaMethodName) ? "" : javaMethodName + "("));
if (javaContext.needsExtraFilterParameter(javaMethodName))
params.add(javaContext.getExtraFilterParameter(javaMethodName));
implementRealParams(params);
if (!javaContext.isSelfReturnType(clsName, javaMethodName)) {
out.print(")");

View file

@ -41,6 +41,7 @@ public class ClassMirror extends Mirror {
out.println();
writeFields(cClass.getFields());
if (!cClass.isStatic()) {
writeEqualsAndHashCodeMethods();
writeInstanceMap();
writeConstructor();
writeInstanceGetter();
@ -53,6 +54,73 @@ public class ClassMirror extends Mirror {
nativeBinds = null;
}
private void writeEqualsMethodBeginning() {
out.println(INTEND + "@Override");
out.println(INTEND + "public boolean equals(Object o) {");
out.println(INTEND + INTEND + "if (this == o) return true;");
out.println(INTEND + INTEND + "if (o == null || getClass() != o.getClass()) return false;");
out.println();
out.println(INTEND + INTEND + cClass.getName() + " other = (" + cClass.getName() + ")o;");
out.println();
}
private void writeEqualsMethodEnding() {
out.println();
out.println(INTEND + INTEND + "return true;");
out.println(INTEND + "}");
}
private void writeHashCodeMethodBeginning() {
out.println();
out.println(INTEND + "@Override");
out.println(INTEND + "public int hashCode() {");
out.println(INTEND + INTEND + "int result;");
}
private void writeHashCodeMethodEnding() {
out.println(INTEND + INTEND + "return result;");
out.println(INTEND + "}");
out.println();
}
// for the simpler cases where the BWAPI object has an ID, and our equality test
// only needs to be a comparison of this ID. ID is assumed to be an int
private void writeEqualsAndHashCodeMethodsForObjWithID() {
writeEqualsMethodBeginning();
out.println(INTEND + INTEND + "if (getID() != other.getID()) return false;");
writeEqualsMethodEnding();
writeHashCodeMethodBeginning();
out.println(INTEND + INTEND + "result = getID();");
writeHashCodeMethodEnding();
}
// HACK: manually stuffing in some implementations for specific classes for now.
// it would probably take a fair bit of work to rig up some general-purpose
// solution for all classes ....
private void writeEqualsAndHashCodeMethods() {
String className = cClass.getName();
String packageName = context.getPackage();
if (className.equals("BaseLocation")) {
writeEqualsMethodBeginning();
out.println(INTEND + INTEND + "if (!getPosition().equals(other.getPosition())) return false;");
writeEqualsMethodEnding();
writeHashCodeMethodBeginning();
out.println(INTEND + INTEND + "result = getPosition().hashCode();");
writeHashCodeMethodEnding();
} else if (className.equals("Bullet")) {
writeEqualsAndHashCodeMethodsForObjWithID();
} else if (className.equals("Force")) {
writeEqualsAndHashCodeMethodsForObjWithID();
} else if (className.equals("Player")) {
writeEqualsAndHashCodeMethodsForObjWithID();
} else if (className.equals("Region") && packageName.equals("bwapi")) {
writeEqualsAndHashCodeMethodsForObjWithID();
} else if (className.equals("Unit")) {
writeEqualsAndHashCodeMethodsForObjWithID();
}
}
private void writeInstanceMap() {
out.println(INTEND + "private" + SPACE + "static" + SPACE + "Map<Long," + SPACE + cClass.getName() + "> instances = " +
"new HashMap<Long," + SPACE + cClass.getName() + ">()" + SEMICOLON);

View file

@ -1,26 +1,20 @@
package bwapi;
import bwapi.AIModule;
import bwapi.BWEventListener;
import java.io.*;
import java.io.File;
import java.lang.Exception;
import java.lang.UnsupportedOperationException;
import java.util.*;
import java.util.regex.Pattern;
import java.util.zip.*;
public class Mirror {
public static final String LOG_TAG = "[BWMirror] ";
private Game game;
private AIModule module = new AIModule();
private FrameCallback frameCallback;
private static final boolean EXTRACT_JAR = true;
private static void extractResourceFile(String resourceFilename, String outputFilename) throws Exception {
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceFilename);
if (in == null)
@ -42,16 +36,16 @@ public class Mirror {
private static boolean extractAndLoadNativeLibraries() {
try {
System.out.println("Extracting bwapi_bridge.dll");
System.out.println(LOG_TAG + "Extracting bwapi_bridge.dll");
extractResourceFile("bwapi_bridge.dll", "./bwapi_bridge.dll");
System.out.println("Extracting libgmp-10.dll");
System.out.println(LOG_TAG + "Extracting libgmp-10.dll");
extractResourceFile("libgmp-10.dll", "./libgmp-10.dll");
System.out.println("Extracting libmpfr-4.dll");
System.out.println(LOG_TAG + "Extracting libmpfr-4.dll");
extractResourceFile("libmpfr-4.dll", "./libmpfr-4.dll");
System.out.println("Loading native library bwapi_bridge.dll");
System.out.println(LOG_TAG + "Loading native library bwapi_bridge.dll");
System.load(new File("./bwapi_bridge.dll").getAbsolutePath());
} catch (Exception e) {
@ -66,12 +60,11 @@ public class Mirror {
try {
Collection<String> bwtaFilenames = ResourceList.getResources(Pattern.compile("bwapi\\-data/BWTA2/[a-zA-Z0-9]+\\.bwta"));
System.out.println("Creating ./bwapi-data/BWTA2 directory");
System.out.println(LOG_TAG + "Creating ./bwapi-data/BWTA2 directory");
new File("./bwapi-data/BWTA2").mkdirs();
System.out.println("Extracting " + bwtaFilenames.size() + " BWTA2 files:");
System.out.println("Extracting " + bwtaFilenames.size() + " BWTA2 files to ./bwapi-data/BWTA2");
for (String filename : bwtaFilenames) {
System.out.println(filename);
String outputFilename = "./" + filename;
extractResourceFile(filename, outputFilename);
}
@ -84,16 +77,24 @@ public class Mirror {
return true;
}
public static boolean is64BitJRE() {
String bits = System.getProperty("sun.arch.data.model");
if (bits == null)
return System.getProperty("java.vm.name").contains("64");
else
return bits.equals("64");
}
static {
String arch = System.getProperty("os.arch");
if(!arch.equals("x86")){
throw new UnsupportedOperationException("BWMirror API supports only x86 architecture.");
}
if (is64BitJRE())
throw new UnsupportedOperationException("BWMirror must be run on a 32-bit JRE/JDK.");
if (!extractAndLoadNativeLibraries())
System.exit(1);
if (!extractBwtaDataFiles())
System.exit(1);
initTables();
}
public Game getGame() {
@ -105,26 +106,112 @@ public class Mirror {
}
/**
* Starts the API, initializes all constants ( {@link UnitType}, {@link WeaponType} ) and the {@link Game} object.
* This method blocks until the end of game.
* Initializes all BWAPI constant lookup tables.
*/
public native void startGame();
private void update() {
if (frameCallback != null) {
frameCallback.update();
}
}
/*public void setFrameCallback(bwapi.Mirror.FrameCallback frameCallback) {
this.frameCallback = frameCallback;
} */
public static native void initTables();
/**
* The simplest interface to receive update event from Broodwar. The {@link #update()} method is called once each frame.
* For a simple bot and implementation of this interface is enough, to receive all in game events, implement {@link BWEventListener}.
* Initializes a connection to Broodwar, initializes the a {@link Game} object, and dispatches
* events to your listener as long as Broodwar is in a game. Will automatically attempt to
* reconnect to Broodwar if the connection is lost at any point. If this method is called
* before Broodwar is running, will wait until an initial connection can be established.
*
* The {@link Game} instance returned by {@link #getGame()} is only valid while this method
* is running. If your code holds a copy of this object anywhere else, do not try to use it
* again after this method returns.
*
* @param returnOnMatchEnd
* If true, will disconnect from Broodwar and return after the first match ends
* (regardless of how it ended). You can call {@link #startGame} again to run another
* match as needed.
* If false, will run an infinite loop allowing you to keep your bot running as many
* subsequent matches as desired.
*/
/*public*/ private interface FrameCallback {
public void update();
public void startGame(boolean returnOnMatchEnd) {
System.out.println(LOG_TAG + "Connecting to Broodwar...");
if (reconnect())
System.out.println(LOG_TAG + "Connection successful, starting match...");
else {
System.out.println(LOG_TAG + "Connection attempt aborted.");
return;
}
game = getInternalGame();
boolean inGame = game.isInGame();
boolean previouslyInGame = inGame;
if (inGame)
System.out.println(LOG_TAG + "Match already running.");
while (true) {
if (!inGame) {
if (previouslyInGame) {
System.out.println(LOG_TAG + "Match ended.");
if (returnOnMatchEnd)
break;
}
update();
} else {
if (!previouslyInGame)
System.out.println(LOG_TAG + "Game ready!!!");
processGameEvents();
update();
}
if (!isConnected()) {
System.out.println(LOG_TAG + "Reconnecting...");
reconnect();
}
if (Thread.interrupted()) {
System.out.println(LOG_TAG + "Interrupted.");
break;
}
previouslyInGame = inGame;
inGame = game.isInGame();
}
System.out.println(LOG_TAG + "Finished.");
System.out.println(LOG_TAG + "Disconnecting from Broodwar...");
if (isConnected())
disconnect();
game = null;
System.out.println(LOG_TAG + "Returning...");
}
}
private boolean reconnect() {
while (!connect()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// needed to set the interrupt status of this thread.
// else, subsequent calls to Thread.interrupted() will return false.
Thread.currentThread().interrupt();
return false;
}
}
return true;
}
/**
* Returns the current connection state to a running Broodwar instance.
*/
public static native boolean isConnected();
private static native boolean connect();
private static native void disconnect();
private static native void update();
private native Game getInternalGame();
private native void processGameEvents();
}

View file

@ -43,8 +43,7 @@ public class ResourceList {
return retval;
}
private static Collection<String> getResources(final String element,
final Pattern pattern) {
private static Collection<String> getResources(final String element, final Pattern pattern) {
final ArrayList<String> retval = new ArrayList<>();
final File file = new File(element);
if (file.isDirectory()) {
@ -55,8 +54,16 @@ public class ResourceList {
throw new java.lang.Error(e);
}
retval.addAll(getResourcesFromDirectory(file, baseDirectory, pattern));
} else {
} else if (file.isFile()) {
retval.addAll(getResourcesFromJarFile(file, pattern));
} else {
// if 'element' did not point to an existing file or directory, we don't
// add anything to the list of resources to return
// sometimes, build tools / IDEs that start up a java process will
// specifically add classpath entries that do not point to files/directories
// that exist (e.g. Leiningen adds a 'test' directory to the classpath
// and this will be listed in 'java.class.path', whether it exists or not).
// so it is actually pretty important that we not throw an exception here!
}
return retval;
}