code-battles 1.5.9 → 1.6.0
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/cjs/index.js +3 -3
- 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 +82 -28
- package/dist/code_battles/utilities.py +6 -0
- package/dist/esm/index.js +3 -3
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -2520,7 +2520,7 @@ const RunSimulationBlock = () => {
|
|
|
2520
2520
|
};
|
|
2521
2521
|
const startRunNoUI = () => {
|
|
2522
2522
|
setRunningNoUI(true);
|
|
2523
|
-
runNoUI(map, apis, playerBots, seed.toString(),
|
|
2523
|
+
runNoUI(map, apis, playerBots, seed.toString(), false);
|
|
2524
2524
|
};
|
|
2525
2525
|
const startRunNoUIN = (n) => {
|
|
2526
2526
|
setLocalStorage("Results", {});
|
|
@@ -14575,7 +14575,7 @@ const Round = () => {
|
|
|
14575
14575
|
React.createElement(core.Button, { leftSection: React.createElement("i", { className: "fa-solid fa-play" }), size: "xs", onClick: () => navigate(`/simulation/${round.map.replaceAll(" ", "-")}/${round.players.join("-")}?showcase=true`) }, "Simulate"),
|
|
14576
14576
|
React.createElement(core.Button, { leftSection: React.createElement("i", { className: "fa-solid fa-forward" }), size: "xs", onClick: () => {
|
|
14577
14577
|
if (roundIterations === 1) {
|
|
14578
|
-
runNoUI(round.map, apis, round.players, "",
|
|
14578
|
+
runNoUI(round.map, apis, round.players, "", false);
|
|
14579
14579
|
}
|
|
14580
14580
|
else {
|
|
14581
14581
|
currentMap = round.map;
|
|
@@ -16086,7 +16086,7 @@ const Simulation = () => {
|
|
|
16086
16086
|
React.createElement(PlayPauseButton, null),
|
|
16087
16087
|
downloadBytes && (React.createElement(core.Button, { ml: "xs", radius: "20px", leftSection: React.createElement("i", { className: "fa-solid fa-download" }), color: "blue", onClick: () => downloadFile(`${playerNames.join("-")}.btl`, "text/plain",
|
|
16088
16088
|
// @ts-ignore
|
|
16089
|
-
window.simulationToDownload) }, "Download
|
|
16089
|
+
window.simulationToDownload) }, "Download"))),
|
|
16090
16090
|
showcaseMode && React.createElement("span", { id: "render-status" }),
|
|
16091
16091
|
React.createElement("p", { style: { margin: 0 } }, "Playback Speed"),
|
|
16092
16092
|
React.createElement(core.Slider, { style: { flex: "none" }, mb: 30, w: 500, maw: "85%", min: -2, defaultValue: 0, marks: [
|
|
Binary file
|
|
Binary file
|
|
@@ -10,7 +10,7 @@ import sys
|
|
|
10
10
|
import traceback
|
|
11
11
|
import gzip
|
|
12
12
|
|
|
13
|
-
from typing import Any, Dict, Generic, List, Optional, Tuple, TypeVar
|
|
13
|
+
from typing import Any, Dict, Generic, List, Optional, Set, Tuple, TypeVar
|
|
14
14
|
from code_battles.utilities import (
|
|
15
15
|
GameCanvas,
|
|
16
16
|
console_log,
|
|
@@ -135,6 +135,7 @@ class CodeBattles(
|
|
|
135
135
|
_eliminated: List[int]
|
|
136
136
|
_sounds: Dict[str, "js.Audio"] = {}
|
|
137
137
|
_decisions: List[bytes]
|
|
138
|
+
_breakpoints: Set[int]
|
|
138
139
|
_since_last_render: int
|
|
139
140
|
|
|
140
141
|
def render(self) -> None:
|
|
@@ -292,6 +293,16 @@ class CodeBattles(
|
|
|
292
293
|
"""Configure the version of the game, which is stored in the simulation files."""
|
|
293
294
|
return "1.0.0"
|
|
294
295
|
|
|
296
|
+
@web_only
|
|
297
|
+
def download_image(self, url: str) -> "asyncio.Future[js.Image]":
|
|
298
|
+
from js import Image
|
|
299
|
+
|
|
300
|
+
result = asyncio.Future()
|
|
301
|
+
image = Image.new()
|
|
302
|
+
image.onload = lambda _: result.set_result(image)
|
|
303
|
+
image.src = url
|
|
304
|
+
return result
|
|
305
|
+
|
|
295
306
|
@web_only
|
|
296
307
|
def download_images(
|
|
297
308
|
self, sources: List[Tuple[str, str]]
|
|
@@ -393,7 +404,7 @@ class CodeBattles(
|
|
|
393
404
|
self.play_sound("player_eliminated")
|
|
394
405
|
self._eliminated.append(player_index)
|
|
395
406
|
self.log(
|
|
396
|
-
f"[
|
|
407
|
+
f"[Battles {self.step + 1}] Player #{player_index + 1} ({self.player_names[player_index]}) was eliminated: {reason}",
|
|
397
408
|
-1,
|
|
398
409
|
"white",
|
|
399
410
|
)
|
|
@@ -420,10 +431,17 @@ class CodeBattles(
|
|
|
420
431
|
)
|
|
421
432
|
|
|
422
433
|
@web_only
|
|
423
|
-
def play_sound(self, sound: str):
|
|
424
|
-
"""
|
|
434
|
+
def play_sound(self, sound: str, force=False):
|
|
435
|
+
"""
|
|
436
|
+
Plays the given sound, from the URL given by :func:`configure_sound_url`.
|
|
437
|
+
|
|
438
|
+
If ``force`` is set, will play the sound even if the simulation is not :attr:`verbose`.
|
|
439
|
+
"""
|
|
425
440
|
from js import window, Audio
|
|
426
441
|
|
|
442
|
+
if not force and not self.verbose:
|
|
443
|
+
return
|
|
444
|
+
|
|
427
445
|
if sound not in self._sounds:
|
|
428
446
|
self._sounds[sound] = Audio.new(self.configure_sound_url(sound))
|
|
429
447
|
|
|
@@ -441,6 +459,14 @@ class CodeBattles(
|
|
|
441
459
|
|
|
442
460
|
asyncio.get_event_loop().run_until_complete(p())
|
|
443
461
|
|
|
462
|
+
def pause(self):
|
|
463
|
+
"""
|
|
464
|
+
Pauses the current simulation. Useful for letting bots insert breakpoints wherever they wish.
|
|
465
|
+
|
|
466
|
+
**Important:** call this method only from the :func:`make_decisions` method.
|
|
467
|
+
"""
|
|
468
|
+
self._should_pause = True
|
|
469
|
+
|
|
444
470
|
@property
|
|
445
471
|
def time(self) -> str:
|
|
446
472
|
"""The current step of the simulation, as a string with justification to fill 5 characters."""
|
|
@@ -473,6 +499,7 @@ class CodeBattles(
|
|
|
473
499
|
seed = Random().randint(0, 2**128)
|
|
474
500
|
self._logs = []
|
|
475
501
|
self._decisions = []
|
|
502
|
+
self._breakpoints = set()
|
|
476
503
|
self._decision_index = 0
|
|
477
504
|
self._seed = seed
|
|
478
505
|
self.step = 0
|
|
@@ -511,6 +538,7 @@ class CodeBattles(
|
|
|
511
538
|
self.verbose = False
|
|
512
539
|
self._initialize_simulation(player_codes, seed)
|
|
513
540
|
while not self.over:
|
|
541
|
+
self._should_pause = False
|
|
514
542
|
self._logs = []
|
|
515
543
|
decisions = self.make_decisions()
|
|
516
544
|
logs = self._logs
|
|
@@ -521,6 +549,7 @@ class CodeBattles(
|
|
|
521
549
|
base64.b64encode(decisions).decode(),
|
|
522
550
|
json.dumps(logs),
|
|
523
551
|
"true" if self.over else "false",
|
|
552
|
+
"true" if self._should_pause else "false",
|
|
524
553
|
)
|
|
525
554
|
|
|
526
555
|
if not self.over:
|
|
@@ -644,7 +673,7 @@ class CodeBattles(
|
|
|
644
673
|
self.player_names = simulation.player_names
|
|
645
674
|
self.background = False
|
|
646
675
|
self.console_visible = True
|
|
647
|
-
self.verbose =
|
|
676
|
+
self.verbose = True
|
|
648
677
|
self._initialize_simulation(
|
|
649
678
|
["" for _ in simulation.player_names], simulation.seed
|
|
650
679
|
)
|
|
@@ -654,8 +683,8 @@ class CodeBattles(
|
|
|
654
683
|
document.getElementById("simulation"),
|
|
655
684
|
self.configure_board_count(),
|
|
656
685
|
self.map_image,
|
|
657
|
-
|
|
658
|
-
|
|
686
|
+
self._get_canvas_width(),
|
|
687
|
+
self._get_canvas_height(),
|
|
659
688
|
self.configure_extra_width(),
|
|
660
689
|
self.configure_extra_height(),
|
|
661
690
|
)
|
|
@@ -701,12 +730,8 @@ class CodeBattles(
|
|
|
701
730
|
document.getElementById("simulation"),
|
|
702
731
|
self.configure_board_count(),
|
|
703
732
|
self.map_image,
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
else document.body.clientWidth - 40,
|
|
707
|
-
document.body.clientHeight - 280
|
|
708
|
-
if console_visible
|
|
709
|
-
else document.body.clientHeight - 160,
|
|
733
|
+
self._get_canvas_width(),
|
|
734
|
+
self._get_canvas_height(),
|
|
710
735
|
self.configure_extra_width(),
|
|
711
736
|
self.configure_extra_height(),
|
|
712
737
|
)
|
|
@@ -730,6 +755,26 @@ class CodeBattles(
|
|
|
730
755
|
except Exception:
|
|
731
756
|
traceback.print_exc()
|
|
732
757
|
|
|
758
|
+
@web_only
|
|
759
|
+
def _get_canvas_width(self):
|
|
760
|
+
from js import document
|
|
761
|
+
|
|
762
|
+
return (
|
|
763
|
+
document.body.clientWidth - 440
|
|
764
|
+
if self.console_visible
|
|
765
|
+
else document.body.clientWidth - 40
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
@web_only
|
|
769
|
+
def _get_canvas_height(self):
|
|
770
|
+
from js import document
|
|
771
|
+
|
|
772
|
+
return (
|
|
773
|
+
document.body.clientHeight - 255
|
|
774
|
+
if self.console_visible
|
|
775
|
+
else document.body.clientHeight - 180
|
|
776
|
+
)
|
|
777
|
+
|
|
733
778
|
def _get_simulation(self):
|
|
734
779
|
return Simulation(
|
|
735
780
|
self.map,
|
|
@@ -742,14 +787,19 @@ class CodeBattles(
|
|
|
742
787
|
self._seed,
|
|
743
788
|
)
|
|
744
789
|
|
|
745
|
-
def _update_step(
|
|
790
|
+
def _update_step(
|
|
791
|
+
self, decisions_str: str, logs_str: str, is_over_str: str, should_pause_str: str
|
|
792
|
+
):
|
|
746
793
|
from js import window, document
|
|
747
794
|
|
|
748
795
|
now = time.time()
|
|
749
796
|
decisions = base64.b64decode(str(decisions_str))
|
|
750
797
|
logs: list = json.loads(str(logs_str))
|
|
751
798
|
is_over = str(is_over_str) == "true"
|
|
799
|
+
should_pause = str(should_pause_str) == "true"
|
|
752
800
|
|
|
801
|
+
if should_pause:
|
|
802
|
+
self._breakpoints.add(len(self._decisions))
|
|
753
803
|
self._decisions.append(decisions)
|
|
754
804
|
self._logs.append(logs)
|
|
755
805
|
|
|
@@ -836,22 +886,24 @@ class CodeBattles(
|
|
|
836
886
|
|
|
837
887
|
@web_only
|
|
838
888
|
def _resize_canvas(self):
|
|
839
|
-
from js import document
|
|
840
|
-
|
|
841
889
|
if not hasattr(self, "canvas"):
|
|
842
890
|
return
|
|
843
891
|
|
|
844
892
|
self.canvas._fit_into(
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
else document.body.clientWidth - 40,
|
|
848
|
-
document.body.clientHeight - 280
|
|
849
|
-
if self.console_visible
|
|
850
|
-
else document.body.clientHeight - 160,
|
|
893
|
+
self._get_canvas_width(),
|
|
894
|
+
self._get_canvas_height(),
|
|
851
895
|
)
|
|
852
896
|
if not self.background:
|
|
853
897
|
self.render()
|
|
854
898
|
|
|
899
|
+
@web_only
|
|
900
|
+
def _ensure_paused(self):
|
|
901
|
+
from js import document
|
|
902
|
+
|
|
903
|
+
if "Pause" in document.getElementById("playpause").textContent:
|
|
904
|
+
# Make it apparent that the game is stopped.
|
|
905
|
+
document.getElementById("playpause").click()
|
|
906
|
+
|
|
855
907
|
@web_only
|
|
856
908
|
def _step(self):
|
|
857
909
|
from js import document, setTimeout
|
|
@@ -894,12 +946,8 @@ class CodeBattles(
|
|
|
894
946
|
else:
|
|
895
947
|
self._since_last_render += 1
|
|
896
948
|
|
|
897
|
-
if
|
|
898
|
-
self.
|
|
899
|
-
and "Pause" in document.getElementById("playpause").textContent
|
|
900
|
-
):
|
|
901
|
-
# Make it apparent that the game is stopped.
|
|
902
|
-
document.getElementById("playpause").click()
|
|
949
|
+
if self.over:
|
|
950
|
+
self._ensure_paused()
|
|
903
951
|
elif self.verbose:
|
|
904
952
|
document.getElementById("noui-progress").style.display = "block"
|
|
905
953
|
document.getElementById(
|
|
@@ -924,6 +972,12 @@ class CodeBattles(
|
|
|
924
972
|
if self.step == self._get_breakpoint():
|
|
925
973
|
return False
|
|
926
974
|
|
|
975
|
+
if self.step in self._breakpoints and self.console_visible:
|
|
976
|
+
self._breakpoints.remove(self.step)
|
|
977
|
+
self._ensure_paused()
|
|
978
|
+
self.log(f"[Battles {self.step + 1}] Pause requested!")
|
|
979
|
+
return False
|
|
980
|
+
|
|
927
981
|
return True
|
|
928
982
|
|
|
929
983
|
@web_only
|
|
@@ -5,6 +5,7 @@ import math
|
|
|
5
5
|
import sys
|
|
6
6
|
from typing import Callable, List, Union
|
|
7
7
|
from enum import Enum
|
|
8
|
+
from functools import wraps
|
|
8
9
|
|
|
9
10
|
try:
|
|
10
11
|
import js
|
|
@@ -33,6 +34,7 @@ def web_only(method):
|
|
|
33
34
|
if is_web():
|
|
34
35
|
return method
|
|
35
36
|
|
|
37
|
+
@wraps(method)
|
|
36
38
|
def wrapper(*args, **kwargs):
|
|
37
39
|
print(f"Warning: {method.__name__} should only be called in a web context.")
|
|
38
40
|
|
|
@@ -223,6 +225,10 @@ class GameCanvas:
|
|
|
223
225
|
stroke_width=10,
|
|
224
226
|
board_index=0,
|
|
225
227
|
):
|
|
228
|
+
"""
|
|
229
|
+
Draws a line between the given ``(start_x, start_y)`` and ``(end_x, end_y)`` coordinates (in map pixels) with the given stroke.
|
|
230
|
+
"""
|
|
231
|
+
|
|
226
232
|
start_x, start_y = self._translate_position(board_index, start_x, start_y)
|
|
227
233
|
end_x, end_y = self._translate_position(board_index, end_x, end_y)
|
|
228
234
|
|
package/dist/esm/index.js
CHANGED
|
@@ -2518,7 +2518,7 @@ const RunSimulationBlock = () => {
|
|
|
2518
2518
|
};
|
|
2519
2519
|
const startRunNoUI = () => {
|
|
2520
2520
|
setRunningNoUI(true);
|
|
2521
|
-
runNoUI(map, apis, playerBots, seed.toString(),
|
|
2521
|
+
runNoUI(map, apis, playerBots, seed.toString(), false);
|
|
2522
2522
|
};
|
|
2523
2523
|
const startRunNoUIN = (n) => {
|
|
2524
2524
|
setLocalStorage("Results", {});
|
|
@@ -14573,7 +14573,7 @@ const Round = () => {
|
|
|
14573
14573
|
React.createElement(Button, { leftSection: React.createElement("i", { className: "fa-solid fa-play" }), size: "xs", onClick: () => navigate(`/simulation/${round.map.replaceAll(" ", "-")}/${round.players.join("-")}?showcase=true`) }, "Simulate"),
|
|
14574
14574
|
React.createElement(Button, { leftSection: React.createElement("i", { className: "fa-solid fa-forward" }), size: "xs", onClick: () => {
|
|
14575
14575
|
if (roundIterations === 1) {
|
|
14576
|
-
runNoUI(round.map, apis, round.players, "",
|
|
14576
|
+
runNoUI(round.map, apis, round.players, "", false);
|
|
14577
14577
|
}
|
|
14578
14578
|
else {
|
|
14579
14579
|
currentMap = round.map;
|
|
@@ -16084,7 +16084,7 @@ const Simulation = () => {
|
|
|
16084
16084
|
React.createElement(PlayPauseButton, null),
|
|
16085
16085
|
downloadBytes && (React.createElement(Button, { ml: "xs", radius: "20px", leftSection: React.createElement("i", { className: "fa-solid fa-download" }), color: "blue", onClick: () => downloadFile(`${playerNames.join("-")}.btl`, "text/plain",
|
|
16086
16086
|
// @ts-ignore
|
|
16087
|
-
window.simulationToDownload) }, "Download
|
|
16087
|
+
window.simulationToDownload) }, "Download"))),
|
|
16088
16088
|
showcaseMode && React.createElement("span", { id: "render-status" }),
|
|
16089
16089
|
React.createElement("p", { style: { margin: 0 } }, "Playback Speed"),
|
|
16090
16090
|
React.createElement(Slider, { style: { flex: "none" }, mb: 30, w: 500, maw: "85%", min: -2, defaultValue: 0, marks: [
|
package/package.json
CHANGED