code-battles-cli 1.0.0__tar.gz → 1.1.0__tar.gz
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.
- {code_battles_cli-1.0.0 → code_battles_cli-1.1.0}/PKG-INFO +2 -2
- {code_battles_cli-1.0.0 → code_battles_cli-1.1.0}/README.md +1 -1
- {code_battles_cli-1.0.0 → code_battles_cli-1.1.0}/pyproject.toml +1 -1
- {code_battles_cli-1.0.0 → code_battles_cli-1.1.0}/src/code_battles_cli/api.py +167 -35
- {code_battles_cli-1.0.0 → code_battles_cli-1.1.0}/src/code_battles_cli/cli.py +1 -0
- {code_battles_cli-1.0.0 → code_battles_cli-1.1.0}/src/code_battles_cli/commands/download.py +1 -0
- code_battles_cli-1.1.0/src/code_battles_cli/commands/run.py +114 -0
- {code_battles_cli-1.0.0 → code_battles_cli-1.1.0}/src/code_battles_cli/commands/upload.py +1 -0
- {code_battles_cli-1.0.0 → code_battles_cli-1.1.0}/src/code_battles_cli/log.py +2 -1
- code_battles_cli-1.0.0/src/code_battles_cli/commands/run.py +0 -46
- {code_battles_cli-1.0.0 → code_battles_cli-1.1.0}/.gitignore +0 -0
- {code_battles_cli-1.0.0 → code_battles_cli-1.1.0}/.python-version +0 -0
- {code_battles_cli-1.0.0 → code_battles_cli-1.1.0}/LICENSE.md +0 -0
- {code_battles_cli-1.0.0 → code_battles_cli-1.1.0}/requirements-dev.lock +0 -0
- {code_battles_cli-1.0.0 → code_battles_cli-1.1.0}/requirements.lock +0 -0
- {code_battles_cli-1.0.0 → code_battles_cli-1.1.0}/src/code_battles_cli/__init__.py +0 -0
- {code_battles_cli-1.0.0 → code_battles_cli-1.1.0}/src/code_battles_cli/__main__.py +0 -0
- {code_battles_cli-1.0.0 → code_battles_cli-1.1.0}/src/code_battles_cli/commands/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: code-battles-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: A CLI for competitors in any Code Battles event!
|
|
5
5
|
License-File: LICENSE.md
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -77,5 +77,5 @@ print(client.run_simulation("NYC", ["bots/example.py", "bots/another_example.py"
|
|
|
77
77
|
|
|
78
78
|
- [x] Download bots.
|
|
79
79
|
- [x] Upload bots.
|
|
80
|
-
- [
|
|
80
|
+
- [x] Run no-UI simulations locally.
|
|
81
81
|
- [x] Be usable in scripts.
|
|
@@ -4,19 +4,27 @@ Code Battles Python Client API
|
|
|
4
4
|
Firestore client implementation inspired by https://medium.com/@bobthomas295/client-side-authentication-with-python-firestore-and-firebase-352e484a2634
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import base64
|
|
8
|
+
import datetime
|
|
9
|
+
import logging
|
|
10
|
+
import gzip
|
|
7
11
|
import os
|
|
8
12
|
import json
|
|
9
13
|
import shutil
|
|
10
14
|
import subprocess
|
|
11
15
|
import sys
|
|
12
|
-
from typing import Dict, List, Optional, Union
|
|
16
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
13
17
|
from dataclasses import dataclass
|
|
14
18
|
|
|
15
19
|
import requests
|
|
16
20
|
from google.oauth2.credentials import Credentials
|
|
17
21
|
from google.cloud.firestore import Client as FirestoreClient
|
|
18
22
|
from rich.prompt import Prompt
|
|
19
|
-
from code_battles_cli.log import console, log
|
|
23
|
+
from code_battles_cli.log import console, log, progress
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
SIMULATION_FINISHED_MARK = b"--- SIMULATION FINISHED ---"
|
|
27
|
+
SIMULATION_STEP_MARK = b"__CODE_BATTLES_ADVANCE_STEP"
|
|
20
28
|
|
|
21
29
|
|
|
22
30
|
@dataclass
|
|
@@ -35,6 +43,53 @@ class SimulationResults:
|
|
|
35
43
|
logs: List[LogEntry]
|
|
36
44
|
|
|
37
45
|
|
|
46
|
+
@dataclass
|
|
47
|
+
class Simulation:
|
|
48
|
+
map: str
|
|
49
|
+
player_names: str
|
|
50
|
+
game: str
|
|
51
|
+
version: str
|
|
52
|
+
timestamp: datetime.datetime
|
|
53
|
+
logs: list
|
|
54
|
+
decisions: List[bytes]
|
|
55
|
+
seed: int
|
|
56
|
+
|
|
57
|
+
def dump(self):
|
|
58
|
+
return base64.b64encode(
|
|
59
|
+
gzip.compress(
|
|
60
|
+
json.dumps(
|
|
61
|
+
{
|
|
62
|
+
"map": self.map,
|
|
63
|
+
"playerNames": self.player_names,
|
|
64
|
+
"game": self.game,
|
|
65
|
+
"version": self.version,
|
|
66
|
+
"timestamp": self.timestamp.isoformat(),
|
|
67
|
+
"logs": self.logs,
|
|
68
|
+
"decisions": [
|
|
69
|
+
base64.b64encode(decision).decode()
|
|
70
|
+
for decision in self.decisions
|
|
71
|
+
],
|
|
72
|
+
"seed": self.seed,
|
|
73
|
+
}
|
|
74
|
+
).encode()
|
|
75
|
+
)
|
|
76
|
+
).decode()
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def load(file: str):
|
|
80
|
+
contents: Dict[str, Any] = json.loads(gzip.decompress(base64.b64decode(file)))
|
|
81
|
+
return Simulation(
|
|
82
|
+
contents["map"],
|
|
83
|
+
contents["playerNames"],
|
|
84
|
+
contents["game"],
|
|
85
|
+
contents["version"],
|
|
86
|
+
datetime.datetime.fromisoformat(contents["timestamp"]),
|
|
87
|
+
contents["logs"],
|
|
88
|
+
[base64.b64decode(decision) for decision in contents["decisions"]],
|
|
89
|
+
contents["seed"],
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
38
93
|
class Client:
|
|
39
94
|
def __init__(
|
|
40
95
|
self,
|
|
@@ -141,13 +196,89 @@ class Client:
|
|
|
141
196
|
"""
|
|
142
197
|
self.document.set(bots, merge)
|
|
143
198
|
|
|
199
|
+
def _possibly_download(self, force_download=False):
|
|
200
|
+
code_directory = os.path.expanduser(
|
|
201
|
+
f"~/.cache/code-battles/{''.join([c for c in self.url.removeprefix("https").removesuffix(".web.app") if c.isalnum()])}"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if force_download and os.path.exists(code_directory):
|
|
205
|
+
shutil.rmtree(code_directory)
|
|
206
|
+
|
|
207
|
+
if not os.path.exists(code_directory):
|
|
208
|
+
os.makedirs(code_directory)
|
|
209
|
+
|
|
210
|
+
with console.status("[blue]Fetching configuration from game website..."):
|
|
211
|
+
pyscript_configuration = requests.get(self.url + "/config.json").json()
|
|
212
|
+
logging.info(
|
|
213
|
+
f"Fetched configuration containing {len(pyscript_configuration["files"])} game files."
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
with progress:
|
|
217
|
+
progress_id = progress.add_task(
|
|
218
|
+
"[blue]Fetching game files...",
|
|
219
|
+
total=len(pyscript_configuration["files"]),
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
for url_path, path in pyscript_configuration["files"].items():
|
|
223
|
+
if not path.endswith(".py"):
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
local_path = os.path.join(code_directory, *path.split("/"))
|
|
227
|
+
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
|
228
|
+
text = requests.get(self.url + url_path).text
|
|
229
|
+
with open(local_path, "w") as f:
|
|
230
|
+
f.write(text)
|
|
231
|
+
progress.update(progress_id, advance=1)
|
|
232
|
+
progress.update(progress_id, visible=False)
|
|
233
|
+
|
|
234
|
+
logging.info("Finished fetching game files.")
|
|
235
|
+
|
|
236
|
+
return code_directory
|
|
237
|
+
|
|
238
|
+
def _get_simulation_output(
|
|
239
|
+
self,
|
|
240
|
+
p: subprocess.Popen,
|
|
241
|
+
json_output=False,
|
|
242
|
+
on_step: Optional[Callable[[], None]] = None,
|
|
243
|
+
) -> Union[SimulationResults, str]:
|
|
244
|
+
while True:
|
|
245
|
+
line: bytes = p.stdout.readline()
|
|
246
|
+
line = line.strip()
|
|
247
|
+
if line == SIMULATION_FINISHED_MARK:
|
|
248
|
+
break
|
|
249
|
+
elif line == SIMULATION_STEP_MARK:
|
|
250
|
+
if on_step is not None:
|
|
251
|
+
on_step()
|
|
252
|
+
elif len(line) != 0:
|
|
253
|
+
logging.info(line.decode())
|
|
254
|
+
|
|
255
|
+
output: bytes = p.stdout.read()
|
|
256
|
+
if json_output:
|
|
257
|
+
return output.decode()
|
|
258
|
+
|
|
259
|
+
output = json.loads(output)
|
|
260
|
+
return SimulationResults(
|
|
261
|
+
output["winner_index"],
|
|
262
|
+
output["winner"],
|
|
263
|
+
output["steps"],
|
|
264
|
+
[
|
|
265
|
+
LogEntry(
|
|
266
|
+
entry["step"], entry["text"], entry["color"], entry["player_index"]
|
|
267
|
+
)
|
|
268
|
+
for entry in output["logs"]
|
|
269
|
+
],
|
|
270
|
+
)
|
|
271
|
+
|
|
144
272
|
def run_simulation(
|
|
145
273
|
self,
|
|
146
274
|
map: str,
|
|
147
275
|
bot_filenames: List[str],
|
|
148
276
|
bot_names: Optional[List[str]] = None,
|
|
277
|
+
seed: Optional[int] = None,
|
|
149
278
|
force_download=False,
|
|
150
279
|
json_output=False,
|
|
280
|
+
on_step: Optional[Callable[[], None]] = None,
|
|
281
|
+
output_file: Optional[str] = None,
|
|
151
282
|
) -> Union[SimulationResults, str]:
|
|
152
283
|
"""
|
|
153
284
|
Runs the given simulation without UI locally.
|
|
@@ -164,52 +295,53 @@ class Client:
|
|
|
164
295
|
for filename in bot_filenames
|
|
165
296
|
]
|
|
166
297
|
|
|
167
|
-
code_directory =
|
|
168
|
-
f"~/.cache/code-battles/{''.join([c for c in self.url.removeprefix("https").removesuffix(".web.app") if c.isalnum()])}"
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
if force_download and os.path.exists(code_directory):
|
|
172
|
-
shutil.rmtree(code_directory)
|
|
173
|
-
|
|
174
|
-
if not os.path.exists(code_directory):
|
|
175
|
-
os.makedirs(code_directory)
|
|
176
|
-
|
|
177
|
-
pyscript_configuration = requests.get(self.url + "/config.json").json()
|
|
178
|
-
|
|
179
|
-
for url_path, path in pyscript_configuration["files"].items():
|
|
180
|
-
local_path = os.path.join(
|
|
181
|
-
code_directory, *path.replace(".pyi", ".py").split("/")
|
|
182
|
-
)
|
|
183
|
-
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
|
184
|
-
text = requests.get(self.url + url_path).text
|
|
185
|
-
with open(local_path, "w") as f:
|
|
186
|
-
f.write(text)
|
|
298
|
+
code_directory = self._possibly_download(force_download=force_download)
|
|
187
299
|
|
|
188
300
|
p = subprocess.Popen(
|
|
189
301
|
[
|
|
190
302
|
sys.executable,
|
|
191
303
|
os.path.join(code_directory, "main.py"),
|
|
304
|
+
"simulate",
|
|
305
|
+
str(seed),
|
|
306
|
+
str(output_file),
|
|
192
307
|
map,
|
|
193
308
|
"-".join(bot_names),
|
|
194
309
|
]
|
|
195
310
|
+ [os.path.abspath(f) for f in bot_filenames],
|
|
196
311
|
env={"PYTHONPATH": os.path.join(code_directory, "code_battles")},
|
|
197
312
|
stdout=subprocess.PIPE,
|
|
313
|
+
stderr=subprocess.PIPE,
|
|
198
314
|
)
|
|
199
|
-
p.wait()
|
|
200
|
-
output = p.stdout.read().split(b"--- SIMULATION FINISHED ---")[-1]
|
|
201
|
-
if json_output:
|
|
202
|
-
return output.decode()
|
|
203
315
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
316
|
+
return self._get_simulation_output(p, json_output, on_step)
|
|
317
|
+
|
|
318
|
+
def run_simulation_from_file(
|
|
319
|
+
self,
|
|
320
|
+
simulation_file: str,
|
|
321
|
+
force_download=False,
|
|
322
|
+
json_output=False,
|
|
323
|
+
on_step: Optional[Callable[[], None]] = None,
|
|
324
|
+
) -> Union[SimulationResults, str]:
|
|
325
|
+
"""
|
|
326
|
+
Runs the given simulation without UI locally from the given simulation file.
|
|
327
|
+
|
|
328
|
+
If required (or ``force_download``), this method downloads the simulation code from the website.
|
|
329
|
+
|
|
330
|
+
If ``json_output`` is ``True``, returns the JSON string of the results instead.
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
code_directory = self._possibly_download(force_download=force_download)
|
|
334
|
+
|
|
335
|
+
p = subprocess.Popen(
|
|
209
336
|
[
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
337
|
+
sys.executable,
|
|
338
|
+
os.path.join(code_directory, "main.py"),
|
|
339
|
+
"simulate-from-file",
|
|
340
|
+
simulation_file,
|
|
214
341
|
],
|
|
342
|
+
env={"PYTHONPATH": os.path.join(code_directory, "code_battles")},
|
|
343
|
+
stdout=subprocess.PIPE,
|
|
344
|
+
stderr=subprocess.PIPE,
|
|
215
345
|
)
|
|
346
|
+
|
|
347
|
+
return self._get_simulation_output(p, json_output, on_step)
|
|
@@ -15,6 +15,7 @@ from code_battles_cli.log import log
|
|
|
15
15
|
@click.option("-p", "--password", help="Your team's password")
|
|
16
16
|
@click.option(
|
|
17
17
|
"--dump-credentials",
|
|
18
|
+
is_flag=True,
|
|
18
19
|
default=True,
|
|
19
20
|
show_default=True,
|
|
20
21
|
help="Dump the URL, username and password to a `code-battles.json` file",
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from typing import List, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from code_battles_cli.api import Client, Simulation
|
|
9
|
+
from code_battles_cli.log import log, progress
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command()
|
|
13
|
+
@click.argument("map", required=False)
|
|
14
|
+
@click.argument("bots", nargs=-1)
|
|
15
|
+
@click.option(
|
|
16
|
+
"-U", "--url", help="The competitions URL, for example https://code-battles.web.app"
|
|
17
|
+
)
|
|
18
|
+
@click.option("-u", "--username", help="Your team's username, for example mercedes")
|
|
19
|
+
@click.option("-p", "--password", help="Your team's password")
|
|
20
|
+
@click.option(
|
|
21
|
+
"--dump-credentials",
|
|
22
|
+
is_flag=True,
|
|
23
|
+
default=True,
|
|
24
|
+
show_default=True,
|
|
25
|
+
help="Dump the URL, username and password to a `code-battles.json` file",
|
|
26
|
+
)
|
|
27
|
+
@click.option(
|
|
28
|
+
"--force-download",
|
|
29
|
+
is_flag=True,
|
|
30
|
+
default=False,
|
|
31
|
+
show_default=True,
|
|
32
|
+
help="Re-downloads the simulation code from the website",
|
|
33
|
+
)
|
|
34
|
+
@click.option(
|
|
35
|
+
"-s", "--seed", type=int, help="Sets the given randomness seed for the simulation"
|
|
36
|
+
)
|
|
37
|
+
@click.option(
|
|
38
|
+
"-S",
|
|
39
|
+
"--simulation-file",
|
|
40
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
41
|
+
help="Re-downloads the simulation code from the website",
|
|
42
|
+
)
|
|
43
|
+
@click.option(
|
|
44
|
+
"-O",
|
|
45
|
+
"output_file",
|
|
46
|
+
type=click.Path(exists=False),
|
|
47
|
+
help="Output file to dump the simulation into",
|
|
48
|
+
)
|
|
49
|
+
def run(
|
|
50
|
+
url: Optional[str],
|
|
51
|
+
username: Optional[str],
|
|
52
|
+
password: Optional[str],
|
|
53
|
+
dump_credentials: bool,
|
|
54
|
+
map: str,
|
|
55
|
+
bots: Tuple[str],
|
|
56
|
+
force_download: bool,
|
|
57
|
+
seed: Optional[int],
|
|
58
|
+
simulation_file: Optional[str],
|
|
59
|
+
output_file: Optional[str],
|
|
60
|
+
):
|
|
61
|
+
logging.getLogger("rich").setLevel(logging.WARNING)
|
|
62
|
+
client = Client(url, username, password, dump_credentials)
|
|
63
|
+
client._possibly_download(force_download)
|
|
64
|
+
start = time.time()
|
|
65
|
+
|
|
66
|
+
with progress:
|
|
67
|
+
progress_id = progress.add_task("[green]Simulating...", total=None)
|
|
68
|
+
|
|
69
|
+
def on_step():
|
|
70
|
+
progress.update(progress_id, advance=1)
|
|
71
|
+
|
|
72
|
+
if simulation_file is not None:
|
|
73
|
+
assert (
|
|
74
|
+
map is None
|
|
75
|
+
), "The simulation's map is given from the simulation file!"
|
|
76
|
+
assert (
|
|
77
|
+
len(bots) == 0
|
|
78
|
+
), "The simulation's bots are given from the simulation file!"
|
|
79
|
+
assert (
|
|
80
|
+
seed is None
|
|
81
|
+
), "The simulation's seed is given from the simulation file!"
|
|
82
|
+
assert (
|
|
83
|
+
output_file is None
|
|
84
|
+
), "Shouldn't dump simualtion file from existing simulation file!"
|
|
85
|
+
|
|
86
|
+
with open(simulation_file, "r") as f:
|
|
87
|
+
simulation_text = f.read()
|
|
88
|
+
simulation = Simulation.load(simulation_text)
|
|
89
|
+
logging.info(
|
|
90
|
+
f"Detected simulation file of {simulation.game} {simulation.version}"
|
|
91
|
+
)
|
|
92
|
+
logging.info(
|
|
93
|
+
f"Simulation happened in {simulation.map} between {', '.join(simulation.player_names)} with seed {simulation.seed}"
|
|
94
|
+
)
|
|
95
|
+
logging.info(f"Simulation contains {len(simulation.decisions)} decisions")
|
|
96
|
+
progress.update(progress_id, total=len(simulation.decisions) - 1)
|
|
97
|
+
results = client.run_simulation_from_file(
|
|
98
|
+
simulation_file, force_download=False, on_step=on_step
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
assert map is not None, "Map is required for simulation!"
|
|
102
|
+
assert len(bots) > 0, "Bots are required for simulation!"
|
|
103
|
+
|
|
104
|
+
results = client.run_simulation(
|
|
105
|
+
map,
|
|
106
|
+
list(bots),
|
|
107
|
+
seed=seed,
|
|
108
|
+
force_download=False,
|
|
109
|
+
on_step=on_step,
|
|
110
|
+
output_file=output_file,
|
|
111
|
+
)
|
|
112
|
+
print("The winner is", results.winner, "after", results.steps, "steps.")
|
|
113
|
+
end = time.time()
|
|
114
|
+
log.info(f"Simulation took {(end - start) :.2f}s.")
|
|
@@ -15,6 +15,7 @@ from code_battles_cli.log import log
|
|
|
15
15
|
@click.option("-p", "--password", help="Your team's password")
|
|
16
16
|
@click.option(
|
|
17
17
|
"--dump-credentials",
|
|
18
|
+
is_flag=True,
|
|
18
19
|
default=True,
|
|
19
20
|
show_default=True,
|
|
20
21
|
help="Dump the URL, username and password to a `code-battles.json` file",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import logging
|
|
2
3
|
|
|
3
4
|
from rich.console import Console
|
|
@@ -25,7 +26,7 @@ def setup_logging():
|
|
|
25
26
|
FORMAT = "%(message)s"
|
|
26
27
|
|
|
27
28
|
logging.basicConfig(
|
|
28
|
-
level=
|
|
29
|
+
level=os.environ.get("CODE_BATTLES_LOG_LEVEL", "INFO"),
|
|
29
30
|
format=FORMAT,
|
|
30
31
|
datefmt="[%X]",
|
|
31
32
|
handlers=[RichHandler(console=console, markup=True)],
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
import logging
|
|
3
|
-
from typing import List, Optional, Tuple
|
|
4
|
-
|
|
5
|
-
import click
|
|
6
|
-
|
|
7
|
-
from code_battles_cli.api import Client
|
|
8
|
-
from code_battles_cli.log import log
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@click.command()
|
|
12
|
-
@click.argument("map")
|
|
13
|
-
@click.argument("bots", nargs=-1)
|
|
14
|
-
@click.option(
|
|
15
|
-
"-U", "--url", help="The competitions URL, for example https://code-battles.web.app"
|
|
16
|
-
)
|
|
17
|
-
@click.option("-u", "--username", help="Your team's username, for example mercedes")
|
|
18
|
-
@click.option("-p", "--password", help="Your team's password")
|
|
19
|
-
@click.option(
|
|
20
|
-
"--dump-credentials",
|
|
21
|
-
default=True,
|
|
22
|
-
show_default=True,
|
|
23
|
-
help="Dump the URL, username and password to a `code-battles.json` file",
|
|
24
|
-
)
|
|
25
|
-
@click.option(
|
|
26
|
-
"--force-download",
|
|
27
|
-
default=False,
|
|
28
|
-
show_default=True,
|
|
29
|
-
help="Re-downloads the simulation code from the website",
|
|
30
|
-
)
|
|
31
|
-
def run(
|
|
32
|
-
url: Optional[str],
|
|
33
|
-
username: Optional[str],
|
|
34
|
-
password: Optional[str],
|
|
35
|
-
dump_credentials: bool,
|
|
36
|
-
map: str,
|
|
37
|
-
bots: Tuple[str],
|
|
38
|
-
force_download: bool,
|
|
39
|
-
):
|
|
40
|
-
logging.getLogger("rich").setLevel(logging.WARNING)
|
|
41
|
-
client = Client(url, username, password, dump_credentials)
|
|
42
|
-
start = time.time()
|
|
43
|
-
results = client.run_simulation(map, list(bots), force_download=force_download)
|
|
44
|
-
print("The winner is", results.winner, "after", results.steps, "steps.")
|
|
45
|
-
end = time.time()
|
|
46
|
-
log.info(f"Simulation took {(end - start) :.2f}s.")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|