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,128 @@
1
+ # Copyright 2019 DeepMind Technologies Limited
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
+ """An observation of a game.
16
+
17
+ This is intended to be the main way to get observations of states in Python.
18
+ The usage pattern is as follows:
19
+
20
+ 0. Create the game we will be playing
21
+ 1. Create each kind of observation required, using `make_observation`
22
+ 2. Every time a new observation is required, call:
23
+ `observation.set_from(state, player)`
24
+ The tensor contained in the Observation class will be updated with an
25
+ observation of the supplied state. This tensor is updated in-place, so if
26
+ you wish to retain it, you must make a copy.
27
+
28
+ The following options are available when creating an Observation:
29
+ - perfect_recall: if true, each observation must allow the observing player to
30
+ reconstruct their history of actions and observations.
31
+ - public_info: if true, the observation should include public information
32
+ - private_info: specifies for which players private information should be
33
+ included - all players, the observing player, or no players
34
+ - params: game-specific parameters for observations
35
+
36
+ We ultimately aim to have all games support all combinations of these arguments.
37
+ However, initially many games will only support the combinations corresponding
38
+ to ObservationTensor and InformationStateTensor:
39
+ - ObservationTensor: perfect_recall=False, public_info=True,
40
+ private_info=SinglePlayer
41
+ - InformationStateTensor: perfect_recall=True, public_info=True,
42
+ private_info=SinglePlayer
43
+
44
+ Three formats of observation are supported:
45
+ a. 1-D numpy array, accessed by `observation.tensor`
46
+ b. Dict of numpy arrays, accessed by `observation.dict`. These are pieces of the
47
+ 1-D array, reshaped. The np.array objects refer to the same memory as the
48
+ 1-D array (no copying!).
49
+ c. String, hopefully human-readable (primarily for debugging purposes)
50
+
51
+ For usage examples, see `observation_test.py`.
52
+ """
53
+
54
+ import numpy as np
55
+ import pyspiel
56
+
57
+ # Corresponds to the old information_state_XXX methods.
58
+ INFO_STATE_OBS_TYPE = pyspiel.IIGObservationType(perfect_recall=True)
59
+
60
+
61
+ class _Observation:
62
+ """Contains an observation from a game."""
63
+
64
+ def __init__(self, game, observer):
65
+ self._observation = pyspiel._Observation(game, observer)
66
+ self.dict = {}
67
+ if self._observation.has_tensor():
68
+ self.tensor = np.frombuffer(self._observation, np.float32)
69
+ offset = 0
70
+ for tensor_info in self._observation.tensors_info():
71
+ size = np.prod(tensor_info.shape, dtype=np.int64)
72
+ values = self.tensor[offset : offset + size].reshape(tensor_info.shape)
73
+ self.dict[tensor_info.name] = values
74
+ offset += size
75
+ else:
76
+ self.tensor = None
77
+
78
+ def set_from(self, state, player):
79
+ self._observation.set_from(state, player)
80
+
81
+ def string_from(self, state, player):
82
+ return self._observation.string_from(state, player) if self._observation.has_string() else None
83
+
84
+ def compress(self):
85
+ return self._observation.compress()
86
+
87
+ def decompress(self, compressed_observation):
88
+ self._observation.decompress(compressed_observation)
89
+
90
+
91
+ def make_observation(
92
+ game,
93
+ imperfect_information_observation_type=None,
94
+ params=None,
95
+ ):
96
+ """Returns an _Observation instance if the imperfect_information_observation_type is supported, otherwise None."""
97
+ params = params or {}
98
+ if hasattr(game, "make_py_observer"):
99
+ return game.make_py_observer(imperfect_information_observation_type, params)
100
+ else:
101
+ if imperfect_information_observation_type is not None:
102
+ observer = game.make_observer(imperfect_information_observation_type, params)
103
+ else:
104
+ observer = game.make_observer(params)
105
+ if observer is None:
106
+ return None
107
+ return _Observation(game, observer)
108
+
109
+
110
+ class IIGObserverForPublicInfoGame:
111
+ """Observer for imperfect information obvservations of public-info games."""
112
+
113
+ def __init__(self, iig_obs_type, params):
114
+ if params:
115
+ raise ValueError(f"Observation parameters not supported; passed {params}")
116
+ self._iig_obs_type = iig_obs_type
117
+ self.tensor = None
118
+ self.dict = {}
119
+
120
+ def set_from(self, state, player):
121
+ pass
122
+
123
+ def string_from(self, state, player):
124
+ del player
125
+ if self._iig_obs_type.public_info:
126
+ return state.history_str()
127
+ else:
128
+ return "" # No private information to return
@@ -0,0 +1,565 @@
1
+ """Kaggle environment wrapper for OpenSpiel games."""
2
+
3
+ import copy
4
+ import importlib
5
+ import json
6
+ import logging
7
+ import os
8
+ import pathlib
9
+ import random
10
+ import sys
11
+ from typing import Any, Callable
12
+
13
+ import numpy as np
14
+ import pyspiel
15
+
16
+ from kaggle_environments import core, utils
17
+
18
+ ERROR = "ERROR"
19
+ DONE = "DONE"
20
+ INACTIVE = "INACTIVE"
21
+ ACTIVE = "ACTIVE"
22
+ TIMEOUT = "TIMEOUT"
23
+ INVALID = "INVALID"
24
+
25
+ _log = logging.getLogger(__name__)
26
+ _log.setLevel(logging.INFO)
27
+ _handler = logging.StreamHandler(sys.stdout)
28
+ _formatter = logging.Formatter("[%(name)s] %(levelname)s: %(message)s")
29
+ _handler.setFormatter(_formatter)
30
+ _log.addHandler(_handler)
31
+
32
+ # --- Import proxy games ---
33
+ _log.debug("Auto-importing OpenSpiel game proxies...")
34
+ GAMES_DIR = pathlib.Path(__file__).parent / "games"
35
+ for proxy_file in GAMES_DIR.glob("**/*_proxy.py"):
36
+ try:
37
+ relative_path = proxy_file.relative_to(GAMES_DIR.parent)
38
+ module_path = str(relative_path.with_suffix("")).replace(os.path.sep, ".")
39
+ importlib.import_module("." + module_path, package=__package__)
40
+ _log.debug(f" - Imported: {module_path}")
41
+ except Exception as e: # pylint: disable=broad-exception-caught
42
+ _log.debug(f" - FAILED to import proxy from {proxy_file.name}: {e}")
43
+
44
+
45
+ # --- Constants ---
46
+ # TODO(jhtschultz): Make this configurable per-game. For instance, in poker, a
47
+ # invalid action would likely result in a fold, forfeiting the player's
48
+ # contribution to the pot.
49
+ DEFAULT_INVALID_ACTION_REWARD = -1
50
+
51
+ # Can be used by agents to signal an internal error to the environement.
52
+ AGENT_ERROR_ACTION = -2
53
+
54
+ DEFAULT_ACT_TIMEOUT = 60 * 60 # sixty minutes
55
+ DEFAULT_RUN_TIMEOUT = 60 * 60 * 48 # thirty hours
56
+ # Buffer in addition to max game length to account for timeouts, retrys, etc.
57
+ DEFAULT_STEP_BUFFER = 100
58
+ # TODO(jhtschultz): Add individual game descriptions.
59
+ DEFAULT_DESCRIPTION = """
60
+ Kaggle environment wrapper for OpenSpiel games.
61
+ For game implementation details see:
62
+ https://github.com/google-deepmind/open_spiel/tree/master/open_spiel/games
63
+ """.strip()
64
+
65
+ CONFIGURATION_SPEC_TEMPLATE = {
66
+ "episodeSteps": -1,
67
+ "actTimeout": DEFAULT_ACT_TIMEOUT,
68
+ "runTimeout": DEFAULT_RUN_TIMEOUT,
69
+ "openSpielGameString": {
70
+ "description": "The full game string including parameters.",
71
+ "type": "string",
72
+ "default": "PLACEHOLDER_GAME_STRING",
73
+ },
74
+ "openSpielGameName": {
75
+ "description": "The short_name of the OpenSpiel game to load.",
76
+ "type": "string",
77
+ "default": "PLACEHOLDER_GAME_SHORT_NAME",
78
+ },
79
+ "openSpielGameParameters": {"description": "Game parameters for Open Spiel game.", "type": "object", "default": {}},
80
+ "useOpenings": {
81
+ "description": "Whether to start from a position in an opening book.",
82
+ "type": "boolean",
83
+ "default": False,
84
+ },
85
+ "useImage": {
86
+ "description": (
87
+ "If true, indicates the observation is intended to be rendered as"
88
+ " an image. Note that currently the agent harness is responsible"
89
+ " for the actual rendering; no image is passed in the observation."
90
+ ),
91
+ "type": "boolean",
92
+ "default": False,
93
+ },
94
+ "seed": {
95
+ "description": "Integer currently only used for selecting starting position.",
96
+ "type": "number",
97
+ },
98
+ "initialActions": {
99
+ "description": "Actions applied to initial state before play begins to set up starting position.",
100
+ "type": "array",
101
+ "items": {"type": "integer"},
102
+ },
103
+ "metadata": {"description": "Arbitrary metadata.", "type": "object", "default": {}},
104
+ }
105
+
106
+ OBSERVATION_SPEC_TEMPLATE = {
107
+ "properties": {
108
+ "openSpielGameString": {"description": "Full game string including parameters.", "type": "string"},
109
+ "openSpielGameName": {"description": "Short name of the OpenSpiel game.", "type": "string"},
110
+ "observationString": {"description": "String representation of state.", "type": "string"},
111
+ "legalActions": {
112
+ "description": "List of OpenSpiel legal action integers.",
113
+ "type": "array",
114
+ "items": {"type": "integer"},
115
+ },
116
+ "legalActionStrings": {
117
+ "description": "List of OpenSpiel legal actions strings.",
118
+ "type": "array",
119
+ "items": {"type": "string"},
120
+ },
121
+ "currentPlayer": {"description": "ID of player whose turn it is.", "type": "integer"},
122
+ "playerId": {"description": "ID of the agent receiving this observation.", "type": "integer"},
123
+ "isTerminal": {"description": "Boolean indicating game end.", "type": "boolean"},
124
+ "serializedGameAndState": {
125
+ "description": "Enables reconstructing the Game and State objects.",
126
+ "type": "string",
127
+ },
128
+ "remainingOverageTime": 60,
129
+ "step": 0,
130
+ },
131
+ "default": {},
132
+ }
133
+
134
+ ACTION_SPEC_TEMPLATE = {
135
+ "description": "Action object MUST contain a field `submission`, and MAY contain arbitrary additional information.",
136
+ "type": "object",
137
+ "default": {"submission": -1},
138
+ }
139
+
140
+ ENV_SPEC_TEMPLATE = {
141
+ "name": "PLACEHOLDER_NAME",
142
+ "title": "PLACEHOLDER_TITLE",
143
+ "description": DEFAULT_DESCRIPTION,
144
+ "version": "0.1.0",
145
+ "agents": ["PLACEHOLDER_NUM_AGENTS"],
146
+ "configuration": CONFIGURATION_SPEC_TEMPLATE,
147
+ "observation": OBSERVATION_SPEC_TEMPLATE,
148
+ "action": ACTION_SPEC_TEMPLATE,
149
+ "reward": {"type": ["number"], "default": 0.0},
150
+ }
151
+
152
+
153
+ def _get_initial_actions(
154
+ configuration: dict[str, Any],
155
+ ) -> tuple[list[int], dict[str, Any]]:
156
+ initial_actions = configuration.get("initialActions", [])
157
+ if initial_actions:
158
+ if configuration.get("useOpenings"):
159
+ raise ValueError("Cannot set both useOpenings and initialActions.")
160
+ else:
161
+ return initial_actions, {}
162
+ if not configuration.get("useOpenings"):
163
+ return [], {}
164
+ seed = configuration.get("seed", None)
165
+ if seed is None:
166
+ raise ValueError("Must provide seed if useOpenings is True.")
167
+ openings_path = pathlib.Path(
168
+ GAMES_DIR,
169
+ configuration.get("openSpielGameName"),
170
+ "openings.jsonl",
171
+ )
172
+ if not openings_path.is_file():
173
+ raise ValueError(f"No opening file found at {openings_path}")
174
+ with open(openings_path, "r", encoding="utf-8") as f:
175
+ openings = f.readlines()
176
+ opening = json.loads(openings[seed % len(openings)])
177
+ initial_actions = opening.pop("initialActions")
178
+ return initial_actions, opening
179
+
180
+
181
+ def _get_image_config(configuration: dict[str, Any]) -> dict[str, Any]:
182
+ use_image = configuration.get("useImage", None)
183
+ if use_image is None:
184
+ raise ValueError("_get_image_config called but useImage missing from env config.")
185
+ if not use_image:
186
+ raise ValueError("_get_image_config called but useImage is False.")
187
+ seed = configuration.get("seed", None)
188
+ if seed is None:
189
+ raise ValueError("Must provide seed if useImage is True.")
190
+ image_config_path = pathlib.Path(
191
+ GAMES_DIR,
192
+ configuration.get("openSpielGameName"),
193
+ "image_config.jsonl",
194
+ )
195
+ if not image_config_path.is_file():
196
+ raise ValueError(f"No image config file found at {image_config_path}")
197
+ with open(image_config_path, "r", encoding="utf-8") as f:
198
+ image_configs = f.readlines()
199
+ image_config = json.loads(image_configs[seed % len(image_configs)])
200
+ return image_config
201
+
202
+
203
+ # --- Core step logic ---
204
+
205
+
206
+ def interpreter(
207
+ state: list[utils.Struct],
208
+ env: core.Environment,
209
+ logs: list[dict[str, Any]],
210
+ ) -> list[utils.Struct]:
211
+ """Updates environment using player responses and returns new observations."""
212
+ kaggle_state = state # Not to be confused with OpenSpiel state.
213
+ del state
214
+
215
+ # TODO(jhtschultz): Test reset behavior. Currently containers are restarted
216
+ # after each episode.
217
+ if env.done:
218
+ return kaggle_state
219
+
220
+ # --- Get and maybe initialize game and state on the env object ---
221
+ if not hasattr(env, "os_game"):
222
+ game_string = env.configuration.get("openSpielGameString")
223
+ env.os_game = pyspiel.load_game(game_string)
224
+ if not hasattr(env, "os_state"):
225
+ env.os_state = env.os_game.new_initial_state()
226
+ if "stateHistory" not in env.info:
227
+ env.info["stateHistory"] = [str(env.os_state)]
228
+ env.info["actionHistory"] = []
229
+ env.info["moveDurations"] = []
230
+ initial_actions, metadata = _get_initial_actions(env.configuration)
231
+ if initial_actions:
232
+ env.info["initialActions"] = initial_actions
233
+ env.info["openingMetadata"] = metadata
234
+ for action in initial_actions:
235
+ env.os_state.apply_action(action)
236
+ env.info["actionHistory"].append(str(action))
237
+ env.info["stateHistory"].append(str(env.os_state))
238
+ if env.configuration.get("useImage", False):
239
+ env.configuration["imageConfig"] = _get_image_config(env.configuration)
240
+
241
+ os_game = env.os_game
242
+ os_state = env.os_state
243
+ num_players = os_game.num_players()
244
+
245
+ # TODO(jhtschultz): Test reset behavior.
246
+ is_initial_step = len(env.steps) == 1
247
+ if is_initial_step and os_state.is_terminal():
248
+ env.os_state = os_game.new_initial_state()
249
+ os_state = env.os_state
250
+
251
+ # --- Apply agent action ---
252
+ acting_agent = os_state.current_player()
253
+ action_submitted: int | None = None
254
+ action_submitted_to_string: str | None = None
255
+ action_applied: int | None = None
256
+ move_duration: float | None = None
257
+ if is_initial_step:
258
+ pass
259
+ elif 0 <= acting_agent < num_players:
260
+ if kaggle_state[acting_agent]["status"] != "ACTIVE":
261
+ pass
262
+ else:
263
+ action_submitted = kaggle_state[acting_agent]["action"]["submission"]
264
+ if action_submitted in os_state.legal_actions():
265
+ action_submitted_to_string = os_state.action_to_string(action_submitted)
266
+ os_state.apply_action(action_submitted)
267
+ action_applied = action_submitted
268
+ env.info["actionHistory"].append(str(action_applied))
269
+ env.info["stateHistory"].append(str(os_state))
270
+ elif action_submitted == AGENT_ERROR_ACTION:
271
+ kaggle_state[acting_agent]["status"] = "ERROR"
272
+ else:
273
+ kaggle_state[acting_agent]["status"] = "INVALID"
274
+ try:
275
+ if "duration" in logs[acting_agent]:
276
+ move_duration = round(logs[acting_agent]["duration"], 3)
277
+ env.info["moveDurations"].append(move_duration)
278
+ else:
279
+ env.info["moveDurations"].append(None)
280
+ except Exception:
281
+ pass # No logs when stepping the env manually.
282
+
283
+ elif acting_agent == pyspiel.PlayerId.SIMULTANEOUS:
284
+ raise NotImplementedError
285
+ elif acting_agent == pyspiel.PlayerId.TERMINAL:
286
+ pass
287
+ elif acting_agent == pyspiel.PlayerId.CHANCE:
288
+ raise ValueError("Interpreter should not be called at chance nodes.")
289
+ else:
290
+ raise ValueError(f"Unknown OpenSpiel player ID: {acting_agent}")
291
+
292
+ # --- Step chance nodes ---
293
+ while os_state.is_chance_node():
294
+ outcomes, probs = zip(*os_state.chance_outcomes())
295
+ chance_action = np.random.choice(outcomes, p=probs)
296
+ os_state.apply_action(chance_action)
297
+ env.info["actionHistory"].append(str(chance_action))
298
+ env.info["stateHistory"].append(str(os_state))
299
+
300
+ # --- Update agent states ---
301
+ agent_error = any(kaggle_state[player_id]["status"] in ["TIMEOUT", "ERROR"] for player_id in range(num_players))
302
+ if agent_error:
303
+ _log.info("AGENT ERROR DETECTED")
304
+
305
+ invalid_action = any(kaggle_state[player_id]["status"] == "INVALID" for player_id in range(num_players))
306
+ if invalid_action:
307
+ _log.info("INVALID ACTION DETECTED")
308
+
309
+ status: str | None = None
310
+ for player_id, agent_state in enumerate(kaggle_state):
311
+ reward = None
312
+ if agent_error:
313
+ # Set all agent statuses to ERROR in order not to score episode. Preserve
314
+ # TIMEOUT which has the same effect.
315
+ if agent_state["status"] == "TIMEOUT":
316
+ status = "TIMEOUT"
317
+ else:
318
+ status = "ERROR"
319
+ elif invalid_action:
320
+ if agent_state["status"] == "INVALID":
321
+ reward = DEFAULT_INVALID_ACTION_REWARD
322
+ else:
323
+ reward = -DEFAULT_INVALID_ACTION_REWARD
324
+ status = "DONE"
325
+ elif os_state.is_terminal():
326
+ status = "DONE"
327
+ reward = os_state.returns()[player_id]
328
+ elif os_state.current_player() == player_id:
329
+ status = "ACTIVE"
330
+ if not os_state.legal_actions(player_id):
331
+ raise ValueError(f"Active agent {i} has no legal actions in state {os_state}.")
332
+ else:
333
+ status = "INACTIVE"
334
+ assert status is not None
335
+
336
+ info_dict = {}
337
+ if acting_agent == player_id:
338
+ info_dict["actionSubmitted"] = action_submitted
339
+ info_dict["actionSubmittedToString"] = action_submitted_to_string
340
+ info_dict["actionApplied"] = action_applied
341
+ info_dict["timeTaken"] = move_duration
342
+ info_dict["agentSelfReportedStatus"] = (
343
+ kaggle_state[acting_agent]["action"].get("status")
344
+ if kaggle_state[acting_agent]["action"]
345
+ else "unknown"
346
+ )
347
+
348
+ obs_update_dict = {
349
+ "observationString": os_state.observation_string(player_id),
350
+ "legalActions": os_state.legal_actions(player_id),
351
+ "legalActionStrings": [os_state.action_to_string(action) for action in os_state.legal_actions(player_id)],
352
+ "currentPlayer": os_state.current_player(),
353
+ "playerId": player_id,
354
+ "isTerminal": os_state.is_terminal(),
355
+ "serializedGameAndState": pyspiel.serialize_game_and_state(os_game, os_state),
356
+ }
357
+ if "imageConfig" in env.configuration:
358
+ obs_update_dict["imageConfig"] = env.configuration["imageConfig"]
359
+
360
+ # Apply updates
361
+ for k, v in obs_update_dict.items():
362
+ setattr(agent_state.observation, k, v)
363
+ agent_state["reward"] = reward
364
+ agent_state["info"] = info_dict
365
+ agent_state["status"] = status
366
+
367
+ return kaggle_state
368
+
369
+
370
+ # --- Rendering ---
371
+
372
+
373
+ def renderer(state: list[utils.Struct], env: core.Environment) -> str:
374
+ """Kaggle environment text renderer."""
375
+ if hasattr(env, "os_state"):
376
+ return str(env.os_state)
377
+ else:
378
+ return "Game state uninitialized."
379
+
380
+
381
+ # TODO(jhtschultz): Use custom player.html that replays from env.info instead
382
+ # of player steps. The full game state is stored in env.info, player steps only
383
+ # contain player observations.
384
+ def _default_html_renderer() -> str:
385
+ """Provides the JavaScript string for the default HTML renderer."""
386
+ return """
387
+ function renderer(context) {
388
+ const { parent, environment, step } = context;
389
+ parent.innerHTML = ''; // Clear previous rendering
390
+
391
+ const currentStepData = environment.steps[step];
392
+ if (!currentStepData) {
393
+ parent.textContent = "Waiting for step data...";
394
+ return;
395
+ }
396
+ const agentObsIndex = 0
397
+ let obsString = "Observation not available for this step.";
398
+ let title = `Step: ${step}`;
399
+
400
+ if (environment.configuration && environment.configuration.openSpielGameName) {
401
+ title = `${environment.configuration.openSpielGameName} - Step: ${step}`;
402
+ }
403
+
404
+ // Try to get obs_string from game_master of current step
405
+ if (currentStepData[agentObsIndex] &&
406
+ currentStepData[agentObsIndex].observation &&
407
+ typeof currentStepData[agentObsIndex].observation.observationString === 'string') {
408
+ obsString = currentStepData[agentObsIndex].observation.observationString;
409
+ }
410
+ // Fallback to initial step if current is unavailable (e.g. very first render call)
411
+ else if (step === 0 && environment.steps[0] && environment.steps[0][agentObsIndex] &&
412
+ environment.steps[0][agentObsIndex].observation &&
413
+ typeof environment.steps[0][agentObsIndex].observation.observationString === 'string') {
414
+ obsString = environment.steps[0][agentObsIndex].observation.observationString;
415
+ }
416
+
417
+ const pre = document.createElement("pre");
418
+ pre.style.fontFamily = "monospace";
419
+ pre.style.margin = "10px";
420
+ pre.style.border = "1px solid #ccc";
421
+ pre.style.padding = "10px";
422
+ pre.style.backgroundColor = "#f9f9f9";
423
+ pre.style.whiteSpace = "pre-wrap";
424
+ pre.style.wordBreak = "break-all";
425
+
426
+ pre.textContent = `${title}\\n\\n${obsString}`;
427
+ parent.appendChild(pre);
428
+ }
429
+ """
430
+
431
+
432
+ def _get_html_renderer_content(
433
+ open_spiel_short_name: str, base_path_for_custom_renderers: pathlib.Path, default_renderer_func: Callable[[], str]
434
+ ) -> str:
435
+ """Tries to load a custom JS renderer for the game, falls back to default."""
436
+ custom_renderer_js_path = pathlib.Path(
437
+ base_path_for_custom_renderers,
438
+ open_spiel_short_name,
439
+ f"{open_spiel_short_name}.js",
440
+ )
441
+ if custom_renderer_js_path.is_file():
442
+ try:
443
+ with open(custom_renderer_js_path, "r", encoding="utf-8") as f:
444
+ content = f.read()
445
+ _log.debug(f"Using custom HTML renderer for {open_spiel_short_name} from {custom_renderer_js_path}")
446
+ return content
447
+ except Exception as e: # pylint: disable=broad-exception-caught
448
+ _log.debug(e)
449
+ return default_renderer_func()
450
+
451
+
452
+ # --- Agents ---
453
+
454
+
455
+ def random_agent(
456
+ observation: dict[str, Any],
457
+ configuration: dict[str, Any],
458
+ ) -> int:
459
+ """A built-in random agent specifically for OpenSpiel environments."""
460
+ del configuration
461
+ legal_actions = observation.get("legalActions")
462
+ if not legal_actions:
463
+ return None
464
+ action = random.choice(legal_actions)
465
+ return {"submission": int(action)}
466
+
467
+
468
+ AGENT_REGISTRY = {
469
+ "random": random_agent,
470
+ }
471
+
472
+
473
+ # --- Build and register environments ---
474
+
475
+
476
+ def _build_env(game_string: str) -> dict[str, Any]:
477
+ game = pyspiel.load_game(game_string)
478
+ short_name = game.get_type().short_name
479
+
480
+ proxy_path = GAMES_DIR / short_name / f"{short_name}_proxy.py"
481
+ if proxy_path.is_file():
482
+ game = pyspiel.load_game(short_name + "_proxy", game.get_parameters())
483
+
484
+ game_type = game.get_type()
485
+ if not game_type.provides_observation_string:
486
+ raise ValueError(f"No observation string for game: {game_string}")
487
+
488
+ env_spec = copy.deepcopy(ENV_SPEC_TEMPLATE)
489
+ env_spec["name"] = f"open_spiel_{short_name}"
490
+ env_spec["title"] = f"Open Spiel: {short_name}"
491
+ env_spec["agents"] = [game.num_players()]
492
+
493
+ env_config = env_spec["configuration"]
494
+ env_config["episodeSteps"] = game.max_history_length() + DEFAULT_STEP_BUFFER
495
+ env_config["openSpielGameString"]["default"] = str(game)
496
+ env_config["openSpielGameName"]["default"] = short_name
497
+ env_config["openSpielGameParameters"]["default"] = game.get_parameters()
498
+
499
+ env_obs = env_spec["observation"]
500
+ env_obs["properties"]["openSpielGameString"]["default"] = str(game)
501
+ env_obs["properties"]["openSpielGameName"]["default"] = short_name
502
+
503
+ # Building html_renderer_callable is a bit convoluted but other approaches
504
+ # fail for a variety of reasons. Returning a simple lambda function
505
+ # doesn't work because of late-binding -- the last env registered will
506
+ # overwrite all previous renderers.
507
+ js_string_content = _get_html_renderer_content(
508
+ open_spiel_short_name=short_name,
509
+ base_path_for_custom_renderers=GAMES_DIR,
510
+ default_renderer_func=_default_html_renderer,
511
+ )
512
+
513
+ def create_html_renderer_closure(captured_content):
514
+ def html_renderer_callable_no_args():
515
+ return captured_content
516
+
517
+ return html_renderer_callable_no_args
518
+
519
+ html_renderer_callable = create_html_renderer_closure(js_string_content)
520
+
521
+ return {
522
+ "specification": env_spec,
523
+ "interpreter": interpreter,
524
+ "renderer": renderer,
525
+ "html_renderer": html_renderer_callable,
526
+ "agents": AGENT_REGISTRY,
527
+ }
528
+
529
+
530
+ def _register_game_envs(games_list: list[str]) -> dict[str, Any]:
531
+ skipped_games = []
532
+ registered_envs = {}
533
+ for game_string in games_list:
534
+ try:
535
+ env_config = _build_env(game_string)
536
+ if env_config is None:
537
+ continue
538
+ env_name = env_config["specification"]["name"]
539
+ if env_name in registered_envs:
540
+ raise ValueError(f"Attempting to overwrite existing env: {env_name}")
541
+ registered_envs[env_name] = env_config
542
+ except Exception as e: # pylint: disable=broad-exception-caught
543
+ _log.debug(e)
544
+ skipped_games.append(game_string)
545
+
546
+ _log.info(f"Successfully loaded OpenSpiel environments: {len(registered_envs)}.")
547
+ for env_name in registered_envs:
548
+ _log.info(f" {env_name}")
549
+ _log.info(f"OpenSpiel games skipped: {len(skipped_games)}.")
550
+ for game_string in skipped_games:
551
+ _log.info(f" {game_string}")
552
+
553
+ return registered_envs
554
+
555
+
556
+ GAMES_LIST = [
557
+ "chess",
558
+ "connect_four",
559
+ "gin_rummy",
560
+ "go(board_size=9)",
561
+ "tic_tac_toe",
562
+ "universal_poker(betting=nolimit,bettingAbstraction=fullgame,blind=2 1,firstPlayer=2 1 1 1,numBoardCards=0 3 1 1,numHoleCards=2,numPlayers=2,numRanks=13,numRounds=4,numSuits=4,stack=400 400)",
563
+ ]
564
+
565
+ ENV_REGISTRY = _register_game_envs(GAMES_LIST)