synth-ai 0.2.4.dev4__py3-none-any.whl → 0.2.4.dev6__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.
Files changed (123) hide show
  1. synth_ai/environments/examples/__init__.py +1 -0
  2. synth_ai/environments/examples/crafter_classic/__init__.py +8 -0
  3. synth_ai/environments/examples/crafter_classic/config_logging.py +111 -0
  4. synth_ai/environments/examples/crafter_classic/debug_translation.py +0 -0
  5. synth_ai/environments/examples/crafter_classic/engine.py +579 -0
  6. synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +63 -0
  7. synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +5 -0
  8. synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +74 -0
  9. synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +266 -0
  10. synth_ai/environments/examples/crafter_classic/environment.py +364 -0
  11. synth_ai/environments/examples/crafter_classic/taskset.py +233 -0
  12. synth_ai/environments/examples/crafter_classic/trace_hooks_v3.py +229 -0
  13. synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +298 -0
  14. synth_ai/environments/examples/crafter_custom/__init__.py +4 -0
  15. synth_ai/environments/examples/crafter_custom/crafter/__init__.py +7 -0
  16. synth_ai/environments/examples/crafter_custom/crafter/config.py +182 -0
  17. synth_ai/environments/examples/crafter_custom/crafter/constants.py +8 -0
  18. synth_ai/environments/examples/crafter_custom/crafter/engine.py +269 -0
  19. synth_ai/environments/examples/crafter_custom/crafter/env.py +266 -0
  20. synth_ai/environments/examples/crafter_custom/crafter/objects.py +418 -0
  21. synth_ai/environments/examples/crafter_custom/crafter/recorder.py +187 -0
  22. synth_ai/environments/examples/crafter_custom/crafter/worldgen.py +119 -0
  23. synth_ai/environments/examples/crafter_custom/dataset_builder.py +373 -0
  24. synth_ai/environments/examples/crafter_custom/environment.py +312 -0
  25. synth_ai/environments/examples/crafter_custom/run_dataset.py +305 -0
  26. synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +156 -0
  27. synth_ai/environments/examples/enron/art_helpers/local_email_db.py +280 -0
  28. synth_ai/environments/examples/enron/art_helpers/types_enron.py +24 -0
  29. synth_ai/environments/examples/enron/engine.py +291 -0
  30. synth_ai/environments/examples/enron/environment.py +165 -0
  31. synth_ai/environments/examples/enron/taskset.py +112 -0
  32. synth_ai/environments/examples/minigrid/__init__.py +48 -0
  33. synth_ai/environments/examples/minigrid/engine.py +589 -0
  34. synth_ai/environments/examples/minigrid/environment.py +274 -0
  35. synth_ai/environments/examples/minigrid/environment_mapping.py +242 -0
  36. synth_ai/environments/examples/minigrid/puzzle_loader.py +416 -0
  37. synth_ai/environments/examples/minigrid/taskset.py +583 -0
  38. synth_ai/environments/examples/nethack/__init__.py +7 -0
  39. synth_ai/environments/examples/nethack/achievements.py +337 -0
  40. synth_ai/environments/examples/nethack/engine.py +738 -0
  41. synth_ai/environments/examples/nethack/environment.py +255 -0
  42. synth_ai/environments/examples/nethack/helpers/__init__.py +42 -0
  43. synth_ai/environments/examples/nethack/helpers/action_mapping.py +301 -0
  44. synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +401 -0
  45. synth_ai/environments/examples/nethack/helpers/observation_utils.py +433 -0
  46. synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +201 -0
  47. synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +268 -0
  48. synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +308 -0
  49. synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +430 -0
  50. synth_ai/environments/examples/nethack/taskset.py +323 -0
  51. synth_ai/environments/examples/red/__init__.py +7 -0
  52. synth_ai/environments/examples/red/config_logging.py +110 -0
  53. synth_ai/environments/examples/red/engine.py +693 -0
  54. synth_ai/environments/examples/red/engine_helpers/__init__.py +1 -0
  55. synth_ai/environments/examples/red/engine_helpers/memory_map.py +28 -0
  56. synth_ai/environments/examples/red/engine_helpers/reward_components.py +275 -0
  57. synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +142 -0
  58. synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +56 -0
  59. synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +283 -0
  60. synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +149 -0
  61. synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +137 -0
  62. synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +56 -0
  63. synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +330 -0
  64. synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +120 -0
  65. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +558 -0
  66. synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +312 -0
  67. synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +147 -0
  68. synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +246 -0
  69. synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +367 -0
  70. synth_ai/environments/examples/red/engine_helpers/state_extraction.py +139 -0
  71. synth_ai/environments/examples/red/environment.py +235 -0
  72. synth_ai/environments/examples/red/taskset.py +77 -0
  73. synth_ai/environments/examples/sokoban/__init__.py +1 -0
  74. synth_ai/environments/examples/sokoban/engine.py +675 -0
  75. synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +1 -0
  76. synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +656 -0
  77. synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +17 -0
  78. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +3 -0
  79. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +129 -0
  80. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +370 -0
  81. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +331 -0
  82. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +305 -0
  83. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +66 -0
  84. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +114 -0
  85. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +122 -0
  86. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +394 -0
  87. synth_ai/environments/examples/sokoban/environment.py +228 -0
  88. synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +438 -0
  89. synth_ai/environments/examples/sokoban/puzzle_loader.py +311 -0
  90. synth_ai/environments/examples/sokoban/taskset.py +425 -0
  91. synth_ai/environments/examples/tictactoe/__init__.py +1 -0
  92. synth_ai/environments/examples/tictactoe/engine.py +368 -0
  93. synth_ai/environments/examples/tictactoe/environment.py +239 -0
  94. synth_ai/environments/examples/tictactoe/taskset.py +214 -0
  95. synth_ai/environments/examples/verilog/__init__.py +10 -0
  96. synth_ai/environments/examples/verilog/engine.py +328 -0
  97. synth_ai/environments/examples/verilog/environment.py +349 -0
  98. synth_ai/environments/examples/verilog/taskset.py +418 -0
  99. synth_ai/environments/examples/wordle/__init__.py +29 -0
  100. synth_ai/environments/examples/wordle/engine.py +391 -0
  101. synth_ai/environments/examples/wordle/environment.py +154 -0
  102. synth_ai/environments/examples/wordle/helpers/generate_instances_wordfreq.py +75 -0
  103. synth_ai/environments/examples/wordle/taskset.py +222 -0
  104. synth_ai/environments/service/app.py +8 -0
  105. synth_ai/environments/service/core_routes.py +38 -0
  106. synth_ai/learning/prompts/banking77_injection_eval.py +163 -0
  107. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +201 -0
  108. synth_ai/learning/prompts/mipro.py +273 -1
  109. synth_ai/learning/prompts/random_search.py +247 -0
  110. synth_ai/learning/prompts/run_mipro_banking77.py +160 -0
  111. synth_ai/learning/prompts/run_random_search_banking77.py +305 -0
  112. synth_ai/lm/injection.py +81 -0
  113. synth_ai/lm/overrides.py +204 -0
  114. synth_ai/lm/provider_support/anthropic.py +39 -12
  115. synth_ai/lm/provider_support/openai.py +31 -4
  116. synth_ai/lm/vendors/core/anthropic_api.py +16 -0
  117. synth_ai/lm/vendors/openai_standard.py +35 -5
  118. {synth_ai-0.2.4.dev4.dist-info → synth_ai-0.2.4.dev6.dist-info}/METADATA +2 -1
  119. {synth_ai-0.2.4.dev4.dist-info → synth_ai-0.2.4.dev6.dist-info}/RECORD +123 -13
  120. {synth_ai-0.2.4.dev4.dist-info → synth_ai-0.2.4.dev6.dist-info}/WHEEL +0 -0
  121. {synth_ai-0.2.4.dev4.dist-info → synth_ai-0.2.4.dev6.dist-info}/entry_points.txt +0 -0
  122. {synth_ai-0.2.4.dev4.dist-info → synth_ai-0.2.4.dev6.dist-info}/licenses/LICENSE +0 -0
  123. {synth_ai-0.2.4.dev4.dist-info → synth_ai-0.2.4.dev6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,235 @@
1
+ from __future__ import annotations
2
+ from typing import List, Optional, Any, Dict, Union
3
+ from pydantic import BaseModel, Field
4
+
5
+ # Import logging configuration to suppress JAX debug messages
6
+
7
+ from .engine import (
8
+ PokemonRedEngine,
9
+ PokemonRedPrivateState,
10
+ PokemonRedPublicState,
11
+ PokemonRedEngineSnapshot,
12
+ )
13
+ from .taskset import PokemonRedTaskInstance, INSTANCE as DEFAULT_TASK_INSTANCE
14
+ from synth_ai.environments.environment.shared_engine import (
15
+ GetObservationCallable,
16
+ InternalObservation,
17
+ )
18
+ from synth_ai.environments.reproducibility.core import ReproducibleEnvironment
19
+ from synth_ai.environments.stateful.core import StatefulEnvironment
20
+ from synth_ai.environments.environment.tools import (
21
+ AbstractTool,
22
+ EnvToolCall,
23
+ ToolResult,
24
+ TOOL_REGISTRY,
25
+ register_tool,
26
+ )
27
+
28
+
29
+ # Tool input schemas
30
+ class PressButtonInput(BaseModel):
31
+ button: str = Field(
32
+ ..., description="Game Boy button: A, B, UP, DOWN, LEFT, RIGHT, START, SELECT"
33
+ )
34
+ frames: int = Field(1, description="Number of frames to hold the button")
35
+
36
+
37
+ # Tool definitions
38
+ class PressButtonTool(AbstractTool):
39
+ name = "press_button"
40
+ description = "Press a Game Boy button for the specified number of frames"
41
+ call_schema = PressButtonInput
42
+ result_schema = ToolResult
43
+
44
+ def __init__(self, engine: PokemonRedEngine):
45
+ self.engine = engine
46
+
47
+ async def __call__(self, call: EnvToolCall) -> ToolResult:
48
+ try:
49
+ validated_args = self.call_schema(**call.args)
50
+ action = {"button": validated_args.button, "frames": validated_args.frames}
51
+ priv_state, pub_state = await self.engine._step_engine(action)
52
+ return ToolResult(
53
+ ok=True,
54
+ payload={
55
+ "public": pub_state,
56
+ "private": priv_state,
57
+ },
58
+ )
59
+ except Exception as e:
60
+ # Get current state for error context
61
+ priv_state, pub_state = self.engine._create_states(reward=0.0)
62
+ return ToolResult(
63
+ ok=False,
64
+ error=str(e),
65
+ payload={"public": pub_state},
66
+ )
67
+
68
+
69
+ # Observation callable for Pokemon Red
70
+ class PokemonRedObservationCallable(GetObservationCallable):
71
+ async def get_observation(
72
+ self, pub: PokemonRedPublicState, priv: PokemonRedPrivateState
73
+ ) -> InternalObservation:
74
+ """Convert Pokemon Red states to agent observation"""
75
+ from .engine_helpers.state_extraction import (
76
+ get_badge_count,
77
+ format_position,
78
+ format_hp_status,
79
+ )
80
+
81
+ badge_count = get_badge_count(pub.badges)
82
+ position = format_position(pub.player_x, pub.player_y, pub.map_id)
83
+ hp_status = format_hp_status(pub.party_hp_current, pub.party_hp_max)
84
+
85
+ obs = {
86
+ "position": position,
87
+ "badges_earned": badge_count,
88
+ "badges_bitfield": pub.badges,
89
+ "hp_status": hp_status,
90
+ "party_level": pub.party_level,
91
+ "party_xp": pub.party_xp,
92
+ "in_battle": pub.in_battle,
93
+ "step_count": pub.step_count,
94
+ "reward_last_step": priv.reward_last_step,
95
+ "total_reward": priv.total_reward,
96
+ "terminated": priv.terminated,
97
+ }
98
+
99
+ if pub.error_info:
100
+ obs["error"] = pub.error_info
101
+
102
+ return obs
103
+
104
+
105
+ class PokemonRedEnvironment(StatefulEnvironment, ReproducibleEnvironment[PokemonRedEngine]):
106
+ """Pokemon Red stateful game environment for AI agents"""
107
+
108
+ def __init__(
109
+ self,
110
+ task_instance: Optional[PokemonRedTaskInstance] = None,
111
+ custom_step_obs: Optional[GetObservationCallable] = None,
112
+ custom_ckpt_obs: Optional[GetObservationCallable] = None,
113
+ ):
114
+ self.name = "PokemonRed"
115
+ self.task_instance = task_instance or DEFAULT_TASK_INSTANCE
116
+ self.custom_step_observation_callable = custom_step_obs or PokemonRedObservationCallable()
117
+ self.custom_checkpoint_observation_callable = (
118
+ custom_ckpt_obs or PokemonRedObservationCallable()
119
+ )
120
+ self.engine = PokemonRedEngine(self.task_instance)
121
+
122
+ # Register tools
123
+ self._press_button_tool = PressButtonTool(self.engine)
124
+ if self._press_button_tool.name not in TOOL_REGISTRY:
125
+ register_tool(self._press_button_tool)
126
+
127
+ async def initialize(self) -> InternalObservation:
128
+ """Initialize the Pokemon Red environment"""
129
+ priv, pub = await self.engine._reset_engine()
130
+ return await self._to_observation(priv, pub, self.custom_step_observation_callable)
131
+
132
+ async def terminate(self) -> InternalObservation:
133
+ """Terminate the environment"""
134
+ priv, pub = self.engine._create_states(reward=0.0, terminated=True)
135
+ obs_dict = {
136
+ "terminated": True,
137
+ "message": "Pokemon Red environment terminated.",
138
+ }
139
+ return await self._to_observation(
140
+ priv, pub, self.custom_step_observation_callable, extra_obs=obs_dict
141
+ )
142
+
143
+ def validate_tool_calls(
144
+ self, tool_calls: Union[EnvToolCall, List[EnvToolCall], List[List[EnvToolCall]]]
145
+ ) -> EnvToolCall:
146
+ """Validate and normalize tool calls to single EnvToolCall"""
147
+ if isinstance(tool_calls, list):
148
+ if not tool_calls:
149
+ raise ValueError("Received empty list of tool calls.")
150
+ if isinstance(tool_calls[0], list):
151
+ if not tool_calls[0]:
152
+ raise ValueError("Received empty inner list of tool calls.")
153
+ agent_call = tool_calls[0][0]
154
+ else:
155
+ agent_call = tool_calls[0]
156
+ elif isinstance(tool_calls, EnvToolCall):
157
+ agent_call = tool_calls
158
+ else:
159
+ raise TypeError(f"Unexpected type for tool_calls: {type(tool_calls)}")
160
+
161
+ if not isinstance(agent_call, EnvToolCall):
162
+ raise TypeError(f"Processed call is not EnvToolCall: {type(agent_call)}")
163
+ if agent_call.tool != "press_button":
164
+ raise ValueError(f"Unknown tool: {agent_call.tool}. Expected 'press_button'.")
165
+
166
+ return agent_call
167
+
168
+ async def step(
169
+ self, tool_calls: Union[EnvToolCall, List[EnvToolCall], List[List[EnvToolCall]]]
170
+ ) -> InternalObservation:
171
+ """Execute one step in the Pokemon Red environment"""
172
+ agent_call = self.validate_tool_calls(tool_calls)
173
+ tool_result: ToolResult = await self._press_button_tool(agent_call)
174
+
175
+ payload_dict = tool_result.payload
176
+ if not tool_result.ok or not isinstance(payload_dict, dict):
177
+ # Fallback if tool execution failed
178
+ priv_state, pub_state = self.engine._create_states(reward=0.0)
179
+ if tool_result.error and hasattr(pub_state, "error_info"):
180
+ pub_state.error_info = tool_result.error
181
+ else:
182
+ # Extract states from successful tool execution - now they're dataclass objects
183
+ priv_state = payload_dict.get("private")
184
+ pub_state = payload_dict.get("public")
185
+
186
+ if priv_state is None or pub_state is None:
187
+ priv_state, pub_state = self.engine._create_states(reward=0.0)
188
+ if tool_result.error and hasattr(pub_state, "error_info"):
189
+ pub_state.error_info = tool_result.error
190
+ else:
191
+ # States are already dataclass objects, no need to reconstruct
192
+ if tool_result.error and hasattr(pub_state, "error_info"):
193
+ pub_state.error_info = tool_result.error
194
+
195
+ return await self._to_observation(
196
+ priv_state, pub_state, self.custom_step_observation_callable
197
+ )
198
+
199
+ async def checkpoint(self) -> InternalObservation:
200
+ """Create a checkpoint of the current environment state"""
201
+ engine_snapshot: PokemonRedEngineSnapshot = await self.engine._serialize_engine()
202
+ priv, pub = self.engine._create_states(reward=0.0)
203
+ obs_data = await self._to_observation(
204
+ priv, pub, self.custom_checkpoint_observation_callable
205
+ )
206
+ if isinstance(obs_data, dict):
207
+ obs_data["engine_snapshot_data"] = engine_snapshot.model_dump()
208
+ return obs_data
209
+
210
+ async def _to_observation(
211
+ self,
212
+ priv: PokemonRedPrivateState,
213
+ pub: PokemonRedPublicState,
214
+ obs_cb: Optional[GetObservationCallable],
215
+ extra_obs: Optional[Dict[str, Any]] = None,
216
+ ) -> InternalObservation:
217
+ """Convert states to observation using the specified callback"""
218
+ active_obs_cb = obs_cb or PokemonRedObservationCallable()
219
+ observation = await active_obs_cb.get_observation(pub, priv)
220
+ if extra_obs and isinstance(observation, dict):
221
+ observation.update(extra_obs)
222
+ return observation
223
+
224
+ # ReproducibleEnvironment methods
225
+ async def _serialize_engine(self) -> PokemonRedEngineSnapshot:
226
+ return await self.engine._serialize_engine()
227
+
228
+ @classmethod
229
+ async def _deserialize_engine(
230
+ cls, snapshot: PokemonRedEngineSnapshot, task_instance: PokemonRedTaskInstance
231
+ ) -> "PokemonRedEnvironment":
232
+ eng = await PokemonRedEngine._deserialize_engine(snapshot, task_instance)
233
+ env = cls(task_instance)
234
+ env.engine = eng
235
+ return env
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+ import uuid
5
+ from synth_ai.environments.tasks.core import (
6
+ Task,
7
+ TaskInstance,
8
+ Impetus,
9
+ Intent,
10
+ TaskInstanceMetadata,
11
+ )
12
+
13
+ # Define the main task for Pokemon Red
14
+ TASK = Task(
15
+ global_premises="You are playing Pokemon Red. Start in Pewter City with a level-10 Pikachu.",
16
+ global_constraints="No glitches or exploits. Play within normal game mechanics.",
17
+ global_objectives="Defeat Brock at the Pewter Gym to earn the Boulder Badge.",
18
+ shared_env_params={},
19
+ )
20
+
21
+ # Path to initial save state (would contain a save near Pewter Gym)
22
+ INITIAL_SNAPSHOT = Path(__file__).parent / "snapshots" / "pewter_start.state"
23
+
24
+
25
+ @dataclass
26
+ class PokemonRedTaskInstance(TaskInstance):
27
+ """Task instance for Pokemon Red challenges"""
28
+
29
+ async def serialize(self) -> dict:
30
+ """Serialize the task instance to a dictionary"""
31
+ return {
32
+ "id": str(self.id),
33
+ "impetus": {"instructions": self.impetus.instructions},
34
+ "intent": {
35
+ "rubric": self.intent.rubric,
36
+ "gold_trajectories": None,
37
+ "gold_state_diff": self.intent.gold_state_diff,
38
+ },
39
+ "metadata": {},
40
+ "is_reproducible": self.is_reproducible,
41
+ "initial_engine_snapshot": str(self.initial_engine_snapshot)
42
+ if self.initial_engine_snapshot
43
+ else None,
44
+ }
45
+
46
+ @classmethod
47
+ async def deserialize(cls, data: dict) -> "PokemonRedTaskInstance":
48
+ """Deserialize a task instance from a dictionary"""
49
+ return cls(
50
+ id=uuid.UUID(data["id"]),
51
+ impetus=Impetus(instructions=data["impetus"]["instructions"]),
52
+ intent=Intent(
53
+ rubric=data["intent"]["rubric"],
54
+ gold_trajectories=None,
55
+ gold_state_diff=data["intent"]["gold_state_diff"],
56
+ ),
57
+ metadata=TaskInstanceMetadata(),
58
+ is_reproducible=data["is_reproducible"],
59
+ initial_engine_snapshot=None,
60
+ )
61
+
62
+
63
+ # Main task instance - beat Brock for Boulder Badge
64
+ INSTANCE = PokemonRedTaskInstance(
65
+ id=uuid.UUID("12345678-1234-5678-9abc-123456789abc"),
66
+ impetus=Impetus(
67
+ instructions="Navigate to Pewter Gym and defeat Brock to earn the Boulder Badge. Use strategic Pokemon battles and item management."
68
+ ),
69
+ intent=Intent(
70
+ rubric="Successfully obtain the Boulder Badge by defeating Brock at Pewter Gym. Efficiency measured by minimal steps and strategic Pokemon usage.",
71
+ gold_trajectories=None,
72
+ gold_state_diff={"badges": 1},
73
+ ),
74
+ metadata=TaskInstanceMetadata(),
75
+ is_reproducible=True,
76
+ initial_engine_snapshot=INITIAL_SNAPSHOT if INITIAL_SNAPSHOT.exists() else None,
77
+ )
@@ -0,0 +1 @@
1
+ """Sokoban environment example."""