Skip to content

Commit f586151

Browse files
authored
Merge pull request #108 from JavaBWAPI/macos-openbw-support
Support OpenBW client on macOS (including M series)
2 parents 484be45 + 8e6f24f commit f586151

7 files changed

Lines changed: 177 additions & 76 deletions

File tree

‎.github/workflows/e2e-linux.yml‎

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
name: E2E OpenBW Linux
2+
3+
on:
4+
- pull_request
5+
6+
jobs:
7+
e2e:
8+
runs-on: ubuntu-latest
9+
timeout-minutes: 10
10+
11+
steps:
12+
- uses: actions/checkout@v4
13+
14+
- name: Set up JDK 8
15+
uses: actions/setup-java@v4
16+
with:
17+
java-version: '8'
18+
distribution: 'temurin'
19+
cache: maven
20+
21+
- name: Cache OpenBW runtime and MPQs
22+
id: cache-openbw
23+
uses: actions/cache@v4
24+
with:
25+
path: openbw-runtime
26+
key: openbw-linux-v2
27+
28+
- name: Build OpenBW and fetch MPQs
29+
if: steps.cache-openbw.outputs.cache-hit != 'true'
30+
run: |
31+
sudo apt-get update
32+
sudo apt-get install -y clang libsdl2-dev libsdl2-mixer-dev
33+
34+
git clone --depth 1 https://github.com/basil-ladder/openbw
35+
git clone --depth 1 -b linux-client-support https://github.com/basil-ladder/bwapi
36+
sed -i 's/switch *(command\.type)/switch ((UnitCommandType) command.type)/g' bwapi/bwapi/include/BWAPI/Client/CommandTemp.h
37+
cmake -S bwapi -B bwapi/build \
38+
-DCMAKE_C_COMPILER=clang \
39+
-DCMAKE_CXX_COMPILER=clang++ \
40+
-DCMAKE_CXX_FLAGS="-include cstdint" \
41+
-DCMAKE_BUILD_TYPE=Release \
42+
-DOPENBW_DIR="$PWD/openbw" \
43+
-DOPENBW_ENABLE_UI=1 \
44+
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
45+
cmake --build bwapi/build --target BWAPILauncher -j$(nproc)
46+
47+
mkdir -p openbw-runtime/bin openbw-runtime/lib
48+
cp bwapi/build/bin/BWAPILauncher openbw-runtime/bin/
49+
cp bwapi/build/lib/*.so openbw-runtime/lib/
50+
ldd bwapi/build/lib/libOpenBWData.so | awk '/=> \// {print $3}' | grep -vE '/(libc|libm|libmvec|libdl|libpthread|librt|libgcc_s|libstdc\+\+|ld-linux)(\.so|-)' | xargs -r cp -L -t openbw-runtime/lib/
51+
52+
git clone --depth 1 https://github.com/dgant/mpqs
53+
cp mpqs/STARDAT.MPQ openbw-runtime/StarDat.mpq
54+
cp mpqs/BROODAT.MPQ openbw-runtime/BrooDat.mpq
55+
cp mpqs/patch_rt.mpq openbw-runtime/Patch_rt.mpq
56+
57+
- name: Compile JBWAPI and test bots
58+
run: |
59+
mvn -B test-compile dependency:build-classpath -Dmdep.outputFile=cp.txt --file pom.xml
60+
javac -cp "target/classes:$(cat cp.txt)" -d target/test-classes it/bots/SittingDuck/src/main/java/SittingDuck.java
61+
62+
- name: Run MarineHell vs SittingDuck match
63+
run: |
64+
mkdir -p openbw-runtime/maps openbw-runtime/bwapi-data
65+
cp "it/(2)Destination.scx" "openbw-runtime/maps/(2)Destination.scx"
66+
67+
cat << 'EOF' > openbw-runtime/bwapi-data/bwapi.ini
68+
[ai]
69+
ai = NULL
70+
ai_dbg = NULL
71+
72+
[auto_menu]
73+
auto_menu = LAN
74+
pause_dbg = OFF
75+
lan_mode = Local Area Network (UDP)
76+
auto_restart = OFF
77+
map = maps/(2)Destination.scx
78+
mapiteration = Random
79+
race = Terran
80+
enemy_count = 1
81+
enemy_race = Terran
82+
game_type = MELEE
83+
wait_for_min_players = 2
84+
wait_for_max_players = 2
85+
wait_for_time = 60000
86+
87+
[config]
88+
holiday = OFF
89+
shared_memory = ON
90+
show_warnings = YES
91+
EOF
92+
93+
BOT_CP="$PWD/target/classes:$PWD/target/test-classes:$(cat cp.txt)"
94+
95+
cd openbw-runtime
96+
export LD_LIBRARY_PATH="$PWD/lib:${LD_LIBRARY_PATH:-}"
97+
rm -rf /tmp/openbw /tmp/bwapi_socket_* /dev/shm/bwapi_shared_memory_* /tmp/launcher1.log /tmp/launcher2.log /tmp/duck.log /tmp/marinehell.log
98+
99+
BWAPI_CONFIG_AUTO_MENU__CHARACTER_NAME="SittingDuck" OPENBW_ENABLE_UI=0 ./bin/BWAPILauncher > /tmp/launcher1.log 2>&1 &
100+
L1_PID=$!
101+
102+
java -cp "$BOT_CP" SittingDuck > /tmp/duck.log 2>&1 &
103+
B1_PID=$!
104+
105+
for i in $(seq 1 30); do
106+
if grep -q "Connected" /tmp/duck.log 2>/dev/null; then
107+
echo "SittingDuck connected!"
108+
break
109+
fi
110+
sleep 0.5
111+
done
112+
113+
BWAPI_CONFIG_AUTO_MENU__CHARACTER_NAME="MarineHell" OPENBW_ENABLE_UI=0 ./bin/BWAPILauncher > /tmp/launcher2.log 2>&1 &
114+
L2_PID=$!
115+
116+
timeout 60 java -cp "$BOT_CP" marinehell.MarineHell | tee /tmp/marinehell.log || true
117+
kill $L1_PID $L2_PID $B1_PID 2>/dev/null || true
118+
119+
if ! grep -q "Hello from JBWAPI!" /tmp/launcher1.log /tmp/launcher2.log || ! grep -q "Game ended! Winner: true" /tmp/marinehell.log; then
120+
echo "=== launcher1.log ===" && cat /tmp/launcher1.log || true
121+
echo "=== launcher2.log ===" && cat /tmp/launcher2.log || true
122+
echo "=== duck.log ===" && cat /tmp/duck.log || true
123+
exit 1
124+
fi

‎.github/workflows/e2e-linux.yml.disabled‎

Lines changed: 0 additions & 50 deletions
This file was deleted.

‎build_with_openbw.md‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,32 @@ cmake .. -DCMAKE_BUILD_TYPE=Release -DOPENBW_DIR=../../openbw -DOPENBW_ENABLE_UI
1616
make -j4
1717

1818
# Download runtime deps
19-
curl http://www.cs.mun.ca/~dchurchill/starcraftaicomp/files/Starcraft_1161.zip -o starcraft.zip
20-
unzip starcraft.zip patch_rt.mpq BROODAT.MPQ STARDAT.MPQ
21-
mv patch_rt.mpq Patch_rt.mpq && mv BROODAT.MPQ BrooDat.mpq && mv STARDAT.MPQ StarDat.mpq
19+
git clone --depth 1 https://github.com/dgant/mpqs
20+
cp mpqs/patch_rt.mpq Patch_rt.mpq && cp mpqs/BROODAT.MPQ BrooDat.mpq && cp mpqs/STARDAT.MPQ StarDat.mpq
2221

23-
# Run openbw using the map: Destination 1.1.scx
24-
unzip starcraft.zip "maps/BroodWar/ICCup/ICCup Destination 1.1.scx"
25-
26-
BWAPI_CONFIG_AUTO_MENU__RACE=Terran BWAPI_CONFIG_AUTO_MENU__MAP="maps/BroodWar/ICCup/ICCup Destination 1.1.scx" ./bin/BWAPILauncher
22+
# Run openbw using the map: (2)Destination.scx
23+
BWAPI_CONFIG_AUTO_MENU__RACE=Terran BWAPI_CONFIG_AUTO_MENU__MAP="it/(2)Destination.scx" ./bin/BWAPILauncher
2724
```
2825

29-
Compiling and running openbw on macos with client support (not working):
26+
Compiling and running openbw on macos with client support:
3027

3128
```bash
32-
brew install cmake sdl2 sdl2_mixer gcc
29+
# Download build deps
30+
brew install cmake sdl2 sdl2_mixer
3331

34-
cmake .. -D CMAKE_C_COMPILER=gcc-11 -D CMAKE_CXX_COMPILER=g++-11 -DCMAKE_BUILD_TYPE=Release -DOPENBW_DIR=../../openbw -DOPENBW_ENABLE_UI=1
35-
make -j4
36-
```
32+
# Build openbw with client support
33+
git clone https://github.com/basil-ladder/openbw
34+
git clone -b linux-client-support https://github.com/basil-ladder/bwapi
35+
cd bwapi
36+
mkdir build
37+
cd build
38+
cmake .. -DCMAKE_BUILD_TYPE=Release -DOPENBW_DIR=../../openbw -DOPENBW_ENABLE_UI=1 -DCMAKE_POLICY_VERSION_MINIMUM=3.5
39+
make -j4
40+
41+
# Download runtime deps
42+
git clone --depth 1 https://github.com/dgant/mpqs
43+
cp mpqs/patch_rt.mpq Patch_rt.mpq && cp mpqs/BROODAT.MPQ BrooDat.mpq && cp mpqs/STARDAT.MPQ StarDat.mpq
44+
45+
# Run openbw using the map: (2)Destination.scx
46+
BWAPI_CONFIG_AUTO_MENU__RACE=Terran BWAPI_CONFIG_AUTO_MENU__MAP="it/(2)Destination.scx" OPENBW_ENABLE_UI=1 ./bin/BWAPILauncher
47+
```

‎it/(2)Destination.scx‎

75.2 KB
Binary file not shown.

‎pom.xml‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,14 @@
144144
</profiles>
145145
<dependencies>
146146
<dependency>
147-
<groupId>no.fiken.oss.junixsocket</groupId>
147+
<groupId>com.kohlschutter.junixsocket</groupId>
148148
<artifactId>junixsocket-common</artifactId>
149-
<version>1.0.2</version>
149+
<version>2.10.1</version>
150150
</dependency>
151151
<dependency>
152-
<groupId>no.fiken.oss.junixsocket</groupId>
152+
<groupId>com.kohlschutter.junixsocket</groupId>
153153
<artifactId>junixsocket-native-common</artifactId>
154-
<version>1.0.2</version>
154+
<version>2.10.1</version>
155155
</dependency>
156156

157157
<dependency>

‎src/main/java/bwapi/ClientConnectionPosix.java‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.sun.jna.LastErrorException;
44
import com.sun.jna.Library;
55
import com.sun.jna.Native;
6+
import com.sun.jna.NativeLong;
67
import com.sun.jna.Pointer;
78
import com.sun.jna.platform.linux.Fcntl;
89
import com.sun.jna.platform.linux.LibRT;
@@ -24,7 +25,7 @@ class ClientConnectionPosix implements ClientConnection {
2425
interface LibCExt extends LibC {
2526
LibCExt INSTANCE = Native.load(LibCExt.class);
2627

27-
Pointer mmap(Pointer addr, int length, int prot, int flags, int fd, int offset) throws LastErrorException;
28+
Pointer mmap(Pointer addr, NativeLong length, int prot, int flags, int fd, NativeLong offset) throws LastErrorException;
2829
}
2930

3031
interface PosixShm extends Library {
@@ -57,17 +58,26 @@ public void disconnect() {
5758
public WrappedBuffer getGameTable() {
5859
int fd = PosixShm.INSTANCE.shm_open("/bwapi_shared_memory_game_list", Fcntl.O_RDWR, 0);
5960
if (fd < 0) throw new IllegalStateException("SHM not found");
60-
Pointer gameTableView = LibCExt.INSTANCE.mmap(Pointer.NULL, GameTable.SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
61-
return new WrappedBuffer(gameTableView, GameTable.SIZE);
61+
try {
62+
Pointer gameTableView = LibCExt.INSTANCE.mmap(Pointer.NULL, new NativeLong(GameTable.SIZE), PROT_READ | PROT_WRITE, MAP_SHARED, fd, new NativeLong(0));
63+
return new WrappedBuffer(gameTableView, GameTable.SIZE);
64+
} finally {
65+
LibCExt.INSTANCE.close(fd);
66+
}
6267
}
6368

6469
@Override
6570
public WrappedBuffer getSharedMemory(int serverProcID) {
6671
String sharedMemoryName = "/bwapi_shared_memory_" + serverProcID;
6772
try {
68-
Pointer mapFileView = LibCExt.INSTANCE.mmap(Pointer.NULL, ClientData.GameData.SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, LibRT.INSTANCE.shm_open(sharedMemoryName, Fcntl.O_RDWR, 0),
69-
0);
70-
return new WrappedBuffer(mapFileView, ClientData.GameData.SIZE);
73+
int fd = PosixShm.INSTANCE.shm_open(sharedMemoryName, Fcntl.O_RDWR, 0);
74+
if (fd < 0) throw new IllegalStateException("SHM not found");
75+
try {
76+
Pointer mapFileView = LibCExt.INSTANCE.mmap(Pointer.NULL, new NativeLong(ClientData.GameData.SIZE), PROT_READ | PROT_WRITE, MAP_SHARED, fd, new NativeLong(0));
77+
return new WrappedBuffer(mapFileView, ClientData.GameData.SIZE);
78+
} finally {
79+
LibCExt.INSTANCE.close(fd);
80+
}
7181
} catch (Exception e) {
7282
throw new SharedMemoryConnectionException(sharedMemoryName, e);
7383
}

‎src/test/java/marinehell/MarineHell.java‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public void onStart() {
7272
BWTA.analyze();
7373

7474
int i = 0;
75-
System.out.printf("Map name: %s; Map file name: %s\n", game.mapName(), game.mapFileName());
75+
System.out.printf("Map name: %s; Map file name: %s; Race: %s; Units: %d\n", game.mapName(), game.mapFileName(), self.getRace(), self.getUnits().size());
7676

7777
// Add a DrawShape call to ensure this doesn't block
7878
game.drawCircleScreen(new Position(200, 200), 3, Color.Black);
@@ -170,8 +170,9 @@ public void onFrame() {
170170
// patch
171171
if (myUnit.getType().isWorker() && myUnit.isIdle()) {
172172
boolean skip = false;
173-
if (bunker == null && bunkerBuilder != null && myUnit.equals(bunkerBuilder)
174-
&& barracks.isEmpty() == false) {
173+
if ((bunker == null && bunkerBuilder != null && myUnit.equals(bunkerBuilder)
174+
&& barracks.isEmpty() == false)
175+
|| (myUnit.equals(searcher) && searchingScv > 0)) {
175176
skip = true;
176177
}
177178

@@ -283,7 +284,7 @@ public void onFrame() {
283284

284285
for (BaseLocation b : BWTA.getBaseLocations()) {
285286
// If this is a possible start location,
286-
if (b.isStartLocation()) {
287+
if (b.isStartLocation() && !b.getTilePosition().equals(self.getStartLocation())) {
287288
baseLocations.add(b);
288289
}
289290

@@ -399,6 +400,11 @@ public void onFrame() {
399400
// game.drawTextScreen(10, 25, units.toString());
400401
}
401402

403+
@Override
404+
public void onEnd(boolean isWinner) {
405+
System.out.println("Game ended! Winner: " + isWinner);
406+
}
407+
402408
public static void main(String[] args) {
403409
new MarineHell().run();
404410
}

0 commit comments

Comments
 (0)