code-battles-cli 0.1.1__tar.gz → 1.0.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-0.1.1 → code_battles_cli-1.0.0}/PKG-INFO +11 -3
- {code_battles_cli-0.1.1 → code_battles_cli-1.0.0}/README.md +10 -2
- {code_battles_cli-0.1.1 → code_battles_cli-1.0.0}/pyproject.toml +1 -1
- {code_battles_cli-0.1.1 → code_battles_cli-1.0.0}/src/code_battles_cli/api.py +96 -3
- {code_battles_cli-0.1.1 → code_battles_cli-1.0.0}/src/code_battles_cli/cli.py +3 -1
- {code_battles_cli-0.1.1 → code_battles_cli-1.0.0}/src/code_battles_cli/commands/download.py +1 -1
- code_battles_cli-1.0.0/src/code_battles_cli/commands/run.py +46 -0
- {code_battles_cli-0.1.1 → code_battles_cli-1.0.0}/src/code_battles_cli/commands/upload.py +1 -1
- {code_battles_cli-0.1.1 → code_battles_cli-1.0.0}/.gitignore +0 -0
- {code_battles_cli-0.1.1 → code_battles_cli-1.0.0}/.python-version +0 -0
- {code_battles_cli-0.1.1 → code_battles_cli-1.0.0}/LICENSE.md +0 -0
- {code_battles_cli-0.1.1 → code_battles_cli-1.0.0}/requirements-dev.lock +0 -0
- {code_battles_cli-0.1.1 → code_battles_cli-1.0.0}/requirements.lock +0 -0
- {code_battles_cli-0.1.1 → code_battles_cli-1.0.0}/src/code_battles_cli/__init__.py +0 -0
- {code_battles_cli-0.1.1 → code_battles_cli-1.0.0}/src/code_battles_cli/__main__.py +0 -0
- {code_battles_cli-0.1.1 → code_battles_cli-1.0.0}/src/code_battles_cli/commands/__init__.py +0 -0
- /code_battles_cli-0.1.1/src/code_battles_cli/logging.py → /code_battles_cli-1.0.0/src/code_battles_cli/log.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: code-battles-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.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
|
|
@@ -16,6 +16,7 @@ Description-Content-Type: text/markdown
|
|
|
16
16
|
<h1 align="center">
|
|
17
17
|
Code Battles CLI
|
|
18
18
|
<br />
|
|
19
|
+
<img src="https://img.shields.io/pypi/v/code-battles-cli">
|
|
19
20
|
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg">
|
|
20
21
|
<img src="https://img.shields.io/badge/license-MIT-blue.svg">
|
|
21
22
|
</h1>
|
|
@@ -34,7 +35,7 @@ Description-Content-Type: text/markdown
|
|
|
34
35
|
# Getting Started
|
|
35
36
|
|
|
36
37
|
- Install the library by running `pip install code-battles-cli`.
|
|
37
|
-
- Make sure you have the Python scripts folder in your
|
|
38
|
+
- Make sure you have the Python scripts folder in your `PATH` by running `code-battles --help`.
|
|
38
39
|
- If that fails, run `py -m code_battles_cli --help`.
|
|
39
40
|
|
|
40
41
|
The first time you run any command you will be asked for your URL, username and password, and they'll be saved in a `code-battles.json` file for successive calls.
|
|
@@ -51,7 +52,13 @@ You can update a bot in the website by running `code-battles upload bots/example
|
|
|
51
52
|
|
|
52
53
|
If you want to choose a custom bot name, you can run `code-battles upload bots/example.py -n myamazingbot`.
|
|
53
54
|
|
|
54
|
-
##
|
|
55
|
+
## Running a simulation
|
|
56
|
+
|
|
57
|
+
You can run a simulation by running `code-battles run NYC bots/example.py bots/another_example.py`, where `NYC` is an example of a map.
|
|
58
|
+
|
|
59
|
+
You will probably find the scripting API nicer to work with if you want to run simulations locally.
|
|
60
|
+
|
|
61
|
+
## Usage in scripts
|
|
55
62
|
|
|
56
63
|
You can import the `code_battles_cli.api` module and utilize its `Client` class to hack Code Battles for your needs!
|
|
57
64
|
|
|
@@ -63,6 +70,7 @@ from code_battles_cli.api import Client
|
|
|
63
70
|
client = Client()
|
|
64
71
|
|
|
65
72
|
print(client.get_bots())
|
|
73
|
+
print(client.run_simulation("NYC", ["bots/example.py", "bots/another_example.py"]))
|
|
66
74
|
```
|
|
67
75
|
|
|
68
76
|
# Roadmap
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<h1 align="center">
|
|
5
5
|
Code Battles CLI
|
|
6
6
|
<br />
|
|
7
|
+
<img src="https://img.shields.io/pypi/v/code-battles-cli">
|
|
7
8
|
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg">
|
|
8
9
|
<img src="https://img.shields.io/badge/license-MIT-blue.svg">
|
|
9
10
|
</h1>
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
# Getting Started
|
|
23
24
|
|
|
24
25
|
- Install the library by running `pip install code-battles-cli`.
|
|
25
|
-
- Make sure you have the Python scripts folder in your
|
|
26
|
+
- Make sure you have the Python scripts folder in your `PATH` by running `code-battles --help`.
|
|
26
27
|
- If that fails, run `py -m code_battles_cli --help`.
|
|
27
28
|
|
|
28
29
|
The first time you run any command you will be asked for your URL, username and password, and they'll be saved in a `code-battles.json` file for successive calls.
|
|
@@ -39,7 +40,13 @@ You can update a bot in the website by running `code-battles upload bots/example
|
|
|
39
40
|
|
|
40
41
|
If you want to choose a custom bot name, you can run `code-battles upload bots/example.py -n myamazingbot`.
|
|
41
42
|
|
|
42
|
-
##
|
|
43
|
+
## Running a simulation
|
|
44
|
+
|
|
45
|
+
You can run a simulation by running `code-battles run NYC bots/example.py bots/another_example.py`, where `NYC` is an example of a map.
|
|
46
|
+
|
|
47
|
+
You will probably find the scripting API nicer to work with if you want to run simulations locally.
|
|
48
|
+
|
|
49
|
+
## Usage in scripts
|
|
43
50
|
|
|
44
51
|
You can import the `code_battles_cli.api` module and utilize its `Client` class to hack Code Battles for your needs!
|
|
45
52
|
|
|
@@ -51,6 +58,7 @@ from code_battles_cli.api import Client
|
|
|
51
58
|
client = Client()
|
|
52
59
|
|
|
53
60
|
print(client.get_bots())
|
|
61
|
+
print(client.run_simulation("NYC", ["bots/example.py", "bots/another_example.py"]))
|
|
54
62
|
```
|
|
55
63
|
|
|
56
64
|
# Roadmap
|
|
@@ -6,13 +6,33 @@ Firestore client implementation inspired by https://medium.com/@bobthomas295/cli
|
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
8
|
import json
|
|
9
|
-
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from typing import Dict, List, Optional, Union
|
|
13
|
+
from dataclasses import dataclass
|
|
10
14
|
|
|
11
15
|
import requests
|
|
12
16
|
from google.oauth2.credentials import Credentials
|
|
13
17
|
from google.cloud.firestore import Client as FirestoreClient
|
|
14
18
|
from rich.prompt import Prompt
|
|
15
|
-
from code_battles_cli.
|
|
19
|
+
from code_battles_cli.log import console, log
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class LogEntry:
|
|
24
|
+
step: int
|
|
25
|
+
text: str
|
|
26
|
+
color: str
|
|
27
|
+
player_index: Optional[int]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class SimulationResults:
|
|
32
|
+
winner_index: int
|
|
33
|
+
winner: str
|
|
34
|
+
steps: int
|
|
35
|
+
logs: List[LogEntry]
|
|
16
36
|
|
|
17
37
|
|
|
18
38
|
class Client:
|
|
@@ -117,6 +137,79 @@ class Client:
|
|
|
117
137
|
def set_bots(self, bots: Dict[str, str], merge=True) -> None:
|
|
118
138
|
"""
|
|
119
139
|
Sets the bots in the website to the specified bots.
|
|
120
|
-
Doesn't remove any bot unless
|
|
140
|
+
Doesn't remove any bot unless ``merge`` is ``False``, in which case only bots specified in ``bots`` will remain.
|
|
121
141
|
"""
|
|
122
142
|
self.document.set(bots, merge)
|
|
143
|
+
|
|
144
|
+
def run_simulation(
|
|
145
|
+
self,
|
|
146
|
+
map: str,
|
|
147
|
+
bot_filenames: List[str],
|
|
148
|
+
bot_names: Optional[List[str]] = None,
|
|
149
|
+
force_download=False,
|
|
150
|
+
json_output=False,
|
|
151
|
+
) -> Union[SimulationResults, str]:
|
|
152
|
+
"""
|
|
153
|
+
Runs the given simulation without UI locally.
|
|
154
|
+
If ``bot_names`` is not specified, they will be the filenames without the extension.
|
|
155
|
+
|
|
156
|
+
If required (or ``force_download``), this method downloads the simulation code from the website.
|
|
157
|
+
|
|
158
|
+
If ``json_output`` is ``True``, returns the JSON string of the results instead.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
if bot_names is None:
|
|
162
|
+
bot_names = [
|
|
163
|
+
os.path.splitext(os.path.basename(filename))[0]
|
|
164
|
+
for filename in bot_filenames
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
code_directory = os.path.expanduser(
|
|
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)
|
|
187
|
+
|
|
188
|
+
p = subprocess.Popen(
|
|
189
|
+
[
|
|
190
|
+
sys.executable,
|
|
191
|
+
os.path.join(code_directory, "main.py"),
|
|
192
|
+
map,
|
|
193
|
+
"-".join(bot_names),
|
|
194
|
+
]
|
|
195
|
+
+ [os.path.abspath(f) for f in bot_filenames],
|
|
196
|
+
env={"PYTHONPATH": os.path.join(code_directory, "code_battles")},
|
|
197
|
+
stdout=subprocess.PIPE,
|
|
198
|
+
)
|
|
199
|
+
p.wait()
|
|
200
|
+
output = p.stdout.read().split(b"--- SIMULATION FINISHED ---")[-1]
|
|
201
|
+
if json_output:
|
|
202
|
+
return output.decode()
|
|
203
|
+
|
|
204
|
+
output = json.loads(output)
|
|
205
|
+
return SimulationResults(
|
|
206
|
+
output["winner_index"],
|
|
207
|
+
output["winner"],
|
|
208
|
+
output["steps"],
|
|
209
|
+
[
|
|
210
|
+
LogEntry(
|
|
211
|
+
entry["step"], entry["text"], entry["color"], entry["player_index"]
|
|
212
|
+
)
|
|
213
|
+
for entry in output["logs"]
|
|
214
|
+
],
|
|
215
|
+
)
|
|
@@ -9,15 +9,17 @@ def cli():
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def main() -> int:
|
|
12
|
-
from code_battles_cli.
|
|
12
|
+
from code_battles_cli.log import setup_logging, log
|
|
13
13
|
|
|
14
14
|
setup_logging()
|
|
15
15
|
|
|
16
16
|
from code_battles_cli.commands.download import download
|
|
17
17
|
from code_battles_cli.commands.upload import upload
|
|
18
|
+
from code_battles_cli.commands.run import run
|
|
18
19
|
|
|
19
20
|
cli.add_command(download)
|
|
20
21
|
cli.add_command(upload)
|
|
22
|
+
cli.add_command(run)
|
|
21
23
|
cli(standalone_mode=False)
|
|
22
24
|
|
|
23
25
|
return 0
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
|
File without changes
|