code-battles 1.4.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 CHANGED
@@ -411,11 +411,11 @@ const toPlacing = (n) => {
411
411
  }
412
412
  return n.toString() + DIGITS[n % 10];
413
413
  };
414
- const runNoUI = (map, apis, playerBots, verbose) => {
414
+ const runNoUI = (map, apis, playerBots, seed, verbose) => {
415
415
  const players = playerBots.map((api) => (api === "None" ? "" : apis[api]));
416
416
  tryUntilSuccess(() =>
417
417
  // @ts-ignore
418
- window._startSimulation(map, players, playerBots, true, false, verbose));
418
+ window._startSimulation(map, players, playerBots, true, false, verbose, seed));
419
419
  };
420
420
  const tryUntilSuccess = (f, timeout = 500) => {
421
421
  try {
@@ -2504,6 +2504,10 @@ const RunSimulationBlock = () => {
2504
2504
  key: "Player Bots",
2505
2505
  defaultValue: ["None", "None"],
2506
2506
  });
2507
+ const [seed, setSeed] = useLocalStorage({
2508
+ key: "Seed",
2509
+ defaultValue: "",
2510
+ });
2507
2511
  const [runningNoUI, setRunningNoUI] = React.useState(false);
2508
2512
  const [runningNoUIN, setRunningNoUIN] = React.useState({});
2509
2513
  const navigate = reactRouterDom.useNavigate();
@@ -2514,15 +2518,16 @@ const RunSimulationBlock = () => {
2514
2518
  const run = () => {
2515
2519
  // @ts-ignore
2516
2520
  window._isSimulationFromFile = false;
2517
- navigate(`/simulation/${map.replaceAll(" ", "-")}/${playerBots.join("-")}`);
2521
+ navigate(`/simulation/${map.replaceAll(" ", "-")}/${playerBots.join("-")}?seed=${seed}`);
2518
2522
  };
2519
2523
  const startRunNoUI = () => {
2520
2524
  setRunningNoUI(true);
2521
- runNoUI(map, apis, playerBots, true);
2525
+ runNoUI(map, apis, playerBots, seed.toString(), true);
2522
2526
  };
2523
2527
  const startRunNoUIN = (n) => {
2528
+ setLocalStorage("Results", {});
2524
2529
  setRunningNoUIN({ [n.toString()]: n });
2525
- runNoUI(map, apis, playerBots, false);
2530
+ runNoUI(map, apis, playerBots, seed.toString(), false);
2526
2531
  };
2527
2532
  React.useEffect(() => {
2528
2533
  // @ts-ignore
@@ -2576,7 +2581,7 @@ const RunSimulationBlock = () => {
2576
2581
  setLocalStorage("Results", {});
2577
2582
  }
2578
2583
  else {
2579
- runNoUI(map, apis, playerBots, false);
2584
+ runNoUI(map, apis, playerBots, seed.toString(), false);
2580
2585
  }
2581
2586
  }
2582
2587
  }, [runningNoUIN]);
@@ -2587,6 +2592,7 @@ const RunSimulationBlock = () => {
2587
2592
  }
2588
2593
  } }),
