code-battles 1.4.0 → 1.5.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/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)")),
@@ -14512,7 +14518,7 @@ const Round = () => {
14512
14518
  }
14513
14519
  React.useEffect(() => {
14514
14520
  if (remaining > 0) {
14515
- runNoUI(currentMap, apis, currentPlayers, false);
14521
+ runNoUI(currentMap, apis, currentPlayers, "", false);
14516
14522
  }
14517
14523
  }, [remaining]);
14518
14524
  React.useEffect(updatePointModifier, [results]);
@@ -14571,7 +14577,7 @@ const Round = () => {
14571
14577
  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"),
14572
14578
  React.createElement(core.Button, { leftSection: React.createElement("i", { className: "fa-solid fa-forward" }), size: "xs", onClick: () => {
14573
14579
  if (roundIterations === 1) {
14574
- runNoUI(round.map, apis, round.players, true);
14580
+ runNoUI(round.map, apis, round.players, "", true);
14575
14581
  }
14576
14582
  else {
14577
14583
  currentMap = round.map;
@@ -15844,7 +15850,10 @@ const confetti = {
15844
15850
  };
15845
15851
 
15846
15852
  const AutoScrollButton = () => {
15847
- const [autoScroll, setAutoScroll] = React.useState(true);
15853
+ const [autoScroll, setAutoScroll] = useLocalStorage({
15854
+ key: "Auto Scroll",
15855
+ defaultValue: true,
15856
+ });
15848
15857
  React.useEffect(() => {
15849
15858
  // @ts-ignore
15850
15859
  window.autoScroll = autoScroll;
@@ -15862,8 +15871,14 @@ const ShowLogsButtons = ({ playerNames, showLogs, setShowLogs, }) => {
15862
15871
  };
15863
15872
 
15864
15873
  const LogViewer = ({ playerNames }) => {
15865
- const [showLogs, setShowLogs] = React.useState([]);
15866
- const [logs, setLogs] = React.useState([]);
15874
+ const [showLogs, setShowLogs] = useLocalStorage({
15875
+ key: "Show Logs",
15876
+ defaultValue: [],
15877
+ });
15878
+ const [logs, setLogs] = useLocalStorage({
15879
+ key: "Logs",
15880
+ defaultValue: [],
15881
+ });
15867
15882
  React.useEffect(() => {
15868
15883
  // @ts-ignore
15869
15884
  setShowLogs(playerNames.map(() => true));
@@ -15938,6 +15953,7 @@ const Simulation = () => {
15938
15953
  const [apis, loading] = useAPIs();
15939
15954
  let { map, playerapis } = reactRouterDom.useParams();
15940
15955
  const location = reactRouterDom.useLocation();
15956
+ const [searchParams] = reactRouterDom.useSearchParams();
15941
15957
  const [winner, setWinner] = React.useState();
15942
15958
  const [downloadBytes, setDownloadBytes] = React.useState(false);
15943
15959
  const navigate = reactRouterDom.useNavigate();
@@ -15979,14 +15995,16 @@ const Simulation = () => {
15979
15995
  const playerNames = (_a = playerapis === null || playerapis === void 0 ? void 0 : playerapis.split("-")) !== null && _a !== void 0 ? _a : [];
15980
15996
  const players = playerNames.map((api) => (api === "None" ? "" : apis[api]));
15981
15997
  React.useEffect(() => {
15998
+ var _a;
15982
15999
  if (!loading &&
15983
16000
  players &&
15984
16001
  playerapis &&
15985
16002
  // @ts-ignore
15986
16003
  window._isSimulationFromFile !== true) {
16004
+ const seed = (_a = searchParams.get("seed")) !== null && _a !== void 0 ? _a : "";
15987
16005
  tryUntilSuccess(() =>
15988
16006
  // @ts-ignore
15989
- window._startSimulation(map, players, playerNames, false, !showcaseMode, true));
16007
+ window._startSimulation(map, players, playerNames, false, !showcaseMode, true, seed));
15990
16008
  }
15991
16009
  }, [loading]);
15992
16010
  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:
@@ -395,6 +404,9 @@ class CodeBattles(
395
404
 
396
405
  For game-global log entries (not coming from a specific player), don't specify a ``player_index``.
397
406
  """
407
+ if not isinstance(text, str):
408
+ text = str(text)
409
+
398
410
  if is_web():
399
411
  console_log(-1 if player_index is None else player_index, text, color)
400
412
  else:
@@ -456,13 +468,19 @@ class CodeBattles(
456
468
 
457
469
  self._initialized = True
458
470
 
459
- def _initialize_simulation(self, player_codes: List[str]):
460
- self.step = 0
471
+ def _initialize_simulation(
472
+ self, player_codes: List[str], seed: Optional[int] = None
473
+ ):
474
+ if seed is None:
475
+ seed = Random().randint(0, 2**128)
461
476
  self._logs = []
462
477
  self._decisions = []
463
478
  self._decision_index = 0
479
+ self._seed = seed
480
+ self.step = 0
464
481
  self.active_players = list(range(len(self.player_names)))
465
- self.active_players = list(range(len(self.player_names)))
482
+ self.random = Random(seed)
483
+ self.player_randoms = [Random(self.random.random()) for _ in self.player_names]
466
484
  self.state = self.create_initial_state()
467
485
  self.player_requests = [
468
486
  self.create_initial_player_requests(i)
@@ -474,7 +492,11 @@ class CodeBattles(
474
492
  self._start_time = time.time()
475
493
 
476
494
  def _run_webworker_simulation(
477
- self, map: str, player_names_str: str, player_codes_str: str
495
+ self,
496
+ map: str,
497
+ player_names_str: str,
498
+ player_codes_str: str,
499
+ seed: Optional[int] = None,
478
500
  ):
479
501
  from pyscript import sync
480
502
 
@@ -487,7 +509,7 @@ class CodeBattles(
487
509
  self.background = True
488
510
  self.console_visible = False
489
511
  self.verbose = False
490
- self._initialize_simulation(player_codes)
512
+ self._initialize_simulation(player_codes, seed)
491
513
  while not self.over:
492
514
  self._logs = []
493
515
  decisions = self.make_decisions()
@@ -505,16 +527,17 @@ class CodeBattles(
505
527
  self.step += 1
506
528
 
507
529
  def _run_local_simulation(self):
508
- self.map = sys.argv[1]
509
- self.player_names = sys.argv[2].split("-")
530
+ seed = None if sys.argv[1] == "None" else int(sys.argv[1])
531
+ self.map = sys.argv[2]
532
+ self.player_names = sys.argv[3].split("-")
510
533
  self.background = True
511
534
  self.console_visible = False
512
535
  self.verbose = False
513
536
  player_codes = []
514
- for filename in sys.argv[3:]:
537
+ for filename in sys.argv[4:]:
515
538
  with open(filename, "r") as f:
516
539
  player_codes.append(f.read())
517
- self._initialize_simulation(player_codes)
540
+ self._initialize_simulation(player_codes, seed)
518
541
 
519
542
  while not self.over:
520
543
  self.apply_decisions(self.make_decisions())
@@ -589,7 +612,9 @@ class CodeBattles(
589
612
  self.background = False
590
613
  self.console_visible = True
591
614
  self.verbose = False
592
- self._initialize_simulation(["" for _ in simulation.player_names])
615
+ self._initialize_simulation(
616
+ ["" for _ in simulation.player_names], simulation.seed
617
+ )
593
618
  self._decisions = simulation.decisions
594
619
  self._logs = simulation.logs
595
620
  self.canvas = GameCanvas(
@@ -616,6 +641,7 @@ class CodeBattles(
616
641
  background: bool,
617
642
  console_visible: bool,
618
643
  verbose: bool,
644
+ seed="",
619
645
  ):
620
646
  from js import document
621
647
  from pyscript import workers
@@ -635,7 +661,7 @@ class CodeBattles(
635
661
  self.background = background
636
662
  self.console_visible = console_visible
637
663
  self.verbose = verbose
638
- self._initialize_simulation(player_codes)
664
+ self._initialize_simulation(player_codes, None if seed == "" else int(seed))
639
665
 
640
666
  if not self.background:
641
667
  self.canvas = GameCanvas(
@@ -661,11 +687,11 @@ class CodeBattles(
661
687
  document.getElementById("loader").style.display = "none"
662
688
  self.render()
663
689
 
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
- )
690
+ self._worker = await workers["worker"]
691
+ self._worker.update_step = self._update_step
692
+ self._worker._run_webworker_simulation(
693
+ map, json.dumps(player_names), json.dumps(player_codes), self._seed
694
+ )
669
695
 
670
696
  if self.background:
671
697
  await self._play_pause()
@@ -693,6 +719,7 @@ class CodeBattles(
693
719
  datetime.datetime.now(),
694
720
  self._logs,
695
721
  self._decisions,
722
+ self._seed,
696
723
  )
697
724
  window.simulationToDownload = simulation.dump()
698
725
  show_download()
@@ -721,8 +748,8 @@ class CodeBattles(
721
748
  "context": context,
722
749
  **self.get_api().__dict__,
723
750
  }
724
- | self.configure_bot_globals()
725
- for context in contexts
751
+ | self.configure_bot_globals(player_index)
752
+ for player_index, context in enumerate(contexts)
726
753
  ]
727
754
  for index, api_code in enumerate(player_codes):
728
755
  if api_code != "" and api_code is not None:
@@ -820,7 +847,8 @@ class CodeBattles(
820
847
  set_results(
821
848
  self.player_names, self._eliminated[::-1], self.map, self.verbose
822
849
  )
823
- self.render()
850
+ if not self.background:
851
+ self.render()
824
852
 
825
853
  if not self.background:
826
854
  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)")),
@@ -14510,7 +14516,7 @@ const Round = () => {
14510
14516
  }
14511
14517
  useEffect(() => {
14512
14518
  if (remaining > 0) {
14513
- runNoUI(currentMap, apis, currentPlayers, false);
14519
+ runNoUI(currentMap, apis, currentPlayers, "", false);
14514
14520
  }
14515
14521
  }, [remaining]);
14516
14522
  useEffect(updatePointModifier, [results]);
@@ -14569,7 +14575,7 @@ const Round = () => {
14569
14575
  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"),
14570
14576
  React.createElement(Button, { leftSection: React.createElement("i", { className: "fa-solid fa-forward" }), size: "xs", onClick: () => {
14571
14577
  if (roundIterations === 1) {
14572
- runNoUI(round.map, apis, round.players, true);
14578
+ runNoUI(round.map, apis, round.players, "", true);
14573
14579
  }
14574
14580
  else {
14575
14581
  currentMap = round.map;
@@ -15842,7 +15848,10 @@ const confetti = {
15842
15848
  };
15843
15849
 
15844
15850
  const AutoScrollButton = () => {
15845
- const [autoScroll, setAutoScroll] = useState(true);
15851
+ const [autoScroll, setAutoScroll] = useLocalStorage({
15852
+ key: "Auto Scroll",
15853
+ defaultValue: true,
15854
+ });
15846
15855
  useEffect(() => {
15847
15856
  // @ts-ignore
15848
15857
  window.autoScroll = autoScroll;
@@ -15860,8 +15869,14 @@ const ShowLogsButtons = ({ playerNames, showLogs, setShowLogs, }) => {
15860
15869
  };
15861
15870
 
15862
15871
  const LogViewer = ({ playerNames }) => {
15863
- const [showLogs, setShowLogs] = useState([]);
15864
- const [logs, setLogs] = useState([]);
15872
+ const [showLogs, setShowLogs] = useLocalStorage({
15873
+ key: "Show Logs",
15874
+ defaultValue: [],
15875
+ });
15876
+ const [logs, setLogs] = useLocalStorage({
15877
+ key: "Logs",
15878
+ defaultValue: [],
15879
+ });
15865
15880
  useEffect(() => {
15866
15881
  // @ts-ignore
15867
15882
  setShowLogs(playerNames.map(() => true));
@@ -15936,6 +15951,7 @@ const Simulation = () => {
15936
15951
  const [apis, loading] = useAPIs();
15937
15952
  let { map, playerapis } = useParams();
15938
15953
  const location = useLocation();
15954
+ const [searchParams] = useSearchParams();
15939
15955
  const [winner, setWinner] = useState();
15940
15956
  const [downloadBytes, setDownloadBytes] = useState(false);
15941
15957
  const navigate = useNavigate();
@@ -15977,14 +15993,16 @@ const Simulation = () => {
15977
15993
  const playerNames = (_a = playerapis === null || playerapis === void 0 ? void 0 : playerapis.split("-")) !== null && _a !== void 0 ? _a : [];
15978
15994
  const players = playerNames.map((api) => (api === "None" ? "" : apis[api]));
15979
15995
  useEffect(() => {
15996
+ var _a;
15980
15997
  if (!loading &&
15981
15998
  players &&
15982
15999
  playerapis &&
15983
16000
  // @ts-ignore
15984
16001
  window._isSimulationFromFile !== true) {
16002
+ const seed = (_a = searchParams.get("seed")) !== null && _a !== void 0 ? _a : "";
15985
16003
  tryUntilSuccess(() =>
15986
16004
  // @ts-ignore
15987
- window._startSimulation(map, players, playerNames, false, !showcaseMode, true));
16005
+ window._startSimulation(map, players, playerNames, false, !showcaseMode, true, seed));
15988
16006
  }
15989
16007
  }, [loading]);
15990
16008
  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.1",
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",