kaggle-environments 0.2.0__py3-none-any.whl → 1.20.0__py3-none-any.whl

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.

Potentially problematic release.


This version of kaggle-environments might be problematic. Click here for more details.

Files changed (215) hide show
  1. kaggle_environments/__init__.py +49 -13
  2. kaggle_environments/agent.py +177 -124
  3. kaggle_environments/api.py +31 -0
  4. kaggle_environments/core.py +298 -173
  5. kaggle_environments/envs/cabt/cabt.js +164 -0
  6. kaggle_environments/envs/cabt/cabt.json +28 -0
  7. kaggle_environments/envs/cabt/cabt.py +186 -0
  8. kaggle_environments/envs/cabt/cg/__init__.py +0 -0
  9. kaggle_environments/envs/cabt/cg/cg.dll +0 -0
  10. kaggle_environments/envs/cabt/cg/game.py +75 -0
  11. kaggle_environments/envs/cabt/cg/libcg.so +0 -0
  12. kaggle_environments/envs/cabt/cg/sim.py +48 -0
  13. kaggle_environments/envs/cabt/test_cabt.py +120 -0
  14. kaggle_environments/envs/chess/chess.js +4289 -0
  15. kaggle_environments/envs/chess/chess.json +60 -0
  16. kaggle_environments/envs/chess/chess.py +4241 -0
  17. kaggle_environments/envs/chess/test_chess.py +60 -0
  18. kaggle_environments/envs/connectx/connectx.ipynb +3186 -0
  19. kaggle_environments/envs/connectx/connectx.js +1 -1
  20. kaggle_environments/envs/connectx/connectx.json +15 -1
  21. kaggle_environments/envs/connectx/connectx.py +6 -23
  22. kaggle_environments/envs/connectx/test_connectx.py +70 -24
  23. kaggle_environments/envs/football/football.ipynb +75 -0
  24. kaggle_environments/envs/football/football.json +91 -0
  25. kaggle_environments/envs/football/football.py +277 -0
  26. kaggle_environments/envs/football/helpers.py +95 -0
  27. kaggle_environments/envs/football/test_football.py +360 -0
  28. kaggle_environments/envs/halite/__init__.py +0 -0
  29. kaggle_environments/envs/halite/halite.ipynb +44741 -0
  30. kaggle_environments/envs/halite/halite.js +199 -83
  31. kaggle_environments/envs/halite/halite.json +31 -18
  32. kaggle_environments/envs/halite/halite.py +164 -303
  33. kaggle_environments/envs/halite/helpers.py +720 -0
  34. kaggle_environments/envs/halite/test_halite.py +190 -0
  35. kaggle_environments/envs/hungry_geese/__init__.py +0 -0
  36. kaggle_environments/envs/{battlegeese/battlegeese.js → hungry_geese/hungry_geese.js} +38 -22
  37. kaggle_environments/envs/{battlegeese/battlegeese.json → hungry_geese/hungry_geese.json} +22 -15
  38. kaggle_environments/envs/hungry_geese/hungry_geese.py +316 -0
  39. kaggle_environments/envs/hungry_geese/test_hungry_geese.py +0 -0
  40. kaggle_environments/envs/identity/identity.json +6 -5
  41. kaggle_environments/envs/identity/identity.py +15 -2
  42. kaggle_environments/envs/kore_fleets/__init__.py +0 -0
  43. kaggle_environments/envs/kore_fleets/helpers.py +1005 -0
  44. kaggle_environments/envs/kore_fleets/kore_fleets.ipynb +114 -0
  45. kaggle_environments/envs/kore_fleets/kore_fleets.js +658 -0
  46. kaggle_environments/envs/kore_fleets/kore_fleets.json +164 -0
  47. kaggle_environments/envs/kore_fleets/kore_fleets.py +555 -0
  48. kaggle_environments/envs/kore_fleets/starter_bots/java/Bot.java +54 -0
  49. kaggle_environments/envs/kore_fleets/starter_bots/java/README.md +26 -0
  50. kaggle_environments/envs/kore_fleets/starter_bots/java/jars/hamcrest-core-1.3.jar +0 -0
  51. kaggle_environments/envs/kore_fleets/starter_bots/java/jars/junit-4.13.2.jar +0 -0
  52. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Board.java +518 -0
  53. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Cell.java +61 -0
  54. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Configuration.java +24 -0
  55. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Direction.java +166 -0
  56. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Fleet.java +72 -0
  57. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/KoreJson.java +97 -0
  58. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Observation.java +72 -0
  59. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Pair.java +13 -0
  60. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Player.java +68 -0
  61. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Point.java +65 -0
  62. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Shipyard.java +70 -0
  63. kaggle_environments/envs/kore_fleets/starter_bots/java/kore/ShipyardAction.java +59 -0
  64. kaggle_environments/envs/kore_fleets/starter_bots/java/main.py +73 -0
  65. kaggle_environments/envs/kore_fleets/starter_bots/java/test/BoardTest.java +567 -0
  66. kaggle_environments/envs/kore_fleets/starter_bots/java/test/ConfigurationTest.java +25 -0
  67. kaggle_environments/envs/kore_fleets/starter_bots/java/test/KoreJsonTest.java +62 -0
  68. kaggle_environments/envs/kore_fleets/starter_bots/java/test/ObservationTest.java +46 -0
  69. kaggle_environments/envs/kore_fleets/starter_bots/java/test/PointTest.java +21 -0
  70. kaggle_environments/envs/kore_fleets/starter_bots/java/test/ShipyardTest.java +22 -0
  71. kaggle_environments/envs/kore_fleets/starter_bots/java/test/configuration.json +1 -0
  72. kaggle_environments/envs/kore_fleets/starter_bots/java/test/fullob.json +1 -0
  73. kaggle_environments/envs/kore_fleets/starter_bots/java/test/observation.json +1 -0
  74. kaggle_environments/envs/kore_fleets/starter_bots/python/__init__.py +0 -0
  75. kaggle_environments/envs/kore_fleets/starter_bots/python/main.py +27 -0
  76. kaggle_environments/envs/kore_fleets/starter_bots/ts/Bot.ts +34 -0
  77. kaggle_environments/envs/kore_fleets/starter_bots/ts/DoNothingBot.ts +12 -0
  78. kaggle_environments/envs/kore_fleets/starter_bots/ts/MinerBot.ts +62 -0
  79. kaggle_environments/envs/kore_fleets/starter_bots/ts/README.md +55 -0
  80. kaggle_environments/envs/kore_fleets/starter_bots/ts/interpreter.ts +402 -0
  81. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Board.ts +514 -0
  82. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Cell.ts +63 -0
  83. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Configuration.ts +25 -0
  84. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Direction.ts +169 -0
  85. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Fleet.ts +76 -0
  86. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/KoreIO.ts +70 -0
  87. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Observation.ts +45 -0
  88. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Pair.ts +11 -0
  89. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Player.ts +68 -0
  90. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Point.ts +65 -0
  91. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Shipyard.ts +72 -0
  92. kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/ShipyardAction.ts +58 -0
  93. kaggle_environments/envs/kore_fleets/starter_bots/ts/main.py +73 -0
  94. kaggle_environments/envs/kore_fleets/starter_bots/ts/miner.py +73 -0
  95. kaggle_environments/envs/kore_fleets/starter_bots/ts/package.json +23 -0
  96. kaggle_environments/envs/kore_fleets/starter_bots/ts/test/BoardTest.ts +551 -0
  97. kaggle_environments/envs/kore_fleets/starter_bots/ts/test/ConfigurationTest.ts +16 -0
  98. kaggle_environments/envs/kore_fleets/starter_bots/ts/test/ObservationTest.ts +33 -0
  99. kaggle_environments/envs/kore_fleets/starter_bots/ts/test/PointTest.ts +17 -0
  100. kaggle_environments/envs/kore_fleets/starter_bots/ts/test/ShipyardTest.ts +18 -0
  101. kaggle_environments/envs/kore_fleets/starter_bots/ts/test/configuration.json +1 -0
  102. kaggle_environments/envs/kore_fleets/starter_bots/ts/test/fullob.json +1 -0
  103. kaggle_environments/envs/kore_fleets/starter_bots/ts/test/observation.json +1 -0
  104. kaggle_environments/envs/kore_fleets/starter_bots/ts/tsconfig.json +22 -0
  105. kaggle_environments/envs/kore_fleets/test_kore_fleets.py +331 -0
  106. kaggle_environments/envs/lux_ai_2021/README.md +3 -0
  107. kaggle_environments/envs/lux_ai_2021/__init__.py +0 -0
  108. kaggle_environments/envs/lux_ai_2021/agents.py +11 -0
  109. kaggle_environments/envs/lux_ai_2021/dimensions/754.js +2 -0
  110. kaggle_environments/envs/lux_ai_2021/dimensions/754.js.LICENSE.txt +296 -0
  111. kaggle_environments/envs/lux_ai_2021/dimensions/main.js +1 -0
  112. kaggle_environments/envs/lux_ai_2021/index.html +43 -0
  113. kaggle_environments/envs/lux_ai_2021/lux_ai_2021.json +100 -0
  114. kaggle_environments/envs/lux_ai_2021/lux_ai_2021.py +231 -0
  115. kaggle_environments/envs/lux_ai_2021/test_agents/__init__.py +0 -0
  116. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/game_constants.js +6 -0
  117. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/game_constants.json +59 -0
  118. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/game_objects.js +145 -0
  119. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/io.js +14 -0
  120. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/kit.js +209 -0
  121. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/map.js +107 -0
  122. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/parser.js +79 -0
  123. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/main.js +88 -0
  124. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/main.py +75 -0
  125. kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/simple.tar.gz +0 -0
  126. kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/__init__.py +0 -0
  127. kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/annotate.py +20 -0
  128. kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/constants.py +25 -0
  129. kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game.py +86 -0
  130. kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game_constants.json +59 -0
  131. kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game_constants.py +7 -0
  132. kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game_map.py +106 -0
  133. kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game_objects.py +154 -0
  134. kaggle_environments/envs/lux_ai_2021/test_agents/python/random_agent.py +38 -0
  135. kaggle_environments/envs/lux_ai_2021/test_agents/python/simple_agent.py +82 -0
  136. kaggle_environments/envs/lux_ai_2021/test_lux.py +19 -0
  137. kaggle_environments/envs/lux_ai_2021/testing.md +23 -0
  138. kaggle_environments/envs/lux_ai_2021/todo.md.og +18 -0
  139. kaggle_environments/envs/lux_ai_s3/README.md +21 -0
  140. kaggle_environments/envs/lux_ai_s3/agents.py +5 -0
  141. kaggle_environments/envs/lux_ai_s3/index.html +42 -0
  142. kaggle_environments/envs/lux_ai_s3/lux_ai_s3.json +47 -0
  143. kaggle_environments/envs/lux_ai_s3/lux_ai_s3.py +178 -0
  144. kaggle_environments/envs/lux_ai_s3/luxai_s3/__init__.py +1 -0
  145. kaggle_environments/envs/lux_ai_s3/luxai_s3/env.py +819 -0
  146. kaggle_environments/envs/lux_ai_s3/luxai_s3/globals.py +9 -0
  147. kaggle_environments/envs/lux_ai_s3/luxai_s3/params.py +101 -0
  148. kaggle_environments/envs/lux_ai_s3/luxai_s3/profiler.py +141 -0
  149. kaggle_environments/envs/lux_ai_s3/luxai_s3/pygame_render.py +222 -0
  150. kaggle_environments/envs/lux_ai_s3/luxai_s3/spaces.py +27 -0
  151. kaggle_environments/envs/lux_ai_s3/luxai_s3/state.py +464 -0
  152. kaggle_environments/envs/lux_ai_s3/luxai_s3/utils.py +12 -0
  153. kaggle_environments/envs/lux_ai_s3/luxai_s3/wrappers.py +156 -0
  154. kaggle_environments/envs/lux_ai_s3/test_agents/python/agent.py +78 -0
  155. kaggle_environments/envs/lux_ai_s3/test_agents/python/lux/__init__.py +0 -0
  156. kaggle_environments/envs/lux_ai_s3/test_agents/python/lux/kit.py +31 -0
  157. kaggle_environments/envs/lux_ai_s3/test_agents/python/lux/utils.py +17 -0
  158. kaggle_environments/envs/lux_ai_s3/test_agents/python/main.py +66 -0
  159. kaggle_environments/envs/lux_ai_s3/test_lux.py +9 -0
  160. kaggle_environments/envs/mab/__init__.py +0 -0
  161. kaggle_environments/envs/mab/agents.py +12 -0
  162. kaggle_environments/envs/mab/mab.js +100 -0
  163. kaggle_environments/envs/mab/mab.json +74 -0
  164. kaggle_environments/envs/mab/mab.py +146 -0
  165. kaggle_environments/envs/open_spiel/__init__.py +0 -0
  166. kaggle_environments/envs/open_spiel/games/__init__.py +0 -0
  167. kaggle_environments/envs/open_spiel/games/chess/chess.js +441 -0
  168. kaggle_environments/envs/open_spiel/games/chess/image_config.jsonl +20 -0
  169. kaggle_environments/envs/open_spiel/games/chess/openings.jsonl +20 -0
  170. kaggle_environments/envs/open_spiel/games/connect_four/__init__.py +0 -0
  171. kaggle_environments/envs/open_spiel/games/connect_four/connect_four.js +284 -0
  172. kaggle_environments/envs/open_spiel/games/connect_four/connect_four_proxy.py +86 -0
  173. kaggle_environments/envs/open_spiel/games/go/__init__.py +0 -0
  174. kaggle_environments/envs/open_spiel/games/go/go.js +481 -0
  175. kaggle_environments/envs/open_spiel/games/go/go_proxy.py +99 -0
  176. kaggle_environments/envs/open_spiel/games/tic_tac_toe/__init__.py +0 -0
  177. kaggle_environments/envs/open_spiel/games/tic_tac_toe/tic_tac_toe.js +345 -0
  178. kaggle_environments/envs/open_spiel/games/tic_tac_toe/tic_tac_toe_proxy.py +98 -0
  179. kaggle_environments/envs/open_spiel/games/universal_poker/__init__.py +0 -0
  180. kaggle_environments/envs/open_spiel/games/universal_poker/universal_poker.js +431 -0
  181. kaggle_environments/envs/open_spiel/games/universal_poker/universal_poker_proxy.py +159 -0
  182. kaggle_environments/envs/open_spiel/html_playthrough_generator.py +31 -0
  183. kaggle_environments/envs/open_spiel/observation.py +128 -0
  184. kaggle_environments/envs/open_spiel/open_spiel.py +565 -0
  185. kaggle_environments/envs/open_spiel/proxy.py +138 -0
  186. kaggle_environments/envs/open_spiel/test_open_spiel.py +191 -0
  187. kaggle_environments/envs/rps/__init__.py +0 -0
  188. kaggle_environments/envs/rps/agents.py +84 -0
  189. kaggle_environments/envs/rps/helpers.py +25 -0
  190. kaggle_environments/envs/rps/rps.js +117 -0
  191. kaggle_environments/envs/rps/rps.json +63 -0
  192. kaggle_environments/envs/rps/rps.py +90 -0
  193. kaggle_environments/envs/rps/test_rps.py +110 -0
  194. kaggle_environments/envs/rps/utils.py +7 -0
  195. kaggle_environments/envs/tictactoe/test_tictactoe.py +43 -77
  196. kaggle_environments/envs/tictactoe/tictactoe.ipynb +1397 -0
  197. kaggle_environments/envs/tictactoe/tictactoe.json +10 -2
  198. kaggle_environments/envs/tictactoe/tictactoe.py +1 -1
  199. kaggle_environments/errors.py +2 -4
  200. kaggle_environments/helpers.py +377 -0
  201. kaggle_environments/main.py +214 -50
  202. kaggle_environments/schemas.json +23 -18
  203. kaggle_environments/static/player.html +206 -74
  204. kaggle_environments/utils.py +46 -73
  205. kaggle_environments-1.20.0.dist-info/METADATA +25 -0
  206. kaggle_environments-1.20.0.dist-info/RECORD +211 -0
  207. {kaggle_environments-0.2.0.dist-info → kaggle_environments-1.20.0.dist-info}/WHEEL +1 -2
  208. kaggle_environments-1.20.0.dist-info/entry_points.txt +3 -0
  209. kaggle_environments/envs/battlegeese/battlegeese.py +0 -219
  210. kaggle_environments/temp.py +0 -14
  211. kaggle_environments-0.2.0.dist-info/METADATA +0 -393
  212. kaggle_environments-0.2.0.dist-info/RECORD +0 -33
  213. kaggle_environments-0.2.0.dist-info/entry_points.txt +0 -3
  214. kaggle_environments-0.2.0.dist-info/top_level.txt +0 -1
  215. {kaggle_environments-0.2.0.dist-info → kaggle_environments-1.20.0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,1005 @@
1
+ # Copyright 2021 Kaggle Inc
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import math
16
+ import sys
17
+ from copy import deepcopy
18
+ from enum import Enum, auto
19
+ from functools import wraps
20
+ from typing import *
21
+
22
+ import kaggle_environments.helpers
23
+ from kaggle_environments.helpers import Direction, Point, group_by
24
+
25
+
26
+ # region Data Model Classes
27
+ class Observation(kaggle_environments.helpers.Observation):
28
+ """
29
+ Observation primarily used as a helper to construct the Board from the raw observation.
30
+ This provides bindings for the observation type described at https://github.com/Kaggle/kaggle-environments/blob/master/kaggle_environments/envs/kore/kore.json
31
+ """
32
+
33
+ @property
34
+ def kore(self) -> List[float]:
35
+ """Serialized list of available kore per cell on the board."""
36
+ return self["kore"]
37
+
38
+ @property
39
+ def players(self) -> List[List[int]]:
40
+ """List of players and their assets."""
41
+ return self["players"]
42
+
43
+ @property
44
+ def player(self) -> int:
45
+ """The current agent's player index."""
46
+ return self["player"]
47
+
48
+
49
+ class Configuration(kaggle_environments.helpers.Configuration):
50
+ """
51
+ Configuration provides access to tunable parameters in the environment.
52
+ This provides bindings for the configuration type described at https://github.com/Kaggle/kaggle-environments/blob/master/kaggle_environments/envs/kore/kore.json
53
+ """
54
+
55
+ @property
56
+ def agent_timeout(self) -> float:
57
+ """Maximum runtime (seconds) to initialize an agent."""
58
+ return self["agentTimeout"]
59
+
60
+ @property
61
+ def starting_kore(self) -> int:
62
+ """The starting amount of kore available on the board."""
63
+ return self["startingKore"]
64
+
65
+ @property
66
+ def size(self) -> int:
67
+ """The number of cells vertically and horizontally on the board."""
68
+ return self["size"]
69
+
70
+ @property
71
+ def spawn_cost(self) -> int:
72
+ """The amount of kore to spawn a new ship."""
73
+ return self["spawnCost"]
74
+
75
+ @property
76
+ def convert_cost(self) -> int:
77
+ """The amount of ships needed from a fleet to create a shipyard."""
78
+ return self["convertCost"]
79
+
80
+ @property
81
+ def regen_rate(self) -> float:
82
+ """The rate kore regenerates on the board."""
83
+ return self["regenRate"]
84
+
85
+ @property
86
+ def max_cell_kore(self) -> int:
87
+ """The maximum kore that can be in any cell."""
88
+ return self["maxRegenCellKore"]
89
+
90
+ @property
91
+ def random_seed(self) -> int:
92
+ """The seed to the random number generator (0 means no seed)."""
93
+ return self["randomSeed"]
94
+
95
+
96
+ class ShipyardActionType(Enum):
97
+ SPAWN = auto()
98
+ LAUNCH = auto()
99
+
100
+ def __str__(self) -> str:
101
+ return self.name
102
+
103
+
104
+ class ShipyardAction:
105
+ def __init__(self, type: ShipyardActionType, num_ships: Optional[int], flight_plan: Optional[str]) -> None:
106
+ self._type = type
107
+ assert num_ships >= 0, "must be a non-negative number"
108
+ assert num_ships == int(num_ships), "must be an integer"
109
+ self._num_ships = num_ships
110
+ self._flight_plan = flight_plan
111
+
112
+ def __str__(self) -> str:
113
+ if self._type == ShipyardActionType.SPAWN:
114
+ return f"{self._type.name}_{self._num_ships}"
115
+ if self._type == ShipyardActionType.LAUNCH:
116
+ return f"{self._type.name}_{self._num_ships}_{self._flight_plan}"
117
+
118
+ @property
119
+ def name(self):
120
+ return str(self)
121
+
122
+ @staticmethod
123
+ def from_str(raw: str):
124
+ if not raw:
125
+ return None
126
+ if raw.startswith(ShipyardActionType.SPAWN.name):
127
+ _, ship_str = raw.split("_")
128
+ try:
129
+ num_ships = int(ship_str.split(".")[0])
130
+ except _:
131
+ num_ships = 0
132
+ return ShipyardAction.spawn_ships(num_ships)
133
+ if raw.startswith(ShipyardActionType.LAUNCH.name):
134
+ _, ship_str, plan_str = raw.split("_")
135
+ try:
136
+ num_ships = int(ship_str.split(".")[0])
137
+ except _:
138
+ num_ships = 0
139
+ return ShipyardAction.launch_fleet_with_flight_plan(num_ships, plan_str)
140
+
141
+ @staticmethod
142
+ def launch_fleet_in_direction(number_ships: int, direction: Direction):
143
+ flight_plan = None
144
+ if isinstance(direction, Direction):
145
+ flight_plan = direction.to_char()
146
+ else:
147
+ flight_plan = flight_plan.upper()
148
+ return ShipyardAction.launch_fleet_with_flight_plan(number_ships, flight_plan)
149
+
150
+ @staticmethod
151
+ def launch_fleet_with_flight_plan(number_ships: int, flight_plan: str):
152
+ flight_plan = flight_plan.upper()
153
+ assert number_ships > 0, "must be a positive number_ships"
154
+ assert number_ships == int(number_ships), "must be an integer number_ships"
155
+ assert flight_plan is not None and len(flight_plan) > 0, "flight_plan must be a str of len > 0"
156
+ assert flight_plan[0].isalpha() and flight_plan[0] in "NESW", (
157
+ "flight_plan must start with a valid direciton NESW"
158
+ )
159
+ assert all([c in "NESWC0123456789" for c in flight_plan]), (
160
+ "flight_plan (" + flight_plan + ")can only contain NESWC0-9"
161
+ )
162
+ if len(flight_plan) > Fleet.max_flight_plan_len_for_ship_count(number_ships):
163
+ print(
164
+ "flight plan will be truncated: flight plan for "
165
+ + str(number_ships)
166
+ + " must be at most "
167
+ + str(Fleet.max_flight_plan_len_for_ship_count(number_ships))
168
+ )
169
+ return ShipyardAction(ShipyardActionType.LAUNCH, number_ships, flight_plan)
170
+
171
+ @staticmethod
172
+ def spawn_ships(number_ships: int):
173
+ assert number_ships == int(number_ships), "must be an integer number_ships"
174
+ return ShipyardAction(ShipyardActionType.SPAWN, number_ships, None)
175
+
176
+ @property
177
+ def action_type(self) -> ShipyardActionType:
178
+ return self._type
179
+
180
+ @property
181
+ def num_ships(self) -> Optional[int]:
182
+ return self._num_ships
183
+
184
+ @property
185
+ def flight_plan(self) -> Optional[str]:
186
+ return self._flight_plan
187
+
188
+
189
+ FleetId = NewType("FleetId", str)
190
+ ShipyardId = NewType("ShipyardId", str)
191
+ PlayerId = NewType("PlayerId", int)
192
+
193
+
194
+ class Cell:
195
+ def __init__(
196
+ self,
197
+ position: Point,
198
+ kore: float,
199
+ shipyard_id: Optional[ShipyardId],
200
+ fleet_id: Optional[FleetId],
201
+ board: "Board",
202
+ ) -> None:
203
+ self._position = position
204
+ self._kore = kore
205
+ self._shipyard_id = shipyard_id
206
+ self._fleet_id = fleet_id
207
+ self._board = board
208
+
209
+ @property
210
+ def position(self) -> Point:
211
+ return self._position
212
+
213
+ @property
214
+ def kore(self) -> float:
215
+ return self._kore
216
+
217
+ @property
218
+ def shipyard_id(self) -> Optional[ShipyardId]:
219
+ return self._shipyard_id
220
+
221
+ @property
222
+ def fleet_id(self) -> Optional[FleetId]:
223
+ return self._fleet_id
224
+
225
+ @property
226
+ def fleet(self) -> Optional["Fleet"]:
227
+ """Returns the fleet on this cell if it exists and None otherwise."""
228
+ return self._board.fleets.get(self.fleet_id)
229
+
230
+ @property
231
+ def shipyard(self) -> Optional["Shipyard"]:
232
+ """Returns the shipyard on this cell if it exists and None otherwise."""
233
+ return self._board.shipyards.get(self.shipyard_id)
234
+
235
+ def neighbor(self, offset: Point) -> "Cell":
236
+ """Returns the cell at self.position + offset."""
237
+ (x, y) = self.position + offset
238
+ return self._board[x, y]
239
+
240
+ @property
241
+ def north(self) -> "Cell":
242
+ """Returns the cell north of this cell."""
243
+ return self.neighbor(Direction.NORTH.to_point())
244
+
245
+ @property
246
+ def south(self) -> "Cell":
247
+ """Returns the cell south of this cell."""
248
+ return self.neighbor(Direction.SOUTH.to_point())
249
+
250
+ @property
251
+ def east(self) -> "Cell":
252
+ """Returns the cell east of this cell."""
253
+ return self.neighbor(Direction.EAST.to_point())
254
+
255
+ @property
256
+ def west(self) -> "Cell":
257
+ """Returns the cell west of this cell."""
258
+ return self.neighbor(Direction.WEST.to_point())
259
+
260
+
261
+ class Fleet:
262
+ def __init__(
263
+ self,
264
+ fleet_id: FleetId,
265
+ ship_count: int,
266
+ direction: Direction,
267
+ position: Point,
268
+ kore: int,
269
+ flight_plan: str,
270
+ player_id: PlayerId,
271
+ board: "Board",
272
+ ) -> None:
273
+ self._id = fleet_id
274
+ self._ship_count = ship_count
275
+ self._direction = direction
276
+ self._position = position
277
+ self._flight_plan = flight_plan
278
+ self._kore = kore
279
+ self._player_id = player_id
280
+ self._board = board
281
+
282
+ @property
283
+ def id(self) -> FleetId:
284
+ return self._id
285
+
286
+ @property
287
+ def ship_count(self) -> int:
288
+ return self._ship_count
289
+
290
+ @property
291
+ def direction(self) -> Direction:
292
+ return self._direction
293
+
294
+ @property
295
+ def position(self) -> Point:
296
+ return self._position
297
+
298
+ @property
299
+ def kore(self) -> int:
300
+ return self._kore
301
+
302
+ @property
303
+ def player_id(self) -> PlayerId:
304
+ return self._player_id
305
+
306
+ @property
307
+ def cell(self) -> Cell:
308
+ """Returns the cell this fleet is on."""
309
+ return self._board[self.position]
310
+
311
+ @property
312
+ def player(self) -> "Player":
313
+ """Returns the player that owns this ship."""
314
+ return self._board.players[self.player_id]
315
+
316
+ @property
317
+ def flight_plan(self) -> str:
318
+ """Returns the current flight plan of the fleet"""
319
+ return self._flight_plan
320
+
321
+ @property
322
+ def collection_rate(self) -> float:
323
+ """ln(ship_count) / 10"""
324
+ return min(math.log(self.ship_count) / 20, 0.99)
325
+
326
+ @staticmethod
327
+ def max_flight_plan_len_for_ship_count(ship_count) -> int:
328
+ """Returns the length of the longest possible flight plan this fleet can be assigned"""
329
+ return math.floor(2 * math.log(ship_count)) + 1
330
+
331
+ @property
332
+ def _observation(self) -> List[int]:
333
+ """Converts a fleet back to the normalized observation subset that constructed it."""
334
+ return [
335
+ self.position.to_index(self._board.configuration.size),
336
+ self.kore,
337
+ self.ship_count,
338
+ self.direction.to_index(),
339
+ self.flight_plan,
340
+ ]
341
+
342
+ def less_than_other_allied_fleet(self, other):
343
+ if not self.ship_count == other.ship_count:
344
+ return self.ship_count < other.ship_count
345
+ if not self.kore == other.kore:
346
+ return self.kore < other.kore
347
+ return self.direction.to_index() > other.direction.to_index()
348
+
349
+
350
+ upgrade_times = [pow(i, 2) + 1 for i in range(1, 10)]
351
+ SPAWN_VALUES = []
352
+ current = 0
353
+ for t in upgrade_times:
354
+ current += t
355
+ SPAWN_VALUES.append(current)
356
+
357
+
358
+ class Shipyard:
359
+ def __init__(
360
+ self,
361
+ shipyard_id: ShipyardId,
362
+ ship_count: int,
363
+ position: Point,
364
+ player_id: PlayerId,
365
+ turns_controlled: int,
366
+ board: "Board",
367
+ next_action: Optional[ShipyardAction] = None,
368
+ ) -> None:
369
+ self._id = shipyard_id
370
+ self._ship_count = ship_count
371
+ self._position = position
372
+ self._player_id = player_id
373
+ self._turns_controlled = turns_controlled
374
+ self._board = board
375
+ self._next_action = next_action
376
+
377
+ @property
378
+ def id(self) -> ShipyardId:
379
+ return self._id
380
+
381
+ @property
382
+ def ship_count(self):
383
+ return self._ship_count
384
+
385
+ @property
386
+ def position(self) -> Point:
387
+ return self._position
388
+
389
+ @property
390
+ def player_id(self) -> PlayerId:
391
+ return self._player_id
392
+
393
+ @property
394
+ def max_spawn(self) -> int:
395
+ for idx, target in enumerate(SPAWN_VALUES):
396
+ if self._turns_controlled < target:
397
+ return idx + 1
398
+ return len(SPAWN_VALUES) + 1
399
+
400
+ @property
401
+ def cell(self) -> Cell:
402
+ """Returns the cell this shipyard is on."""
403
+ return self._board[self.position]
404
+
405
+ @property
406
+ def player(self) -> "Player":
407
+ return self._board.players[self.player_id]
408
+
409
+ @property
410
+ def next_action(self) -> ShipyardAction:
411
+ """Returns the action that will be executed by this shipyard when Board.next() is called (when the current turn ends)."""
412
+ return self._next_action
413
+
414
+ @next_action.setter
415
+ def next_action(self, value: Optional[ShipyardAction]) -> None:
416
+ """Sets the action that will be executed by this shipyard when Board.next() is called (when the current turn ends)."""
417
+ self._next_action = value
418
+
419
+ @property
420
+ def _observation(self) -> List[int]:
421
+ """Converts a shipyard back to the normalized observation subset that constructed it."""
422
+ return [self.position.to_index(self._board.configuration.size), self.ship_count, self._turns_controlled]
423
+
424
+
425
+ class Player:
426
+ def __init__(
427
+ self, player_id: PlayerId, kore: int, shipyard_ids: List[ShipyardId], fleet_ids: List[FleetId], board: "Board"
428
+ ) -> None:
429
+ self._id = player_id
430
+ self._kore = kore
431
+ self._shipyard_ids = shipyard_ids
432
+ self._fleet_ids = fleet_ids
433
+ self._board = board
434
+
435
+ @property
436
+ def id(self) -> PlayerId:
437
+ return self._id
438
+
439
+ @property
440
+ def kore(self) -> int:
441
+ return self._kore
442
+
443
+ @property
444
+ def shipyard_ids(self) -> List[ShipyardId]:
445
+ return self._shipyard_ids
446
+
447
+ @property
448
+ def fleet_ids(self) -> List[FleetId]:
449
+ return self._fleet_ids
450
+
451
+ @property
452
+ def shipyards(self) -> List[Shipyard]:
453
+ """Returns all shipyards owned by this player."""
454
+ return [self._board.shipyards[shipyard_id] for shipyard_id in self.shipyard_ids]
455
+
456
+ @property
457
+ def fleets(self) -> List[Fleet]:
458
+ """Returns all fleets owned by this player."""
459
+ return [self._board.fleets[fleet_id] for fleet_id in self.fleet_ids]
460
+
461
+ @property
462
+ def is_current_player(self) -> bool:
463
+ """Returns whether this player is the current player (generally if this returns True, this player is you)."""
464
+ return self.id == self._board.current_player_id
465
+
466
+ @property
467
+ def next_actions(self) -> Dict[str, str]:
468
+ """Returns all queued fleet and shipyard actions for this player formatted for the kore interpreter to receive as an agent response."""
469
+ shipyard_actions = {
470
+ shipyard.id: shipyard.next_action.name for shipyard in self.shipyards if shipyard.next_action is not None
471
+ }
472
+ return {**shipyard_actions}
473
+
474
+ @property
475
+ def _observation(self):
476
+ """Converts a player back to the normalized observation subset that constructed it."""
477
+ shipyards = {shipyard.id: shipyard._observation for shipyard in self.shipyards}
478
+ fleets = {fleet.id: fleet._observation for fleet in self.fleets}
479
+ return [self.kore, shipyards, fleets]
480
+
481
+
482
+ # endregion
483
+
484
+
485
+ class Board:
486
+ def __init__(
487
+ self,
488
+ raw_observation: Dict[str, Any],
489
+ raw_configuration: Union[Configuration, Dict[str, Any]],
490
+ next_actions: Optional[List[Dict[str, str]]] = None,
491
+ ) -> None:
492
+ """
493
+ Creates a board from the provided observation, configuration, and next_actions as specified by
494
+ https://github.com/Kaggle/kaggle-environments/blob/master/kaggle_environments/envs/kore/kore.json
495
+ Board tracks players (by id), fleets (by id), shipyards (by id), and cells (by position).
496
+ Each entity contains both key values (e.g. fleet.player_id) as well as entity references (e.g. fleet.player).
497
+ References are deep and chainable e.g.
498
+ [fleet.kore for player in board.players for fleet in player.fleets]
499
+ fleet.player.shipyards[0].cell.north.east.fleet
500
+ Consumers should not set or modify any attributes except and Shipyard.next_action
501
+ """
502
+ observation = Observation(raw_observation)
503
+ # next_actions is effectively a Dict[Union[[FleetId, FleetAction], [ShipyardId, ShipyardAction]]]
504
+ # but that type's not very expressible so we simplify it to Dict[str, str]
505
+ # Later we'll iterate through it once for each fleet and shipyard to pull all the actions out
506
+ next_actions = next_actions or ([{}] * len(observation.players))
507
+
508
+ self._step = observation.step
509
+ self._remaining_overage_time = observation.remaining_overage_time
510
+ self._configuration = Configuration(raw_configuration)
511
+ self._current_player_id = observation.player
512
+ self._players: Dict[PlayerId, Player] = {}
513
+ self._fleets: Dict[FleetId, Fleet] = {}
514
+ self._shipyards: Dict[ShipyardId, Shipyard] = {}
515
+ self._cells: Dict[Point, Cell] = {}
516
+
517
+ size = self.configuration.size
518
+ # Create a cell for every point in a size x size grid
519
+ for x in range(size):
520
+ for y in range(size):
521
+ position = Point(x, y)
522
+ kore = observation.kore[position.to_index(size)]
523
+ # We'll populate the cell's fleets and shipyards in _add_fleet and _add_shipyard
524
+ self.cells[position] = Cell(position, kore, None, None, self)
525
+
526
+ for player_id, player_observation in enumerate(observation.players):
527
+ # We know the len(player_observation) == 3 based on the schema -- this is a hack to have a tuple in json
528
+ [player_kore, player_shipyards, player_fleets] = player_observation
529
+ # We'll populate the player's fleets and shipyards in _add_fleet and _add_shipyard
530
+ self.players[player_id] = Player(player_id, player_kore, [], [], self)
531
+ player_actions = next_actions[player_id] or {}
532
+
533
+ for fleet_id, [fleet_index, fleet_kore, ship_count, direction, flight_plan] in player_fleets.items():
534
+ fleet_position = Point.from_index(fleet_index, size)
535
+ fleet_direction = Direction.from_index(direction)
536
+ self._add_fleet(
537
+ Fleet(
538
+ fleet_id, ship_count, fleet_direction, fleet_position, fleet_kore, flight_plan, player_id, self
539
+ )
540
+ )
541
+
542
+ for shipyard_id, [shipyard_index, ship_count, turns_controlled] in player_shipyards.items():
543
+ shipyard_position = Point.from_index(shipyard_index, size)
544
+ raw_action = player_actions.get(shipyard_id)
545
+ action = ShipyardAction.from_str(raw_action)
546
+ self._add_shipyard(
547
+ Shipyard(shipyard_id, ship_count, shipyard_position, player_id, turns_controlled, self, action)
548
+ )
549
+
550
+ @property
551
+ def configuration(self) -> Configuration:
552
+ return self._configuration
553
+
554
+ @property
555
+ def players(self) -> Dict[PlayerId, Player]:
556
+ return self._players
557
+
558
+ @property
559
+ def fleets(self) -> Dict[FleetId, Fleet]:
560
+ """Returns all fleets on the current board."""
561
+ return self._fleets
562
+
563
+ @property
564
+ def shipyards(self) -> Dict[ShipyardId, Shipyard]:
565
+ """Returns all shipyards on the current board."""
566
+ return self._shipyards
567
+
568
+ @property
569
+ def cells(self) -> Dict[Point, Cell]:
570
+ """Returns all cells on the current board."""
571
+ return self._cells
572
+
573
+ @property
574
+ def step(self) -> int:
575
+ return self._step
576
+
577
+ @property
578
+ def current_player_id(self) -> PlayerId:
579
+ return self._current_player_id
580
+
581
+ @property
582
+ def current_player(self) -> Player:
583
+ """Returns the current player (generally this is you)."""
584
+ return self._players[self.current_player_id]
585
+
586
+ @property
587
+ def opponents(self) -> List[Player]:
588
+ """
589
+ Returns all players that aren't the current player.
590
+ You can get all opponent fleets with [fleet for fleet in player.fleets for player in board.opponents]
591
+ """
592
+ return [player for player in self.players.values() if not player.is_current_player]
593
+
594
+ @property
595
+ def observation(self) -> Dict[str, Any]:
596
+ """Converts a Board back to the normalized observation that constructed it."""
597
+ size = self.configuration.size
598
+ kore = [self[Point.from_index(index, size)].kore for index in range(size * size)]
599
+ players = [player._observation for player in self.players.values()]
600
+
601
+ return {
602
+ "kore": kore,
603
+ "players": players,
604
+ "player": self.current_player_id,
605
+ "step": self.step,
606
+ "remainingOverageTime": self._remaining_overage_time,
607
+ }
608
+
609
+ def __deepcopy__(self, _) -> "Board":
610
+ actions = [player.next_actions for player in self.players.values()]
611
+ return Board(self.observation, self.configuration, actions)
612
+
613
+ def __getitem__(self, point: Union[Tuple[int, int], Point]) -> Cell:
614
+ """
615
+ This method will wrap the supplied position to fit within the board size and return the cell at that location.
616
+ e.g. on a 3x3 board, board[2, 1] is the same as board[5, 1]
617
+ """
618
+ if not isinstance(point, Point):
619
+ (x, y) = point
620
+ point = Point(x, y)
621
+ return self._cells[point % self.configuration.size]
622
+
623
+ def __str__(self) -> str:
624
+ """
625
+ The board is printed in a grid with the following rules:
626
+ Capital letters are shipyards
627
+ Lower case letters are fleets
628
+ Digits are cell kore and scale from 0-9 directly proportional to a value between 0 and self.configuration.max_cell_kore
629
+ Player 1 is letter a/A
630
+ Player 2 is letter b/B
631
+ etc.
632
+ """
633
+ size = self.configuration.size
634
+ result = ""
635
+ for y in range(size):
636
+ for x in range(size):
637
+ cell = self[(x, size - y - 1)]
638
+ result += "|"
639
+ result += chr(ord("a") + cell.ship.player_id) if cell.fleet is not None else " "
640
+ # This normalizes a value from 0 to max_cell kore to a value from 0 to 9
641
+ normalized_kore = int(9.0 * cell.kore / float(self.configuration.max_cell_kore))
642
+ result += str(normalized_kore)
643
+ result += chr(ord("A") + cell.shipyard.player_id) if cell.shipyard is not None else " "
644
+ result += "|\n"
645
+ return result
646
+
647
+ def _add_fleet(self: "Board", fleet: Fleet) -> None:
648
+ fleet.player.fleet_ids.append(fleet.id)
649
+ fleet.cell._fleet_id = fleet.id
650
+ self._fleets[fleet.id] = fleet
651
+
652
+ def _add_shipyard(self: "Board", shipyard: Shipyard) -> None:
653
+ shipyard.player.shipyard_ids.append(shipyard.id)
654
+ shipyard.cell._shipyard_id = shipyard.id
655
+ shipyard.cell._kore = 0
656
+ self._shipyards[shipyard.id] = shipyard
657
+
658
+ def _delete_fleet(self: "Board", fleet: Fleet) -> None:
659
+ fleet.player.fleet_ids.remove(fleet.id)
660
+ if fleet.cell.fleet_id == fleet.id:
661
+ fleet.cell._fleet_id = None
662
+ del self._fleets[fleet.id]
663
+
664
+ def _delete_shipyard(self: "Board", shipyard: Shipyard) -> None:
665
+ shipyard.player.shipyard_ids.remove(shipyard.id)
666
+ if shipyard.cell.shipyard_id == shipyard.id:
667
+ shipyard.cell._shipyard_id = None
668
+ del self._shipyards[shipyard.id]
669
+
670
+ def get_fleet_at_point(self: "Board", position: Point) -> Optional[Fleet]:
671
+ matches = [fleet for fleet in self.fleets.values() if fleet.position == position]
672
+ if matches:
673
+ assert len(matches) == 1
674
+ return matches[0]
675
+ return None
676
+
677
+ def get_shipyard_at_point(self: "Board", position: Point) -> Optional[Shipyard]:
678
+ matches = [shipyard for shipyard in self.shipyards.values() if shipyard.position == position]
679
+ if matches:
680
+ assert len(matches) == 1
681
+ return matches[0]
682
+ return None
683
+
684
+ def get_cell_at_point(self: "Board", position: Point):
685
+ return self.cells.get(position)
686
+
687
+ def print(self: "Board") -> None:
688
+ size = self.configuration.size
689
+ player_chars = {pid: alpha for pid, alpha in zip(self.players, "abcdef"[: len(self.players)])}
690
+ print(self.configuration.size * "=")
691
+ for i in range(size):
692
+ row = ""
693
+ for j in range(size):
694
+ pos = Point(j, size - 1 - i)
695
+ curr_cell = self.cells[pos]
696
+ if curr_cell.shipyard is not None:
697
+ row += player_chars[curr_cell.shipyard.player_id].upper()
698
+ elif curr_cell.fleet is not None:
699
+ row += player_chars[curr_cell.fleet.player_id]
700
+ elif curr_cell.kore <= 50:
701
+ row += " "
702
+ elif curr_cell.kore <= 250:
703
+ row += "."
704
+ elif curr_cell.kore <= 400:
705
+ row += "*"
706
+ elif curr_cell.kore > 400:
707
+ row += "o"
708
+ print(row)
709
+ print(self.configuration.size * "=")
710
+
711
+ def print_kore(self: "Board") -> None:
712
+ size = self.configuration.size
713
+ print(self.configuration.size * "=")
714
+ for i in range(size):
715
+ row = ""
716
+ for j in range(size):
717
+ pos = Point(j, size - 1 - i)
718
+ curr_cell = self.cells[pos]
719
+ row += str(int(curr_cell.kore)) + ","
720
+ print(row)
721
+ print(self.configuration.size * "=")
722
+
723
+ def next(self) -> "Board":
724
+ """
725
+ Returns a new board with the current board's next actions applied.
726
+ The current board is unmodified.
727
+ This can form a kore interpreter, e.g.
728
+ next_observation = Board(current_observation, configuration, actions).next().observation
729
+ """
730
+ # Create a copy of the board to modify so we don't affect the current board
731
+ board = deepcopy(self)
732
+ configuration = board.configuration
733
+ convert_cost = configuration.convert_cost
734
+ spawn_cost = configuration.spawn_cost
735
+ uid_counter = 0
736
+
737
+ # This is a consistent way to generate unique strings to form fleet and shipyard ids
738
+ def create_uid():
739
+ nonlocal uid_counter
740
+ uid_counter += 1
741
+ return f"{self.step + 1}-{uid_counter}"
742
+
743
+ # this checks the validity of a flight plan
744
+ def is_valid_flight_plan(flight_plan):
745
+ return len([c for c in flight_plan if c not in "NESWC0123456789"]) == 0
746
+
747
+ # Process actions and store the results in the fleets and shipyards lists for collision checking
748
+ for player in board.players.values():
749
+ for shipyard in player.shipyards:
750
+ if shipyard.next_action == None:
751
+ pass
752
+ elif shipyard.next_action.num_ships == 0:
753
+ pass
754
+ elif (
755
+ shipyard.next_action.action_type == ShipyardActionType.SPAWN
756
+ and player.kore >= spawn_cost * shipyard.next_action.num_ships
757
+ and shipyard.next_action.num_ships <= shipyard.max_spawn
758
+ ):
759
+ # Handle SPAWN actions
760
+ player._kore -= spawn_cost * shipyard.next_action.num_ships
761
+ shipyard._ship_count += shipyard.next_action.num_ships
762
+ elif (
763
+ shipyard.next_action.action_type == ShipyardActionType.LAUNCH
764
+ and shipyard.ship_count >= shipyard.next_action.num_ships
765
+ ):
766
+ flight_plan = shipyard.next_action.flight_plan
767
+ if not flight_plan or not is_valid_flight_plan(flight_plan):
768
+ continue
769
+ shipyard._ship_count -= shipyard.next_action.num_ships
770
+ direction = Direction.from_char(flight_plan[0])
771
+ max_flight_plan_len = Fleet.max_flight_plan_len_for_ship_count(shipyard.next_action.num_ships)
772
+ if len(flight_plan) > max_flight_plan_len:
773
+ flight_plan = flight_plan[:max_flight_plan_len]
774
+ board._add_fleet(
775
+ Fleet(
776
+ FleetId(create_uid()),
777
+ shipyard.next_action.num_ships,
778
+ direction,
779
+ shipyard.position,
780
+ 0,
781
+ flight_plan,
782
+ player.id,
783
+ board,
784
+ )
785
+ )
786
+
787
+ # Clear the shipyard's action so it doesn't repeat the same action automatically
788
+ for shipyard in player.shipyards:
789
+ shipyard.next_action = None
790
+ shipyard._turns_controlled += 1
791
+
792
+ def find_first_non_digit(candidate_str):
793
+ for i in range(len(candidate_str)):
794
+ if not candidate_str[i].isdigit():
795
+ return i
796
+ else:
797
+ return len(candidate_str) + 1
798
+ return 0
799
+
800
+ for fleet in player.fleets:
801
+ # remove any errant 0s
802
+ while fleet.flight_plan and fleet.flight_plan.startswith("0"):
803
+ fleet._flight_plan = fleet.flight_plan[1:]
804
+ if (
805
+ fleet.flight_plan
806
+ and fleet.flight_plan[0] == "C"
807
+ and fleet.ship_count >= convert_cost
808
+ and fleet.cell.shipyard_id is None
809
+ ):
810
+ player._kore += fleet.kore
811
+ fleet.cell._kore = 0
812
+ board._add_shipyard(
813
+ Shipyard(
814
+ ShipyardId(create_uid()),
815
+ fleet.ship_count - convert_cost,
816
+ fleet.position,
817
+ player.id,
818
+ 0,
819
+ board,
820
+ )
821
+ )
822
+ board._delete_fleet(fleet)
823
+ continue
824
+
825
+ while fleet.flight_plan and fleet.flight_plan[0] == "C":
826
+ # couldn't build, remove the Convert and continue with flight plan
827
+ fleet._flight_plan = fleet.flight_plan[1:]
828
+
829
+ if fleet.flight_plan and fleet.flight_plan[0].isalpha():
830
+ fleet._direction = Direction.from_char(fleet.flight_plan[0])
831
+ fleet._flight_plan = fleet.flight_plan[1:]
832
+ elif fleet.flight_plan:
833
+ idx = find_first_non_digit(fleet.flight_plan)
834
+ digits = int(fleet.flight_plan[:idx])
835
+ rest = fleet.flight_plan[idx:]
836
+ digits -= 1
837
+ if digits > 0:
838
+ fleet._flight_plan = str(digits) + rest
839
+ else:
840
+ fleet._flight_plan = rest
841
+
842
+ # continue moving in the fleet's direction
843
+ fleet.cell._fleet_id = None
844
+ fleet._position = fleet.position.translate(fleet.direction.to_point(), configuration.size)
845
+ # We don't set the new cell's fleet_id here as it would be overwritten by another fleet in the case of collision.
846
+
847
+ def combine_fleets(fid1: FleetId, fid2: FleetId) -> FleetId:
848
+ f1 = board.fleets[fid1]
849
+ f2 = board.fleets[fid2]
850
+ if f1.less_than_other_allied_fleet(f2):
851
+ f1, f2 = f2, f1
852
+ fid1, fid2 = fid2, fid1
853
+ f1._kore += f2.kore
854
+ f1._ship_count += f2._ship_count
855
+ board._delete_fleet(f2)
856
+ return fid1
857
+
858
+ # resolve any allied fleets that ended up in the same square
859
+ fleets_by_loc = group_by(player.fleets, lambda fleet: fleet.position.to_index(configuration.size))
860
+ for value in fleets_by_loc.values():
861
+ value.sort(key=lambda fleet: (fleet.ship_count, fleet.kore, -fleet.direction.to_index()), reverse=True)
862
+ fid = value[0].id
863
+ for i in range(1, len(value)):
864
+ fid = combine_fleets(fid, value[i].id)
865
+
866
+ # Lets just check and make sure.
867
+ assert player.kore >= 0
868
+
869
+ def resolve_collision(fleets: List[Fleet]) -> Tuple[Optional[Fleet], List[Fleet]]:
870
+ """
871
+ Accepts the list of fleets at a particular position (must not be empty).
872
+ Returns the fleet with the most ships or None in the case of a tie along with all other fleets.
873
+ """
874
+ if len(fleets) == 1:
875
+ return fleets[0], []
876
+ fleets_by_ships = group_by(fleets, lambda fleet: fleet.ship_count)
877
+ most_ships = max(fleets_by_ships.keys())
878
+ largest_fleets = fleets_by_ships[most_ships]
879
+ if len(largest_fleets) == 1:
880
+ # There was a winner, return it
881
+ winner = largest_fleets[0]
882
+ return winner, [fleet for fleet in fleets if fleet != winner]
883
+ # There was a tie for most ships, all are deleted
884
+ return None, fleets
885
+
886
+ # Check for fleet to fleet collisions
887
+ fleet_collision_groups = group_by(board.fleets.values(), lambda fleet: fleet.position)
888
+ for position, collided_fleets in fleet_collision_groups.items():
889
+ winner, deleted = resolve_collision(collided_fleets)
890
+ shipyard = group_by(board.shipyards.values(), lambda shipyard: shipyard.position).get(position)
891
+ if winner is not None:
892
+ winner.cell._fleet_id = winner.id
893
+ max_enemy_size = max([fleet.ship_count for fleet in deleted]) if deleted else 0
894
+ winner._ship_count -= max_enemy_size
895
+ for fleet in deleted:
896
+ board._delete_fleet(fleet)
897
+ if winner is not None:
898
+ # Winner takes deleted fleets' kore
899
+ winner._kore += fleet.kore
900
+ elif winner is None and shipyard and shipyard[0].player:
901
+ # Desposit the kore into the shipyard
902
+ shipyard[0].player._kore += fleet.kore
903
+ elif winner is None:
904
+ # Desposit the kore on the square
905
+ board.cells[position]._kore += fleet.kore
906
+
907
+ # Check for fleet to shipyard collisions
908
+ for shipyard in list(board.shipyards.values()):
909
+ fleet = shipyard.cell.fleet
910
+ if fleet is not None and fleet.player_id != shipyard.player_id:
911
+ if fleet.ship_count > shipyard.ship_count:
912
+ count = fleet.ship_count - shipyard.ship_count
913
+ board._delete_shipyard(shipyard)
914
+ board._add_shipyard(
915
+ Shipyard(ShipyardId(create_uid()), count, shipyard.position, fleet.player.id, 1, board)
916
+ )
917
+ fleet.player._kore += fleet.kore
918
+ board._delete_fleet(fleet)
919
+ else:
920
+ shipyard._ship_count -= fleet.ship_count
921
+ shipyard.player._kore += fleet.kore
922
+ board._delete_fleet(fleet)
923
+
924
+ # Deposit kore from fleets into shipyards
925
+ for shipyard in list(board.shipyards.values()):
926
+ fleet = shipyard.cell.fleet
927
+ if fleet is not None and fleet.player_id == shipyard.player_id:
928
+ shipyard.player._kore += fleet.kore
929
+ shipyard._ship_count += fleet.ship_count
930
+ board._delete_fleet(fleet)
931
+
932
+ # apply fleet to fleet damage on all orthagonally adjacent cells
933
+ incoming_fleet_dmg = DefaultDict(lambda: DefaultDict(int))
934
+ for fleet in board.fleets.values():
935
+ for direction in Direction.list_directions():
936
+ curr_pos = fleet.position.translate(direction.to_point(), board.configuration.size)
937
+ fleet_at_pos = board.get_fleet_at_point(curr_pos)
938
+ if fleet_at_pos and not fleet_at_pos.player_id == fleet.player_id:
939
+ incoming_fleet_dmg[fleet_at_pos.id][fleet.id] = fleet.ship_count
940
+
941
+ # dump 1/2 kore to the cell of killed fleets
942
+ # mark the other 1/2 kore to go to surrounding fleets proportionally
943
+ to_distribute = DefaultDict(lambda: DefaultDict(int))
944
+ for fleet_id, fleet_dmg_dict in incoming_fleet_dmg.items():
945
+ fleet = board.fleets[fleet_id]
946
+ damage = sum(fleet_dmg_dict.values())
947
+ if damage >= fleet.ship_count:
948
+ fleet.cell._kore += fleet.kore / 2
949
+ to_split = fleet.kore / 2
950
+ for f_id, dmg in fleet_dmg_dict.items():
951
+ to_distribute[f_id][fleet.position.to_index(board.configuration.size)] = to_split * dmg / damage
952
+ board._delete_fleet(fleet)
953
+ else:
954
+ fleet._ship_count -= damage
955
+
956
+ # give kore claimed above to surviving fleets, otherwise add it to the kore of the tile where the fleet died
957
+ for fleet_id, loc_kore_dict in to_distribute.items():
958
+ fleet = board.fleets.get(fleet_id)
959
+ if fleet:
960
+ fleet._kore += sum(loc_kore_dict.values())
961
+ else:
962
+ for loc_idx, kore in loc_kore_dict.items():
963
+ board.cells.get(Point.from_index(loc_idx, board.configuration.size))._kore += kore
964
+
965
+ # Collect kore from cells into fleets
966
+ for fleet in board.fleets.values():
967
+ cell = fleet.cell
968
+ delta_kore = round(cell.kore * min(fleet.collection_rate, 0.99), 3)
969
+ if delta_kore > 0:
970
+ fleet._kore += delta_kore
971
+ cell._kore -= delta_kore
972
+
973
+ # Regenerate kore in cells
974
+ for cell in board.cells.values():
975
+ if cell.fleet_id is None and cell.shipyard_id is None:
976
+ if cell.kore < configuration.max_cell_kore:
977
+ next_kore = round(cell.kore * (1 + configuration.regen_rate), 3)
978
+ cell._kore = next_kore
979
+
980
+ board._step += 1
981
+
982
+ # self.print()
983
+
984
+ return board
985
+
986
+
987
+ def board_agent(agent: Callable[[Board], None]):
988
+ """
989
+ Decorator used to create an agent that modifies a board rather than an observation and a configuration
990
+ Automatically returns the modified board's next actions
991
+
992
+ @board_agent
993
+ def my_agent(board: Board) -> None:
994
+ ...
995
+ """
996
+
997
+ @wraps(agent)
998
+ def agent_wrapper(obs, config) -> Dict[str, str]:
999
+ board = Board(obs, config)
1000
+ agent(board)
1001
+ return board.current_player.next_actions
1002
+
1003
+ if agent.__module__ is not None and agent.__module__ in sys.modules:
1004
+ setattr(sys.modules[agent.__module__], agent.__name__, agent_wrapper)
1005
+ return agent_wrapper