2589
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 }),
2590
2596
  React.createElement(core.Button.Group, { mt: "xs" },
2591
2597
  React.createElement(core.Button, { variant: "default", w: "50%", leftSection: React.createElement("i", { className: "fa-solid fa-play" }), onClick: run }, "Run"),
2592
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)")),
@@ -15938,6 +15944,7 @@ const Simulation = () => {
15938
15944
  const [apis, loading] = useAPIs();
15939
15945
  let { map, playerapis } = reactRouterDom.useParams();
15940
15946
  const location = reactRouterDom.useLocation();
15947
+ const [searchParams] = reactRouterDom.useSearchParams();
15941
15948
  const [winner, setWinner] = React.useState();
15942
15949
  const [downloadBytes, setDownloadBytes] = React.useState(false);
15943
15950
  const navigate = reactRouterDom.useNavigate();
@@ -15979,14 +15986,16 @@ const Simulation = () => {
15979
15986
  const playerNames = (_a = playerapis === null || playerapis === void 0 ? void 0 : playerapis.split("-")) !== null && _a !== void 0 ? _a : [];
15980
15987
  const players = playerNames.map((api) => (api === "None" ? "" : apis[api]));
15981
15988
  React.useEffect(() => {
15989
+ var _a;
15982
15990
  if (!loading &&
15983
15991
  players &&
15984
15992
  playerapis &&
15985
15993
  // @ts-ignore
15986
15994
  window._isSimulationFromFile !== true) {
15995
+ const seed = (_a = searchParams.get("seed")) !== null && _a !== void 0 ? _a : "";
15987
15996
  tryUntilSuccess(() =>
15988
15997
  // @ts-ignore
15989
- window._startSimulation(map, players, playerNames, false, !showcaseMode, true));
15998
+ window._startSimulation(map, players, playerNames, false, !showcaseMode, true, seed));
15990
15999
  }
15991
16000
  }, [loading]);
15992
16001
  const newRank = getRank(getLocalStorage("Cached tournament/info"), winner, getLocalStorage("Point Modifier")) + 1;
@@ -10,6 +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;
13
+ export declare const runNoUI: (map: string, apis: Record<string, any>, playerBots: string[], seed: string, verbose: boolean) => void;
14
14
  export declare const tryUntilSuccess: (f: () => void, timeout?: number) => void;
15
15
  export declare const downloadFile: (filename: string, mimeType: string, contents: string) => void;
@@ -5,7 +5,7 @@ import datetime
5
5
  import json
6
6
  import math
7
7
  import time
8
- import random
8
+ from random import Random
9
9
  import sys
10
10
  import traceback
11
11
  import gzip
@@ -43,6 +43,7 @@ class Simulation:
43
43
  timestamp: datetime.datetime
44
44
  logs: list
45
45
  decisions: List[bytes]
46
+ seed: int
46
47
 
47
48
  def dump(self):
48
49
  return base64.b64encode(
@@ -59,6 +60,7 @@ class Simulation:
59
60
  base64.b64encode(decision).decode()
60
61
  for decision in self.decisions
61
62
  ],
63
+ "seed": self.seed,
62
64
  }
63
65
  ).encode()
64
66
  )
@@ -66,7 +68,7 @@ class Simulation:
66
68
 
67
69
  @staticmethod
68
70
  def load(file: str):
69
- contents = json.loads(gzip.decompress(base64.b64decode(file)))
71
+ contents: Dict[str, Any] = json.loads(gzip.decompress(base64.b64decode(file)))
70
72
  return Simulation(
71
73
  contents["map"],
72
74
  contents["playerNames"],
@@ -75,6 +77,7 @@ class Simulation:
75
77
  datetime.datetime.fromisoformat(contents["timestamp"]),
76
78
  contents["logs"],
77
79
  [base64.b64decode(decision) for decision in contents["decisions"]],
80
+ contents["seed"],
78
81
  )
79
82
 
80
83
 
