Skip to content

Commit e7d6e6a

Browse files
committed
fix crossplay bugs
1 parent 29adbe4 commit e7d6e6a

11 files changed

Lines changed: 326 additions & 217 deletions

File tree

engine/src/crossplay_python/battlecode/classes.py

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class Team(_Enum):
3636
B = 1
3737
NEUTRAL = 2
3838

39-
def opposite(self):
39+
def opponent(self) -> 'Team':
4040
match self:
4141
case Team.A:
4242
return Team.B
@@ -45,26 +45,16 @@ def opposite(self):
4545
case Team.NEUTRAL:
4646
return Team.NEUTRAL
4747

48-
def __str__(self):
48+
def ordinal(self) -> int:
49+
return self.value
50+
51+
def __str__(self) -> str:
4952
return self.name
5053

51-
def __repr__(self):
54+
def __repr__(self) -> str:
5255
return f"Team.{self.name}"
5356

5457

55-
_dir_to_index = {
56-
(0, 1): 0,
57-
(1, 1): 1,
58-
(1, 0): 2,
59-
(1, -1): 3,
60-
(0, -1): 4,
61-
(-1, -1): 5,
62-
(-1, 0): 6,
63-
(-1, 1): 7,
64-
(0, 0): 8
65-
}
66-
67-
6858
# make sure this matches the Java Direction enum, this could cause bugs
6959
class Direction(_Enum):
7060
NORTH = (0, 1)
@@ -84,38 +74,42 @@ def __init__(self, dx: int, dy: int):
8474
def opposite(self) -> 'Direction':
8575
if self == Direction.CENTER:
8676
return self
87-
return _dir_order[(_dir_to_index[self.value] + 4) % 8]
77+
return _dir_order[(_dir_to_index[self] + 4) % 8]
8878

8979
def rotate_left(self) -> 'Direction':
9080
if self == Direction.CENTER:
9181
return self
92-
return _dir_order[(_dir_to_index[self.value] - 1) % 8]
82+
return _dir_order[(_dir_to_index[self] - 1) % 8]
9383

9484
def rotate_right(self) -> 'Direction':
9585
if self == Direction.CENTER:
9686
return self
97-
return _dir_order[(_dir_to_index[self.value] + 1) % 8]
98-
99-
def all_directions():
100-
return Direction.__members__.values()
87+
return _dir_order[(_dir_to_index[self] + 1) % 8]
10188

102-
def cardinal_directions():
89+
def all_directions() -> list['Direction']:
90+
return list(Direction)
91+
92+
def cardinal_directions() -> list['Direction']:
10393
return [Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST]
10494

105-
def get_delta_x(self):
95+
def get_delta_x(self) -> int:
10696
return self.dx
10797

108-
def get_delta_y(self):
98+
def get_delta_y(self) -> int:
10999
return self.dy
110100

111-
def __str__(self):
101+
def ordinal(self) -> int:
102+
return _dir_to_index[self]
103+
104+
def __str__(self) -> str:
112105
return self.name
113106

114-
def __repr__(self):
107+
def __repr__(self) -> str:
115108
return f"Direction.{self.name}"
116109

117110

118111
_dir_order = list(Direction)
112+
_dir_to_index = {dir: index for index, dir in enumerate(_dir_order)}
119113

120114

121115
class MapLocation:
@@ -132,15 +126,15 @@ def subtract(self, dir: Direction) -> 'MapLocation':
132126
def translate(self, dx: int, dy: int) -> 'MapLocation':
133127
return MapLocation(self.x + dx, self.y + dy)
134128

135-
def __eq__(self, other):
129+
def __eq__(self, other) -> bool:
136130
if not isinstance(other, MapLocation):
137131
return False
138132
return self.x == other.x and self.y == other.y
139133

140-
def __str__(self):
134+
def __str__(self) -> str:
141135
return f"[{self.x}, {self.y}]"
142136

143-
def __repr__(self):
137+
def __repr__(self) -> str:
144138
return f"MapLocation({self.x}, {self.y})"
145139

146140

@@ -179,13 +173,20 @@ def is_rat_king_type(self) -> bool:
179173

180174
def is_cat_type(self) -> bool:
181175
return self == UnitType.CAT
176+
177+
def ordinal(self) -> int:
178+
return _ut_to_index[self]
182179

183-
def __str__(self):
180+
def __str__(self) -> str:
184181
return self.name
185182

186-
def __repr__(self):
183+
def __repr__(self) -> str:
187184
return f"UnitType.{self.name}"
188-
185+
186+
187+
_ut_order = list(UnitType)
188+
_ut_to_index = {ut: index for index, ut in enumerate(_ut_order)}
189+
189190

