code-battles 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/code_battles/__pycache__/__init__.cpython-312.pyc +0 -0
- package/dist/code_battles/__pycache__/battles.cpython-312.pyc +0 -0
- package/dist/code_battles/__pycache__/utilities.cpython-312.pyc +0 -0
- package/dist/code_battles/battles.py +80 -45
- package/dist/code_battles/js.pyi +16 -0
- package/dist/code_battles/utilities.py +28 -6
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -21,7 +21,9 @@ APIType = TypeVar("APIType")
|
|
|
21
21
|
PlayerRequestsType = TypeVar("PlayerRequestsType")
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
class CodeBattles(
|
|
24
|
+
class CodeBattles(
|
|
25
|
+
Generic[GameStateType, APIImplementationType, APIType, PlayerRequestsType]
|
|
26
|
+
):
|
|
25
27
|
"""
|
|
26
28
|
The base class for a Code Battles game.
|
|
27
29
|
|
|
@@ -92,15 +94,15 @@ class CodeBattles(Generic[GameStateType, APIImplementationType, APIType, PlayerR
|
|
|
92
94
|
"""
|
|
93
95
|
|
|
94
96
|
raise NotImplementedError("make_decisions")
|
|
95
|
-
|
|
97
|
+
|
|
96
98
|
def apply_decisions(self, decisions: bytes) -> None:
|
|
97
99
|
"""
|
|
98
100
|
**You must override this method.**
|
|
99
101
|
|
|
100
102
|
Use the current state and the specified decisions to update the current state to be the next state.
|
|
101
|
-
|
|
103
|
+
|
|
102
104
|
This function should not take a lot of time.
|
|
103
|
-
|
|
105
|
+
|
|
104
106
|
Do NOT update :attr:`step`.
|
|
105
107
|
"""
|
|
106
108
|
|
|
@@ -123,13 +125,13 @@ class CodeBattles(Generic[GameStateType, APIImplementationType, APIType, PlayerR
|
|
|
123
125
|
"""
|
|
124
126
|
|
|
125
127
|
raise NotImplementedError("create_initial_state")
|
|
126
|
-
|
|
128
|
+
|
|
127
129
|
def create_initial_player_requests(self, player_index: int) -> PlayerRequestsType:
|
|
128
130
|
"""
|
|
129
131
|
**You must override this method.**
|
|
130
132
|
|
|
131
133
|
Create the initial player requests for each simulation, to store in the :attr:`player_requests` attribute.
|
|
132
|
-
|
|
134
|
+
|
|
133
135
|
Should probably be empty.
|
|
134
136
|
"""
|
|
135
137
|
|
|
@@ -157,6 +159,11 @@ class CodeBattles(Generic[GameStateType, APIImplementationType, APIType, PlayerR
|
|
|
157
159
|
|
|
158
160
|
pass
|
|
159
161
|
|
|
162
|
+
def configure_extra_width(self) -> int:
|
|
163
|
+
"""Optionally add extra height to the right of the boards. 0 by default."""
|
|
164
|
+
|
|
165
|
+
return 0
|
|
166
|
+
|
|
160
167
|
def configure_extra_height(self) -> int:
|
|
161
168
|
"""Optionally add extra height below the boards. 0 by default."""
|
|
162
169
|
|
|
@@ -187,6 +194,24 @@ class CodeBattles(Generic[GameStateType, APIImplementationType, APIType, PlayerR
|
|
|
187
194
|
|
|
188
195
|
return "CodeBattlesBot"
|
|
189
196
|
|
|
197
|
+
def configure_bot_globals(self) -> Dict[str, Any]:
|
|
198
|
+
"""
|
|
199
|
+
Configure additional available global items, such as libraries from the Python standard library, bots can use.
|
|
200
|
+
|
|
201
|
+
By default, this is math, time and random.
|
|
202
|
+
|
|
203
|
+
.. warning::
|
|
204
|
+
Bots will also have `api`, `context`, `player_api`, and the bot base class name (CodeBattlesBot by default) available as part of the globals, alongside everything in `api`.
|
|
205
|
+
|
|
206
|
+
Any additional imports will be stripped (not as a security mechanism).
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
"math": math,
|
|
211
|
+
"time": time,
|
|
212
|
+
"random": random,
|
|
213
|
+
}
|
|
214
|
+
|
|
190
215
|
def download_images(
|
|
191
216
|
self, sources: List[Tuple[str, str]]
|
|
192
217
|
) -> asyncio.Future[Dict[str, Image]]:
|
|
@@ -237,7 +262,7 @@ class CodeBattles(Generic[GameStateType, APIImplementationType, APIType, PlayerR
|
|
|
237
262
|
def run_bot_method(self, player_index: int, method_name: str):
|
|
238
263
|
"""
|
|
239
264
|
Runs the specifid method of the given player.
|
|
240
|
-
|
|
265
|
+
|
|
241
266
|
Upon exception, shows an alert (does not terminate the bot).
|
|
242
267
|
"""
|
|
243
268
|
|
|
@@ -268,7 +293,6 @@ class CodeBattles(Generic[GameStateType, APIImplementationType, APIType, PlayerR
|
|
|
268
293
|
"fa-solid fa-exclamation",
|
|
269
294
|
)
|
|
270
295
|
|
|
271
|
-
|
|
272
296
|
def eliminate_player(self, player_index: int, reason=""):
|
|
273
297
|
"""Eliminate the specified player for the specified reason from the simulation."""
|
|
274
298
|
|
|
@@ -337,41 +361,47 @@ class CodeBattles(Generic[GameStateType, APIImplementationType, APIType, PlayerR
|
|
|
337
361
|
console_visible: bool,
|
|
338
362
|
verbose: bool,
|
|
339
363
|
):
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
self.
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
self.
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
+
try:
|
|
365
|
+
self.map = map
|
|
366
|
+
self.player_names = player_names
|
|
367
|
+
self.map_image = await download_image(self.configure_map_image_url(map))
|
|
368
|
+
self.background = background
|
|
369
|
+
self.console_visible = console_visible
|
|
370
|
+
self.verbose = verbose
|
|
371
|
+
self.step = 0
|
|
372
|
+
self.active_players = list(range(len(player_names)))
|
|
373
|
+
self.state = self.create_initial_state()
|
|
374
|
+
self.player_requests = [
|
|
375
|
+
self.create_initial_player_requests(i) for i in range(len(player_names))
|
|
376
|
+
]
|
|
377
|
+
self._eliminated = []
|
|
378
|
+
self._player_globals = self._get_initial_player_globals(player_codes)
|
|
379
|
+
if not self.background:
|
|
380
|
+
self.canvas = GameCanvas(
|
|
381
|
+
document.getElementById("simulation"),
|
|
382
|
+
self.configure_board_count(),
|
|
383
|
+
self.map_image,
|
|
384
|
+
document.body.clientWidth - 440
|
|
385
|
+
if console_visible
|
|
386
|
+
else document.body.clientWidth - 40,
|
|
387
|
+
document.body.clientHeight - 280,
|
|
388
|
+
self.configure_extra_width(),
|
|
389
|
+
self.configure_extra_height(),
|
|
390
|
+
)
|
|
391
|
+
await self.setup()
|
|
364
392
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
393
|
+
if not self.background:
|
|
394
|
+
if not hasattr(self, "_initialized"):
|
|
395
|
+
self._initialize()
|
|
368
396
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
397
|
+
# Show that loading finished
|
|
398
|
+
document.getElementById("loader").style.display = "none"
|
|
399
|
+
self.render()
|
|
372
400
|
|
|
373
|
-
|
|
374
|
-
|
|
401
|
+
if self.background:
|
|
402
|
+
await self._play_pause()
|
|
403
|
+
except Exception:
|
|
404
|
+
traceback.print_exc()
|
|
375
405
|
|
|
376
406
|
def _get_initial_player_globals(self, player_codes: List[str]):
|
|
377
407
|
contexts = [
|
|
@@ -381,15 +411,13 @@ class CodeBattles(Generic[GameStateType, APIImplementationType, APIType, PlayerR
|
|
|
381
411
|
|
|
382
412
|
player_globals = [
|
|
383
413
|
{
|
|
384
|
-
"math": math,
|
|
385
|
-
"time": time,
|
|
386
|
-
"random": random,
|
|
387
414
|
"api": self.get_api(),
|
|
388
415
|
bot_base_class_name: getattr(self.get_api(), bot_base_class_name),
|
|
389
416
|
"player_api": None,
|
|
390
417
|
"context": context,
|
|
391
418
|
**self.get_api().__dict__,
|
|
392
419
|
}
|
|
420
|
+
| self.configure_bot_globals()
|
|
393
421
|
for context in contexts
|
|
394
422
|
]
|
|
395
423
|
for index, api_code in enumerate(player_codes):
|
|
@@ -456,6 +484,8 @@ class CodeBattles(Generic[GameStateType, APIImplementationType, APIType, PlayerR
|
|
|
456
484
|
def _step(self):
|
|
457
485
|
if not self.over:
|
|
458
486
|
self.apply_decisions(self.make_decisions())
|
|
487
|
+
|
|
488
|
+
if not self.over:
|
|
459
489
|
self.step += 1
|
|
460
490
|
|
|
461
491
|
if self.over:
|
|
@@ -518,11 +548,16 @@ class CodeBattles(Generic[GameStateType, APIImplementationType, APIType, PlayerR
|
|
|
518
548
|
await asyncio.sleep(0.05)
|
|
519
549
|
while self._should_play():
|
|
520
550
|
start = time.time()
|
|
521
|
-
|
|
551
|
+
try:
|
|
552
|
+
self._step()
|
|
553
|
+
except Exception:
|
|
554
|
+
traceback.print_exc()
|
|
522
555
|
if not self.background:
|
|
523
556
|
await asyncio.sleep(
|
|
524
557
|
max(
|
|
525
|
-
1
|
|
558
|
+
1
|
|
559
|
+
/ self.configure_steps_per_second()
|
|
560
|
+
/ self._get_playback_speed()
|
|
526
561
|
- (time.time() - start),
|
|
527
562
|
0,
|
|
528
563
|
)
|
package/dist/code_battles/js.pyi
CHANGED
|
@@ -14,7 +14,23 @@ class TwoDContext:
|
|
|
14
14
|
]
|
|
15
15
|
font: str
|
|
16
16
|
fillStyle: str
|
|
17
|
+
strokeStyle: str
|
|
17
18
|
|
|
19
|
+
@staticmethod
|
|
20
|
+
def beginPath(): ...
|
|
21
|
+
@staticmethod
|
|
22
|
+
def arc(
|
|
23
|
+
x: int,
|
|
24
|
+
y: int,
|
|
25
|
+
radius: float,
|
|
26
|
+
startAngle: float,
|
|
27
|
+
endAngle: float,
|
|
28
|
+
counterclockwise=False,
|
|
29
|
+
): ...
|
|
30
|
+
@staticmethod
|
|
31
|
+
def stroke(): ...
|
|
32
|
+
@staticmethod
|
|
33
|
+
def fill(): ...
|
|
18
34
|
@staticmethod
|
|
19
35
|
def fillText(text: str, x: int, y: int): ...
|
|
20
36
|
@staticmethod
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Generic useful utilities for creating games with PyScript."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import math
|
|
4
5
|
from typing import Callable, List, Union
|
|
5
6
|
from enum import Enum
|
|
6
7
|
|
|
@@ -75,12 +76,14 @@ class GameCanvas:
|
|
|
75
76
|
map_image: Image,
|
|
76
77
|
max_width: int,
|
|
77
78
|
max_height: int,
|
|
79
|
+
extra_width: int,
|
|
78
80
|
extra_height: int,
|
|
79
81
|
):
|
|
80
82
|
self.canvas = canvas
|
|
81
83
|
self.player_count = player_count
|
|
82
84
|
self.map_image = map_image
|
|
83
85
|
self.extra_height = extra_height
|
|
86
|
+
self.extra_width = extra_width
|
|
84
87
|
|
|
85
88
|
self._fit_into(max_width, max_height)
|
|
86
89
|
|
|
@@ -140,6 +143,23 @@ class GameCanvas:
|
|
|
140
143
|
self.context.fillStyle = color
|
|
141
144
|
self.context.fillText(text, x, y)
|
|
142
145
|
|
|
146
|
+
def draw_circle(
|
|
147
|
+
self,
|
|
148
|
+
x: int,
|
|
149
|
+
y: int,
|
|
150
|
+
radius: float,
|
|
151
|
+
fill="black",
|
|
152
|
+
stroke="transparent",
|
|
153
|
+
board_index=0,
|
|
154
|
+
):
|
|
155
|
+
x, y = self._translate_position(board_index, x, y)
|
|
156
|
+
self.context.fillStyle = fill
|
|
157
|
+
self.context.strokeStyle = stroke
|
|
158
|
+
self.context.beginPath()
|
|
159
|
+
self.context.arc(x, y, radius, 0, 2 * math.pi)
|
|
160
|
+
self.context.stroke()
|
|
161
|
+
self.context.fill()
|
|
162
|
+
|
|
143
163
|
def clear(self):
|
|
144
164
|
"""Clears the canvas and re-draws the players' maps."""
|
|
145
165
|
|
|
@@ -165,10 +185,8 @@ class GameCanvas:
|
|
|
165
185
|
def _fit_into(self, max_width: int, max_height: int):
|
|
166
186
|
if self.map_image.width == 0 or self.map_image.height == 0:
|
|
167
187
|
raise Exception("Map image invalid!")
|
|
168
|
-
aspect_ratio = (
|
|
169
|
-
self.map_image.
|
|
170
|
-
* self.player_count
|
|
171
|
-
/ (self.map_image.height + self.extra_height)
|
|
188
|
+
aspect_ratio = (self.map_image.width * self.player_count + self.extra_width) / (
|
|
189
|
+
self.map_image.height + self.extra_height
|
|
172
190
|
)
|
|
173
191
|
width = min(max_width, max_height * aspect_ratio)
|
|
174
192
|
height = width / aspect_ratio
|
|
@@ -176,12 +194,16 @@ class GameCanvas:
|
|
|
176
194
|
self.canvas.style.height = f"{height}px"
|
|
177
195
|
self.canvas.width = width * window.devicePixelRatio
|
|
178
196
|
self.canvas.height = height * window.devicePixelRatio
|
|
179
|
-
self._scale = self.canvas.width /
|
|
197
|
+
self._scale = self.canvas.width / (
|
|
198
|
+
self.player_count * self.map_image.width + self.extra_width
|
|
199
|
+
)
|
|
180
200
|
self.context = self.canvas.getContext("2d")
|
|
181
201
|
self.context.textAlign = "center"
|
|
182
202
|
self.context.textBaseline = "middle"
|
|
183
203
|
|
|
184
|
-
self.canvas_map_width =
|
|
204
|
+
self.canvas_map_width = (
|
|
205
|
+
self.canvas.width - self._scale * self.extra_width
|
|
206
|
+
) / self.player_count
|
|
185
207
|
self.canvas_map_height = (
|
|
186
208
|
self.canvas_map_width * self.map_image.height / self.map_image.width
|
|
187
209
|
)
|
package/package.json
CHANGED