@@ -109,6 +112,12 @@ class CodeBattles(
109
112
  """The current state of the game. You should modify this in :func:`apply_decisions`."""
110
113
  player_requests: List[PlayerRequestsType]
111
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`."""
112
121
 
113
122
  background: bool
114
123
  """Whether the current simulation is occuring in the background (without UI)."""
@@ -144,12 +153,12 @@ class CodeBattles(
144
153
  Use the current state and bots to make decisions in order to reach the next state.
145
154
  You may use :func:`run_bot_method` to run a specific player's method (for instance, `run`).
146
155
 
156
+ If you need any randomness, use :attr:`make_decisions_random`.
157
+
147
158
  This function may take a lot of time to execute.
148
159
 
149
160
  .. warning::
150
161
  Do not call any other method other than :func:`run_bot_method` in here. This method will run in a web worker.
151
-
152
- Do NOT update :attr:`state` or :attr:`step`.
153
162
  """
154
163
 
155
164
  raise NotImplementedError("make_decisions")
@@ -261,11 +270,11 @@ class CodeBattles(
261
270
  """
262
271
  return 1
263
272
 
264
- def configure_bot_globals(self) -> Dict[str, Any]:
273
+ def configure_bot_globals(self, player_index: int) -> Dict[str, Any]:
265
274
  """
266
275
  Configure additional available global items, such as libraries from the Python standard library, bots can use.
267
276
 
268
- 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`.
269
278
 
270
279
  .. warning::
271
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`.
@@ -276,7 +285,7 @@ class CodeBattles(
276
285
  return {
277
286
  "math": math,
278
287
  "time": time,
279
- "random": random,
288
+ "random": self.player_randoms[player_index],
280
289
  }
281
290
 
282
291
  def configure_version(self) -> str:
@@ -456,13 +465,19 @@ class CodeBattles(
456
465
 
457
466
  self._initialized = True
458
467
 
459
- def _initialize_simulation(self, player_codes: List[str]):
460
- self.step = 0
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)
461
473
  self._logs = []
462
474
  self._decisions = []
463
475
  self._decision_index = 0
476
+ self._seed = seed
477
+ self.step = 0
464
478
  self.active_players = list(range(len(self.player_names)))
465
- self.active_players = list(range(len(self.player_names)))
479
+ self.random = Random(seed)
480
+ self.player_randoms = [Random(self.random.random()) for _ in self.player_names]
466
481
  self.state = self.create_initial_state()
467
482
  self.player_requests = [
468
483
  self.create_initial_player_requests(i)
@@ -474,7 +489,11 @@ class CodeBattles(
474
489
  self._start_time = time.time()
475
490
 
476
491
  def _run_webworker_simulation(
477
- self, map: str, player_names_str: str, player_codes_str: str
492
+ self,
493
+ map: str,
494
+ player_names_str: str,
495
+ player_codes_str: str,
496
+ seed: Optional[int] = None,
478
497
  ):
479
498
  from pyscript import sync
480
499
 
@@ -487,7 +506,7 @@ class CodeBattles(
487
506
  self.background = True
488
507
  self.console_visible = False
489
508
  self.verbose = False
490
- self._initialize_simulation(player_codes)
509
+ self._initialize_simulation(player_codes, seed)
491
510
  while not self.over:
492
511
  self._logs = []
493
512
  decisions = self.make_decisions()
@@ -505,16 +524,17 @@ class CodeBattles(
505
524
  self.step += 1
506
525
 
507
526
  def _run_local_simulation(self):
508
- self.map = sys.argv[1]
509
- self.player_names = sys.argv[2].split("-")
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("-")
510
530
  self.background = True
511
531
  self.console_visible = False
512
532
  self.verbose = False
513
533
  player_codes = []
514
- for filename in sys.argv[3:]:
534
+ for filename in sys.argv[4:]:
515
535
  with open(filename, "r") as f:
516
536
  player_codes.append(f.read())
517
- self._initialize_simulation(player_codes)
537
+ self._initialize_simulation(player_codes, seed)
518
538
 
519
539
  while not self.over:
520
540
  self.apply_decisions(self.make_decisions())
@@ -589,7 +609,9 @@ class CodeBattles(
589
609
  self.background = False
590
610
  self.console_visible = True
591
611
  self.verbose = False
592
- self._initialize_simulation(["" for _ in simulation.player_names])
612
+ self._initialize_simulation(
613
+ ["" for _ in simulation.player_names], simulation.seed
614
+ )
593
615
  self._decisions = simulation.decisions
594
616
  self._logs = simulation.logs
595
617
  self.canvas = GameCanvas(
@@ -616,6 +638,7 @@ class CodeBattles(
616
638
  background: bool,
617
639
  console_visible: bool,
618
640
  verbose: bool,
641
+ seed="",
619
642
  ):
620
643
  from js import document
621
644
  from pyscript import workers
@@ -635,7 +658,7 @@ class CodeBattles(
635
658
  self.background = background
636
659
  self.console_visible = console_visible
637
660
  self.verbose = verbose
638
- self._initialize_simulation(player_codes)
661
+ self._initialize_simulation(player_codes, None if seed == "" else int(seed))
639
662
 
640
663
  if not self.background:
641
664
  self.canvas = GameCanvas(
@@ -661,11 +684,11 @@ class CodeBattles(
661
684
  document.getElementById("loader").style.display = "none"
662
685
  self.render()
663
686
 
664
- self._worker = await workers["worker"]
665
- self._worker.update_step = self._update_step
666
- self._worker._run_webworker_simulation(
667
- map, json.dumps(player_names), json.dumps(player_codes)
668
- )
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
+ )
669
692
 
670
693
  if self.background:
671
694
  await self._play_pause()
@@ -693,6 +716,7 @@ class CodeBattles(
693
716
  datetime.datetime.now(),
694
717
  self._logs,
695
718
  self._decisions,
719
+ self._seed,
696
720
  )
697
721
  window.simulationToDownload = simulation.dump()
698
722
  show_download()
@@ -721,8 +745,8 @@ class CodeBattles(
721
745
  "context": context,
722
746
  **self.get_api().__dict__,
723
747
  }
724
- | self.configure_bot_globals()
725
- for context in contexts
748
+ | self.configure_bot_globals(player_index)
749
+ for player_index, context in enumerate(contexts)
726
750
  ]
727
751
  for index, api_code in enumerate(player_codes):
728
752
  if api_code != "" and api_code is not None:
@@ -820,7 +844,8 @@ class CodeBattles(
820
844
  set_results(
821
845
  self.player_names, self._eliminated[::-1], self.map, self.verbose
822
846
  )
823
- self.render()
847
+ if not self.background:
848
+ self.render()
824
849
 
825
850
  if not self.background:
826
851
  if self._since_last_render >= self.configure_render_rate(
package/dist/esm/index.js CHANGED
@@ -10,7 +10,7 @@ import { initializeApp } from 'firebase/app';
10
10
  import { onAuthStateChanged, signInWithEmailAndPassword, signOut, getAuth } from 'firebase/auth';
11
11
  import { onSnapshot, refEqual, doc, getDoc, setDoc, Timestamp, getFirestore } from 'firebase/firestore';
12
12
  import React, { useEffect, useReducer, useCallback, useMemo, createContext, useContext, useState, useRef, cloneElement, Component, createElement } from 'react';
13
- 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
14
  import { Dropzone } from '@mantine/dropzone';
15
15
  import { jsx } from 'react/jsx-runtime';
16
16
 
@@ -409,11 +409,11 @@ const toPlacing = (n) => {
409
409
  }
410
410
  return n.toString() + DIGITS[n % 10];
411
411
  };
412
- const runNoUI = (map, apis, playerBots, verbose) => {
412
+ const runNoUI = (map, apis, playerBots, seed, verbose) => {
413
413
  const players = playerBots.map((api) => (api === "None" ? "" : apis[api]));
414
414
  tryUntilSuccess(() =>
415
415
  // @ts-ignore
416
- window._startSimulation(map, players, playerBots, true, false, verbose));
416
+ window._startSimulation(map, players, playerBots, true, false, verbose, seed));
417
417
  };
418
418
  const tryUntilSuccess = (f, timeout = 500) => {
419
419
  try {
@@ -2502,6 +2502,10 @@ const RunSimulationBlock = () => {
2502
2502
  key: "Player Bots",
2503
2503
  defaultValue: ["None", "None"],
2504
2504
  });
2505
+ const [seed, setSeed] = useLocalStorage({
2506
+ key: "Seed",
2507
+ defaultValue: "",
2508
+ });
2505
2509
  const [runningNoUI, setRunningNoUI] = useState(false);
2506
2510
  const [runningNoUIN, setRunningNoUIN] = useState({});
2507
2511
  const navigate = useNavigate();
@@ -2512,15 +2516,16 @@ const RunSimulationBlock = () => {
2512
2516
  const run = () => {
2513
2517
  // @ts-ignore
2514
2518
  window._isSimulationFromFile = false;
2515
- navigate(`/simulation/${map.replaceAll(" ", "-")}/${playerBots.join("-")}`);
2519
+ navigate(`/simulation/${map.replaceAll(" ", "-")}/${playerBots.join("-")}?seed=${seed}`);
2516
2520
  };
2517
2521
  const startRunNoUI = () => {
2518
2522
  setRunningNoUI(true);
2519
- runNoUI(map, apis, playerBots, true);
2523
+ runNoUI(map, apis, playerBots, seed.toString(), true);
2520
2524
  };
2521
2525
  const startRunNoUIN = (n) => {
2526
+ setLocalStorage("Results", {});
2522
2527
  setRunningNoUIN({ [n.toString()]: n });
2523
- runNoUI(map, apis, playerBots, false);
2528
+ runNoUI(map, apis, playerBots, seed.toString(), false);
2524
2529
  };
2525
2530
  useEffect(() => {
2526
2531
  // @ts-ignore
@@ -2574,7 +2579,7 @@ const RunSimulationBlock = () => {
2574
2579
  setLocalStorage("Results", {});
2575
2580
  }
2576
2581
  else {
2577
- runNoUI(map, apis, playerBots, false);
2582
+ runNoUI(map, apis, playerBots, seed.toString(), false);
2578
2583
  }
2579
2584
  }
2580
2585
  }, [runningNoUIN]);
@@ -2585,6 +2590,7 @@ const RunSimulationBlock = () => {
2585
2590
  }
2586
2591
  } }),
2587
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 }),
2588
2594
  React.createElement(Button.Group, { mt: "xs" },
2589
2595
  React.createElement(Button, { variant: "default", w: "50%", leftSection: React.createElement("i", { className: "fa-solid fa-play" }), onClick: run }, "Run"),
2590
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)")),
@@ -15936,6 +15942,7 @@ const Simulation = () => {
15936
15942
  const [apis, loading] = useAPIs();
15937
15943
  let { map, playerapis } = useParams();
15938
15944
  const location = useLocation();
15945
+ const [searchParams] = useSearchParams();
15939
15946
  const [winner, setWinner] = useState();
15940
15947
  const [downloadBytes, setDownloadBytes] = useState(false);
15941
15948
  const navigate = useNavigate();
@@ -15977,14 +15984,16 @@ const Simulation = () => {
15977
15984
  const playerNames = (_a = playerapis === null || playerapis === void 0 ? void 0 : playerapis.split("-")) !== null && _a !== void 0 ? _a : [];
15978
15985
  const players = playerNames.map((api) => (api === "None" ? "" : apis[api]));
15979
15986
  useEffect(() => {
15987
+ var _a;
15980
15988
  if (!loading &&
15981
15989
  players &&
15982
15990
  playerapis &&
15983
15991
  // @ts-ignore
15984
15992
  window._isSimulationFromFile !== true) {
15993
+ const seed = (_a = searchParams.get("seed")) !== null && _a !== void 0 ? _a : "";
15985
15994
  tryUntilSuccess(() =>
15986
15995
  // @ts-ignore
15987
- window._startSimulation(map, players, playerNames, false, !showcaseMode, true));
15996
+ window._startSimulation(map, players, playerNames, false, !showcaseMode, true, seed));
15988
15997
  }
15989
15998
  }, [loading]);
15990
15999
  const newRank = getRank(getLocalStorage("Cached tournament/info"), winner, getLocalStorage("Point Modifier")) + 1;
@@ -10,6 +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;
13
+ export declare const runNoUI: (map: string, apis: Record<string, any>, playerBots: string[], seed: string, verbose: boolean) => void;
14
14
  export declare const tryUntilSuccess: (f: () => void, timeout?: number) => void;
15
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.4.0",
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",