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.
Files changed (86) hide show
  1. agario_kit-2026.1.0/.gitignore +39 -0
  2. agario_kit-2026.1.0/.python-version +1 -0
  3. agario_kit-2026.1.0/PKG-INFO +47 -0
  4. agario_kit-2026.1.0/README.md +38 -0
  5. agario_kit-2026.1.0/docs/game_rules.md +72 -0
  6. agario_kit-2026.1.0/docs/releasing.md +62 -0
  7. agario_kit-2026.1.0/docs/scoring.md +13 -0
  8. agario_kit-2026.1.0/mypy.ini +5 -0
  9. agario_kit-2026.1.0/pyproject.toml +57 -0
  10. agario_kit-2026.1.0/ruff.toml +77 -0
  11. agario_kit-2026.1.0/scripts/match_simulator.py +176 -0
  12. agario_kit-2026.1.0/src/agario_kit/__init__.py +1 -0
  13. agario_kit-2026.1.0/src/agario_visualiser/__init__.py +1 -0
  14. agario_kit-2026.1.0/src/agario_visualiser/__main__.py +5 -0
  15. agario_kit-2026.1.0/src/agario_visualiser/examples/__init__.py +1 -0
  16. agario_kit-2026.1.0/src/agario_visualiser/examples/example.py +63 -0
  17. agario_kit-2026.1.0/src/agario_visualiser/launch_local_match.py +245 -0
  18. agario_kit-2026.1.0/src/agario_visualiser/visualiser_submission.py +1148 -0
  19. agario_kit-2026.1.0/src/engine/__init__.py +0 -0
  20. agario_kit-2026.1.0/src/engine/__main__.py +12 -0
  21. agario_kit-2026.1.0/src/engine/config/expansion_config.py +1 -0
  22. agario_kit-2026.1.0/src/engine/config/io_config.py +16 -0
  23. agario_kit-2026.1.0/src/engine/game/tile_subscriber.py +110 -0
  24. agario_kit-2026.1.0/src/engine/game_engine.py +146 -0
  25. agario_kit-2026.1.0/src/engine/interface/io/censor_event.py +79 -0
  26. agario_kit-2026.1.0/src/engine/interface/io/exceptions.py +38 -0
  27. agario_kit-2026.1.0/src/engine/interface/io/game_result.py +26 -0
  28. agario_kit-2026.1.0/src/engine/interface/io/input_validator.py +37 -0
  29. agario_kit-2026.1.0/src/engine/interface/io/player_connection.py +285 -0
  30. agario_kit-2026.1.0/src/engine/interface/logging/event_factory.py +36 -0
  31. agario_kit-2026.1.0/src/engine/interface/logging/event_inspector.py +76 -0
  32. agario_kit-2026.1.0/src/engine/state/blob_state.py +43 -0
  33. agario_kit-2026.1.0/src/engine/state/game_state.py +230 -0
  34. agario_kit-2026.1.0/src/engine/state/physics_utils.py +73 -0
  35. agario_kit-2026.1.0/src/engine/state/player_state.py +71 -0
  36. agario_kit-2026.1.0/src/engine/state/state_mutator.py +518 -0
  37. agario_kit-2026.1.0/src/helper/__init__.py +0 -0
  38. agario_kit-2026.1.0/src/helper/__main__.py +2 -0
  39. agario_kit-2026.1.0/src/helper/client_state.py +31 -0
  40. agario_kit-2026.1.0/src/helper/game.py +37 -0
  41. agario_kit-2026.1.0/src/helper/interface.py +56 -0
  42. agario_kit-2026.1.0/src/helper/py.typed +0 -0
  43. agario_kit-2026.1.0/src/helper/state/README.md +1 -0
  44. agario_kit-2026.1.0/src/helper/state/client_player_state.py +37 -0
  45. agario_kit-2026.1.0/src/helper/state_mutator.py +80 -0
  46. agario_kit-2026.1.0/src/lib/__init__.py +0 -0
  47. agario_kit-2026.1.0/src/lib/__main__.py +2 -0
  48. agario_kit-2026.1.0/src/lib/config/arena.py +11 -0
  49. agario_kit-2026.1.0/src/lib/config/player.py +12 -0
  50. agario_kit-2026.1.0/src/lib/game/game_logic.py +12 -0
  51. agario_kit-2026.1.0/src/lib/interact/map.py +39 -0
  52. agario_kit-2026.1.0/src/lib/interact/penguin.py +52 -0
  53. agario_kit-2026.1.0/src/lib/interface/events/base_event.py +5 -0
  54. agario_kit-2026.1.0/src/lib/interface/events/event_food_eaten.py +11 -0
  55. agario_kit-2026.1.0/src/lib/interface/events/event_food_spawned.py +9 -0
  56. agario_kit-2026.1.0/src/lib/interface/events/event_game_ended.py +7 -0
  57. agario_kit-2026.1.0/src/lib/interface/events/event_game_started.py +26 -0
  58. agario_kit-2026.1.0/src/lib/interface/events/event_ice_shrunk.py +8 -0
  59. agario_kit-2026.1.0/src/lib/interface/events/event_penguin_fallen_off.py +12 -0
  60. agario_kit-2026.1.0/src/lib/interface/events/event_penguins_moved.py +9 -0
  61. agario_kit-2026.1.0/src/lib/interface/events/event_player_bannned.py +14 -0
  62. agario_kit-2026.1.0/src/lib/interface/events/event_player_eaten.py +15 -0
  63. agario_kit-2026.1.0/src/lib/interface/events/event_player_moved.py +13 -0
  64. agario_kit-2026.1.0/src/lib/interface/events/event_player_won.py +8 -0
  65. agario_kit-2026.1.0/src/lib/interface/events/event_private_physics_tick.py +8 -0
  66. agario_kit-2026.1.0/src/lib/interface/events/moves/base_move.py +5 -0
  67. agario_kit-2026.1.0/src/lib/interface/events/moves/move_penguins.py +10 -0
  68. agario_kit-2026.1.0/src/lib/interface/events/moves/move_player.py +11 -0
  69. agario_kit-2026.1.0/src/lib/interface/events/moves/typing.py +11 -0
  70. agario_kit-2026.1.0/src/lib/interface/events/typing.py +40 -0
  71. agario_kit-2026.1.0/src/lib/interface/events/visualiser_push_penguins.py +8 -0
  72. agario_kit-2026.1.0/src/lib/interface/io/ban_type.py +10 -0
  73. agario_kit-2026.1.0/src/lib/interface/queries/base_query.py +9 -0
  74. agario_kit-2026.1.0/src/lib/interface/queries/query_move.py +18 -0
  75. agario_kit-2026.1.0/src/lib/interface/queries/query_move_penguins.py +7 -0
  76. agario_kit-2026.1.0/src/lib/interface/queries/typing.py +16 -0
  77. agario_kit-2026.1.0/src/lib/interface/readme.md +6 -0
  78. agario_kit-2026.1.0/src/lib/models/blob_model.py +13 -0
  79. agario_kit-2026.1.0/src/lib/models/food_model.py +6 -0
  80. agario_kit-2026.1.0/src/lib/models/penguin_model.py +35 -0
  81. agario_kit-2026.1.0/src/lib/models/player_model.py +35 -0
  82. agario_kit-2026.1.0/src/lib/models/virus_model.py +7 -0
  83. agario_kit-2026.1.0/src/lib/py.typed +0 -0
  84. agario_kit-2026.1.0/tests/game_logic_test.py +492 -0
  85. agario_kit-2026.1.0/tests/split_mechanics_test.py +276 -0
  86. 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,5 @@
1
+ [mypy]
2
+ files = src
3
+ python_executable = .venv/bin/python
4
+ strict = true
5
+ disable_error_code = import-untyped
@@ -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,5 @@
1
+ from agario_visualiser.launch_local_match import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -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()