Skip to content

Commit 1761a38

Browse files
committed
Merge branch 'master' into engine-release
2 parents 563d3e4 + 3845baf commit 1761a38

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+4002
-280
lines changed

build.gradle

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ task headless(type: JavaExec, dependsOn: [':engine:build', ':example-bots:build'
7878
'-Dbc.engine.show-indicators=' + (project.findProperty('showIndicators') ?: 'true'),
7979
'-Dbc.game.team-a=' + project.property('teamA'),
8080
'-Dbc.game.team-b=' + project.property('teamB'),
81+
'-Dbc.game.team-a.language=' + (project.findProperty('languageA') ?: 'java'),
82+
'-Dbc.game.team-b.language=' + (project.findProperty('languageB') ?: 'java'),
8183
'-Dbc.game.team-a.url=' + (project.findProperty('classLocationA') ?: defaultClassLocation),
8284
'-Dbc.game.team-b.url=' + (project.findProperty('classLocationB') ?: defaultClassLocation),
8385
'-Dbc.game.team-a.package=' + (project.findProperty('packageNameA') ?: project.property('teamA')),
@@ -89,10 +91,29 @@ task headless(type: JavaExec, dependsOn: [':engine:build', ':example-bots:build'
8991
]
9092
}
9193

94+
task crossPlayPy(type: Exec, dependsOn: [':engine:build']) {
95+
commandLine 'python', 'engine/src/crossplay_python/main.py',
96+
'--teamA', (project.findProperty('languageA') == 'java' ? '/' : project.property('teamA')),
97+
'--teamB', (project.findProperty('languageB') == 'java' ? '/' : project.property('teamB')),
98+
'--dirA', 'example-bots/src/crossplay_python',
99+
'--dirB', 'example-bots/src/crossplay_python',
100+
'--new-process'
101+
}
102+
103+
task headlessPy(dependsOn: ['headless']) {}
104+
headlessPy.mustRunAfter crossPlayPy
105+
92106
// keep the client happy because it references this step
93107
task unpackClient() {}
94108

95-
task run(dependsOn: ['headless', 'unpackClient']) {}
109+
task run(dependsOn: ['unpackClient']) {}
110+
111+
if (project.hasProperty('languageA') && project.property('languageA') == 'python' ||
112+
project.hasProperty('languageB') && project.property('languageB') == 'python') {
113+
run.dependsOn crossPlayPy, headlessPy
114+
} else {
115+
run.dependsOn headless
116+
}
96117

97118
task runClient {
98119
doLast {

client/src/components/sidebar/map-editor/map-editor.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export const MapEditorPage: React.FC<Props> = (props) => {
187187
}
188188

189189
useEffect(() => {
190+
if (!canvasMouseDown || !hoveredTile) return;
190191
if (canvasMouseDown && hoveredTile) applyBrush(hoveredTile)
191192
// added defensive checks just in case stuff is null for some reason
192193
if (canvasMouseDown && hoveredTile && typeof hoveredTile.x === 'number' && typeof hoveredTile.y === 'number') {

engine/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ dependencies {
5858
// There are no valid released versions on Maven Central or Sonatype.
5959
// If you need JSI, download jsi-1.0.jar manually and use:
6060
[group: 'net.sf.trove4j', name: 'trove4j', version: '2.1.0'],
61+
62+
[group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.15.2'],
6163
)
6264
// implementation files('lib/jsi-1.0.jar')
6365

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
**/_version.py
2+
*.egg-info/
3+
dist/
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .classes import *
2+
from .wrappers import *
3+
from main import main as _main
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
from enum import Enum as _Enum
2+
3+
4+
class GameActionExceptionType(_Enum):
5+
INTERNAL_ERROR = 0
6+
NOT_ENOUGH_RESOURCE = 1
7+
CANT_MOVE_THERE = 2
8+
IS_NOT_READY = 3
9+
CANT_SENSE_THAT = 4
10+
OUT_OF_RANGE = 5
11+
CANT_DO_THAT = 6
12+
NO_ROBOT_THERE = 7
13+
ROUND_OUT_OF_RANGE = 8
14+
15+
def __str__(self):
16+
return self.name
17+
18+
def __repr__(self):
19+
return f"GameActionExceptionType.{self.name}"
20+
21+
22+
class GameActionException(Exception):
23+
def __init__(self, exc_type: GameActionExceptionType, message: str):
24+
super().__init__(message)
25+
self.type = exc_type
26+
27+
def __str__(self):
28+
return f"GameActionException of type {self.type}: {super().__str__()}"
29+
30+
def __repr__(self):
31+
return f"GameActionException(type={repr(self.type)}, message={repr(super().__str__())})"
32+
33+
34+
class Team(_Enum):
35+
A = 0
36+
B = 1
37+
NEUTRAL = 2
38+
39+
def opponent(self) -> 'Team':
40+
match self:
41+
case Team.A:
42+
return Team.B
43+
case Team.B:
44+
return Team.A
45+
case Team.NEUTRAL:
46+
return Team.NEUTRAL
47+
48+
def ordinal(self) -> int:
49+
return self.value
50+
51+
def __str__(self) -> str:
52+
return self.name
53+
54+
def __repr__(self) -> str:
55+
return f"Team.{self.name}"
56+
57+
58+
# make sure this matches the Java Direction enum, this could cause bugs
59+
class Direction(_Enum):
60+
NORTH = (0, 1)
61+
NORTHEAST = (1, 1)
62+
EAST = (1, 0)
63+
SOUTHEAST = (1, -1)
64+
SOUTH = (0, -1)
65+
SOUTHWEST = (-1, -1)
66+
WEST = (-1, 0)
67+
NORTHWEST = (-1, 1)
68+
CENTER = (0, 0)
69+
70+
def __init__(self, dx: int, dy: int):
71+
self.dx = dx
72+
self.dy = dy
73+
74+
def opposite(self) -> 'Direction':
75+
if self == Direction.CENTER:
76+
return self
77+
return _dir_order[(_dir_to_index[self] + 4) % 8]
78+
79+
def rotate_left(self) -> 'Direction':
80+
if self == Direction.CENTER:
81+
return self
82+
return _dir_order[(_dir_to_index[self] - 1) % 8]
83+
84+
def rotate_right(self) -> 'Direction':
85+
if self == Direction.CENTER:
86+
return self
87+
return _dir_order[(_dir_to_index[self] + 1) % 8]
88+
89+
def all_directions() -> list['Direction']:
90+
return list(Direction)
91+
92+
def cardinal_directions() -> list['Direction']:
93+
return [Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST]
94+
95+
def get_delta_x(self) -> int:
96+
return self.dx
97+
98+
def get_delta_y(self) -> int:
99+
return self.dy
100+
101+
def ordinal(self) -> int:
102+
return _dir_to_index[self]
103+
104+
def __str__(self) -> str:
105+
return self.name
106+
107+
def __repr__(self) -> str:
108+
return f"Direction.{self.name}"
109+
110+
111+
_dir_order = list(Direction)
112+
_dir_to_index = {dir: index for index, dir in enumerate(_dir_order)}
113+
114+
115+
class MapLocation:
116+
def __init__(self, x: int, y: int):
117+
self.x = x
118+
self.y = y
119+
120+
def add(self, dir: Direction) -> 'MapLocation':
121+
return MapLocation(self.x + dir.dx, self.y + dir.dy)
122+
123+
def subtract(self, dir: Direction) -> 'MapLocation':
124+
return MapLocation(self.x - dir.dx, self.y - dir.dy)
125+
126+
def translate(self, dx: int, dy: int) -> 'MapLocation':
127+
return MapLocation(self.x + dx, self.y + dy)
128+
129+
def __eq__(self, other) -> bool:
130+
if not isinstance(other, MapLocation):
131+
return False
132+
return self.x == other.x and self.y == other.y
133+
134+
def __str__(self) -> str:
135+
return f"[{self.x}, {self.y}]"
136+
137+
def __repr__(self) -> str:
138+
return f"MapLocation({self.x}, {self.y})"
139+
140+
141+
class UnitType(_Enum):
142+
BABY_RAT = (100, 1, 20, 90, 10, 10, 17500)
143+
RAT_KING = (500, 3, 25, 360, 10, 40, 20000)
144+
CAT = (10_000, 2, 17, 180, 15, 20, 17500)
145+
146+
def __init__(self, health: int, size: int, vision_cone_radius_squared: int, vision_cone_angle: int,
147+
action_cooldown: int, movement_cooldown: int, bytecode_limit: int):
148+
self.health = health
149+
self.size = size
150+
self.vision_cone_radius_squared = vision_cone_radius_squared
151+
self.vision_cone_angle = vision_cone_angle
152+
self.action_cooldown = action_cooldown
153+
self.movement_cooldown = movement_cooldown
154+
self.bytecode_limit = bytecode_limit
155+
156+
def uses_bottom_left_location_for_distance(self) -> bool:
157+
return self == UnitType.CAT
158+
159+
def is_robot_type(self) -> bool:
160+
return self in {UnitType.BABY_RAT, UnitType.RAT_KING, UnitType.CAT}
161+
162+
def is_throwable_type(self) -> bool:
163+
return self == UnitType.BABY_RAT
164+
165+
def is_throwing_type(self) -> bool:
166+
return self == UnitType.BABY_RAT
167+
168+
def is_baby_rat_type(self) -> bool:
169+
return self == UnitType.BABY_RAT
170+
171+
def is_rat_king_type(self) -> bool:
172+
return self == UnitType.RAT_KING
173+
174+
def is_cat_type(self) -> bool:
175+
return self == UnitType.CAT
176+
177+
def ordinal(self) -> int:
178+
return _ut_to_index[self]
179+
180+
def __str__(self) -> str:
181+
return self.name
182+
183+
def __repr__(self) -> str:
184+
return f"UnitType.{self.name}"
185+
186+
187+
_ut_order = list(UnitType)
188+
_ut_to_index = {ut: index for index, ut in enumerate(_ut_order)}
189+
190+
191+
class TrapType(_Enum):
192+
RAT_TRAP = (30, 50, 20, 25, 15, 0, 25, 2)
193+
CAT_TRAP = (10, 100, 20, 5, 10, 0, 10, 2)
194+
NONE = (0, 0, 0, 0, 0, 0, 0, 0)
195+
196+
def __init__(self, build_cost: int, damage: int, stun_time: int, trap_limit: int,
197+
action_cooldown: int, spawn_cheese_amount: int, max_count: int, trigger_radius_squared: int):
198+
self.build_cost = build_cost
199+
self.damage = damage
200+
self.stun_time = stun_time
201+
self.trap_limit = trap_limit
202+
self.action_cooldown = action_cooldown
203+
self.spawn_cheese_amount = spawn_cheese_amount
204+
self.max_count = max_count
205+
self.trigger_radius_squared = trigger_radius_squared
206+
207+
def ordinal(self) -> int:
208+
return _trap_to_index[self]
209+
210+
def __str__(self) -> str:
211+
return self.name
212+
213+
def __repr__(self) -> str:
214+
return f"TrapType.{self.name}"
215+
216+
217+
_trap_order = list(TrapType)
218+
_trap_to_index = {trap: index for index, trap in enumerate(_trap_order)}
219+
220+
221+
class MapInfo:
222+
def __init__(self, location: MapLocation, is_passable: bool, is_wall: bool, is_dirt: bool,
223+
cheese_amount: int, trap: TrapType, has_cheese_mine: bool):
224+
self.location = location
225+
self.is_passable = is_passable
226+
self.is_wall = is_wall
227+
self.is_dirt = is_dirt
228+
self.cheese_amount = cheese_amount
229+
self.trap = trap
230+
self.has_cheese_mine = has_cheese_mine
231+
232+
233+
class RobotInfo:
234+
def __init__(self, id: int, team: Team, unit_type: UnitType, health: int,
235+
location: MapLocation, direction: Direction,
236+
chirality: int, cheese_amount: int, carrying_robot: 'RobotInfo'):
237+
self.id = id
238+
self.team = team
239+
self.type = unit_type
240+
self.health = health
241+
self.location = location
242+
self.direction = direction
243+
self.chirality = chirality
244+
self.cheese_amount = cheese_amount
245+
self.carrying_robot = carrying_robot
246+
247+
248+
class Message:
249+
def __init__(self, message_bytes: int, sender_id: int, round: int, source_loc: MapLocation):
250+
self.bytes = message_bytes
251+
self.sender_id = sender_id
252+
self.round = round
253+
self.source_loc = source_loc
254+
255+
def __str__(self) -> str:
256+
return f"Message with value {self.bytes} sent from robot with ID {self.sender_id} during round {self.round} from location {self.source_loc}."
257+
258+
def __repr__(self) -> str:
259+
return f"Message({self.bytes}, sender_id={self.sender_id}, round={self.round}, source_loc={self.source_loc})"
260+
261+
262+
class GameConstants:
263+
"""
264+
GameConstants defines constants that affect gameplay.
265+
Modifying these constants on the Python side does not affect the game at all,
266+
so there is no reason to modify them.
267+
"""
268+
269+
SPEC_VERSION = "1"
270+
MAP_MIN_HEIGHT = 20
271+
MAP_MAX_HEIGHT = 60
272+
MAP_MIN_WIDTH = 20
273+
MAP_MAX_WIDTH = 60
274+
MIN_CHEESE_MINE_SPACING_SQUARED = 25
275+
MAX_DIRT_PERCENTAGE = 50
276+
MAX_WALL_PERCENTAGE = 20
277+
GAME_DEFAULT_SEED = 6370
278+
GAME_MAX_NUMBER_OF_ROUNDS = 2000
279+
INDICATOR_STRING_MAX_LENGTH = 256
280+
TIMELINE_LABEL_MAX_LENGTH = 64
281+
EXCEPTION_BYTECODE_PENALTY = 500
282+
INITIAL_TEAM_CHEESE = 2500
283+
MAX_NUMBER_OF_RAT_KINGS = 5
284+
MAX_TEAM_EXECUTION_TIME = 1200000000000
285+
MOVE_STRAFE_COOLDOWN = 18
286+
CHEESE_COOLDOWN_PENALTY = 0.01
287+
RAT_KING_CHEESE_CONSUMPTION = 2
288+
RAT_KING_HEALTH_LOSS = 10
289+
CHEESE_MINE_SPAWN_PROBABILITY = 0.01
290+
CHEESE_SPAWN_RADIUS = 4
291+
CHEESE_SPAWN_AMOUNT = 20
292+
NUMBER_INITIAL_RAT_KINGS = 1
293+
CHEESE_TRANSFER_RADIUS_SQUARED = 9
294+
CHEESE_PICK_UP_RADIUS_SQUARED = 2
295+
BUILD_ROBOT_RADIUS_SQUARED = 4
296+
BUILD_ROBOT_BASE_COST = 10
297+
BUILD_ROBOT_COST_INCREASE = 10
298+
NUM_ROBOTS_FOR_COST_INCREASE = 4
299+
BUILD_DISTANCE_SQUARED = 2
300+
RAT_KING_ATTACK_DISTANCE_SQUARED = 8
301+
MESSAGE_ROUND_DURATION = 5
302+
MAX_MESSAGES_SENT_ROBOT = 1
303+
SQUEAK_RADIUS_SQUARED = 16
304+
THROW_DAMAGE = 20
305+
THROW_DAMAGE_PER_TILE = 5
306+
RAT_BITE_DAMAGE = 10
307+
CAT_SCRATCH_DAMAGE = 50
308+
CAT_POUNCE_MAX_DISTANCE_SQUARED = 9
309+
CAT_DIG_ADDITIONAL_COOLDOWN = 5
310+
HEALTH_GRAB_THRESHOLD = 0
311+
RAT_KING_UPGRADE_CHEESE_COST = 50
312+
DIG_DIRT_CHEESE_COST = 10
313+
PLACE_DIRT_CHEESE_COST = 10
314+
SHARED_ARRAY_SIZE = 64
315+
COMM_ARRAY_MAX_VALUE = 1023
316+
COOLDOWN_LIMIT = 10
317+
COOLDOWNS_PER_TURN = 10
318+
TURNING_COOLDOWN = 10
319+
BUILD_ROBOT_COOLDOWN = 10
320+
CHEESE_TRANSFER_COOLDOWN = 10
321+
DIG_COOLDOWN = 25
322+
CARRY_COOLDOWN_MULTIPLIER = 1.5
323+
MAX_CARRY_TOWER_HEIGHT = 2
324+
MAX_CARRY_DURATION = 10
325+
THROW_DURATION = 4
326+
HIT_GROUND_COOLDOWN = 10
327+
HIT_TARGET_COOLDOWN = 30
328+
CAT_SLEEP_TIME = 2

0 commit comments

Comments
 (0)