code-battles 1.3.0 → 1.5.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 +73 -14
- package/dist/cjs/types/index.d.ts +1 -0
- package/dist/cjs/types/utilities.d.ts +3 -2
- package/dist/code_battles/__init__.py +3 -0
- 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 +229 -37
- package/dist/code_battles/utilities.py +22 -0
- package/dist/esm/index.js +74 -15
- package/dist/esm/types/index.d.ts +1 -0
- package/dist/esm/types/utilities.d.ts +3 -2
- package/package.json +8 -7
package/dist/cjs/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require('@mantine/core/styles.css');
|
|
4
4
|
require('@mantine/dates/styles.css');
|
|
5
5
|
require('@mantine/notifications/styles.css');
|
|
6
|
+
require('@mantine/dropzone/styles.css');
|
|
6
7
|
var core = require('@mantine/core');
|
|
7
8
|
var dates = require('@mantine/dates');
|
|
8
9
|
var hooks = require('@mantine/hooks');
|
|
@@ -12,6 +13,7 @@ var auth = require('firebase/auth');
|
|
|
12
13
|
var firestore = require('firebase/firestore');
|
|
13
14
|
var React = require('react');
|
|
14
15
|
var reactRouterDom = require('react-router-dom');
|
|
16
|
+
var dropzone = require('@mantine/dropzone');
|
|
15
17
|
var jsxRuntime = require('react/jsx-runtime');
|
|
16
18
|
|
|
17
19
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
@@ -409,21 +411,30 @@ const toPlacing = (n) => {
|
|
|
409
411
|
}
|
|
410
412
|
return n.toString() + DIGITS[n % 10];
|
|
411
413
|
};
|
|
412
|
-
const runNoUI = (map, apis, playerBots, verbose) => {
|
|
414
|
+
const runNoUI = (map, apis, playerBots, seed, verbose) => {
|
|
413
415
|
const players = playerBots.map((api) => (api === "None" ? "" : apis[api]));
|
|
414
|
-
|
|
416
|
+
tryUntilSuccess(() =>
|
|
415
417
|
// @ts-ignore
|
|
416
|
-
window._startSimulation(map, players, playerBots, true, false, verbose));
|
|
418
|
+
window._startSimulation(map, players, playerBots, true, false, verbose, seed));
|
|
417
419
|
};
|
|
418
|
-
const
|
|
420
|
+
const tryUntilSuccess = (f, timeout = 500) => {
|
|
419
421
|
try {
|
|
420
422
|
f();
|
|
421
423
|
}
|
|
422
424
|
catch (error) {
|
|
423
425
|
console.log("Failed, waiting for timeout...", error === null || error === void 0 ? void 0 : error.message);
|
|
424
|
-
setTimeout(() =>
|
|
426
|
+
setTimeout(() => tryUntilSuccess(f, timeout), timeout);
|
|
425
427
|
}
|
|
426
428
|
};
|
|
429
|
+
const downloadFile = (filename, mimeType, contents) => {
|
|
430
|
+
const a = document.createElement("a");
|
|
431
|
+
a.style.display = "none";
|
|
432
|
+
document.body.appendChild(a);
|
|
433
|
+
a.href = `data:${mimeType};charset=utf-8,${encodeURIComponent(contents)}`;
|
|
434
|
+
a.download = filename;
|
|
435
|
+
a.click();
|
|
436
|
+
document.body.removeChild(a);
|
|
437
|
+
};
|
|
427
438
|
|
|
428
439
|
const useLocalStorage = ({ key, defaultValue, }) => {
|
|
429
440
|
const [value, setValue] = React.useState(getLocalStorage(key, defaultValue !== null && defaultValue !== void 0 ? defaultValue : null));
|
|
@@ -2493,6 +2504,10 @@ const RunSimulationBlock = () => {
|
|
|
2493
2504
|
key: "Player Bots",
|
|
2494
2505
|
defaultValue: ["None", "None"],
|
|
2495
2506
|
});
|
|
2507
|
+
const [seed, setSeed] = useLocalStorage({
|
|
2508
|
+
key: "Seed",
|
|
2509
|
+
defaultValue: "",
|
|
2510
|
+
});
|
|
2496
2511
|
const [runningNoUI, setRunningNoUI] = React.useState(false);
|
|
2497
2512
|
const [runningNoUIN, setRunningNoUIN] = React.useState({});
|
|
2498
2513
|
const navigate = reactRouterDom.useNavigate();
|
|
@@ -2501,15 +2516,18 @@ const RunSimulationBlock = () => {
|
|
|
2501
2516
|
remaining = Math.max(...Object.values(runningNoUIN));
|
|
2502
2517
|
}
|
|
2503
2518
|
const run = () => {
|
|
2504
|
-
|
|
2519
|
+
// @ts-ignore
|
|
2520
|
+
window._isSimulationFromFile = false;
|
|
2521
|
+
navigate(`/simulation/${map.replaceAll(" ", "-")}/${playerBots.join("-")}?seed=${seed}`);
|
|
2505
2522
|
};
|
|
2506
2523
|
const startRunNoUI = () => {
|
|
2507
2524
|
setRunningNoUI(true);
|
|
2508
|
-
runNoUI(map, apis, playerBots, true);
|
|
2525
|
+
runNoUI(map, apis, playerBots, seed.toString(), true);
|
|
2509
2526
|
};
|
|
2510
2527
|
const startRunNoUIN = (n) => {
|
|
2528
|
+
setLocalStorage("Results", {});
|
|
2511
2529
|
setRunningNoUIN({ [n.toString()]: n });
|
|
2512
|
-
runNoUI(map, apis, playerBots, false);
|
|
2530
|
+
runNoUI(map, apis, playerBots, seed.toString(), false);
|
|
2513
2531
|
};
|
|
2514
2532
|
React.useEffect(() => {
|
|
2515
2533
|
// @ts-ignore
|
|
@@ -2563,7 +2581,7 @@ const RunSimulationBlock = () => {
|
|
|
2563
2581
|
setLocalStorage("Results", {});
|
|
2564
2582
|
}
|
|
2565
2583
|
else {
|
|
2566
|
-
runNoUI(map, apis, playerBots, false);
|
|
2584
|
+
runNoUI(map, apis, playerBots, seed.toString(), false);
|
|
2567
2585
|
}
|
|
2568
2586
|
}
|
|
2569
2587
|
}, [runningNoUIN]);
|
|
@@ -2574,6 +2592,7 @@ const RunSimulationBlock = () => {
|
|
|
2574
2592
|
}
|
|
2575
2593
|
} }),
|
|
2576
2594
|
React.createElement(BotSelector, { playerCount: playerCount, setPlayerCount: setPlayerCount, playerBots: playerBots, setPlayerBots: setPlayerBots, apis: apis }),
|
|
2595
|
+
React.createElement(core.NumberInput, { leftSection: React.createElement("i", { className: "fa-solid fa-dice" }), label: "Randomness Seed", min: 0, value: seed, onChange: setSeed }),
|
|
2577
2596
|
React.createElement(core.Button.Group, { mt: "xs" },
|
|
2578
2597
|
React.createElement(core.Button, { variant: "default", w: "50%", leftSection: React.createElement("i", { className: "fa-solid fa-play" }), onClick: run }, "Run"),
|
|
2579
2598
|
React.createElement(core.Button, { variant: "default", w: "50%", leftSection: React.createElement("i", { className: "fa-solid fa-forward" }), onClick: startRunNoUI, loading: runningNoUI || loading }, "Run (No UI)")),
|
|
@@ -2583,7 +2602,31 @@ const RunSimulationBlock = () => {
|
|
|
2583
2602
|
remaining !== 0 && (React.createElement("p", { style: { textAlign: "center", marginTop: 10 } },
|
|
2584
2603
|
"Remaining Simulations: ",
|
|
2585
2604
|
remaining)),
|
|
2586
|
-
React.createElement("p", { style: { textAlign: "center", display: "none", marginTop: 10 }, id: "noui-progress" })
|
|
2605
|
+
React.createElement("p", { style: { textAlign: "center", display: "none", marginTop: 10 }, id: "noui-progress" }),
|
|
2606
|
+
React.createElement(dropzone.Dropzone, { mt: "xs", multiple: false, onDrop: (files) => __awaiter(void 0, void 0, void 0, function* () {
|
|
2607
|
+
if (files.length !== 1) {
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
const file = files[0];
|
|
2611
|
+
const text = yield file.text();
|
|
2612
|
+
// @ts-ignore
|
|
2613
|
+
window._navigate = navigate;
|
|
2614
|
+
// @ts-ignore
|
|
2615
|
+
window._isSimulationFromFile = true;
|
|
2616
|
+
tryUntilSuccess(() => {
|
|
2617
|
+
// @ts-ignore
|
|
2618
|
+
window._startSimulationFromFile(text);
|
|
2619
|
+
});
|
|
2620
|
+
}), style: {
|
|
2621
|
+
textAlign: "center",
|
|
2622
|
+
paddingTop: 20,
|
|
2623
|
+
paddingBottom: 20,
|
|
2624
|
+
paddingLeft: 10,
|
|
2625
|
+
paddingRight: 10,
|
|
2626
|
+
} },
|
|
2627
|
+
React.createElement("span", null,
|
|
2628
|
+
React.createElement("i", { className: "fa-solid fa-file-code", style: { marginRight: 10 } }),
|
|
2629
|
+
"Drag a simulation file here or click to select a file to run a simulation from a file"))));
|
|
2587
2630
|
};
|
|
2588
2631
|
|
|
2589
2632
|
const TournamentBlock = ({ pointModifier, inline, title }) => {
|
|
@@ -15901,13 +15944,19 @@ const Simulation = () => {
|
|
|
15901
15944
|
const [apis, loading] = useAPIs();
|
|
15902
15945
|
let { map, playerapis } = reactRouterDom.useParams();
|
|
15903
15946
|
const location = reactRouterDom.useLocation();
|
|
15947
|
+
const [searchParams] = reactRouterDom.useSearchParams();
|
|
15904
15948
|
const [winner, setWinner] = React.useState();
|
|
15949
|
+
const [downloadBytes, setDownloadBytes] = React.useState(false);
|
|
15905
15950
|
const navigate = reactRouterDom.useNavigate();
|
|
15906
15951
|
const colorScheme = hooks.useColorScheme();
|
|
15907
15952
|
const showcaseMode = location.search.includes("showcase=true");
|
|
15908
15953
|
React.useEffect(() => {
|
|
15909
15954
|
n((engine) => __awaiter(void 0, void 0, void 0, function* () { return yield loadFull(engine); }));
|
|
15910
15955
|
// @ts-ignore
|
|
15956
|
+
window.showDownload = () => {
|
|
15957
|
+
setDownloadBytes(true);
|
|
15958
|
+
};
|
|
15959
|
+
// @ts-ignore
|
|
15911
15960
|
window.showWinner = (winner, verbose) => {
|
|
15912
15961
|
if (admin) {
|
|
15913
15962
|
setWinner(winner);
|
|
@@ -15937,10 +15986,16 @@ const Simulation = () => {
|
|
|
15937
15986
|
const playerNames = (_a = playerapis === null || playerapis === void 0 ? void 0 : playerapis.split("-")) !== null && _a !== void 0 ? _a : [];
|
|
15938
15987
|
const players = playerNames.map((api) => (api === "None" ? "" : apis[api]));
|
|
15939
15988
|
React.useEffect(() => {
|
|
15940
|
-
|
|
15941
|
-
|
|
15989
|
+
var _a;
|
|
15990
|
+
if (!loading &&
|
|
15991
|
+
players &&
|
|
15992
|
+
playerapis &&
|
|
15993
|
+
// @ts-ignore
|
|
15994
|
+
window._isSimulationFromFile !== true) {
|
|
15995
|
+
const seed = (_a = searchParams.get("seed")) !== null && _a !== void 0 ? _a : "";
|
|
15996
|
+
tryUntilSuccess(() =>
|
|
15942
15997
|
// @ts-ignore
|
|
15943
|
-
window._startSimulation(map, players, playerNames, false, !showcaseMode, true));
|
|
15998
|
+
window._startSimulation(map, players, playerNames, false, !showcaseMode, true, seed));
|
|
15944
15999
|
}
|
|
15945
16000
|
}, [loading]);
|
|
15946
16001
|
const newRank = getRank(getLocalStorage("Cached tournament/info"), winner, getLocalStorage("Point Modifier")) + 1;
|
|
@@ -16023,7 +16078,11 @@ const Simulation = () => {
|
|
|
16023
16078
|
React.createElement("span", { id: "render-status", style: { marginRight: 10 } }),
|
|
16024
16079
|
React.createElement(core.NumberInput, { id: "breakpoint", label: "Breakpoint", min: 0, leftSection: React.createElement("i", { className: "fa-solid fa-stopwatch" }), display: "inline-block", maw: "35%", mr: "xs" }),
|
|
16025
16080
|
React.createElement(core.Button, { style: { flex: "none" }, my: "xs", w: 100, leftSection: React.createElement("i", { className: "fa-solid fa-wand-magic" }), color: "grape", id: "step", mr: "xs", radius: "20px" }, "Step"))),
|
|
16026
|
-
React.createElement(PlayPauseButton, null)
|
|
16081
|
+
React.createElement(PlayPauseButton, null),
|
|
16082
|
+
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",
|
|
16083
|
+
// @ts-ignore
|
|
16084
|
+
window.simulationToDownload) }, "Download Simulation"))),
|
|
16085
|
+
showcaseMode && React.createElement("span", { id: "render-status" }),
|
|
16027
16086
|
React.createElement("p", { style: { margin: 0 } }, "Playback Speed"),
|
|
16028
16087
|
React.createElement(core.Slider, { style: { flex: "none" }, mb: 30, w: 500, maw: "85%", min: -2, defaultValue: 0, marks: [
|
|
16029
16088
|
{ value: -2, label: "1/4" },
|
|
@@ -2,6 +2,7 @@ import "@fortawesome/fontawesome-free/css/all.css";
|
|
|
2
2
|
import "@mantine/core/styles.css";
|
|
3
3
|
import "@mantine/dates/styles.css";
|
|
4
4
|
import "@mantine/notifications/styles.css";
|
|
5
|
+
import "@mantine/dropzone/styles.css";
|
|
5
6
|
import "prismjs/plugins/line-numbers/prism-line-numbers.css";
|
|
6
7
|
import "prismjs/themes/prism.css";
|
|
7
8
|
import "./index.css";
|
|
@@ -10,5 +10,6 @@ export declare const getRank: (tournamentInfo: any, team: string, pointModifier:
|
|
|
10
10
|
export declare const updatePointModifier: () => void;
|
|
11
11
|
export declare const toPlacing: (n: number) => string;
|
|
12
12
|
export declare const zeroPad: (s: string, l: number) => string;
|
|
13
|
-
export declare const runNoUI: (map: string, apis: Record<string, any>, playerBots: string[], verbose: boolean) => void;
|
|
14
|
-
export declare const
|
|
13
|
+
export declare const runNoUI: (map: string, apis: Record<string, any>, playerBots: string[], seed: string, verbose: boolean) => void;
|
|
14
|
+
export declare const tryUntilSuccess: (f: () => void, timeout?: number) => void;
|
|
15
|
+
export declare const downloadFile: (filename: string, mimeType: string, contents: string) => void;
|
|
@@ -14,6 +14,9 @@ def run_game(battles: CodeBattles):
|
|
|
14
14
|
from pyscript.ffi import create_proxy
|
|
15
15
|
|
|
16
16
|
window._startSimulation = create_proxy(battles._start_simulation)
|
|
17
|
+
window._startSimulationFromFile = create_proxy(
|
|
18
|
+
battles._start_simulation_from_file
|
|
19
|
+
)
|
|
17
20
|
elif is_worker():
|
|
18
21
|
setattr(
|
|
19
22
|
sys.modules["__main__"],
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import base64
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import datetime
|
|
3
5
|
import json
|
|
4
6
|
import math
|
|
5
7
|
import time
|
|
6
|
-
import
|
|
8
|
+
from random import Random
|
|
7
9
|
import sys
|
|
8
10
|
import traceback
|
|
11
|
+
import gzip
|
|
9
12
|
|
|
10
13
|
from typing import Any, Dict, Generic, List, Optional, Tuple, TypeVar
|
|
11
14
|
from code_battles.utilities import (
|
|
12
15
|
GameCanvas,
|
|
13
16
|
console_log,
|
|
14
17
|
download_image,
|
|
18
|
+
navigate,
|
|
15
19
|
set_results,
|
|
16
20
|
show_alert,
|
|
21
|
+
show_download,
|
|
17
22
|
web_only,
|
|
18
23
|
is_web,
|
|
19
24
|
)
|
|
@@ -29,6 +34,53 @@ APIType = TypeVar("APIType")
|
|
|
29
34
|
PlayerRequestsType = TypeVar("PlayerRequestsType")
|
|
30
35
|
|
|
31
36
|
|
|
37
|
+
@dataclass
|
|
38
|
+
class Simulation:
|
|
39
|
+
map: str
|
|
40
|
+
player_names: str
|
|
41
|
+
game: str
|
|
42
|
+
version: str
|
|
43
|
+
timestamp: datetime.datetime
|
|
44
|
+
logs: list
|
|
45
|
+
decisions: List[bytes]
|
|
46
|
+
seed: int
|
|
47
|
+
|
|
48
|
+
def dump(self):
|
|
49
|
+
return base64.b64encode(
|
|
50
|
+
gzip.compress(
|
|
51
|
+
json.dumps(
|
|
52
|
+
{
|
|
53
|
+
"map": self.map,
|
|
54
|
+
"playerNames": self.player_names,
|
|
55
|
+
"game": self.game,
|
|
56
|
+
"version": self.version,
|
|
57
|
+
"timestamp": self.timestamp.isoformat(),
|
|
58
|
+
"logs": self.logs,
|
|
59
|
+
"decisions": [
|
|
60
|
+
base64.b64encode(decision).decode()
|
|
61
|
+
for decision in self.decisions
|
|
62
|
+
],
|
|
63
|
+
"seed": self.seed,
|
|
64
|
+
}
|
|
65
|
+
).encode()
|
|
66
|
+
)
|
|
67
|
+
).decode()
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def load(file: str):
|
|
71
|
+
contents: Dict[str, Any] = json.loads(gzip.decompress(base64.b64decode(file)))
|
|
72
|
+
return Simulation(
|
|
73
|
+
contents["map"],
|
|
74
|
+
contents["playerNames"],
|
|
75
|
+
contents["game"],
|
|
76
|
+
contents["version"],
|
|
77
|
+
datetime.datetime.fromisoformat(contents["timestamp"]),
|
|
78
|
+
contents["logs"],
|
|
79
|
+
[base64.b64decode(decision) for decision in contents["decisions"]],
|
|
80
|
+
contents["seed"],
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
32
84
|
class CodeBattles(
|
|
33
85
|
Generic[GameStateType, APIImplementationType, APIType, PlayerRequestsType]
|
|
34
86
|
):
|
|
@@ -60,6 +112,12 @@ class CodeBattles(
|
|
|
60
112
|
"""The current state of the game. You should modify this in :func:`apply_decisions`."""
|
|
61
113
|
player_requests: List[PlayerRequestsType]
|
|
62
114
|
"""The current requests set by the players. Should be read in :func:`make_decisions` (and probably serialized), and set by the API implementation."""
|
|
115
|
+
random: Random
|
|
116
|
+
"""A pseudorandom generator that should be used for all randomness purposes (except :func:`make_decisions`)"""
|
|
117
|
+
player_randoms: List[Random]
|
|
118
|
+
"""A pseudorandom generator that should be used for all randomness purposes in a player's bot. Given to the bots as a global via :func:`configure_bot_globals`."""
|
|
119
|
+
make_decisions_random: Random
|
|
120
|
+
"""A pseudorandom generator that should be used for all randomness purposes in :func:`make_decisions`."""
|
|
63
121
|
|
|
64
122
|
background: bool
|
|
65
123
|
"""Whether the current simulation is occuring in the background (without UI)."""
|
|
@@ -77,6 +135,7 @@ class CodeBattles(
|
|
|
77
135
|
_eliminated: List[int]
|
|
78
136
|
_sounds: Dict[str, "js.Audio"] = {}
|
|
79
137
|
_decisions: List[bytes]
|
|
138
|
+
_since_last_render: int
|
|
80
139
|
|
|
81
140
|
def render(self) -> None:
|
|
82
141
|
"""
|
|
@@ -94,12 +153,12 @@ class CodeBattles(
|
|
|
94
153
|
Use the current state and bots to make decisions in order to reach the next state.
|
|
95
154
|
You may use :func:`run_bot_method` to run a specific player's method (for instance, `run`).
|
|
96
155
|
|
|
156
|
+
If you need any randomness, use :attr:`make_decisions_random`.
|
|
157
|
+
|
|
97
158
|
This function may take a lot of time to execute.
|
|
98
159
|
|
|
99
160
|
.. warning::
|
|
100
161
|
Do not call any other method other than :func:`run_bot_method` in here. This method will run in a web worker.
|
|
101
|
-
|
|
102
|
-
Do NOT update :attr:`state` or :attr:`step`.
|
|
103
162
|
"""
|
|
104
163
|
|
|
105
164
|
raise NotImplementedError("make_decisions")
|
|
@@ -188,12 +247,12 @@ class CodeBattles(
|
|
|
188
247
|
|
|
189
248
|
return 1
|
|
190
249
|
|
|
191
|
-
def configure_map_image_url(self, map: str):
|
|
250
|
+
def configure_map_image_url(self, map: str) -> str:
|
|
192
251
|
"""The URL containing the map image for the given map. By default, this takes the lowercase, replaces spaces with _ and loads from `/images/maps` which is stored in `public/images/maps` in a project."""
|
|
193
252
|
|
|
194
253
|
return "/images/maps/" + map.lower().replace(" ", "_") + ".png"
|
|
195
254
|
|
|
196
|
-
def configure_sound_url(self, name: str):
|
|
255
|
+
def configure_sound_url(self, name: str) -> str:
|
|
197
256
|
"""The URL containing the sound for the given name. By default, this takes the lowercase, replaces spaces with _ and loads from `/sounds` which is stored in `public/sounds` in a project."""
|
|
198
257
|
|
|
199
258
|
return "/sounds/" + name.lower().replace(" ", "_") + ".mp3"
|
|
@@ -203,11 +262,19 @@ class CodeBattles(
|
|
|
203
262
|
|
|
204
263
|
return "CodeBattlesBot"
|
|
205
264
|
|
|
206
|
-
def
|
|
265
|
+
def configure_render_rate(self, playback_speed: float) -> int:
|
|
266
|
+
"""
|
|
267
|
+
The amount of frames to simulate before each render.
|
|
268
|
+
|
|
269
|
+
For games with an intensive `render` method, this is useful for higher playback speeds.
|
|
270
|
+
"""
|
|
271
|
+
return 1
|
|
272
|
+
|
|
273
|
+
def configure_bot_globals(self, player_index: int) -> Dict[str, Any]:
|
|
207
274
|
"""
|
|
208
275
|
Configure additional available global items, such as libraries from the Python standard library, bots can use.
|
|
209
276
|
|
|
210
|
-
By default, this is math, time and random
|
|
277
|
+
By default, this is math, time and random, where random is the corresponding :attr:`player_randoms`.
|
|
211
278
|
|
|
212
279
|
.. warning::
|
|
213
280
|
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`.
|
|
@@ -218,9 +285,13 @@ class CodeBattles(
|
|
|
218
285
|
return {
|
|
219
286
|
"math": math,
|
|
220
287
|
"time": time,
|
|
221
|
-
"random":
|
|
288
|
+
"random": self.player_randoms[player_index],
|
|
222
289
|
}
|
|
223
290
|
|
|
291
|
+
def configure_version(self) -> str:
|
|
292
|
+
"""Configure the version of the game, which is stored in the simulation files."""
|
|
293
|
+
return "1.0.0"
|
|
294
|
+
|
|
224
295
|
@web_only
|
|
225
296
|
def download_images(
|
|
226
297
|
self, sources: List[Tuple[str, str]]
|
|
@@ -356,7 +427,16 @@ class CodeBattles(
|
|
|
356
427
|
volume = window.localStorage.getItem("Volume") or 0
|
|
357
428
|
s = self._sounds[sound].cloneNode(True)
|
|
358
429
|
s.volume = volume
|
|
359
|
-
|
|
430
|
+
|
|
431
|
+
async def p():
|
|
432
|
+
try:
|
|
433
|
+
await s.play()
|
|
434
|
+
except Exception:
|
|
435
|
+
print(
|
|
436
|
+
f"Warning: couldn't play sound '{sound}'. Make sure the `sound` and `configure_sound_url` are correct."
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
asyncio.get_event_loop().run_until_complete(p())
|
|
360
440
|
|
|
361
441
|
@property
|
|
362
442
|
def time(self) -> str:
|
|
@@ -385,12 +465,19 @@ class CodeBattles(
|
|
|
385
465
|
|
|
386
466
|
self._initialized = True
|
|
387
467
|
|
|
388
|
-
def _initialize_simulation(
|
|
389
|
-
self
|
|
468
|
+
def _initialize_simulation(
|
|
469
|
+
self, player_codes: List[str], seed: Optional[int] = None
|
|
470
|
+
):
|
|
471
|
+
if seed is None:
|
|
472
|
+
seed = Random().randint(0, 2**128)
|
|
390
473
|
self._logs = []
|
|
391
474
|
self._decisions = []
|
|
475
|
+
self._decision_index = 0
|
|
476
|
+
self._seed = seed
|
|
477
|
+
self.step = 0
|
|
392
478
|
self.active_players = list(range(len(self.player_names)))
|
|
393
|
-
self.
|
|
479
|
+
self.random = Random(seed)
|
|
480
|
+
self.player_randoms = [Random(self.random.random()) for _ in self.player_names]
|
|
394
481
|
self.state = self.create_initial_state()
|
|
395
482
|
self.player_requests = [
|
|
396
483
|
self.create_initial_player_requests(i)
|
|
@@ -398,10 +485,15 @@ class CodeBattles(
|
|
|
398
485
|
]
|
|
399
486
|
self._eliminated = []
|
|
400
487
|
self._player_globals = self._get_initial_player_globals(player_codes)
|
|
401
|
-
self.
|
|
488
|
+
self._since_last_render = 1
|
|
489
|
+
self._start_time = time.time()
|
|
402
490
|
|
|
403
491
|
def _run_webworker_simulation(
|
|
404
|
-
self,
|
|
492
|
+
self,
|
|
493
|
+
map: str,
|
|
494
|
+
player_names_str: str,
|
|
495
|
+
player_codes_str: str,
|
|
496
|
+
seed: Optional[int] = None,
|
|
405
497
|
):
|
|
406
498
|
from pyscript import sync
|
|
407
499
|
|
|
@@ -414,7 +506,7 @@ class CodeBattles(
|
|
|
414
506
|
self.background = True
|
|
415
507
|
self.console_visible = False
|
|
416
508
|
self.verbose = False
|
|
417
|
-
self._initialize_simulation(player_codes)
|
|
509
|
+
self._initialize_simulation(player_codes, seed)
|
|
418
510
|
while not self.over:
|
|
419
511
|
self._logs = []
|
|
420
512
|
decisions = self.make_decisions()
|
|
@@ -432,16 +524,17 @@ class CodeBattles(
|
|
|
432
524
|
self.step += 1
|
|
433
525
|
|
|
434
526
|
def _run_local_simulation(self):
|
|
435
|
-
|
|
436
|
-
self.
|
|
527
|
+
seed = None if sys.argv[1] == "None" else int(sys.argv[1])
|
|
528
|
+
self.map = sys.argv[2]
|
|
529
|
+
self.player_names = sys.argv[3].split("-")
|
|
437
530
|
self.background = True
|
|
438
531
|
self.console_visible = False
|
|
439
532
|
self.verbose = False
|
|
440
533
|
player_codes = []
|
|
441
|
-
for filename in sys.argv[
|
|
534
|
+
for filename in sys.argv[4:]:
|
|
442
535
|
with open(filename, "r") as f:
|
|
443
536
|
player_codes.append(f.read())
|
|
444
|
-
self._initialize_simulation(player_codes)
|
|
537
|
+
self._initialize_simulation(player_codes, seed)
|
|
445
538
|
|
|
446
539
|
while not self.over:
|
|
447
540
|
self.apply_decisions(self.make_decisions())
|
|
@@ -469,6 +562,73 @@ class CodeBattles(
|
|
|
469
562
|
loop = asyncio.get_event_loop()
|
|
470
563
|
loop.run_until_complete(self._start_simulation_async(*args, **kwargs))
|
|
471
564
|
|
|
565
|
+
def _start_simulation_from_file(self, contents: str):
|
|
566
|
+
loop = asyncio.get_event_loop()
|
|
567
|
+
loop.run_until_complete(self._start_simulation_from_file_async(contents))
|
|
568
|
+
|
|
569
|
+
async def _start_simulation_from_file_async(self, contents: str):
|
|
570
|
+
from js import document
|
|
571
|
+
|
|
572
|
+
try:
|
|
573
|
+
simulation = Simulation.load(str(contents))
|
|
574
|
+
navigate(
|
|
575
|
+
f"/simulation/{simulation.map}/{'-'.join(simulation.player_names)}"
|
|
576
|
+
)
|
|
577
|
+
show_alert(
|
|
578
|
+
"Loaded simulation file!",
|
|
579
|
+
f"{', '.join(simulation.player_names)} competed in {simulation.map} at {simulation.timestamp}",
|
|
580
|
+
"blue",
|
|
581
|
+
"fa-solid fa-file-code",
|
|
582
|
+
0,
|
|
583
|
+
)
|
|
584
|
+
if simulation.game != self.__class__.__name__:
|
|
585
|
+
show_alert(
|
|
586
|
+
"Warning: game mismatch!",
|
|
587
|
+
f"Simulation file is for game {simulation.game} while the website is running {self.__class__.__name__}!",
|
|
588
|
+
"yellow",
|
|
589
|
+
"fa-solid fa-exclamation",
|
|
590
|
+
0,
|
|
591
|
+
)
|
|
592
|
+
if simulation.version != self.configure_version():
|
|
593
|
+
show_alert(
|
|
594
|
+
"Warning: version mismatch!",
|
|
595
|
+
f"Simulation file is for version {simulation.version} while the website is running {self.configure_version()}!",
|
|
596
|
+
"yellow",
|
|
597
|
+
"fa-solid fa-exclamation",
|
|
598
|
+
0,
|
|
599
|
+
)
|
|
600
|
+
while document.getElementById("loader") is None:
|
|
601
|
+
await asyncio.sleep(0.01)
|
|
602
|
+
if not hasattr(self, "_initialized"):
|
|
603
|
+
self._initialize()
|
|
604
|
+
self.map = simulation.map
|
|
605
|
+
self.map_image = await download_image(
|
|
606
|
+
self.configure_map_image_url(simulation.map)
|
|
607
|
+
)
|
|
608
|
+
self.player_names = simulation.player_names
|
|
609
|
+
self.background = False
|
|
610
|
+
self.console_visible = True
|
|
611
|
+
self.verbose = False
|
|
612
|
+
self._initialize_simulation(
|
|
613
|
+
["" for _ in simulation.player_names], simulation.seed
|
|
614
|
+
)
|
|
615
|
+
self._decisions = simulation.decisions
|
|
616
|
+
self._logs = simulation.logs
|
|
617
|
+
self.canvas = GameCanvas(
|
|
618
|
+
document.getElementById("simulation"),
|
|
619
|
+
self.configure_board_count(),
|
|
620
|
+
self.map_image,
|
|
621
|
+
document.body.clientWidth - 440,
|
|
622
|
+
document.body.clientHeight - 280,
|
|
623
|
+
self.configure_extra_width(),
|
|
624
|
+
self.configure_extra_height(),
|
|
625
|
+
)
|
|
626
|
+
document.getElementById("loader").style.display = "none"
|
|
627
|
+
await self.setup()
|
|
628
|
+
self.render()
|
|
629
|
+
except Exception as e:
|
|
630
|
+
print(e)
|
|
631
|
+
|
|
472
632
|
@web_only
|
|
473
633
|
async def _start_simulation_async(
|
|
474
634
|
self,
|
|
@@ -478,6 +638,7 @@ class CodeBattles(
|
|
|
478
638
|
background: bool,
|
|
479
639
|
console_visible: bool,
|
|
480
640
|
verbose: bool,
|
|
641
|
+
seed="",
|
|
481
642
|
):
|
|
482
643
|
from js import document
|
|
483
644
|
from pyscript import workers
|
|
@@ -497,7 +658,7 @@ class CodeBattles(
|
|
|
497
658
|
self.background = background
|
|
498
659
|
self.console_visible = console_visible
|
|
499
660
|
self.verbose = verbose
|
|
500
|
-
self._initialize_simulation(player_codes)
|
|
661
|
+
self._initialize_simulation(player_codes, None if seed == "" else int(seed))
|
|
501
662
|
|
|
502
663
|
if not self.background:
|
|
503
664
|
self.canvas = GameCanvas(
|
|
@@ -507,7 +668,9 @@ class CodeBattles(
|
|
|
507
668
|
document.body.clientWidth - 440
|
|
508
669
|
if console_visible
|
|
509
670
|
else document.body.clientWidth - 40,
|
|
510
|
-
document.body.clientHeight - 280
|
|
671
|
+
document.body.clientHeight - 280
|
|
672
|
+
if console_visible
|
|
673
|
+
else document.body.clientHeight - 160,
|
|
511
674
|
self.configure_extra_width(),
|
|
512
675
|
self.configure_extra_height(),
|
|
513
676
|
)
|
|
@@ -521,11 +684,11 @@ class CodeBattles(
|
|
|
521
684
|
document.getElementById("loader").style.display = "none"
|
|
522
685
|
self.render()
|
|
523
686
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
687
|
+
self._worker = await workers["worker"]
|
|
688
|
+
self._worker.update_step = self._update_step
|
|
689
|
+
self._worker._run_webworker_simulation(
|
|
690
|
+
map, json.dumps(player_names), json.dumps(player_codes), self._seed
|
|
691
|
+
)
|
|
529
692
|
|
|
530
693
|
if self.background:
|
|
531
694
|
await self._play_pause()
|
|
@@ -533,22 +696,39 @@ class CodeBattles(
|
|
|
533
696
|
traceback.print_exc()
|
|
534
697
|
|
|
535
698
|
def _update_step(self, decisions_str: str, logs_str: str, is_over_str: str):
|
|
536
|
-
from js import document
|
|
699
|
+
from js import window, document
|
|
537
700
|
|
|
701
|
+
now = time.time()
|
|
538
702
|
decisions = base64.b64decode(str(decisions_str))
|
|
539
703
|
logs: list = json.loads(str(logs_str))
|
|
540
704
|
is_over = str(is_over_str) == "true"
|
|
541
705
|
|
|
542
706
|
self._decisions.append(decisions)
|
|
543
707
|
self._logs.append(logs)
|
|
544
|
-
|
|
708
|
+
|
|
709
|
+
if is_over:
|
|
710
|
+
try:
|
|
711
|
+
simulation = Simulation(
|
|
712
|
+
self.map,
|
|
713
|
+
self.player_names,
|
|
714
|
+
self.__class__.__name__,
|
|
715
|
+
self.configure_version(),
|
|
716
|
+
datetime.datetime.now(),
|
|
717
|
+
self._logs,
|
|
718
|
+
self._decisions,
|
|
719
|
+
self._seed,
|
|
720
|
+
)
|
|
721
|
+
window.simulationToDownload = simulation.dump()
|
|
722
|
+
show_download()
|
|
723
|
+
except Exception as e:
|
|
724
|
+
print(e)
|
|
545
725
|
|
|
546
726
|
render_status = document.getElementById("render-status")
|
|
547
727
|
if render_status is not None:
|
|
548
728
|
render_status.textContent = (
|
|
549
|
-
"Rendering: Complete!"
|
|
729
|
+
f"Rendering: Complete! ({int(now - self._start_time)}s)"
|
|
550
730
|
if is_over
|
|
551
|
-
else f"Rendering: Frame {self.
|
|
731
|
+
else f"Rendering: Frame {len(self._decisions)} ({int(now - self._start_time)}s)"
|
|
552
732
|
)
|
|
553
733
|
|
|
554
734
|
def _get_initial_player_globals(self, player_codes: List[str]):
|
|
@@ -565,8 +745,8 @@ class CodeBattles(
|
|
|
565
745
|
"context": context,
|
|
566
746
|
**self.get_api().__dict__,
|
|
567
747
|
}
|
|
568
|
-
| self.configure_bot_globals()
|
|
569
|
-
for context in contexts
|
|
748
|
+
| self.configure_bot_globals(player_index)
|
|
749
|
+
for player_index, context in enumerate(contexts)
|
|
570
750
|
]
|
|
571
751
|
for index, api_code in enumerate(player_codes):
|
|
572
752
|
if api_code != "" and api_code is not None:
|
|
@@ -627,7 +807,9 @@ class CodeBattles(
|
|
|
627
807
|
document.body.clientWidth - 440
|
|
628
808
|
if self.console_visible
|
|
629
809
|
else document.body.clientWidth - 40,
|
|
630
|
-
document.body.clientHeight - 280
|
|
810
|
+
document.body.clientHeight - 280
|
|
811
|
+
if self.console_visible
|
|
812
|
+
else document.body.clientHeight - 160,
|
|
631
813
|
)
|
|
632
814
|
if not self.background:
|
|
633
815
|
self.render()
|
|
@@ -638,19 +820,20 @@ class CodeBattles(
|
|
|
638
820
|
from pyscript.ffi import create_proxy
|
|
639
821
|
|
|
640
822
|
if not self.over:
|
|
641
|
-
if len(self._decisions) ==
|
|
823
|
+
if len(self._decisions) == self._decision_index:
|
|
642
824
|
print("Warning: sleeping because decisions were not made yet!")
|
|
643
825
|
setTimeout(create_proxy(self._step), 100)
|
|
644
826
|
return
|
|
645
827
|
else:
|
|
646
|
-
logs = self._logs.
|
|
828
|
+
logs = self._logs[self._decision_index]
|
|
647
829
|
for log in logs:
|
|
648
830
|
console_log(
|
|
649
831
|
-1 if log["player_index"] is None else log["player_index"],
|
|
650
832
|
log["text"],
|
|
651
833
|
log["color"],
|
|
652
834
|
)
|
|
653
|
-
self.apply_decisions(self._decisions.
|
|
835
|
+
self.apply_decisions(self._decisions[self._decision_index])
|
|
836
|
+
self._decision_index += 1
|
|
654
837
|
|
|
655
838
|
if not self.over:
|
|
656
839
|
self.step += 1
|
|
@@ -661,9 +844,18 @@ class CodeBattles(
|
|
|
661
844
|
set_results(
|
|
662
845
|
self.player_names, self._eliminated[::-1], self.map, self.verbose
|
|
663
846
|
)
|
|
847
|
+
if not self.background:
|
|
848
|
+
self.render()
|
|
664
849
|
|
|
665
850
|
if not self.background:
|
|
666
|
-
self.
|
|
851
|
+
if self._since_last_render >= self.configure_render_rate(
|
|
852
|
+
self._get_playback_speed()
|
|
853
|
+
):
|
|
854
|
+
self.render()
|
|
855
|
+
self._since_last_render = 1
|
|
856
|
+
else:
|
|
857
|
+
self._since_last_render += 1
|
|
858
|
+
|
|
667
859
|
if (
|
|
668
860
|
self.over
|
|
669
861
|
and "Pause" in document.getElementById("playpause").textContent
|
|
@@ -79,6 +79,28 @@ def set_results(player_names: List[str], places: List[int], map: str, verbose: b
|
|
|
79
79
|
print(e)
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
@web_only
|
|
83
|
+
def show_download():
|
|
84
|
+
from js import window
|
|
85
|
+
|
|
86
|
+
if hasattr(window, "showDownload"):
|
|
87
|
+
try:
|
|
88
|
+
window.showDownload()
|
|
89
|
+
except Exception as e:
|
|
90
|
+
print(e)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@web_only
|
|
94
|
+
def navigate(route: str):
|
|
95
|
+
from js import window
|
|
96
|
+
|
|
97
|
+
if hasattr(window, "_navigate"):
|
|
98
|
+
try:
|
|
99
|
+
window._navigate(route)
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(e)
|
|
102
|
+
|
|
103
|
+
|
|
82
104
|
@web_only
|
|
83
105
|
def download_json(filename: str, contents: str):
|
|
84
106
|
from js import window
|
package/dist/esm/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import '@mantine/core/styles.css';
|
|
2
2
|
import '@mantine/dates/styles.css';
|
|
3
3
|
import '@mantine/notifications/styles.css';
|
|
4
|
+
import '@mantine/dropzone/styles.css';
|
|
4
5
|
import { TextInput, Button, Alert, Slider, Box, Select, NumberInput, Table, Badge, Autocomplete, Loader, MantineProvider } from '@mantine/core';
|
|
5
6
|
import { TimeInput, DatesProvider } from '@mantine/dates';
|
|
6
7
|
import { useColorScheme, useViewportSize } from '@mantine/hooks';
|
|
@@ -9,7 +10,8 @@ import { initializeApp } from 'firebase/app';
|
|
|
9
10
|
import { onAuthStateChanged, signInWithEmailAndPassword, signOut, getAuth } from 'firebase/auth';
|
|
10
11
|
import { onSnapshot, refEqual, doc, getDoc, setDoc, Timestamp, getFirestore } from 'firebase/firestore';
|
|
11
12
|
import React, { useEffect, useReducer, useCallback, useMemo, createContext, useContext, useState, useRef, cloneElement, Component, createElement } from 'react';
|
|
12
|
-
import { useNavigate, useLocation, useParams, Routes, Route, BrowserRouter } from 'react-router-dom';
|
|
13
|
+
import { useNavigate, useLocation, useParams, useSearchParams, Routes, Route, BrowserRouter } from 'react-router-dom';
|
|
14
|
+
import { Dropzone } from '@mantine/dropzone';
|
|
13
15
|
import { jsx } from 'react/jsx-runtime';
|
|
14
16
|
|
|
15
17
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
@@ -407,21 +409,30 @@ const toPlacing = (n) => {
|
|
|
407
409
|
}
|
|
408
410
|
return n.toString() + DIGITS[n % 10];
|
|
409
411
|
};
|
|
410
|
-
const runNoUI = (map, apis, playerBots, verbose) => {
|
|
412
|
+
const runNoUI = (map, apis, playerBots, seed, verbose) => {
|
|
411
413
|
const players = playerBots.map((api) => (api === "None" ? "" : apis[api]));
|
|
412
|
-
|
|
414
|
+
tryUntilSuccess(() =>
|
|
413
415
|
// @ts-ignore
|
|
414
|
-
window._startSimulation(map, players, playerBots, true, false, verbose));
|
|
416
|
+
window._startSimulation(map, players, playerBots, true, false, verbose, seed));
|
|
415
417
|
};
|
|
416
|
-
const
|
|
418
|
+
const tryUntilSuccess = (f, timeout = 500) => {
|
|
417
419
|
try {
|
|
418
420
|
f();
|
|
419
421
|
}
|
|
420
422
|
catch (error) {
|
|
421
423
|
console.log("Failed, waiting for timeout...", error === null || error === void 0 ? void 0 : error.message);
|
|
422
|
-
setTimeout(() =>
|
|
424
|
+
setTimeout(() => tryUntilSuccess(f, timeout), timeout);
|
|
423
425
|
}
|
|
424
426
|
};
|
|
427
|
+
const downloadFile = (filename, mimeType, contents) => {
|
|
428
|
+
const a = document.createElement("a");
|
|
429
|
+
a.style.display = "none";
|
|
430
|
+
document.body.appendChild(a);
|
|
431
|
+
a.href = `data:${mimeType};charset=utf-8,${encodeURIComponent(contents)}`;
|
|
432
|
+
a.download = filename;
|
|
433
|
+
a.click();
|
|
434
|
+
document.body.removeChild(a);
|
|
435
|
+
};
|
|
425
436
|
|
|
426
437
|
const useLocalStorage = ({ key, defaultValue, }) => {
|
|
427
438
|
const [value, setValue] = useState(getLocalStorage(key, defaultValue !== null && defaultValue !== void 0 ? defaultValue : null));
|
|
@@ -2491,6 +2502,10 @@ const RunSimulationBlock = () => {
|
|
|
2491
2502
|
key: "Player Bots",
|
|
2492
2503
|
defaultValue: ["None", "None"],
|
|
2493
2504
|
});
|
|
2505
|
+
const [seed, setSeed] = useLocalStorage({
|
|
2506
|
+
key: "Seed",
|
|
2507
|
+
defaultValue: "",
|
|
2508
|
+
});
|
|
2494
2509
|
const [runningNoUI, setRunningNoUI] = useState(false);
|
|
2495
2510
|
const [runningNoUIN, setRunningNoUIN] = useState({});
|
|
2496
2511
|
const navigate = useNavigate();
|
|
@@ -2499,15 +2514,18 @@ const RunSimulationBlock = () => {
|
|
|
2499
2514
|
remaining = Math.max(...Object.values(runningNoUIN));
|
|
2500
2515
|
}
|
|
2501
2516
|
const run = () => {
|
|
2502
|
-
|
|
2517
|
+
// @ts-ignore
|
|
2518
|
+
window._isSimulationFromFile = false;
|
|
2519
|
+
navigate(`/simulation/${map.replaceAll(" ", "-")}/${playerBots.join("-")}?seed=${seed}`);
|
|
2503
2520
|
};
|
|
2504
2521
|
const startRunNoUI = () => {
|
|
2505
2522
|
setRunningNoUI(true);
|
|
2506
|
-
runNoUI(map, apis, playerBots, true);
|
|
2523
|
+
runNoUI(map, apis, playerBots, seed.toString(), true);
|
|
2507
2524
|
};
|
|
2508
2525
|
const startRunNoUIN = (n) => {
|
|
2526
|
+
setLocalStorage("Results", {});
|
|
2509
2527
|
setRunningNoUIN({ [n.toString()]: n });
|
|
2510
|
-
runNoUI(map, apis, playerBots, false);
|
|
2528
|
+
runNoUI(map, apis, playerBots, seed.toString(), false);
|
|
2511
2529
|
};
|
|
2512
2530
|
useEffect(() => {
|
|
2513
2531
|
// @ts-ignore
|
|
@@ -2561,7 +2579,7 @@ const RunSimulationBlock = () => {
|
|
|
2561
2579
|
setLocalStorage("Results", {});
|
|
2562
2580
|
}
|
|
2563
2581
|
else {
|
|
2564
|
-
runNoUI(map, apis, playerBots, false);
|
|
2582
|
+
runNoUI(map, apis, playerBots, seed.toString(), false);
|
|
2565
2583
|
}
|
|
2566
2584
|
}
|
|
2567
2585
|
}, [runningNoUIN]);
|
|
@@ -2572,6 +2590,7 @@ const RunSimulationBlock = () => {
|
|
|
2572
2590
|
}
|
|
2573
2591
|
} }),
|
|
2574
2592
|
React.createElement(BotSelector, { playerCount: playerCount, setPlayerCount: setPlayerCount, playerBots: playerBots, setPlayerBots: setPlayerBots, apis: apis }),
|
|
2593
|
+
React.createElement(NumberInput, { leftSection: React.createElement("i", { className: "fa-solid fa-dice" }), label: "Randomness Seed", min: 0, value: seed, onChange: setSeed }),
|
|
2575
2594
|
React.createElement(Button.Group, { mt: "xs" },
|
|
2576
2595
|
React.createElement(Button, { variant: "default", w: "50%", leftSection: React.createElement("i", { className: "fa-solid fa-play" }), onClick: run }, "Run"),
|
|
2577
2596
|
React.createElement(Button, { variant: "default", w: "50%", leftSection: React.createElement("i", { className: "fa-solid fa-forward" }), onClick: startRunNoUI, loading: runningNoUI || loading }, "Run (No UI)")),
|
|
@@ -2581,7 +2600,31 @@ const RunSimulationBlock = () => {
|
|
|
2581
2600
|
remaining !== 0 && (React.createElement("p", { style: { textAlign: "center", marginTop: 10 } },
|
|
2582
2601
|
"Remaining Simulations: ",
|
|
2583
2602
|
remaining)),
|
|
2584
|
-
React.createElement("p", { style: { textAlign: "center", display: "none", marginTop: 10 }, id: "noui-progress" })
|
|
2603
|
+
React.createElement("p", { style: { textAlign: "center", display: "none", marginTop: 10 }, id: "noui-progress" }),
|
|
2604
|
+
React.createElement(Dropzone, { mt: "xs", multiple: false, onDrop: (files) => __awaiter(void 0, void 0, void 0, function* () {
|
|
2605
|
+
if (files.length !== 1) {
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
const file = files[0];
|
|
2609
|
+
const text = yield file.text();
|
|
2610
|
+
// @ts-ignore
|
|
2611
|
+
window._navigate = navigate;
|
|
2612
|
+
// @ts-ignore
|
|
2613
|
+
window._isSimulationFromFile = true;
|
|
2614
|
+
tryUntilSuccess(() => {
|
|
2615
|
+
// @ts-ignore
|
|
2616
|
+
window._startSimulationFromFile(text);
|
|
2617
|
+
});
|
|
2618
|
+
}), style: {
|
|
2619
|
+
textAlign: "center",
|
|
2620
|
+
paddingTop: 20,
|
|
2621
|
+
paddingBottom: 20,
|
|
2622
|
+
paddingLeft: 10,
|
|
2623
|
+
paddingRight: 10,
|
|
2624
|
+
} },
|
|
2625
|
+
React.createElement("span", null,
|
|
2626
|
+
React.createElement("i", { className: "fa-solid fa-file-code", style: { marginRight: 10 } }),
|
|
2627
|
+
"Drag a simulation file here or click to select a file to run a simulation from a file"))));
|
|
2585
2628
|
};
|
|
2586
2629
|
|
|
2587
2630
|
const TournamentBlock = ({ pointModifier, inline, title }) => {
|
|
@@ -15899,13 +15942,19 @@ const Simulation = () => {
|
|
|
15899
15942
|
const [apis, loading] = useAPIs();
|
|
15900
15943
|
let { map, playerapis } = useParams();
|
|
15901
15944
|
const location = useLocation();
|
|
15945
|
+
const [searchParams] = useSearchParams();
|
|
15902
15946
|
const [winner, setWinner] = useState();
|
|
15947
|
+
const [downloadBytes, setDownloadBytes] = useState(false);
|
|
15903
15948
|
const navigate = useNavigate();
|
|
15904
15949
|
const colorScheme = useColorScheme();
|
|
15905
15950
|
const showcaseMode = location.search.includes("showcase=true");
|
|
15906
15951
|
useEffect(() => {
|
|
15907
15952
|
n((engine) => __awaiter(void 0, void 0, void 0, function* () { return yield loadFull(engine); }));
|
|
15908
15953
|
// @ts-ignore
|
|
15954
|
+
window.showDownload = () => {
|
|
15955
|
+
setDownloadBytes(true);
|
|
15956
|
+
};
|
|
15957
|
+
// @ts-ignore
|
|
15909
15958
|
window.showWinner = (winner, verbose) => {
|
|
15910
15959
|
if (admin) {
|
|
15911
15960
|
setWinner(winner);
|
|
@@ -15935,10 +15984,16 @@ const Simulation = () => {
|
|
|
15935
15984
|
const playerNames = (_a = playerapis === null || playerapis === void 0 ? void 0 : playerapis.split("-")) !== null && _a !== void 0 ? _a : [];
|
|
15936
15985
|
const players = playerNames.map((api) => (api === "None" ? "" : apis[api]));
|
|
15937
15986
|
useEffect(() => {
|
|
15938
|
-
|
|
15939
|
-
|
|
15987
|
+
var _a;
|
|
15988
|
+
if (!loading &&
|
|
15989
|
+
players &&
|
|
15990
|
+
playerapis &&
|
|
15991
|
+
// @ts-ignore
|
|
15992
|
+
window._isSimulationFromFile !== true) {
|
|
15993
|
+
const seed = (_a = searchParams.get("seed")) !== null && _a !== void 0 ? _a : "";
|
|
15994
|
+
tryUntilSuccess(() =>
|
|
15940
15995
|
// @ts-ignore
|
|
15941
|
-
window._startSimulation(map, players, playerNames, false, !showcaseMode, true));
|
|
15996
|
+
window._startSimulation(map, players, playerNames, false, !showcaseMode, true, seed));
|
|
15942
15997
|
}
|
|
15943
15998
|
}, [loading]);
|
|
15944
15999
|
const newRank = getRank(getLocalStorage("Cached tournament/info"), winner, getLocalStorage("Point Modifier")) + 1;
|
|
@@ -16021,7 +16076,11 @@ const Simulation = () => {
|
|
|
16021
16076
|
React.createElement("span", { id: "render-status", style: { marginRight: 10 } }),
|
|
16022
16077
|
React.createElement(NumberInput, { id: "breakpoint", label: "Breakpoint", min: 0, leftSection: React.createElement("i", { className: "fa-solid fa-stopwatch" }), display: "inline-block", maw: "35%", mr: "xs" }),
|
|
16023
16078
|
React.createElement(Button, { style: { flex: "none" }, my: "xs", w: 100, leftSection: React.createElement("i", { className: "fa-solid fa-wand-magic" }), color: "grape", id: "step", mr: "xs", radius: "20px" }, "Step"))),
|
|
16024
|
-
React.createElement(PlayPauseButton, null)
|
|
16079
|
+
React.createElement(PlayPauseButton, null),
|
|
16080
|
+
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",
|
|
16081
|
+
// @ts-ignore
|
|
16082
|
+
window.simulationToDownload) }, "Download Simulation"))),
|
|
16083
|
+
showcaseMode && React.createElement("span", { id: "render-status" }),
|
|
16025
16084
|
React.createElement("p", { style: { margin: 0 } }, "Playback Speed"),
|
|
16026
16085
|
React.createElement(Slider, { style: { flex: "none" }, mb: 30, w: 500, maw: "85%", min: -2, defaultValue: 0, marks: [
|
|
16027
16086
|
{ value: -2, label: "1/4" },
|
|
@@ -2,6 +2,7 @@ import "@fortawesome/fontawesome-free/css/all.css";
|
|
|
2
2
|
import "@mantine/core/styles.css";
|
|
3
3
|
import "@mantine/dates/styles.css";
|
|
4
4
|
import "@mantine/notifications/styles.css";
|
|
5
|
+
import "@mantine/dropzone/styles.css";
|
|
5
6
|
import "prismjs/plugins/line-numbers/prism-line-numbers.css";
|
|
6
7
|
import "prismjs/themes/prism.css";
|
|
7
8
|
import "./index.css";
|
|
@@ -10,5 +10,6 @@ export declare const getRank: (tournamentInfo: any, team: string, pointModifier:
|
|
|
10
10
|
export declare const updatePointModifier: () => void;
|
|
11
11
|
export declare const toPlacing: (n: number) => string;
|
|
12
12
|
export declare const zeroPad: (s: string, l: number) => string;
|
|
13
|
-
export declare const runNoUI: (map: string, apis: Record<string, any>, playerBots: string[], verbose: boolean) => void;
|
|
14
|
-
export declare const
|
|
13
|
+
export declare const runNoUI: (map: string, apis: Record<string, any>, playerBots: string[], seed: string, verbose: boolean) => void;
|
|
14
|
+
export declare const tryUntilSuccess: (f: () => void, timeout?: number) => void;
|
|
15
|
+
export declare const downloadFile: (filename: string, mimeType: string, contents: string) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-battles",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "A library for building interactive competitive coding battles",
|
|
5
5
|
"repository": "https://github.com/noamzaks/code-battles",
|
|
6
6
|
"homepage": "https://code-battles.readthedocs.org",
|
|
@@ -38,11 +38,12 @@
|
|
|
38
38
|
"tsparticles": "^3.5.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@mantine/core": "^7.13.
|
|
42
|
-
"@mantine/dates": "^7.13.
|
|
43
|
-
"@mantine/
|
|
44
|
-
"@mantine/
|
|
45
|
-
"
|
|
41
|
+
"@mantine/core": "^7.13.4",
|
|
42
|
+
"@mantine/dates": "^7.13.4",
|
|
43
|
+
"@mantine/dropzone": "^7.13.4",
|
|
44
|
+
"@mantine/hooks": "^7.13.4",
|
|
45
|
+
"@mantine/notifications": "^7.13.4",
|
|
46
|
+
"firebase": "^11.0.1",
|
|
46
47
|
"react": "^18.3.1",
|
|
47
48
|
"react-dom": "^18.3.1",
|
|
48
49
|
"react-router-dom": "^6.27.0"
|
|
@@ -56,7 +57,7 @@
|
|
|
56
57
|
"@types/react": "^18.3.12",
|
|
57
58
|
"@types/react-dom": "^18.3.1",
|
|
58
59
|
"chokidar-cli": "^3.0.0",
|
|
59
|
-
"rollup": "^4.24.
|
|
60
|
+
"rollup": "^4.24.4",
|
|
60
61
|
"rollup-plugin-copy": "^3.5.0",
|
|
61
62
|
"rollup-plugin-dts": "^6.1.1",
|
|
62
63
|
"rollup-plugin-import-css": "^3.5.6",
|