Skip to content

Commit b1a5f90

Browse files
committed
fix bugs (no bytecode checks yet though)
1 parent a21f208 commit b1a5f90

10 files changed

Lines changed: 197 additions & 92 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ frontend/build
2323
### Matches should be ignored
2424
/matches/
2525

26+
### Cross-play temp files
27+
/crossplay_temp/
28+
2629
### Java
2730
*.classpath
2831
*.project

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ task headless(type: JavaExec, dependsOn: [':engine:build', ':example-bots:build'
9191
]
9292
}
9393

94+
task crossPlayPy(type: Exec, dependsOn: [':engine:build']) {
95+
commandLine 'python', 'engine/src/crossplay_python/main.py', '--teamA', project.property('teamA'), '--teamB', project.property('teamB')
96+
}
97+
9498
// keep the client happy because it references this step
9599
task unpackClient() {}
96100

engine/src/crossplay_python/crossplay.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ class CrossPlayObjectType(Enum):
3838

3939
class CrossPlayMethod(Enum):
4040
INVALID = 0
41-
TERMINATE = 1
42-
START_TURN = 2 # returns [rc, round, team, id, end]
41+
START_TURN = 1 # returns [rc, round, team, id, end]
42+
END_TURN = 2 # params: [bytecode_used]
4343
RC_GET_ROUND_NUM = 3
4444
RC_GET_MAP_WIDTH = 4
4545
RC_GET_MAP_HEIGHT = 5
@@ -198,7 +198,8 @@ def from_json(cls, json_data):
198198
params = [CrossPlayObject.from_json(param) for param in json_data["params"]]
199199
return CrossPlayMessage(method, params)
200200

201-
def wait(message: CrossPlayMessage, timeout=1000, timestep=0.1, message_dir=MESSAGE_DIR):
201+
# 0.1 ms timestep, 10 min timeout
202+
def wait(message: CrossPlayMessage, timeout=600, timestep=0.0001, message_dir=MESSAGE_DIR):
202203
read_file = os.path.join(message_dir, MESSAGE_FILE_JAVA)
203204
write_file = os.path.join(message_dir, MESSAGE_FILE_OTHER)
204205
java_lock_file = os.path.join(message_dir, LOCK_FILE_JAVA)

engine/src/crossplay_python/main.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from collections import OrderedDict
77

88
from crossplay_python.runner import RobotRunner
9-
from crossplay_python.crossplay import BYTECODE_LIMIT, CrossPlayMessage, CrossPlayMethod, wait
9+
from crossplay_python.crossplay import BYTECODE_LIMIT, CrossPlayLiteral as lit, \
10+
CrossPlayMessage as mess, CrossPlayMethod as m, CrossPlayObjectType as ot, wait
1011
from crossplay_python.wrappers import _GAME_METHODS, Team
1112

1213
CROSSPLAY_PYTHON_DIR = "example-bots/src/crossplay_python"
@@ -18,6 +19,9 @@
1819
}
1920

2021
def get_code(bot_name):
22+
if bot_name is None:
23+
return None
24+
2125
path = f"{CROSSPLAY_PYTHON_DIR}/{bot_name}"
2226

2327
# read all files in this directory into a dictionary
@@ -40,22 +44,21 @@ def format_print(*args):
4044
def play(team_a=None, team_b=None, debug=False):
4145
print(f"Starting cross-play Python runner with teams {team_a} and {team_b}")
4246

43-
# TODO comment out these lines when done testing
44-
if team_a is None:
45-
team_a = "pytest"
46-
47-
if team_b is None:
48-
team_b = "pytest"
49-
5047
code = {
5148
Team.A: get_code(team_a),
5249
Team.B: get_code(team_b),
5350
Team.NEUTRAL: None
5451
}
5552

5653
while True:
57-
rc, round, team, id, end = wait(CrossPlayMessage(CrossPlayMethod.START_TURN, []))
58-
print(f"Starting turn for RobotController: {rc}, round: {round}, team: {TEAM_NAMES[team]}, ID: {id}, end: {end}")
54+
rc, round, team, id, end = wait(mess(m.START_TURN, []))
55+
56+
if end:
57+
print("Game ended")
58+
break
59+
60+
if debug:
61+
print(f"Starting turn. Round {round}, team {TEAM_NAMES[team]}, ID {id}, end {end}")
5962

6063
runner = RobotRunner(
6164
code=code[team],
@@ -64,11 +67,11 @@ def play(team_a=None, team_b=None, debug=False):
6467
bytecode_limit=BYTECODE_LIMIT,
6568
debug=debug)
6669

