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.
- kaggle_environments/__init__.py +49 -13
- kaggle_environments/agent.py +177 -124
- kaggle_environments/api.py +31 -0
- kaggle_environments/core.py +295 -170
- kaggle_environments/envs/cabt/cabt.js +164 -0
- kaggle_environments/envs/cabt/cabt.json +28 -0
- kaggle_environments/envs/cabt/cabt.py +186 -0
- kaggle_environments/envs/cabt/cg/__init__.py +0 -0
- kaggle_environments/envs/cabt/cg/cg.dll +0 -0
- kaggle_environments/envs/cabt/cg/game.py +75 -0
- kaggle_environments/envs/cabt/cg/libcg.so +0 -0
- kaggle_environments/envs/cabt/cg/sim.py +48 -0
- kaggle_environments/envs/cabt/test_cabt.py +120 -0
- kaggle_environments/envs/chess/chess.js +4289 -0
- kaggle_environments/envs/chess/chess.json +60 -0
- kaggle_environments/envs/chess/chess.py +4241 -0
- kaggle_environments/envs/chess/test_chess.py +60 -0
- kaggle_environments/envs/connectx/connectx.ipynb +3186 -0
- kaggle_environments/envs/connectx/connectx.js +1 -1
- kaggle_environments/envs/connectx/connectx.json +15 -1
- kaggle_environments/envs/connectx/connectx.py +6 -23
- kaggle_environments/envs/connectx/test_connectx.py +70 -24
- kaggle_environments/envs/football/football.ipynb +75 -0
- kaggle_environments/envs/football/football.json +91 -0
- kaggle_environments/envs/football/football.py +277 -0
- kaggle_environments/envs/football/helpers.py +95 -0
- kaggle_environments/envs/football/test_football.py +360 -0
- kaggle_environments/envs/halite/__init__.py +0 -0
- kaggle_environments/envs/halite/halite.ipynb +44741 -0
- kaggle_environments/envs/halite/halite.js +199 -83
- kaggle_environments/envs/halite/halite.json +31 -18
- kaggle_environments/envs/halite/halite.py +164 -303
- kaggle_environments/envs/halite/helpers.py +720 -0
- kaggle_environments/envs/halite/test_halite.py +190 -0
- kaggle_environments/envs/hungry_geese/__init__.py +0 -0
- kaggle_environments/envs/{battlegeese/battlegeese.js → hungry_geese/hungry_geese.js} +38 -22
- kaggle_environments/envs/{battlegeese/battlegeese.json → hungry_geese/hungry_geese.json} +21 -14
- kaggle_environments/envs/hungry_geese/hungry_geese.py +316 -0
- kaggle_environments/envs/hungry_geese/test_hungry_geese.py +0 -0
- kaggle_environments/envs/identity/identity.json +6 -5
- kaggle_environments/envs/identity/identity.py +15 -2
- kaggle_environments/envs/kore_fleets/__init__.py +0 -0
- kaggle_environments/envs/kore_fleets/helpers.py +1005 -0
- kaggle_environments/envs/kore_fleets/kore_fleets.ipynb +114 -0
- kaggle_environments/envs/kore_fleets/kore_fleets.js +658 -0
- kaggle_environments/envs/kore_fleets/kore_fleets.json +164 -0
- kaggle_environments/envs/kore_fleets/kore_fleets.py +555 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/Bot.java +54 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/README.md +26 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/jars/hamcrest-core-1.3.jar +0 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/jars/junit-4.13.2.jar +0 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Board.java +518 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Cell.java +61 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Configuration.java +24 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Direction.java +166 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Fleet.java +72 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/KoreJson.java +97 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Observation.java +72 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Pair.java +13 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Player.java +68 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Point.java +65 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/Shipyard.java +70 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/kore/ShipyardAction.java +59 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/main.py +73 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/BoardTest.java +567 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/ConfigurationTest.java +25 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/KoreJsonTest.java +62 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/ObservationTest.java +46 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/PointTest.java +21 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/ShipyardTest.java +22 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/configuration.json +1 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/fullob.json +1 -0
- kaggle_environments/envs/kore_fleets/starter_bots/java/test/observation.json +1 -0
- kaggle_environments/envs/kore_fleets/starter_bots/python/__init__.py +0 -0
- kaggle_environments/envs/kore_fleets/starter_bots/python/main.py +27 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/Bot.ts +34 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/DoNothingBot.ts +12 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/MinerBot.ts +62 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/README.md +55 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/interpreter.ts +402 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Board.ts +514 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Cell.ts +63 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Configuration.ts +25 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Direction.ts +169 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Fleet.ts +76 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/KoreIO.ts +70 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Observation.ts +45 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Pair.ts +11 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Player.ts +68 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Point.ts +65 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/Shipyard.ts +72 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/kore/ShipyardAction.ts +58 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/main.py +73 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/miner.py +73 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/package.json +23 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/test/BoardTest.ts +551 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/test/ConfigurationTest.ts +16 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/test/ObservationTest.ts +33 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/test/PointTest.ts +17 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/test/ShipyardTest.ts +18 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/test/configuration.json +1 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/test/fullob.json +1 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/test/observation.json +1 -0
- kaggle_environments/envs/kore_fleets/starter_bots/ts/tsconfig.json +22 -0
- kaggle_environments/envs/kore_fleets/test_kore_fleets.py +331 -0
- kaggle_environments/envs/lux_ai_2021/README.md +3 -0
- kaggle_environments/envs/lux_ai_2021/__init__.py +0 -0
- kaggle_environments/envs/lux_ai_2021/agents.py +11 -0
- kaggle_environments/envs/lux_ai_2021/dimensions/754.js +2 -0
- kaggle_environments/envs/lux_ai_2021/dimensions/754.js.LICENSE.txt +296 -0
- kaggle_environments/envs/lux_ai_2021/dimensions/main.js +1 -0
- kaggle_environments/envs/lux_ai_2021/index.html +43 -0
- kaggle_environments/envs/lux_ai_2021/lux_ai_2021.json +100 -0
- kaggle_environments/envs/lux_ai_2021/lux_ai_2021.py +231 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/__init__.py +0 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/game_constants.js +6 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/game_constants.json +59 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/game_objects.js +145 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/io.js +14 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/kit.js +209 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/map.js +107 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/lux/parser.js +79 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/main.js +88 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/main.py +75 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/js_simple/simple.tar.gz +0 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/__init__.py +0 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/annotate.py +20 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/constants.py +25 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game.py +86 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game_constants.json +59 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game_constants.py +7 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game_map.py +106 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/lux/game_objects.py +154 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/random_agent.py +38 -0
- kaggle_environments/envs/lux_ai_2021/test_agents/python/simple_agent.py +82 -0
- kaggle_environments/envs/lux_ai_2021/test_lux.py +19 -0
- kaggle_environments/envs/lux_ai_2021/testing.md +23 -0
- kaggle_environments/envs/lux_ai_2021/todo.md.og +18 -0
- kaggle_environments/envs/lux_ai_s3/README.md +21 -0
- kaggle_environments/envs/lux_ai_s3/agents.py +5 -0
- kaggle_environments/envs/lux_ai_s3/index.html +42 -0
- kaggle_environments/envs/lux_ai_s3/lux_ai_s3.json +47 -0
- kaggle_environments/envs/lux_ai_s3/lux_ai_s3.py +178 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/__init__.py +1 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/env.py +819 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/globals.py +9 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/params.py +101 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/profiler.py +141 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/pygame_render.py +222 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/spaces.py +27 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/state.py +464 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/utils.py +12 -0
- kaggle_environments/envs/lux_ai_s3/luxai_s3/wrappers.py +156 -0
- kaggle_environments/envs/lux_ai_s3/test_agents/python/agent.py +78 -0
- kaggle_environments/envs/lux_ai_s3/test_agents/python/lux/__init__.py +0 -0
- kaggle_environments/envs/lux_ai_s3/test_agents/python/lux/kit.py +31 -0
- kaggle_environments/envs/lux_ai_s3/test_agents/python/lux/utils.py +17 -0
- kaggle_environments/envs/lux_ai_s3/test_agents/python/main.py +66 -0
- kaggle_environments/envs/lux_ai_s3/test_lux.py +9 -0
- kaggle_environments/envs/mab/__init__.py +0 -0
- kaggle_environments/envs/mab/agents.py +12 -0
- kaggle_environments/envs/mab/mab.js +100 -0
- kaggle_environments/envs/mab/mab.json +74 -0
- kaggle_environments/envs/mab/mab.py +146 -0
- kaggle_environments/envs/open_spiel/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/chess/chess.js +441 -0
- kaggle_environments/envs/open_spiel/games/chess/image_config.jsonl +20 -0
- kaggle_environments/envs/open_spiel/games/chess/openings.jsonl +20 -0
- kaggle_environments/envs/open_spiel/games/connect_four/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/connect_four/connect_four.js +284 -0
- kaggle_environments/envs/open_spiel/games/connect_four/connect_four_proxy.py +86 -0
- kaggle_environments/envs/open_spiel/games/go/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/go/go.js +481 -0
- kaggle_environments/envs/open_spiel/games/go/go_proxy.py +99 -0
- kaggle_environments/envs/open_spiel/games/tic_tac_toe/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/tic_tac_toe/tic_tac_toe.js +345 -0
- kaggle_environments/envs/open_spiel/games/tic_tac_toe/tic_tac_toe_proxy.py +98 -0
- kaggle_environments/envs/open_spiel/games/universal_poker/__init__.py +0 -0
- kaggle_environments/envs/open_spiel/games/universal_poker/universal_poker.js +431 -0
- kaggle_environments/envs/open_spiel/games/universal_poker/universal_poker_proxy.py +159 -0
- kaggle_environments/envs/open_spiel/html_playthrough_generator.py +31 -0
- kaggle_environments/envs/open_spiel/observation.py +128 -0
- kaggle_environments/envs/open_spiel/open_spiel.py +565 -0
- kaggle_environments/envs/open_spiel/proxy.py +138 -0
- kaggle_environments/envs/open_spiel/test_open_spiel.py +191 -0
- kaggle_environments/envs/rps/__init__.py +0 -0
- kaggle_environments/envs/rps/agents.py +84 -0
- kaggle_environments/envs/rps/helpers.py +25 -0
- kaggle_environments/envs/rps/rps.js +117 -0
- kaggle_environments/envs/rps/rps.json +63 -0
- kaggle_environments/envs/rps/rps.py +90 -0
- kaggle_environments/envs/rps/test_rps.py +110 -0
- kaggle_environments/envs/rps/utils.py +7 -0
- kaggle_environments/envs/tictactoe/test_tictactoe.py +43 -77
- kaggle_environments/envs/tictactoe/tictactoe.ipynb +1397 -0
- kaggle_environments/envs/tictactoe/tictactoe.json +10 -2
- kaggle_environments/envs/tictactoe/tictactoe.py +1 -1
- kaggle_environments/errors.py +2 -4
- kaggle_environments/helpers.py +377 -0
- kaggle_environments/main.py +340 -0
- kaggle_environments/schemas.json +23 -18
- kaggle_environments/static/player.html +206 -74
- kaggle_environments/utils.py +46 -73
- kaggle_environments-1.20.0.dist-info/METADATA +25 -0
- kaggle_environments-1.20.0.dist-info/RECORD +211 -0
- {kaggle_environments-0.2.1.dist-info → kaggle_environments-1.20.0.dist-info}/WHEEL +1 -2
- kaggle_environments-1.20.0.dist-info/entry_points.txt +3 -0
- kaggle_environments/envs/battlegeese/battlegeese.py +0 -223
- kaggle_environments/temp.py +0 -14
- kaggle_environments-0.2.1.dist-info/METADATA +0 -393
- kaggle_environments-0.2.1.dist-info/RECORD +0 -32
- kaggle_environments-0.2.1.dist-info/entry_points.txt +0 -3
- kaggle_environments-0.2.1.dist-info/top_level.txt +0 -1
- {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)
|