agario-kit 2026.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.
- agario_kit-2026.1.0/.gitignore +39 -0
- agario_kit-2026.1.0/.python-version +1 -0
- agario_kit-2026.1.0/PKG-INFO +47 -0
- agario_kit-2026.1.0/README.md +38 -0
- agario_kit-2026.1.0/docs/game_rules.md +72 -0
- agario_kit-2026.1.0/docs/releasing.md +62 -0
- agario_kit-2026.1.0/docs/scoring.md +13 -0
- agario_kit-2026.1.0/mypy.ini +5 -0
- agario_kit-2026.1.0/pyproject.toml +57 -0
- agario_kit-2026.1.0/ruff.toml +77 -0
- agario_kit-2026.1.0/scripts/match_simulator.py +176 -0
- agario_kit-2026.1.0/src/agario_kit/__init__.py +1 -0
- agario_kit-2026.1.0/src/agario_visualiser/__init__.py +1 -0
- agario_kit-2026.1.0/src/agario_visualiser/__main__.py +5 -0
- agario_kit-2026.1.0/src/agario_visualiser/examples/__init__.py +1 -0
- agario_kit-2026.1.0/src/agario_visualiser/examples/example.py +63 -0
- agario_kit-2026.1.0/src/agario_visualiser/launch_local_match.py +245 -0
- agario_kit-2026.1.0/src/agario_visualiser/visualiser_submission.py +1148 -0
- agario_kit-2026.1.0/src/engine/__init__.py +0 -0
- agario_kit-2026.1.0/src/engine/__main__.py +12 -0
- agario_kit-2026.1.0/src/engine/config/expansion_config.py +1 -0
- agario_kit-2026.1.0/src/engine/config/io_config.py +16 -0
- agario_kit-2026.1.0/src/engine/game/tile_subscriber.py +110 -0
- agario_kit-2026.1.0/src/engine/game_engine.py +146 -0
- agario_kit-2026.1.0/src/engine/interface/io/censor_event.py +79 -0
- agario_kit-2026.1.0/src/engine/interface/io/exceptions.py +38 -0
- agario_kit-2026.1.0/src/engine/interface/io/game_result.py +26 -0
- agario_kit-2026.1.0/src/engine/interface/io/input_validator.py +37 -0
- agario_kit-2026.1.0/src/engine/interface/io/player_connection.py +285 -0
- agario_kit-2026.1.0/src/engine/interface/logging/event_factory.py +36 -0
- agario_kit-2026.1.0/src/engine/interface/logging/event_inspector.py +76 -0
- agario_kit-2026.1.0/src/engine/state/blob_state.py +43 -0
- agario_kit-2026.1.0/src/engine/state/game_state.py +230 -0
- agario_kit-2026.1.0/src/engine/state/physics_utils.py +73 -0
- agario_kit-2026.1.0/src/engine/state/player_state.py +71 -0
- agario_kit-2026.1.0/src/engine/state/state_mutator.py +518 -0
- agario_kit-2026.1.0/src/helper/__init__.py +0 -0
- agario_kit-2026.1.0/src/helper/__main__.py +2 -0
- agario_kit-2026.1.0/src/helper/client_state.py +31 -0
- agario_kit-2026.1.0/src/helper/game.py +37 -0
- agario_kit-2026.1.0/src/helper/interface.py +56 -0
- agario_kit-2026.1.0/src/helper/py.typed +0 -0
- agario_kit-2026.1.0/src/helper/state/README.md +1 -0
- agario_kit-2026.1.0/src/helper/state/client_player_state.py +37 -0
- agario_kit-2026.1.0/src/helper/state_mutator.py +80 -0
- agario_kit-2026.1.0/src/lib/__init__.py +0 -0
- agario_kit-2026.1.0/src/lib/__main__.py +2 -0
- agario_kit-2026.1.0/src/lib/config/arena.py +11 -0
- agario_kit-2026.1.0/src/lib/config/player.py +12 -0
- agario_kit-2026.1.0/src/lib/game/game_logic.py +12 -0
- agario_kit-2026.1.0/src/lib/interact/map.py +39 -0
- agario_kit-2026.1.0/src/lib/interact/penguin.py +52 -0
- agario_kit-2026.1.0/src/lib/interface/events/base_event.py +5 -0
- agario_kit-2026.1.0/src/lib/interface/events/event_food_eaten.py +11 -0
- agario_kit-2026.1.0/src/lib/interface/events/event_food_spawned.py +9 -0
- agario_kit-2026.1.0/src/lib/interface/events/event_game_ended.py +7 -0
- agario_kit-2026.1.0/src/lib/interface/events/event_game_started.py +26 -0
- agario_kit-2026.1.0/src/lib/interface/events/event_ice_shrunk.py +8 -0
- agario_kit-2026.1.0/src/lib/interface/events/event_penguin_fallen_off.py +12 -0
- agario_kit-2026.1.0/src/lib/interface/events/event_penguins_moved.py +9 -0
- agario_kit-2026.1.0/src/lib/interface/events/event_player_bannned.py +14 -0
- agario_kit-2026.1.0/src/lib/interface/events/event_player_eaten.py +15 -0
- agario_kit-2026.1.0/src/lib/interface/events/event_player_moved.py +13 -0
- agario_kit-2026.1.0/src/lib/interface/events/event_player_won.py +8 -0
- agario_kit-2026.1.0/src/lib/interface/events/event_private_physics_tick.py +8 -0
- agario_kit-2026.1.0/src/lib/interface/events/moves/base_move.py +5 -0
- agario_kit-2026.1.0/src/lib/interface/events/moves/move_penguins.py +10 -0
- agario_kit-2026.1.0/src/lib/interface/events/moves/move_player.py +11 -0
- agario_kit-2026.1.0/src/lib/interface/events/moves/typing.py +11 -0
- agario_kit-2026.1.0/src/lib/interface/events/typing.py +40 -0
- agario_kit-2026.1.0/src/lib/interface/events/visualiser_push_penguins.py +8 -0
- agario_kit-2026.1.0/src/lib/interface/io/ban_type.py +10 -0
- agario_kit-2026.1.0/src/lib/interface/queries/base_query.py +9 -0
- agario_kit-2026.1.0/src/lib/interface/queries/query_move.py +18 -0
- agario_kit-2026.1.0/src/lib/interface/queries/query_move_penguins.py +7 -0
- agario_kit-2026.1.0/src/lib/interface/queries/typing.py +16 -0
- agario_kit-2026.1.0/src/lib/interface/readme.md +6 -0
- agario_kit-2026.1.0/src/lib/models/blob_model.py +13 -0
- agario_kit-2026.1.0/src/lib/models/food_model.py +6 -0
- agario_kit-2026.1.0/src/lib/models/penguin_model.py +35 -0
- agario_kit-2026.1.0/src/lib/models/player_model.py +35 -0
- agario_kit-2026.1.0/src/lib/models/virus_model.py +7 -0
- agario_kit-2026.1.0/src/lib/py.typed +0 -0
- agario_kit-2026.1.0/tests/game_logic_test.py +492 -0
- agario_kit-2026.1.0/tests/split_mechanics_test.py +276 -0
- agario_kit-2026.1.0/tests/validator_test.py +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Environments
|
|
7
|
+
.env
|
|
8
|
+
.venv
|
|
9
|
+
venv
|
|
10
|
+
|
|
11
|
+
# Distribution / packaging
|
|
12
|
+
.Python
|
|
13
|
+
build/
|
|
14
|
+
develop-eggs/
|
|
15
|
+
dist/
|
|
16
|
+
eggs/
|
|
17
|
+
.eggs/
|
|
18
|
+
var/
|
|
19
|
+
wheels/
|
|
20
|
+
*.egg-info/
|
|
21
|
+
.installed.cfg
|
|
22
|
+
*.egg
|
|
23
|
+
|
|
24
|
+
# IDE specific files
|
|
25
|
+
.idea/
|
|
26
|
+
.vscode/
|
|
27
|
+
*.swp
|
|
28
|
+
*.swo
|
|
29
|
+
pyrightconfig.json
|
|
30
|
+
|
|
31
|
+
# udit's specific files
|
|
32
|
+
todo.md
|
|
33
|
+
.ignore/
|
|
34
|
+
|
|
35
|
+
# Submission files
|
|
36
|
+
**/submission*/
|
|
37
|
+
**/output/
|
|
38
|
+
**/input/
|
|
39
|
+
/.agario/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agario-kit
|
|
3
|
+
Version: 2026.1.0
|
|
4
|
+
Summary: Public Agar.io competition package
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: dotmap>=1.3.30
|
|
7
|
+
Requires-Dist: pydantic>=2.11.7
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# agario-public
|
|
11
|
+
|
|
12
|
+
Public source repository for the Agar.io bot competition.
|
|
13
|
+
|
|
14
|
+
This repo publishes a single Python package:
|
|
15
|
+
|
|
16
|
+
- `agario-kit`
|
|
17
|
+
|
|
18
|
+
That one distribution contains the modules competitors and private
|
|
19
|
+
infrastructure use:
|
|
20
|
+
|
|
21
|
+
- `lib`: shared models, config, and protocol types
|
|
22
|
+
- `helper`: helper API that student bots import
|
|
23
|
+
- `engine`: the authoritative game engine
|
|
24
|
+
- `agario_visualiser`: local match launcher and first-person visualiser
|
|
25
|
+
|
|
26
|
+
## Layout
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
src/
|
|
30
|
+
agario_kit/
|
|
31
|
+
agario_visualiser/
|
|
32
|
+
engine/
|
|
33
|
+
helper/
|
|
34
|
+
lib/
|
|
35
|
+
examples/
|
|
36
|
+
submissions/
|
|
37
|
+
docs/
|
|
38
|
+
tests/
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Local development
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv sync
|
|
45
|
+
uv run pytest
|
|
46
|
+
uv run agario-local-match --submission examples/submissions/bot_1.py --submission examples/submissions/bot_2.py --submission examples/submissions/aggressive.py --masquerade
|
|
47
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# agario-public
|
|
2
|
+
|
|
3
|
+
Public source repository for the Agar.io bot competition.
|
|
4
|
+
|
|
5
|
+
This repo publishes a single Python package:
|
|
6
|
+
|
|
7
|
+
- `agario-kit`
|
|
8
|
+
|
|
9
|
+
That one distribution contains the modules competitors and private
|
|
10
|
+
infrastructure use:
|
|
11
|
+
|
|
12
|
+
- `lib`: shared models, config, and protocol types
|
|
13
|
+
- `helper`: helper API that student bots import
|
|
14
|
+
- `engine`: the authoritative game engine
|
|
15
|
+
- `agario_visualiser`: local match launcher and first-person visualiser
|
|
16
|
+
|
|
17
|
+
## Layout
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
src/
|
|
21
|
+
agario_kit/
|
|
22
|
+
agario_visualiser/
|
|
23
|
+
engine/
|
|
24
|
+
helper/
|
|
25
|
+
lib/
|
|
26
|
+
examples/
|
|
27
|
+
submissions/
|
|
28
|
+
docs/
|
|
29
|
+
tests/
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Local development
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uv sync
|
|
36
|
+
uv run pytest
|
|
37
|
+
uv run agario-local-match --submission examples/submissions/bot_1.py --submission examples/submissions/bot_2.py --submission examples/submissions/aggressive.py --masquerade
|
|
38
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Agar Bot Battle Rules
|
|
2
|
+
|
|
3
|
+
## Arena
|
|
4
|
+
|
|
5
|
+
- The game takes place in a square arena.
|
|
6
|
+
- The arena size and player count are controlled by config values.
|
|
7
|
+
- Food is spawned randomly and maintained at a fixed count.
|
|
8
|
+
|
|
9
|
+
## Player State
|
|
10
|
+
|
|
11
|
+
Each player controls one or more circular blobs with:
|
|
12
|
+
|
|
13
|
+
- Absolute `x, y` position for each blob
|
|
14
|
+
- Radius for each blob
|
|
15
|
+
- Alive/dead state
|
|
16
|
+
- Per-blob merge cooldown after splitting
|
|
17
|
+
|
|
18
|
+
## Vision
|
|
19
|
+
|
|
20
|
+
Each player has a single square view centered on the mass-weighted centroid of
|
|
21
|
+
their blobs.
|
|
22
|
+
|
|
23
|
+
Players can observe:
|
|
24
|
+
|
|
25
|
+
- Their own authoritative blob list with absolute coordinates, sizes, and cooldowns
|
|
26
|
+
- Food within their current vision square
|
|
27
|
+
- Other visible blobs with absolute coordinates and size
|
|
28
|
+
|
|
29
|
+
The vision size is recalculated by the engine each turn as a function of the
|
|
30
|
+
sum of the player's blob radii.
|
|
31
|
+
|
|
32
|
+
## Turns
|
|
33
|
+
|
|
34
|
+
On each round, every living player submits a movement direction and may optionally request a split.
|
|
35
|
+
|
|
36
|
+
Supported direction formats:
|
|
37
|
+
|
|
38
|
+
- A vector using `x` and `y`
|
|
39
|
+
- An angle in `degrees`
|
|
40
|
+
|
|
41
|
+
The engine normalizes the direction and applies movement using the configured base speed. Larger players move more slowly according to a size-based speed factor.
|
|
42
|
+
|
|
43
|
+
## Splitting And Merging
|
|
44
|
+
|
|
45
|
+
- A blob may split only if its mass is at least the configured split threshold.
|
|
46
|
+
- Mass is proportional to area, so splitting conserves `radius^2`.
|
|
47
|
+
- When a split happens, the new blob is ejected in the submitted movement direction with a configured launch speed.
|
|
48
|
+
- Split blobs cannot merge until their cooldown expires.
|
|
49
|
+
- Blobs owned by the same player are kept non-overlapping by the engine at all times.
|
|
50
|
+
- Same-player blobs are gently pulled together over time, and merge automatically once they are both off cooldown and touching.
|
|
51
|
+
|
|
52
|
+
## Eating Food
|
|
53
|
+
|
|
54
|
+
- Food is eaten when it lies inside any owned blob's radius.
|
|
55
|
+
- Eating food increases that blob's size.
|
|
56
|
+
- After food is consumed, the engine respawns food until the configured food count is restored.
|
|
57
|
+
|
|
58
|
+
## Eating Players
|
|
59
|
+
|
|
60
|
+
- A blob may eat an enemy blob only if it is at least 10% larger.
|
|
61
|
+
- The smaller blob's center must lie inside the larger blob's radius.
|
|
62
|
+
- When a blob is eaten, the larger blob absorbs its size contribution.
|
|
63
|
+
- A player is eliminated only when all of their blobs have been eaten.
|
|
64
|
+
|
|
65
|
+
## End Condition
|
|
66
|
+
|
|
67
|
+
The game ends when either:
|
|
68
|
+
|
|
69
|
+
- Only one player remains alive
|
|
70
|
+
- The configured round limit is reached
|
|
71
|
+
|
|
72
|
+
The winner is the highest-ranked remaining player by alive state and final size.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Releasing `agario-public`
|
|
2
|
+
|
|
3
|
+
## What gets published
|
|
4
|
+
|
|
5
|
+
This repository builds and publishes one Python distribution package:
|
|
6
|
+
|
|
7
|
+
- `agario-kit`
|
|
8
|
+
|
|
9
|
+
That single distribution contains the runtime modules competitors and private
|
|
10
|
+
infrastructure use:
|
|
11
|
+
|
|
12
|
+
- `lib`
|
|
13
|
+
- `helper`
|
|
14
|
+
- `engine`
|
|
15
|
+
- `agario_visualiser`
|
|
16
|
+
|
|
17
|
+
## GitHub Actions
|
|
18
|
+
|
|
19
|
+
- `.github/workflows/ci.yml` runs tests on pushes to `main` and on pull requests.
|
|
20
|
+
- `.github/workflows/publish.yml`:
|
|
21
|
+
- publishes to PyPI on git tags matching `v*`
|
|
22
|
+
- can publish to TestPyPI manually via `workflow_dispatch`
|
|
23
|
+
|
|
24
|
+
The publish workflow builds the distribution into `dist/`, checks it with
|
|
25
|
+
`twine check`, then uploads them through `pypa/gh-action-pypi-publish`.
|
|
26
|
+
|
|
27
|
+
## Trusted Publishing setup
|
|
28
|
+
|
|
29
|
+
Configure the GitHub workflow as a Trusted Publisher for the `agario-kit`
|
|
30
|
+
project on both PyPI and TestPyPI.
|
|
31
|
+
|
|
32
|
+
Use these values when configuring each publisher:
|
|
33
|
+
|
|
34
|
+
- Repository owner: your GitHub org or username
|
|
35
|
+
- Repository name: `agario-public`
|
|
36
|
+
- Workflow filename: `publish.yml`
|
|
37
|
+
- GitHub environment:
|
|
38
|
+
- `pypi` for PyPI
|
|
39
|
+
- `testpypi` for TestPyPI
|
|
40
|
+
|
|
41
|
+
## Release steps
|
|
42
|
+
|
|
43
|
+
1. Update the version in the root `pyproject.toml`.
|
|
44
|
+
2. Commit and push to `main`.
|
|
45
|
+
3. Run the `Publish Package` workflow manually with `repository=testpypi`.
|
|
46
|
+
4. Verify installs from TestPyPI.
|
|
47
|
+
5. Create and push a tag such as `v2026.1.1`.
|
|
48
|
+
6. Approve the `pypi` environment deployment in GitHub if required.
|
|
49
|
+
7. Announce the release to competitors:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv lock --upgrade-package agario-kit==2026.1.1
|
|
53
|
+
uv sync
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Competitor install target
|
|
57
|
+
|
|
58
|
+
Competitors install only this package:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
uv add agario-kit==2026.1.1
|
|
62
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Scoring and Ranks
|
|
2
|
+
- Previously Bot Battle used win rates, with set interval round-robin game runs. I am not sure how I feel about this since, it is not robust to reactions.
|
|
3
|
+
- It also discourages scoring-oriented or deliberately adversarial strategies in a round-robin format.
|
|
4
|
+
|
|
5
|
+
- What I am thinking is Round Robin "Tournament" Should be held at a set interval
|
|
6
|
+
- Overall Rank is a sliding window of the last 30 (not finalised) minutes (so if a game is every minute 30 roundrobin tournaments decide your rank)
|
|
7
|
+
- Ideally the number of points you get should be _offset_ (but not weighted) but how difficult opponent bots are.
|
|
8
|
+
- Offset = k1 * (your_score - agg_opp_mean) / (agg_opp_sd + ε) \
|
|
9
|
+
+ k2 * (your_mean - agg_opp_mean) / (your_sd + agg_opp_sd + ε) capped at [2, -4]
|
|
10
|
+
|
|
11
|
+
- Win Bonus - +4 First, +2 for Second
|
|
12
|
+
|
|
13
|
+
> Definitely not fixed. The advantage of winrate is that it can be run at an arbitrary interval. So to be discussed.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "agario-kit"
|
|
3
|
+
version = "2026.1.0"
|
|
4
|
+
description = "Public Agar.io competition package"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"dotmap>=1.3.30",
|
|
9
|
+
"pydantic>=2.11.7",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
agario-engine = "engine.__main__:main"
|
|
14
|
+
engine = "engine.__main__:main"
|
|
15
|
+
agario-local-match = "agario_visualiser.launch_local_match:main"
|
|
16
|
+
agario-visualiser-submission = "agario_visualiser.visualiser_submission:main"
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["hatchling"]
|
|
20
|
+
build-backend = "hatchling.build"
|
|
21
|
+
|
|
22
|
+
[dependency-groups]
|
|
23
|
+
dev = [
|
|
24
|
+
"build",
|
|
25
|
+
"mypy",
|
|
26
|
+
"pre-commit",
|
|
27
|
+
"pytest>=8.4.1",
|
|
28
|
+
"twine",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.wheel]
|
|
32
|
+
packages = [
|
|
33
|
+
"src/agario_kit",
|
|
34
|
+
"src/agario_visualiser",
|
|
35
|
+
"src/engine",
|
|
36
|
+
"src/helper",
|
|
37
|
+
"src/lib",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.sdist]
|
|
41
|
+
include = [
|
|
42
|
+
"src/agario_kit",
|
|
43
|
+
"src/agario_visualiser",
|
|
44
|
+
"src/engine",
|
|
45
|
+
"src/helper",
|
|
46
|
+
"src/lib",
|
|
47
|
+
"examples",
|
|
48
|
+
"docs",
|
|
49
|
+
"tests",
|
|
50
|
+
"scripts",
|
|
51
|
+
".gitignore",
|
|
52
|
+
".python-version",
|
|
53
|
+
"README.md",
|
|
54
|
+
"mypy.ini",
|
|
55
|
+
"pyproject.toml",
|
|
56
|
+
"ruff.toml",
|
|
57
|
+
]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Exclude a variety of commonly ignored directories.
|
|
2
|
+
exclude = [
|
|
3
|
+
".bzr",
|
|
4
|
+
".direnv",
|
|
5
|
+
".eggs",
|
|
6
|
+
".git",
|
|
7
|
+
".git-rewrite",
|
|
8
|
+
".hg",
|
|
9
|
+
".ipynb_checkpoints",
|
|
10
|
+
".mypy_cache",
|
|
11
|
+
".nox",
|
|
12
|
+
".pants.d",
|
|
13
|
+
".pyenv",
|
|
14
|
+
".pytest_cache",
|
|
15
|
+
".pytype",
|
|
16
|
+
".ruff_cache",
|
|
17
|
+
".svn",
|
|
18
|
+
".tox",
|
|
19
|
+
".venv",
|
|
20
|
+
".vscode",
|
|
21
|
+
"__pypackages__",
|
|
22
|
+
"_build",
|
|
23
|
+
"buck-out",
|
|
24
|
+
"build",
|
|
25
|
+
"dist",
|
|
26
|
+
"node_modules",
|
|
27
|
+
"site-packages",
|
|
28
|
+
"venv",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
# Same as Black.
|
|
32
|
+
line-length = 88
|
|
33
|
+
indent-width = 4
|
|
34
|
+
|
|
35
|
+
# Assume Python 3.9
|
|
36
|
+
target-version = "py313"
|
|
37
|
+
|
|
38
|
+
[lint]
|
|
39
|
+
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
|
40
|
+
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
|
|
41
|
+
# McCabe complexity (`C901`) by default.
|
|
42
|
+
select = ["E4", "E7", "E9", "F"]
|
|
43
|
+
ignore = []
|
|
44
|
+
|
|
45
|
+
# Allow fix for all enabled rules (when `--fix`) is provided.
|
|
46
|
+
fixable = ["ALL"]
|
|
47
|
+
unfixable = []
|
|
48
|
+
|
|
49
|
+
# Allow unused variables when underscore-prefixed.
|
|
50
|
+
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
51
|
+
|
|
52
|
+
[format]
|
|
53
|
+
# Like Black, use double quotes for strings.
|
|
54
|
+
quote-style = "double"
|
|
55
|
+
|
|
56
|
+
# Like Black, indent with spaces, rather than tabs.
|
|
57
|
+
indent-style = "space"
|
|
58
|
+
|
|
59
|
+
# Like Black, respect magic trailing commas.
|
|
60
|
+
skip-magic-trailing-comma = false
|
|
61
|
+
|
|
62
|
+
# Like Black, automatically detect the appropriate line ending.
|
|
63
|
+
line-ending = "auto"
|
|
64
|
+
|
|
65
|
+
# Enable auto-formatting of code examples in docstrings. Markdown,
|
|
66
|
+
# reStructuredText code/literal blocks and doctests are all supported.
|
|
67
|
+
#
|
|
68
|
+
# This is currently disabled by default, but it is planned for this
|
|
69
|
+
# to be opt-out in the future.
|
|
70
|
+
docstring-code-format = false
|
|
71
|
+
|
|
72
|
+
# Set the line length limit used when formatting code snippets in
|
|
73
|
+
# docstrings.
|
|
74
|
+
#
|
|
75
|
+
# This only has an effect when the `docstring-code-format` setting is
|
|
76
|
+
# enabled.
|
|
77
|
+
docstring-code-line-length = "dynamic"
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import shutil
|
|
5
|
+
from signal import SIGKILL
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import os
|
|
9
|
+
from typing import Tuple
|
|
10
|
+
|
|
11
|
+
from lib.config.arena import NUM_PLAYERS
|
|
12
|
+
|
|
13
|
+
PIPE_PERMISSIONS = 0o660
|
|
14
|
+
FILE_PERMISSIOSN = 0o664
|
|
15
|
+
DIRECTORY_PERMISSIONS = 0o775
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main():
|
|
19
|
+
# uv run python scripts/match_simulator.py --submissions 3:examples/submissions/example.py 1:examples/submissions/aggressive.py --engine
|
|
20
|
+
|
|
21
|
+
commands = parse_cmd_args(sys.argv[1:])
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
sources = [
|
|
25
|
+
(int(x.split(":")[0]), x.split(":")[1]) for x in commands["--submissions"]
|
|
26
|
+
]
|
|
27
|
+
except (ValueError, KeyError):
|
|
28
|
+
print_usage()
|
|
29
|
+
|
|
30
|
+
if sum([x[0] for x in sources]) != NUM_PLAYERS:
|
|
31
|
+
print(f"Total players in the match must be {NUM_PLAYERS}.")
|
|
32
|
+
print_usage()
|
|
33
|
+
|
|
34
|
+
setup_environments(sources)
|
|
35
|
+
submission_pids = start_submissions()
|
|
36
|
+
|
|
37
|
+
if "--engine" in commands:
|
|
38
|
+
if len(commands["--engine"]) != 0:
|
|
39
|
+
print_usage()
|
|
40
|
+
start_engine()
|
|
41
|
+
|
|
42
|
+
else:
|
|
43
|
+
print(
|
|
44
|
+
"Once you have finished running the engine, press [Enter] to terminate any still-running submission processes."
|
|
45
|
+
)
|
|
46
|
+
input()
|
|
47
|
+
|
|
48
|
+
for pid in submission_pids:
|
|
49
|
+
print(f"[simulator]: terminating submission pid {pid}.")
|
|
50
|
+
os.kill(pid, SIGKILL)
|
|
51
|
+
|
|
52
|
+
print("[simulator] simulation complete.")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def parse_cmd_args(args: list[str]):
|
|
56
|
+
commands = {}
|
|
57
|
+
|
|
58
|
+
current_command = None
|
|
59
|
+
for arg in args:
|
|
60
|
+
if arg[:2] == "--":
|
|
61
|
+
current_command = arg
|
|
62
|
+
commands[current_command] = []
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
if current_command is None:
|
|
66
|
+
print_usage()
|
|
67
|
+
|
|
68
|
+
commands[current_command].append(arg)
|
|
69
|
+
|
|
70
|
+
for command in commands.keys():
|
|
71
|
+
if command not in ["--submissions", "--engine"]:
|
|
72
|
+
print_usage()
|
|
73
|
+
|
|
74
|
+
return commands
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def print_usage():
|
|
78
|
+
print(
|
|
79
|
+
"Usage: python3 match_simulator.py [options]\n"
|
|
80
|
+
" options:\n"
|
|
81
|
+
" --submissions {<count> | d}:<path> ... Sets the source files for the submissions to run in this match. If <count> is specified, <count> copies\n"
|
|
82
|
+
" of the submission will play in the match, else if 'd' is specified a single copy will play and it\n"
|
|
83
|
+
" will not be automatically started.\n"
|
|
84
|
+
" --engine If present, the simulator will start the engine. To run the match without this flag you need to manually\n"
|
|
85
|
+
" start the engine (for example, while debugging it).\n"
|
|
86
|
+
"\n"
|
|
87
|
+
" examples:\n"
|
|
88
|
+
" uv run python scripts/match_simulator.py --submissions 4:examples/submissions/example.py --engine\n"
|
|
89
|
+
" uv run python scripts/match_simulator.py --submissions 1:examples/submissions/example.py 3:my_submission.py --engine\n"
|
|
90
|
+
" uv run python scripts/match_simulator.py --submissions 3:examples/submissions/example.py d:my_submission.py --engine\n"
|
|
91
|
+
)
|
|
92
|
+
sys.exit(0)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def setup_environments(sources: list[Tuple[int, str]]):
|
|
96
|
+
shutil.rmtree("output", ignore_errors=True)
|
|
97
|
+
os.mkdir("output")
|
|
98
|
+
shutil.rmtree("input", ignore_errors=True)
|
|
99
|
+
os.mkdir("input")
|
|
100
|
+
|
|
101
|
+
count = 0
|
|
102
|
+
source = sources.pop(0)
|
|
103
|
+
for player in range(NUM_PLAYERS):
|
|
104
|
+
if count >= source[0]:
|
|
105
|
+
count = 0
|
|
106
|
+
source = sources.pop(0)
|
|
107
|
+
|
|
108
|
+
clean_environment_for_player(player)
|
|
109
|
+
setup_environment_for_player(player, source[1])
|
|
110
|
+
|
|
111
|
+
count += 1
|
|
112
|
+
|
|
113
|
+
catalog = [{"team_id": i} for i in range(NUM_PLAYERS)]
|
|
114
|
+
with open("input/catalog.json", "w") as f:
|
|
115
|
+
f.write(json.dumps(catalog))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def start_submissions() -> list[int]:
|
|
119
|
+
player_pids = []
|
|
120
|
+
for player in range(NUM_PLAYERS):
|
|
121
|
+
os.chdir(f"submission{player}")
|
|
122
|
+
|
|
123
|
+
with (
|
|
124
|
+
open("io/submission.log", "w") as f_log,
|
|
125
|
+
open("io/submission.err", "w") as f_err,
|
|
126
|
+
):
|
|
127
|
+
process = subprocess.Popen(
|
|
128
|
+
["python3", "submission.py"], stdout=f_log, stderr=f_err
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
player_pids.append(process.pid)
|
|
132
|
+
print(f"[simulator]: started submission {player} (pid={process.pid}).")
|
|
133
|
+
os.chdir("..")
|
|
134
|
+
|
|
135
|
+
return player_pids
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def start_engine():
|
|
139
|
+
print("[simulator] started engine.")
|
|
140
|
+
with (
|
|
141
|
+
open("output/engine.log", "w") as f_log,
|
|
142
|
+
open("output/engine.err", "w") as f_err,
|
|
143
|
+
):
|
|
144
|
+
process = subprocess.Popen(
|
|
145
|
+
["python3", "-m", "engine", "--print-recording-interactive"],
|
|
146
|
+
stdout=subprocess.PIPE,
|
|
147
|
+
stderr=f_err,
|
|
148
|
+
text=True,
|
|
149
|
+
universal_newlines=True,
|
|
150
|
+
bufsize=1,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
while True:
|
|
154
|
+
if process.stdout is not None:
|
|
155
|
+
data = process.stdout.read(1)
|
|
156
|
+
if not data:
|
|
157
|
+
break
|
|
158
|
+
print(data, end="", flush=True)
|
|
159
|
+
f_log.write(data)
|
|
160
|
+
|
|
161
|
+
print("[simulator] engine terminated.")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def setup_environment_for_player(player: int, source: str):
|
|
165
|
+
os.makedirs(f"submission{player}/io", mode=DIRECTORY_PERMISSIONS)
|
|
166
|
+
os.mkfifo(f"submission{player}/io/to_engine.pipe", mode=PIPE_PERMISSIONS)
|
|
167
|
+
os.mkfifo(f"submission{player}/io/from_engine.pipe", mode=PIPE_PERMISSIONS)
|
|
168
|
+
shutil.copy(source, f"submission{player}/submission.py")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def clean_environment_for_player(player: int):
|
|
172
|
+
shutil.rmtree(f"submission{player}", ignore_errors=True)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
if __name__ == "__main__":
|
|
176
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Convenience meta-package for the public Agar.io stack."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Public visualiser package."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Packaged example submissions for fallback local runs."""
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from helper.game import Game
|
|
2
|
+
from lib.interface.events.moves.move_player import MovePlayer
|
|
3
|
+
from lib.interface.events.moves.typing import MoveType
|
|
4
|
+
from lib.interface.queries.query_move import QueryMovePlayer
|
|
5
|
+
from lib.interface.queries.typing import QueryType
|
|
6
|
+
from lib.models.penguin_model import DirectionModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def choose_direction(game: Game) -> tuple[float, float]:
|
|
10
|
+
center_x = game.state.me.x
|
|
11
|
+
center_y = game.state.me.y
|
|
12
|
+
my_radius = game.state.me.radius
|
|
13
|
+
|
|
14
|
+
threats = [
|
|
15
|
+
(blob.pos[0] - center_x, blob.pos[1] - center_y)
|
|
16
|
+
for blob in game.state.visible_blobs
|
|
17
|
+
if blob.radius > my_radius * 1.1
|
|
18
|
+
]
|
|
19
|
+
if threats:
|
|
20
|
+
nearest = min(threats, key=lambda pos: pos[0] * pos[0] + pos[1] * pos[1])
|
|
21
|
+
return (-nearest[0], -nearest[1])
|
|
22
|
+
|
|
23
|
+
prey = [
|
|
24
|
+
(blob.pos[0] - center_x, blob.pos[1] - center_y)
|
|
25
|
+
for blob in game.state.visible_blobs
|
|
26
|
+
if my_radius > blob.radius * 1.1
|
|
27
|
+
]
|
|
28
|
+
if prey:
|
|
29
|
+
return min(prey, key=lambda pos: pos[0] * pos[0] + pos[1] * pos[1])
|
|
30
|
+
|
|
31
|
+
if game.state.visible_food:
|
|
32
|
+
return min(
|
|
33
|
+
[
|
|
34
|
+
(food.pos[0] - center_x, food.pos[1] - center_y)
|
|
35
|
+
for food in game.state.visible_food
|
|
36
|
+
],
|
|
37
|
+
key=lambda pos: pos[0] * pos[0] + pos[1] * pos[1],
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return (0.0, 0.0)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def main() -> None:
|
|
44
|
+
game = Game()
|
|
45
|
+
|
|
46
|
+
while True:
|
|
47
|
+
query = game.get_next_query()
|
|
48
|
+
|
|
49
|
+
def choose_move(query: QueryType) -> MoveType:
|
|
50
|
+
match query:
|
|
51
|
+
case QueryMovePlayer():
|
|
52
|
+
dx, dy = choose_direction(game)
|
|
53
|
+
return MovePlayer(
|
|
54
|
+
player_id=game.state.me.player_id,
|
|
55
|
+
direction=DirectionModel(x=dx, y=dy),
|
|
56
|
+
)
|
|
57
|
+
raise RuntimeError(f"Unsupported query type: {type(query)}")
|
|
58
|
+
|
|
59
|
+
game.send_move(choose_move(query))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
main()
|