190191
class TrapType(_Enum):
191192
RAT_TRAP = (30, 50, 20, 25, 15, 0, 25, 2)
@@ -203,17 +204,24 @@ def __init__(self, build_cost: int, damage: int, stun_time: int, trap_limit: int
203204
self.max_count = max_count
204205
self.trigger_radius_squared = trigger_radius_squared
205206

206-
def __str__(self):
207+
def ordinal(self) -> int:
208+
return _trap_to_index[self]
209+
210+
def __str__(self) -> str:
207211
return self.name
208212

209-
def __repr__(self):
213+
def __repr__(self) -> str:
210214
return f"TrapType.{self.name}"
211215

212216

217+
_trap_order = list(TrapType)
218+
_trap_to_index = {trap: index for index, trap in enumerate(_trap_order)}
219+
220+
213221
class MapInfo:
214-
def __init__(self, loc: MapLocation, is_passable: bool, is_wall: bool, is_dirt: bool,
222+
def __init__(self, location: MapLocation, is_passable: bool, is_wall: bool, is_dirt: bool,
215223
cheese_amount: int, trap: TrapType, has_cheese_mine: bool):
216-
self.loc = loc
224+
self.location = location
217225
self.is_passable = is_passable
218226
self.is_wall = is_wall
219227
self.is_dirt = is_dirt
@@ -243,11 +251,11 @@ def __init__(self, message_bytes: int, sender_id: int, round: int, source_loc: M
243251
self.sender_id = sender_id
244252
self.round = round
245253
self.source_loc = source_loc
246-
247-
def __str__(self):
254+
255+
def __str__(self) -> str:
248256
return f"Message with value {self.bytes} sent from robot with ID {self.sender_id} during round {self.round} from location {self.source_loc}."
249-
250-
def __repr__(self):
257+
258+
def __repr__(self) -> str:
251259
return f"Message({self.bytes}, sender_id={self.sender_id}, round={self.round}, source_loc={self.source_loc})"
252260

253261

@@ -298,7 +306,6 @@ class GameConstants:
298306
RAT_BITE_DAMAGE = 10
299307
CAT_SCRATCH_DAMAGE = 50
300308
CAT_POUNCE_MAX_DISTANCE_SQUARED = 9
301-
CAT_POUNCE_ADJACENT_DAMAGE_PERCENT = 50
302309
CAT_DIG_ADDITIONAL_COOLDOWN = 5
303310
HEALTH_GRAB_THRESHOLD = 0
304311
RAT_KING_UPGRADE_CHEESE_COST = 50

engine/src/crossplay_python/battlecode/crossplay.py

Lines changed: 71 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import time
55
from enum import Enum
6+
from collections import deque
67
from .classes import *
78

89
# Connection constants
@@ -164,7 +165,7 @@ def send_json(self, data):
164165
try:
165166
self.sock.sendall(struct.pack(">I", length))
166167
self.sock.sendall(json_bytes)
167-
print("Json bytes sent:", struct.pack(">I", length) + json_bytes)
168+
# print("Json bytes sent:", struct.pack(">I", length) + json_bytes)
168169
except Exception as e:
169170
raise CrossPlayException(f"Failed to send data: {e}")
170171

@@ -200,6 +201,8 @@ def close(self):
200201

201202
# Global client instance
202203
_client = CrossPlayClient()
204+
# _spawn_queue: deque[tuple[Team, int]] = deque()
205+
_destroy_queue: deque[int] = deque()
203206

204207

205208
def connect():
@@ -263,53 +266,73 @@ def make_json(obj):
263266

264267

265268
def parse(json):
266-
if json is None:
267-
return None
268-
269-
if isinstance(json, dict) and "type" in json:
270-
obj_type = object_types[json["type"]]
271-
272-
match obj_type:
273-
case CrossPlayObjectType.THROWN_GAME_ACTION_EXCEPTION:
274-
raise GameActionException(exception_types[json["etype"]], json["msg"])
275-
case CrossPlayObjectType.DIRECTION:
276-
return directions[json["val"]]
277-
case CrossPlayObjectType.TEAM:
278-
return teams[json["val"]]
279-
case CrossPlayObjectType.MAP_LOCATION:
280-
return MapLocation(json["x"], json["y"])
281-
case CrossPlayObjectType.UNIT_TYPE:
282-
return unit_types[json["val"]]
283-
case CrossPlayObjectType.TRAP_TYPE:
284-
return trap_types[json["val"]]
285-
case CrossPlayObjectType.MESSAGE:
286-
return Message(json["bytes"], json["sid"], json["round"], json["loc"])
287-
case CrossPlayObjectType.ROBOT_INFO:
288-
return RobotInfo(
289-
json["id"],
290-
teams[json["team"]],
291-
unit_types[json["ut"]],
292-
json["hp"],
293-
parse(json["loc"]),
294-
directions[json["dir"]],
295-
json["chir"],
296-
json["ch"],
297-
parse(json["carry"]),
298-
)
299-
case CrossPlayObjectType.MAP_INFO:
300-
return MapInfo(
301-
parse(json["loc"]),
302-
json["pass"],
303-
json["wall"],
304-
json["dirt"],
305-
json["ch"],
306-
trap_types[json["trap"]],
307-
json["cm"],
308-
)
309-
case _:
310-
raise CrossPlayException("Unknown object type received from server: " + str(obj_type))
311-
else:
312-
return json
269+
while True: # so that as many robots as necessary can be destroyed, but usually return after one iteration
270+
if json is None:
271+
return None
272+
273+
if isinstance(json, dict) and "type" in json:
274+
raw_type = json["type"]
275+
276+
if raw_type == "destroy_bot":
277+
_destroy_queue.append(json["id"])
278+
send_null()
279+
json = _client.receive_json()
280+
continue
281+
# elif raw_type == "spawn_bot":
282+
# _spawn_queue.append((teams[json["team"]], json["id"]))
283+
# send_null()
284+
# _client.receive_json() # consume the extra null response
285+
# return None
286+
elif isinstance(raw_type, int):
287+
obj_type = object_types[raw_type]
288+
289+
match obj_type:
290+
case CrossPlayObjectType.THROWN_GAME_ACTION_EXCEPTION:
291+
raise GameActionException(exception_types[json["etype"]], json["msg"])
292+
case CrossPlayObjectType.DIRECTION:
293+
return directions[json["val"]]
294+
case CrossPlayObjectType.TEAM:
295+
return teams[json["val"]]
296+
case CrossPlayObjectType.MAP_LOCATION:
297+
return MapLocation(json["x"], json["y"])
298+
case CrossPlayObjectType.UNIT_TYPE:
299+
return unit_types[json["val"]]
300+
case CrossPlayObjectType.TRAP_TYPE:
301+
return trap_types[json["val"]]
302+
case CrossPlayObjectType.MESSAGE:
303+
return Message(json["bytes"], json["sid"], json["round"], json["loc"])
304+
case CrossPlayObjectType.ROBOT_INFO:
305+
return RobotInfo(
306+
json["id"],
307+
parse(json["team"]),
308+
parse(json["ut"]),
309+
json["hp"],
310+
parse(json["loc"]),
311+
parse(json["dir"]),
312+
json["chir"],
313+
json["ch"],
314+
parse(json["carry"]),
315+
)
316+
case CrossPlayObjectType.MAP_INFO:
317+
return MapInfo(
318+
parse(json["loc"]),
319+
json["pass"],
320+
json["wall"],
321+
json["dirt"],
322+
json["ch"],
323+
parse(json["trap"]),
324+
json["cm"],
325+
)
326+
case _:
327+
raise CrossPlayException("Unknown object type received from server: "
328+
+ str(obj_type))
329+
else:
330+
raise CrossPlayException("Invalid object type received from server: "
331+
+ str(raw_type))
332+
elif isinstance(json, list):
333+
return [parse(item) for item in json]
334+
else:
335+
return json
313336

314337

315338
def send_wait_and_parse(method: CrossPlayMethod, params=None):

engine/src/crossplay_python/battlecode/runner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ def init_robot(self):
236236
# self.error_method(traceback.format_exc(limit=5))
237237

238238
def do_turn(self):
239-
print("Doing turn")
239+
# print("Doing turn")
240240
if 'turn' in self.locals and isinstance(self.locals['turn'], type(lambda: 1)):
241241
# try:
242242
exec(self.locals['turn'].__code__, self.globals, self.locals)

engine/src/crossplay_python/battlecode/wrappers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ def sense_nearby_map_infos(center: MapLocation = ..., radius_squared: int = ...)
293293
return _wait(_m.RC_SENSE_NEARBY_MAP_INFOS__LOC_INT, [center, radius_squared])
294294

295295
@staticmethod
296-
def sense_nearby_robots(center: MapLocation = ..., radius_squared: int = ..., team: Team = ...):
296+
def sense_nearby_robots(center: MapLocation = ..., radius_squared: int = ..., team: Team = ...) -> list[RobotInfo]:
297297
"""
298298
Possible parameter combinations:
299299
- sense_nearby_robots()
@@ -361,8 +361,8 @@ def write_shared_array(index: int, value: int) -> None:
361361
rc = RobotController
362362

363363

364-
def log(message):
365-
return _wait(_m.LOG, [message])
364+
def log(*messages) -> None:
365+
return _wait(_m.LOG, [" ".join(map(str, messages))])
366366

367367

368368
def bottom_left_distance_squared_to(loc1: MapLocation, loc2: MapLocation) -> int:

0 commit comments

Comments
 (0)