70+
runner.init_robot()
6771
runner.run()
68-
69-
if end:
70-
print("Game ended")
71-
break
72+
bytecode = runner.bytecode_limit - runner.bytecode
73+
runner.kill()
74+
wait(mess(m.END_TURN, [lit(ot.INTEGER, bytecode)]))
7275

7376
if __name__ == "__main__":
7477
parser = ArgumentParser()

engine/src/main/battlecode/crossplay/CrossPlay.java

Lines changed: 79 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
import org.json.*;
55

66
import java.io.IOException;
7+
import java.io.OutputStream;
78
import java.nio.file.*;
89

910
import battlecode.common.*;
11+
import battlecode.instrumenter.stream.RoboPrintStream;
1012

1113
/**
1214
* Allows bots written in different languages to be run by the Java engine using a message-passing system.
@@ -21,15 +23,26 @@ public class CrossPlay {
2123
LOCK_FILE_JAVA = "lock_java.txt", // lock file created by the java engine
2224
LOCK_FILE_OTHER = "lock_other.txt"; // lock file created by the other language's runner script
2325

26+
private final boolean finalizer;
27+
private boolean initialized;
2428
private ArrayList<Object> objects;
2529
private RobotController rc;
30+
private CrossPlayReference rcRef;
2631
private int roundNum;
2732
private Team team;
2833
private int id;
29-
private CrossPlayReference rcRef;
34+
private OutputStream out;
3035

3136
public CrossPlay() {
3237
this.objects = new ArrayList<>();
38+
this.finalizer = false;
39+
this.initialized = false;
40+
}
41+
42+
public CrossPlay(boolean finalizer) {
43+
this.objects = new ArrayList<>();
44+
this.finalizer = finalizer;
45+
this.initialized = false;
3346
}
3447

3548
private void clearObjects() {
@@ -116,7 +129,7 @@ private CrossPlayReference setNextObject(CrossPlayObjectType type, Object value)
116129
return ref;
117130
}
118131

119-
public void runMessagePassing() {
132+
public int runMessagePassing() throws GameActionException {
120133
Path crossPlayDir = Paths.get(CROSS_PLAY_DIR);
121134
Path javaMessagePath = crossPlayDir.resolve(MESSAGE_FILE_JAVA);
122135
Path otherMessagePath = crossPlayDir.resolve(MESSAGE_FILE_OTHER);
@@ -127,7 +140,7 @@ public void runMessagePassing() {
127140
while (true) {
128141
try {
129142
if (!Files.exists(otherMessagePath) || Files.exists(javaMessagePath) || Files.exists(otherLockPath)) {
130-
Thread.sleep(100, 0); // TODO make shorter: sleep for 0.1 s
143+
Thread.sleep(0, 100000); // TODO currently 0.1 ms, maybe make shorter
131144
// System.out.println("Still waiting for message Python -> Java...");
132145
continue;
133146
}
@@ -144,20 +157,22 @@ public void runMessagePassing() {
144157

145158
// System.out.println("Received message Python -> Java: " + messageJson.toString());
146159

147-
if (message.method == CrossPlayMethod.TERMINATE) {
148-
Files.delete(otherMessagePath);
149-
Files.delete(javaLockPath);
150-
// System.out.println("Received terminate message, ending cross-play message passing.");
151-
break;
152-
}
153-
154160
CrossPlayObject result = processMessage(message);
155161
String resultContent = result.toJson().toString();
156162
Files.writeString(javaMessagePath, resultContent);
157163

158164
Files.delete(otherMessagePath);
159165
Files.delete(javaLockPath);
160166

167+
if (message.method == CrossPlayMethod.END_TURN) {
168+
// System.out.println("Received terminate message, ending cross-play message passing.");
169+
int bytecodeUsed = getLiteralValue(message.params[0]);
170+
return bytecodeUsed;
171+
} else if (this.finalizer && message.method == CrossPlayMethod.START_TURN) {
172+
// System.out.println("Finalizer received start turn message, ending cross-play message passing.");
173+
return 0;
174+
}
175+
161176
// System.out.println("Sent response Java -> Python: " + resultContent);
162177
// System.out.println("Waiting for message Python -> Java...");
163178
} catch (InterruptedException e) {
@@ -168,71 +183,61 @@ public void runMessagePassing() {
168183
}
169184
}
170185

171-
// private JSONArray processJsonMessages(JSONArray json) {
172-
// System.out.println("Starting cross-play message processing...");
173-
// int length = json.length();
174-
// JSONObject[] resultsArr = new JSONObject[length];
175-
176-
// for (int i = 0; i < length; i++) {
177-
// JSONObject messageJson = json.getJSONObject(i);
178-
// CrossPlayMessage message = CrossPlayMessage.fromJson(messageJson);
179-
// CrossPlayObject result = processMessage(message);
180-
// JSONObject resultJson = result.toJson();
181-
// resultsArr[i] = resultJson;
182-
// }
183-
184-
// JSONArray resultsJson = new JSONArray(resultsArr);
185-
// System.out.println("Finished cross-play message processing.");
186-
// return resultsJson;
187-
// }
188-
189186
private CrossPlayObject processMessage(CrossPlayMessage message) {
190-
CrossPlayObject[] computedParams = new CrossPlayObject[message.params.length];
191-
192-
for (int i = 0; i < message.params.length; i++) {
193-
CrossPlayObject param = message.params[i];
194-
195-
if (param instanceof CrossPlayMessage mess) {
196-
CrossPlayObject innerResult = processMessage(mess);
197-
computedParams[i] = getObject(innerResult);
198-
} else {
199-
computedParams[i] = param;
200-
}
201-
}
202-
203187
CrossPlayObject result;
204188
RobotController rc;
205189

206190
// TODO add cases for all methods
207191
switch (message.method) {
208192
case INVALID:
209193
throw new CrossPlayException("Received invalid cross-play method!");
210-
case TERMINATE:
211-
throw new CrossPlayException("Terminate messages should be handled outside of processMessage.");
212194
case START_TURN:
213-
result = new CrossPlayLiteral(CrossPlayObjectType.ARRAY, new CrossPlayObject[] {
214-
this.rcRef,
215-
new CrossPlayLiteral(CrossPlayObjectType.INTEGER, this.roundNum),
216-
new CrossPlayLiteral(CrossPlayObjectType.TEAM, this.team),
217-
new CrossPlayLiteral(CrossPlayObjectType.INTEGER, this.id),
218-
new CrossPlayLiteral(CrossPlayObjectType.BOOLEAN, false),
219-
});
195+
// System.out.println("Processing START_TURN");
196+
if (this.finalizer) {
197+
result = new CrossPlayLiteral(CrossPlayObjectType.ARRAY, new CrossPlayObject[] {
198+
CrossPlayLiteral.NULL,
199+
CrossPlayLiteral.NULL,
200+
CrossPlayLiteral.NULL,
201+
CrossPlayLiteral.NULL,
202+
CrossPlayLiteral.TRUE
203+
});
204+
} else {
205+
result = new CrossPlayLiteral(CrossPlayObjectType.ARRAY, new CrossPlayObject[] {
206+
this.rcRef,
207+
CrossPlayLiteral.ofInt(this.roundNum),
208+
CrossPlayLiteral.ofTeam(this.team),
209+
CrossPlayLiteral.ofInt(this.id),
210+
CrossPlayLiteral.FALSE
211+
});
212+
}
213+
break;
214+
case END_TURN:
215+
// System.out.println("Processing END_TURN");
216+
result = new CrossPlayLiteral(CrossPlayObjectType.NULL, null);
220217
break;
221218
case RC_GET_ROUND_NUM:
222-
rc = this.<RobotController>getObject(computedParams[0]);
219+
// System.out.println("Processing RC_GET_ROUND_NUM");
220+
rc = this.<RobotController>getObject(message.params[0]);
223221
result = new CrossPlayLiteral(CrossPlayObjectType.INTEGER, rc.getRoundNum());
224222
break;
225223
case RC_GET_MAP_WIDTH:
226-
rc = this.<RobotController>getObject(computedParams[0]);
224+
// System.out.println("Processing RC_GET_MAP_WIDTH");
225+
rc = this.<RobotController>getObject(message.params[0]);
227226
result = new CrossPlayLiteral(CrossPlayObjectType.INTEGER, rc.getMapWidth());
228227
break;
229228
case RC_GET_MAP_HEIGHT:
230-
rc = this.<RobotController>getObject(computedParams[0]);
229+
// System.out.println("Processing RC_GET_MAP_HEIGHT");
230+
rc = this.<RobotController>getObject(message.params[0]);
231231
result = new CrossPlayLiteral(CrossPlayObjectType.INTEGER, rc.getMapHeight());
232232
break;
233233
case LOG:
234-
String msg = getLiteralValue(computedParams[0]);
235-
System.out.println(msg);
234+
// System.out.println("Processing LOG");
235+
String msg = getLiteralValue(message.params[0]);
236+
237+
if (this.out instanceof RoboPrintStream rps) {
238+
rps.println(msg);
239+
}
240+
236241
result = new CrossPlayLiteral(CrossPlayObjectType.NULL, null);
237242
break;
238243
default:
@@ -242,20 +247,32 @@ private CrossPlayObject processMessage(CrossPlayMessage message) {
242247
return result;
243248
}
244249

245-
public void playTurn(RobotController rc) {
250+
public int playTurn(RobotController rc, OutputStream systemOut) throws GameActionException {
251+
// System.out.println("playTurn called for CrossPlay instance " + this.hashCode());
252+
253+
if (this.rc == rc && this.roundNum == rc.getRoundNum()) {
254+
// System.out.println("playTurn returned early for CrossPlay instance " + this.hashCode());
255+
return 0;
256+
}
257+
246258
this.rc = rc;
247259
this.roundNum = rc.getRoundNum();
248260
this.team = rc.getTeam();
249261
this.id = rc.getID();
262+
this.out = systemOut;
250263

251-
if (this.roundNum == 1) {
264+
if (this.initialized) {
265+
this.rcRef = new CrossPlayReference(CrossPlayObjectType.ROBOT_CONTROLLER, 0);
266+
} else {
252267
clearObjects();
253268
this.rcRef = setNextObject(CrossPlayObjectType.ROBOT_CONTROLLER, this.rc);
254-
System.out.println("Cross-play bot initialized!");
255-
} else {
256-
this.rcRef = new CrossPlayReference(CrossPlayObjectType.ROBOT_CONTROLLER, 0);
269+
this.initialized = true;
270+
// System.out.println("Cross-play bot initialized!");
257271
}
258-
259-
runMessagePassing();
272+
273+
// System.out.println("Running message passing for CrossPlay instance " + this.hashCode());
274+
int bytecodeUsed = runMessagePassing();
275+
// System.out.println("playTurn finished for CrossPlay instance " + this.hashCode());
276+
return bytecodeUsed;
260277
}
261278
}

engine/src/main/battlecode/crossplay/CrossPlayLiteral.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,53 @@ public static CrossPlayLiteral fromJson(JSONObject json) {
8686
throw new CrossPlayException("Cannot decode CrossPlayObject of type " + type + " as a literal.");
8787
}
8888
}
89-
}
89+
90+
public static CrossPlayLiteral of(Object value) {
91+
if (value == null) {
92+
return NULL;
93+
} else if (value instanceof Boolean) {
94+
return ofBoolean((Boolean)value);
95+
} else if (value instanceof Integer) {
96+
return ofInt((Integer)value);
97+
} else if (value instanceof Double) {
98+
return ofDouble((Double)value);
99+
} else if (value instanceof String) {
100+
return ofString((String)value);
101+
} else if (value instanceof CrossPlayObject[]) {
102+
return ofArray((CrossPlayObject[])value);
103+
} else if (value instanceof Team) {
104+
return ofTeam((Team)value);
105+
} else {
106+
throw new CrossPlayException("Cannot create CrossPlayLiteral from value of type " + value.getClass().getName());
107+
}
108+
}
109+
110+
public static final CrossPlayLiteral
111+
NULL = new CrossPlayLiteral(CrossPlayObjectType.NULL, null),
112+
TRUE = new CrossPlayLiteral(CrossPlayObjectType.BOOLEAN, true),
113+
FALSE = new CrossPlayLiteral(CrossPlayObjectType.BOOLEAN, false);
114+
115+
public static CrossPlayLiteral ofBoolean(Boolean value) {
116+
return value ? TRUE : FALSE;
117+
}
118+
119+
public static CrossPlayLiteral ofInt(Integer value) {
120+
return new CrossPlayLiteral(CrossPlayObjectType.INTEGER, value);
121+
}
122+
123+
public static CrossPlayLiteral ofDouble(Double value) {
124+
return new CrossPlayLiteral(CrossPlayObjectType.DOUBLE, value);
125+
}
126+
127+
public static CrossPlayLiteral ofString(String value) {
128+
return new CrossPlayLiteral(CrossPlayObjectType.STRING, value);
129+
}
130+
131+
public static CrossPlayLiteral ofArray(CrossPlayObject[] value) {
132+
return new CrossPlayLiteral(CrossPlayObjectType.ARRAY, value);
133+
}
134+
135+
public static CrossPlayLiteral ofTeam(Team value) {
136+
return new CrossPlayLiteral(CrossPlayObjectType.TEAM, value);
137+
}
138+
}

0 commit comments

Comments
 (0)