kaggle-environments 0.2.1__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 +295 -170
  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} +21 -14
  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 +340 -0
  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.1.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 -223
  210. kaggle_environments/temp.py +0 -14
  211. kaggle_environments-0.2.1.dist-info/METADATA +0 -393
  212. kaggle_environments-0.2.1.dist-info/RECORD +0 -32
  213. kaggle_environments-0.2.1.dist-info/entry_points.txt +0 -3
  214. kaggle_environments-0.2.1.dist-info/top_level.txt +0 -1
  215. {kaggle_environments-0.2.1.dist-info → kaggle_environments-1.20.0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,720 @@
1
+ # Copyright 2020 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 sys
16
+ from copy import deepcopy
17
+ from enum import Enum, auto
18
+ from functools import wraps
19
+ from typing import *
20
+
21
+ import kaggle_environments.helpers
22
+ from kaggle_environments.helpers import Point, group_by
23
+
24
+
25
+ # region Data Model Classes
26
+ class Observation(kaggle_environments.helpers.Observation):
27
+ """
28
+ Observation primarily used as a helper to construct the Board from the raw observation.
29
+ This provides bindings for the observation type described at https://github.com/Kaggle/kaggle-environments/blob/master/kaggle_environments/envs/halite/halite.json
30
+ """
31
+
32
+ @property
33
+ def halite(self) -> List[float]:
34
+ """Serialized list of available halite per cell on the board."""
35
+ return self["halite"]
36
+
37
+ @property
38
+ def players(self) -> List[List[int]]:
39
+ """List of players and their assets."""
40
+ return self["players"]
41
+
42
+ @property
43
+ def player(self) -> int:
44
+ """The current agent's player index."""
45
+ return self["player"]
46
+
47
+
48
+ class Configuration(kaggle_environments.helpers.Configuration):
49
+ """
50
+ Configuration provides access to tunable parameters in the environment.
51
+ This provides bindings for the configuration type described at https://github.com/Kaggle/kaggle-environments/blob/master/kaggle_environments/envs/halite/halite.json
52
+ """
53
+
54
+ @property
55
+ def agent_timeout(self) -> float:
56
+ """Maximum runtime (seconds) to initialize an agent."""
57
+ return self["agentTimeout"]
58
+
59
+ @property
60
+ def starting_halite(self) -> int:
61
+ """The starting amount of halite available on the board."""
62
+ return self["startingHalite"]
63
+
64
+ @property
65
+ def size(self) -> int:
66
+ """The number of cells vertically and horizontally on the board."""
67
+ return self["size"]
68
+
69
+ @property
70
+ def spawn_cost(self) -> int:
71
+ """The amount of halite to spawn a new ship."""
72
+ return self["spawnCost"]
73
+
74
+ @property
75
+ def convert_cost(self) -> int:
76
+ """The amount of halite to convert a ship into a shipyard."""
77
+ return self["convertCost"]
78
+
79
+ @property
80
+ def move_cost(self) -> float:
81
+ """The percent deducted from ship's current halite per move."""
82
+ return self["moveCost"]
83
+
84
+ @property
85
+ def collect_rate(self) -> float:
86
+ """The rate of halite collected by a ship from a cell by not moving."""
87
+ return self["collectRate"]
88
+
89
+ @property
90
+ def regen_rate(self) -> float:
91
+ """The rate halite regenerates on the board."""
92
+ return self["regenRate"]
93
+
94
+ @property
95
+ def max_cell_halite(self) -> int:
96
+ """The maximum halite that can be in any cell."""
97
+ return self["maxCellHalite"]
98
+
99
+ @property
100
+ def random_seed(self) -> int:
101
+ """The seed to the random number generator (0 means no seed)."""
102
+ return self["randomSeed"]
103
+
104
+
105
+ class ShipAction(Enum):
106
+ NORTH = auto()
107
+ EAST = auto()
108
+ SOUTH = auto()
109
+ WEST = auto()
110
+ CONVERT = auto()
111
+ # Use None to collect halite on a cell
112
+
113
+ def to_point(self) -> Optional[Point]:
114
+ """
115
+ This returns the position offset associated with a particular action or None if the action does not change the ship's position.
116
+ NORTH -> (0, 1)
117
+ EAST -> (1, 0)
118
+ SOUTH -> (0, -1)
119
+ WEST -> (-1, 0)
120
+ """
121
+ return (
122
+ Point(0, 1)
123
+ if self == ShipAction.NORTH
124
+ else Point(1, 0)
125
+ if self == ShipAction.EAST
126
+ else Point(0, -1)
127
+ if self == ShipAction.SOUTH
128
+ else Point(-1, 0)
129
+ if self == ShipAction.WEST
130
+ else None
131
+ )
132
+
133
+ def __str__(self) -> str:
134
+ return self.name
135
+
136
+ @staticmethod
137
+ def moves() -> List["ShipAction"]:
138
+ return [
139
+ ShipAction.NORTH,
140
+ ShipAction.EAST,
141
+ ShipAction.SOUTH,
142
+ ShipAction.WEST,
143
+ ]
144
+
145
+
146
+ class ShipyardAction(Enum):
147
+ SPAWN = auto()
148
+
149
+ def __str__(self) -> str:
150
+ return self.name
151
+
152
+
153
+ ShipId = NewType("ShipId", str)
154
+ ShipyardId = NewType("ShipyardId", str)
155
+ PlayerId = NewType("PlayerId", int)
156
+
157
+
158
+ class Cell:
159
+ def __init__(
160
+ self,
161
+ position: Point,
162
+ halite: float,
163
+ shipyard_id: Optional[ShipyardId],
164
+ ship_id: Optional[ShipId],
165
+ board: "Board",
166
+ ) -> None:
167
+ self._position = position
168
+ self._halite = halite
169
+ self._shipyard_id = shipyard_id
170
+ self._ship_id = ship_id
171
+ self._board = board
172
+
173
+ @property
174
+ def position(self) -> Point:
175
+ return self._position
176
+
177
+ @property
178
+ def halite(self) -> float:
179
+ return self._halite
180
+
181
+ @property
182
+ def shipyard_id(self) -> Optional[ShipyardId]:
183
+ return self._shipyard_id
184
+
185
+ @property
186
+ def ship_id(self) -> Optional[ShipId]:
187
+ return self._ship_id
188
+
189
+ @property
190
+ def ship(self) -> Optional["Ship"]:
191
+ """Returns the ship on this cell if it exists and None otherwise."""
192
+ return self._board.ships.get(self.ship_id)
193
+
194
+ @property
195
+ def shipyard(self) -> Optional["Shipyard"]:
196
+ """Returns the shipyard on this cell if it exists and None otherwise."""
197
+ return self._board.shipyards.get(self.shipyard_id)
198
+
199
+ def neighbor(self, offset: Point) -> "Cell":
200
+ """Returns the cell at self.position + offset."""
201
+ (x, y) = self.position + offset
202
+ return self._board[x, y]
203
+
204
+ @property
205
+ def north(self) -> "Cell":
206
+ """Returns the cell north of this cell."""
207
+ return self.neighbor(ShipAction.NORTH.to_point())
208
+
209
+ @property
210
+ def south(self) -> "Cell":
211
+ """Returns the cell south of this cell."""
212
+ return self.neighbor(ShipAction.SOUTH.to_point())
213
+
214
+ @property
215
+ def east(self) -> "Cell":
216
+ """Returns the cell east of this cell."""
217
+ return self.neighbor(ShipAction.EAST.to_point())
218
+
219
+ @property
220
+ def west(self) -> "Cell":
221
+ """Returns the cell west of this cell."""
222
+ return self.neighbor(ShipAction.WEST.to_point())
223
+
224
+
225
+ class Ship:
226
+ def __init__(
227
+ self,
228
+ ship_id: ShipId,
229
+ position: Point,
230
+ halite: int,
231
+ player_id: PlayerId,
232
+ board: "Board",
233
+ next_action: Optional[ShipAction] = None,
234
+ ) -> None:
235
+ self._id = ship_id
236
+ self._position = position
237
+ self._halite = halite
238
+ self._player_id = player_id
239
+ self._board = board
240
+ self._next_action = next_action
241
+
242
+ @property
243
+ def id(self) -> ShipId:
244
+ return self._id
245
+
246
+ @property
247
+ def position(self) -> Point:
248
+ return self._position
249
+
250
+ @property
251
+ def halite(self) -> int:
252
+ return self._halite
253
+
254
+ @property
255
+ def player_id(self) -> PlayerId:
256
+ return self._player_id
257
+
258
+ @property
259
+ def cell(self) -> Cell:
260
+ """Returns the cell this ship is on."""
261
+ return self._board[self.position]
262
+
263
+ @property
264
+ def player(self) -> "Player":
265
+ """Returns the player that owns this ship."""
266
+ return self._board.players[self.player_id]
267
+
268
+ @property
269
+ def next_action(self) -> Optional[ShipAction]:
270
+ """Returns the action that will be executed by this ship when Board.next() is called (when the current turn ends)."""
271
+ return self._next_action
272
+
273
+ @next_action.setter
274
+ def next_action(self, value: Optional[ShipAction]) -> None:
275
+ """Sets the action that will be executed by this ship when Board.next() is called (when the current turn ends)."""
276
+ self._next_action = value
277
+
278
+ @property
279
+ def _observation(self) -> List[int]:
280
+ """Converts a ship back to the normalized observation subset that constructed it."""
281
+ return [self.position.to_index(self._board.configuration.size), self.halite]
282
+
283
+
284
+ class Shipyard:
285
+ def __init__(
286
+ self,
287
+ shipyard_id: ShipyardId,
288
+ position: Point,
289
+ player_id: PlayerId,
290
+ board: "Board",
291
+ next_action: Optional[ShipyardAction] = None,
292
+ ) -> None:
293
+ self._id = shipyard_id
294
+ self._position = position
295
+ self._player_id = player_id
296
+ self._board = board
297
+ self._next_action = next_action
298
+
299
+ @property
300
+ def id(self) -> ShipyardId:
301
+ return self._id
302
+
303
+ @property
304
+ def position(self) -> Point:
305
+ return self._position
306
+
307
+ @property
308
+ def player_id(self) -> PlayerId:
309
+ return self._player_id
310
+
311
+ @property
312
+ def cell(self) -> Cell:
313
+ """Returns the cell this shipyard is on."""
314
+ return self._board[self.position]
315
+
316
+ @property
317
+ def player(self) -> "Player":
318
+ return self._board.players[self.player_id]
319
+
320
+ @property
321
+ def next_action(self) -> ShipyardAction:
322
+ """Returns the action that will be executed by this shipyard when Board.next() is called (when the current turn ends)."""
323
+ return self._next_action
324
+
325
+ @next_action.setter
326
+ def next_action(self, value: Optional[ShipyardAction]) -> None:
327
+ """Sets the action that will be executed by this shipyard when Board.next() is called (when the current turn ends)."""
328
+ self._next_action = value
329
+
330
+ @property
331
+ def _observation(self) -> int:
332
+ """Converts a shipyard back to the normalized observation subset that constructed it."""
333
+ return self.position.to_index(self._board.configuration.size)
334
+
335
+
336
+ class Player:
337
+ def __init__(
338
+ self, player_id: PlayerId, halite: int, shipyard_ids: List[ShipyardId], ship_ids: List[ShipId], board: "Board"
339
+ ) -> None:
340
+ self._id = player_id
341
+ self._halite = halite
342
+ self._shipyard_ids = shipyard_ids
343
+ self._ship_ids = ship_ids
344
+ self._board = board
345
+
346
+ @property
347
+ def id(self) -> PlayerId:
348
+ return self._id
349
+
350
+ @property
351
+ def halite(self) -> int:
352
+ return self._halite
353
+
354
+ @property
355
+ def shipyard_ids(self) -> List[ShipyardId]:
356
+ return self._shipyard_ids
357
+
358
+ @property
359
+ def ship_ids(self) -> List[ShipId]:
360
+ return self._ship_ids
361
+
362
+ @property
363
+ def shipyards(self) -> List[Shipyard]:
364
+ """Returns all shipyards owned by this player."""
365
+ return [self._board.shipyards[shipyard_id] for shipyard_id in self.shipyard_ids]
366
+
367
+ @property
368
+ def ships(self) -> List[Ship]:
369
+ """Returns all ships owned by this player."""
370
+ return [self._board.ships[ship_id] for ship_id in self.ship_ids]
371
+
372
+ @property
373
+ def is_current_player(self) -> bool:
374
+ """Returns whether this player is the current player (generally if this returns True, this player is you)."""
375
+ return self.id == self._board.current_player_id
376
+
377
+ @property
378
+ def next_actions(self) -> Dict[str, str]:
379
+ """Returns all queued ship and shipyard actions for this player formatted for the halite interpreter to receive as an agent response."""
380
+ ship_actions = {ship.id: ship.next_action.name for ship in self.ships if ship.next_action is not None}
381
+ shipyard_actions = {
382
+ shipyard.id: shipyard.next_action.name for shipyard in self.shipyards if shipyard.next_action is not None
383
+ }
384
+ return {**ship_actions, **shipyard_actions}
385
+
386
+ @property
387
+ def _observation(self):
388
+ """Converts a player back to the normalized observation subset that constructed it."""
389
+ shipyards = {shipyard.id: shipyard._observation for shipyard in self.shipyards}
390
+ ships = {ship.id: ship._observation for ship in self.ships}
391
+ return [self.halite, shipyards, ships]
392
+
393
+
394
+ # endregion
395
+
396
+
397
+ class Board:
398
+ def __init__(
399
+ self,
400
+ raw_observation: Dict[str, Any],
401
+ raw_configuration: Union[Configuration, Dict[str, Any]],
402
+ next_actions: Optional[List[Dict[str, str]]] = None,
403
+ ) -> None:
404
+ """
405
+ Creates a board from the provided observation, configuration, and next_actions as specified by
406
+ https://github.com/Kaggle/kaggle-environments/blob/master/kaggle_environments/envs/halite/halite.json
407
+ Board tracks players (by id), ships (by id), shipyards (by id), and cells (by position).
408
+ Each entity contains both key values (e.g. ship.player_id) as well as entity references (e.g. ship.player).
409
+ References are deep and chainable e.g.
410
+ [ship.halite for player in board.players for ship in player.ships]
411
+ ship.player.shipyards[0].cell.north.east.ship
412
+ Consumers should not set or modify any attributes except Ship.next_action and Shipyard.next_action
413
+ """
414
+ observation = Observation(raw_observation)
415
+ # next_actions is effectively a Dict[Union[[ShipId, ShipAction], [ShipyardId, ShipyardAction]]]
416
+ # but that type's not very expressible so we simplify it to Dict[str, str]
417
+ # Later we'll iterate through it once for each ship and shipyard to pull all the actions out
418
+ next_actions = next_actions or ([{}] * len(observation.players))
419
+
420
+ self._step = observation.step
421
+ self._remaining_overage_time = observation.remaining_overage_time
422
+ self._configuration = Configuration(raw_configuration)
423
+ self._current_player_id = observation.player
424
+ self._players: Dict[PlayerId, Player] = {}
425
+ self._ships: Dict[ShipId, Ship] = {}
426
+ self._shipyards: Dict[ShipyardId, Shipyard] = {}
427
+ self._cells: Dict[Point, Cell] = {}
428
+
429
+ size = self.configuration.size
430
+ # Create a cell for every point in a size x size grid
431
+ for x in range(size):
432
+ for y in range(size):
433
+ position = Point(x, y)
434
+ halite = observation.halite[position.to_index(size)]
435
+ # We'll populate the cell's ships and shipyards in _add_ship and _add_shipyard
436
+ self.cells[position] = Cell(position, halite, None, None, self)
437
+
438
+ for player_id, player_observation in enumerate(observation.players):
439
+ # We know the len(player_observation) == 3 based on the schema -- this is a hack to have a tuple in json
440
+ [player_halite, player_shipyards, player_ships] = player_observation
441
+ # We'll populate the player's ships and shipyards in _add_ship and _add_shipyard
442
+ self.players[player_id] = Player(player_id, player_halite, [], [], self)
443
+ player_actions = next_actions[player_id] or {}
444
+
445
+ for ship_id, [ship_index, ship_halite] in player_ships.items():
446
+ # In the raw observation, halite is stored as a 1d list but we convert it to a 2d dict for convenience
447
+ # Accordingly we also need to convert our list indices to dict keys / 2d positions
448
+ ship_position = Point.from_index(ship_index, size)
449
+ raw_action = player_actions.get(ship_id)
450
+ action = ShipAction[raw_action] if raw_action in ShipAction.__members__ else None
451
+ self._add_ship(Ship(ship_id, ship_position, ship_halite, player_id, self, action))
452
+
453
+ for shipyard_id, shipyard_index in player_shipyards.items():
454
+ shipyard_position = Point.from_index(shipyard_index, size)
455
+ raw_action = player_actions.get(shipyard_id)
456
+ action = ShipyardAction[raw_action] if raw_action in ShipyardAction.__members__ else None
457
+ self._add_shipyard(Shipyard(shipyard_id, shipyard_position, player_id, self, action))
458
+
459
+ @property
460
+ def configuration(self) -> Configuration:
461
+ return self._configuration
462
+
463
+ @property
464
+ def players(self) -> Dict[PlayerId, Player]:
465
+ return self._players
466
+
467
+ @property
468
+ def ships(self) -> Dict[ShipId, Ship]:
469
+ """Returns all ships on the current board."""
470
+ return self._ships
471
+
472
+ @property
473
+ def shipyards(self) -> Dict[ShipyardId, Shipyard]:
474
+ """Returns all shipyards on the current board."""
475
+ return self._shipyards
476
+
477
+ @property
478
+ def cells(self) -> Dict[Point, Cell]:
479
+ """Returns all cells on the current board."""
480
+ return self._cells
481
+
482
+ @property
483
+ def step(self) -> int:
484
+ return self._step
485
+
486
+ @property
487
+ def current_player_id(self) -> PlayerId:
488
+ return self._current_player_id
489
+
490
+ @property
491
+ def current_player(self) -> Player:
492
+ """Returns the current player (generally this is you)."""
493
+ return self._players[self.current_player_id]
494
+
495
+ @property
496
+ def opponents(self) -> List[Player]:
497
+ """
498
+ Returns all players that aren't the current player.
499
+ You can get all opponent ships with [ship for ship in player.ships for player in board.opponents]
500
+ """
501
+ return [player for player in self.players.values() if not player.is_current_player]
502
+
503
+ @property
504
+ def observation(self) -> Dict[str, Any]:
505
+ """Converts a Board back to the normalized observation that constructed it."""
506
+ size = self.configuration.size
507
+ halite = [self[Point.from_index(index, size)].halite for index in range(size * size)]
508
+ players = [player._observation for player in self.players.values()]
509
+
510
+ return {
511
+ "halite": halite,
512
+ "players": players,
513
+ "player": self.current_player_id,
514
+ "step": self.step,
515
+ "remainingOverageTime": self._remaining_overage_time,
516
+ }
517
+
518
+ def __deepcopy__(self, _) -> "Board":
519
+ actions = [player.next_actions for player in self.players.values()]
520
+ return Board(self.observation, self.configuration, actions)
521
+
522
+ def __getitem__(self, point: Union[Tuple[int, int], Point]) -> Cell:
523
+ """
524
+ This method will wrap the supplied position to fit within the board size and return the cell at that location.
525
+ e.g. on a 3x3 board, board[2, 1] is the same as board[5, 1]
526
+ """
527
+ if not isinstance(point, Point):
528
+ (x, y) = point
529
+ point = Point(x, y)
530
+ return self._cells[point % self.configuration.size]
531
+
532
+ def __str__(self) -> str:
533
+ """
534
+ The board is printed in a grid with the following rules:
535
+ Capital letters are shipyards
536
+ Lower case letters are ships
537
+ Digits are cell halite and scale from 0-9 directly proportional to a value between 0 and self.configuration.max_cell_halite
538
+ Player 1 is letter a/A
539
+ Player 2 is letter b/B
540
+ etc.
541
+ """
542
+ size = self.configuration.size
543
+ result = ""
544
+ for y in range(size):
545
+ for x in range(size):
546
+ cell = self[(x, size - y - 1)]
547
+ result += "|"
548
+ result += chr(ord("a") + cell.ship.player_id) if cell.ship is not None else " "
549
+ # This normalizes a value from 0 to max_cell halite to a value from 0 to 9
550
+ normalized_halite = int(9.0 * cell.halite / float(self.configuration.max_cell_halite))
551
+ result += str(normalized_halite)
552
+ result += chr(ord("A") + cell.shipyard.player_id) if cell.shipyard is not None else " "
553
+ result += "|\n"
554
+ return result
555
+
556
+ def _add_ship(self: "Board", ship: Ship) -> None:
557
+ ship.player.ship_ids.append(ship.id)
558
+ ship.cell._ship_id = ship.id
559
+ self._ships[ship.id] = ship
560
+
561
+ def _add_shipyard(self: "Board", shipyard: Shipyard) -> None:
562
+ shipyard.player.shipyard_ids.append(shipyard.id)
563
+ shipyard.cell._shipyard_id = shipyard.id
564
+ shipyard.cell._halite = 0
565
+ self._shipyards[shipyard.id] = shipyard
566
+
567
+ def _delete_ship(self: "Board", ship: Ship) -> None:
568
+ ship.player.ship_ids.remove(ship.id)
569
+ if ship.cell.ship_id == ship.id:
570
+ ship.cell._ship_id = None
571
+ del self._ships[ship.id]
572
+
573
+ def _delete_shipyard(self: "Board", shipyard: Shipyard) -> None:
574
+ shipyard.player.shipyard_ids.remove(shipyard.id)
575
+ if shipyard.cell.shipyard_id == shipyard.id:
576
+ shipyard.cell._shipyard_id = None
577
+ del self._shipyards[shipyard.id]
578
+
579
+ def next(self) -> "Board":
580
+ """
581
+ Returns a new board with the current board's next actions applied.
582
+ The current board is unmodified.
583
+ This can form a halite interpreter, e.g.
584
+ next_observation = Board(current_observation, configuration, actions).next().observation
585
+ """
586
+ # Create a copy of the board to modify so we don't affect the current board
587
+ board = deepcopy(self)
588
+ configuration = board.configuration
589
+ convert_cost = configuration.convert_cost
590
+ spawn_cost = configuration.spawn_cost
591
+ uid_counter = 0
592
+
593
+ # This is a consistent way to generate unique strings to form ship and shipyard ids
594
+ def create_uid():
595
+ nonlocal uid_counter
596
+ uid_counter += 1
597
+ return f"{self.step + 1}-{uid_counter}"
598
+
599
+ # Process actions and store the results in the ships and shipyards lists for collision checking
600
+ for player in board.players.values():
601
+ leftover_convert_halite = 0
602
+
603
+ for shipyard in player.shipyards:
604
+ if shipyard.next_action == ShipyardAction.SPAWN and player.halite >= spawn_cost:
605
+ # Handle SPAWN actions
606
+ player._halite -= spawn_cost
607
+ board._add_ship(Ship(ShipId(create_uid()), shipyard.position, 0, player.id, board))
608
+ # Clear the shipyard's action so it doesn't repeat the same action automatically
609
+ shipyard.next_action = None
610
+
611
+ for ship in player.ships:
612
+ if ship.next_action == ShipAction.CONVERT:
613
+ # Can't convert on an existing shipyard but you can use halite in a ship to fund conversion
614
+ if ship.cell.shipyard_id is None and (ship.halite + player.halite) >= convert_cost:
615
+ # Handle CONVERT actions
616
+ delta_halite = ship.halite - convert_cost
617
+ # Excess halite leftover from conversion is added to the player's total only after all conversions have completed
618
+ # This is to prevent the edge case of chaining halite from one convert to fund other converts
619
+ leftover_convert_halite += max(delta_halite, 0)
620
+ player._halite += min(delta_halite, 0)
621
+ board._add_shipyard(Shipyard(ShipyardId(create_uid()), ship.position, player.id, board))
622
+ board._delete_ship(ship)
623
+ elif ship.next_action is not None:
624
+ # If the action is not None and is not CONVERT it must be NORTH, SOUTH, EAST, or WEST
625
+ ship.cell._ship_id = None
626
+ ship._position = ship.position.translate(ship.next_action.to_point(), configuration.size)
627
+ ship._halite *= 1 - board.configuration.move_cost
628
+ # We don't set the new cell's ship_id here as it would be overwritten by another ship in the case of collision.
629
+ # Later we'll iterate through all ships and re-set the cell._ship_id as appropriate.
630
+
631
+ player._halite += leftover_convert_halite
632
+ # Lets just check and make sure.
633
+ assert player.halite >= 0
634
+
635
+ def resolve_collision(ships: List[Ship]) -> Tuple[Optional[Ship], List[Ship]]:
636
+ """
637
+ Accepts the list of ships at a particular position (must not be empty).
638
+ Returns the ship with the least halite or None in the case of a tie along with all other ships.
639
+ """
640
+ if len(ships) == 1:
641
+ return ships[0], []
642
+ ships_by_halite = group_by(ships, lambda ship: ship.halite)
643
+ smallest_halite = min(ships_by_halite.keys())
644
+ smallest_ships = ships_by_halite[smallest_halite]
645
+ if len(smallest_ships) == 1:
646
+ # There was a winner, return it
647
+ winner = smallest_ships[0]
648
+ return winner, [ship for ship in ships if ship != winner]
649
+ # There was a tie for least halite, all are deleted
650
+ return None, ships
651
+
652
+ # Check for ship to ship collisions
653
+ ship_collision_groups = group_by(board.ships.values(), lambda ship: ship.position)
654
+ for position, collided_ships in ship_collision_groups.items():
655
+ winner, deleted = resolve_collision(collided_ships)
656
+ if winner is not None:
657
+ winner.cell._ship_id = winner.id
658
+ for ship in deleted:
659
+ board._delete_ship(ship)
660
+ if winner is not None:
661
+ # Winner takes deleted ships' halite
662
+ winner._halite += ship.halite
663
+
664
+ # Check for ship to shipyard collisions
665
+ for shipyard in list(board.shipyards.values()):
666
+ ship = shipyard.cell.ship
667
+ if ship is not None and ship.player_id != shipyard.player_id:
668
+ # Ship to shipyard collision
669
+ board._delete_shipyard(shipyard)
670
+ board._delete_ship(ship)
671
+
672
+ # Deposit halite from ships into shipyards
673
+ for shipyard in list(board.shipyards.values()):
674
+ ship = shipyard.cell.ship
675
+ if ship is not None and ship.player_id == shipyard.player_id:
676
+ shipyard.player._halite += ship.halite
677
+ ship._halite = 0
678
+
679
+ # Collect halite from cells into ships
680
+ for ship in board.ships.values():
681
+ cell = ship.cell
682
+ delta_halite = int(cell.halite * configuration.collect_rate)
683
+ if ship.next_action not in ShipAction.moves() and cell.shipyard_id is None and delta_halite > 0:
684
+ ship._halite += delta_halite
685
+ cell._halite -= delta_halite
686
+ # Clear the ship's action so it doesn't repeat the same action automatically
687
+ ship.next_action = None
688
+
689
+ # Regenerate halite in cells
690
+ for cell in board.cells.values():
691
+ if cell.ship_id is None:
692
+ next_halite = round(cell.halite * (1 + configuration.regen_rate), 3)
693
+ cell._halite = min(next_halite, configuration.max_cell_halite)
694
+ # Lets just check and make sure.
695
+ assert cell.halite >= 0
696
+
697
+ board._step += 1
698
+
699
+ return board
700
+
701
+
702
+ def board_agent(agent: Callable[[Board], None]):
703
+ """
704
+ Decorator used to create an agent that modifies a board rather than an observation and a configuration
705
+ Automatically returns the modified board's next actions
706
+
707
+ @board_agent
708
+ def my_agent(board: Board) -> None:
709
+ ...
710
+ """
711
+
712
+ @wraps(agent)
713
+ def agent_wrapper(obs, config) -> Dict[str, str]:
714
+ board = Board(obs, config)
715
+ agent(board)
716
+ return board.current_player.next_actions
717
+
718
+ if agent.__module__ is not None and agent.__module__ in sys.modules:
719
+ setattr(sys.modules[agent.__module__], agent.__name__, agent_wrapper)
720
+ return agent_wrapper