synth-ai 0.1.9__py3-none-any.whl → 0.2.1.dev0__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 (266) hide show
  1. synth_ai/__init__.py +28 -2
  2. synth_ai/core/system.py +4 -0
  3. synth_ai/environments/__init__.py +35 -0
  4. synth_ai/environments/environment/__init__.py +1 -0
  5. synth_ai/environments/environment/artifacts/__init__.py +1 -0
  6. synth_ai/environments/environment/artifacts/base.py +50 -0
  7. synth_ai/environments/environment/core.py +22 -0
  8. synth_ai/environments/environment/db/__init__.py +1 -0
  9. synth_ai/environments/environment/db/sqlite.py +45 -0
  10. synth_ai/environments/environment/registry.py +24 -0
  11. synth_ai/environments/environment/resources/sqlite.py +46 -0
  12. synth_ai/environments/environment/results.py +1 -0
  13. synth_ai/environments/environment/rewards/__init__.py +1 -0
  14. synth_ai/environments/environment/rewards/core.py +28 -0
  15. synth_ai/environments/environment/shared_engine.py +26 -0
  16. synth_ai/environments/environment/tools/__init__.py +34 -0
  17. synth_ai/environments/examples/__init__.py +1 -0
  18. synth_ai/environments/examples/crafter_classic/__init__.py +8 -0
  19. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_comprehensive_evaluation.py +58 -0
  20. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_browser.py +152 -0
  21. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_framework.py +1194 -0
  22. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_quick_evaluation.py +51 -0
  23. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_react_agent.py +872 -0
  24. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +1412 -0
  25. synth_ai/environments/examples/crafter_classic/agent_demos/test_crafter_react_agent.py +1110 -0
  26. synth_ai/environments/examples/crafter_classic/config_logging.py +111 -0
  27. synth_ai/environments/examples/crafter_classic/engine.py +502 -0
  28. synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +63 -0
  29. synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +5 -0
  30. synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +74 -0
  31. synth_ai/environments/examples/crafter_classic/environment.py +255 -0
  32. synth_ai/environments/examples/crafter_classic/taskset.py +228 -0
  33. synth_ai/environments/examples/enron/agent_demos/test_synth_react.py +535 -0
  34. synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +156 -0
  35. synth_ai/environments/examples/enron/art_helpers/local_email_db.py +280 -0
  36. synth_ai/environments/examples/enron/art_helpers/types_enron.py +24 -0
  37. synth_ai/environments/examples/enron/engine.py +291 -0
  38. synth_ai/environments/examples/enron/environment.py +165 -0
  39. synth_ai/environments/examples/enron/taskset.py +112 -0
  40. synth_ai/environments/examples/enron/units/keyword_stats.py +111 -0
  41. synth_ai/environments/examples/enron/units/test_email_index.py +8 -0
  42. synth_ai/environments/examples/minigrid/__init__.py +48 -0
  43. synth_ai/environments/examples/minigrid/agent_demos/minigrid_evaluation_framework.py +1188 -0
  44. synth_ai/environments/examples/minigrid/agent_demos/minigrid_quick_evaluation.py +47 -0
  45. synth_ai/environments/examples/minigrid/agent_demos/minigrid_react_agent.py +562 -0
  46. synth_ai/environments/examples/minigrid/agent_demos/minigrid_trace_evaluation.py +220 -0
  47. synth_ai/environments/examples/minigrid/agent_demos/test_minigrid_react_agent.py +393 -0
  48. synth_ai/environments/examples/minigrid/engine.py +589 -0
  49. synth_ai/environments/examples/minigrid/environment.py +274 -0
  50. synth_ai/environments/examples/minigrid/environment_mapping.py +242 -0
  51. synth_ai/environments/examples/minigrid/puzzle_loader.py +416 -0
  52. synth_ai/environments/examples/minigrid/taskset.py +583 -0
  53. synth_ai/environments/examples/minigrid/units/test_action_behavior.py +226 -0
  54. synth_ai/environments/examples/minigrid/units/test_debug_messages.py +83 -0
  55. synth_ai/environments/examples/minigrid/units/test_exploration.py +120 -0
  56. synth_ai/environments/examples/minigrid/units/test_minigrid_engine.py +214 -0
  57. synth_ai/environments/examples/minigrid/units/test_minigrid_environment.py +238 -0
  58. synth_ai/environments/examples/minigrid/units/test_minigrid_environment_mapping.py +301 -0
  59. synth_ai/environments/examples/minigrid/units/test_minigrid_taskset.py +210 -0
  60. synth_ai/environments/examples/nethack/__init__.py +7 -0
  61. synth_ai/environments/examples/nethack/achievements.py +337 -0
  62. synth_ai/environments/examples/nethack/agent_demos/nethack_evaluation_framework.py +981 -0
  63. synth_ai/environments/examples/nethack/agent_demos/nethack_quick_evaluation.py +74 -0
  64. synth_ai/environments/examples/nethack/agent_demos/nethack_react_agent.py +832 -0
  65. synth_ai/environments/examples/nethack/agent_demos/test_nethack_react_agent.py +1112 -0
  66. synth_ai/environments/examples/nethack/engine.py +738 -0
  67. synth_ai/environments/examples/nethack/environment.py +255 -0
  68. synth_ai/environments/examples/nethack/helpers/__init__.py +42 -0
  69. synth_ai/environments/examples/nethack/helpers/action_mapping.py +301 -0
  70. synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +401 -0
  71. synth_ai/environments/examples/nethack/helpers/observation_utils.py +433 -0
  72. synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +201 -0
  73. synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +268 -0
  74. synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +308 -0
  75. synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +430 -0
  76. synth_ai/environments/examples/nethack/taskset.py +323 -0
  77. synth_ai/environments/examples/nethack/units/test_nethack_engine.py +277 -0
  78. synth_ai/environments/examples/nethack/units/test_nethack_environment.py +281 -0
  79. synth_ai/environments/examples/nethack/units/test_nethack_taskset.py +213 -0
  80. synth_ai/environments/examples/nethack/units/test_recording.py +307 -0
  81. synth_ai/environments/examples/red/__init__.py +7 -0
  82. synth_ai/environments/examples/red/agent_demos/__init__.py +1 -0
  83. synth_ai/environments/examples/red/agent_demos/test_synth_react.py +1471 -0
  84. synth_ai/environments/examples/red/config_logging.py +110 -0
  85. synth_ai/environments/examples/red/engine.py +693 -0
  86. synth_ai/environments/examples/red/engine_helpers/__init__.py +1 -0
  87. synth_ai/environments/examples/red/engine_helpers/memory_map.py +28 -0
  88. synth_ai/environments/examples/red/engine_helpers/reward_components.py +275 -0
  89. synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +142 -0
  90. synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +56 -0
  91. synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +283 -0
  92. synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +149 -0
  93. synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +137 -0
  94. synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +56 -0
  95. synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +330 -0
  96. synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +120 -0
  97. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +558 -0
  98. synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +312 -0
  99. synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +147 -0
  100. synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +246 -0
  101. synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +367 -0
  102. synth_ai/environments/examples/red/engine_helpers/state_extraction.py +139 -0
  103. synth_ai/environments/examples/red/environment.py +235 -0
  104. synth_ai/environments/examples/red/taskset.py +77 -0
  105. synth_ai/environments/examples/red/test_fixes.py +125 -0
  106. synth_ai/environments/examples/red/test_fixes_mock.py +148 -0
  107. synth_ai/environments/examples/red/units/__init__.py +1 -0
  108. synth_ai/environments/examples/red/units/test_basic_functionality.py +97 -0
  109. synth_ai/environments/examples/red/units/test_button_press_requirements.py +217 -0
  110. synth_ai/environments/examples/red/units/test_engine.py +192 -0
  111. synth_ai/environments/examples/red/units/test_environment.py +455 -0
  112. synth_ai/environments/examples/red/units/test_exploration_strategy.py +227 -0
  113. synth_ai/environments/examples/red/units/test_integration.py +217 -0
  114. synth_ai/environments/examples/red/units/test_memory_extraction.py +111 -0
  115. synth_ai/environments/examples/red/units/test_menu_bug_reproduction.py +1100 -0
  116. synth_ai/environments/examples/red/units/test_movement_debug.py +255 -0
  117. synth_ai/environments/examples/red/units/test_pokemon_mcts_debug.py +163 -0
  118. synth_ai/environments/examples/red/units/test_pokemon_mcts_verbose.py +117 -0
  119. synth_ai/environments/examples/red/units/test_red_basic.py +145 -0
  120. synth_ai/environments/examples/red/units/test_red_comprehensive.py +323 -0
  121. synth_ai/environments/examples/red/units/test_retry_movement.py +195 -0
  122. synth_ai/environments/examples/red/units/test_reward_components.py +186 -0
  123. synth_ai/environments/examples/red/units/test_rom_integration.py +260 -0
  124. synth_ai/environments/examples/red/units/test_taskset.py +116 -0
  125. synth_ai/environments/examples/red/units/test_tree.py +448 -0
  126. synth_ai/environments/examples/sokoban/__init__.py +1 -0
  127. synth_ai/environments/examples/sokoban/agent_demos/sokoban_full_eval.py +900 -0
  128. synth_ai/environments/examples/sokoban/agent_demos/test_dspy_react.py +1 -0
  129. synth_ai/environments/examples/sokoban/agent_demos/test_sokoban_react_agent.py +498 -0
  130. synth_ai/environments/examples/sokoban/agent_demos/test_synth_lats.py +1 -0
  131. synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_locally.py +748 -0
  132. synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_service.py +296 -0
  133. synth_ai/environments/examples/sokoban/engine.py +675 -0
  134. synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +1 -0
  135. synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +656 -0
  136. synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +17 -0
  137. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +3 -0
  138. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +129 -0
  139. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +370 -0
  140. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +331 -0
  141. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +305 -0
  142. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +66 -0
  143. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +114 -0
  144. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +122 -0
  145. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +394 -0
  146. synth_ai/environments/examples/sokoban/environment.py +228 -0
  147. synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +438 -0
  148. synth_ai/environments/examples/sokoban/puzzle_loader.py +311 -0
  149. synth_ai/environments/examples/sokoban/taskset.py +425 -0
  150. synth_ai/environments/examples/sokoban/units/astar_common.py +94 -0
  151. synth_ai/environments/examples/sokoban/units/test_building_task_set.py +49 -0
  152. synth_ai/environments/examples/sokoban/units/test_false_positive.py +120 -0
  153. synth_ai/environments/examples/sokoban/units/test_simple_run_through_environment.py +119 -0
  154. synth_ai/environments/examples/sokoban/units/test_sokoban_environment.py +98 -0
  155. synth_ai/environments/examples/sokoban/units/test_tree.py +364 -0
  156. synth_ai/environments/examples/tictactoe/__init__.py +1 -0
  157. synth_ai/environments/examples/tictactoe/agent_demos/test_synth_react.py +266 -0
  158. synth_ai/environments/examples/tictactoe/agent_demos/test_tictactoe_react_agent.py +470 -0
  159. synth_ai/environments/examples/tictactoe/engine.py +368 -0
  160. synth_ai/environments/examples/tictactoe/environment.py +239 -0
  161. synth_ai/environments/examples/tictactoe/taskset.py +214 -0
  162. synth_ai/environments/examples/tictactoe/units/test_tictactoe_engine.py +393 -0
  163. synth_ai/environments/examples/tictactoe/units/test_tictactoe_environment.py +493 -0
  164. synth_ai/environments/examples/tictactoe/units/test_tictactoe_taskset.py +191 -0
  165. synth_ai/environments/examples/verilog/__init__.py +10 -0
  166. synth_ai/environments/examples/verilog/agent_demos/test_synth_react.py +520 -0
  167. synth_ai/environments/examples/verilog/engine.py +328 -0
  168. synth_ai/environments/examples/verilog/environment.py +349 -0
  169. synth_ai/environments/examples/verilog/taskset.py +418 -0
  170. synth_ai/environments/examples/verilog/units/test_verilog_engine.py +466 -0
  171. synth_ai/environments/examples/verilog/units/test_verilog_environment.py +585 -0
  172. synth_ai/environments/examples/verilog/units/test_verilog_integration.py +383 -0
  173. synth_ai/environments/examples/verilog/units/test_verilog_taskset.py +457 -0
  174. synth_ai/environments/reproducibility/core.py +42 -0
  175. synth_ai/environments/reproducibility/tree.py +364 -0
  176. synth_ai/environments/service/app.py +78 -0
  177. synth_ai/environments/service/core_routes.py +775 -0
  178. synth_ai/environments/service/external_registry.py +57 -0
  179. synth_ai/environments/service/registry.py +9 -0
  180. synth_ai/environments/stateful/__init__.py +1 -0
  181. synth_ai/environments/stateful/core.py +28 -0
  182. synth_ai/environments/stateful/engine.py +21 -0
  183. synth_ai/environments/stateful/state.py +7 -0
  184. synth_ai/environments/tasks/api.py +19 -0
  185. synth_ai/environments/tasks/core.py +78 -0
  186. synth_ai/environments/tasks/filters.py +39 -0
  187. synth_ai/environments/tasks/utils.py +89 -0
  188. synth_ai/environments/v0_observability/history.py +3 -0
  189. synth_ai/environments/v0_observability/log.py +2 -0
  190. synth_ai/lm/caching/constants.py +1 -0
  191. synth_ai/{zyk/lms → lm}/caching/ephemeral.py +4 -8
  192. synth_ai/{zyk/lms → lm}/caching/handler.py +15 -15
  193. synth_ai/{zyk/lms → lm}/caching/initialize.py +2 -4
  194. synth_ai/{zyk/lms → lm}/caching/persistent.py +4 -10
  195. synth_ai/{zyk/lms → lm}/config.py +2 -1
  196. synth_ai/{zyk/lms → lm}/constants.py +2 -2
  197. synth_ai/{zyk/lms → lm}/core/all.py +10 -10
  198. synth_ai/{zyk/lms → lm}/core/main.py +57 -33
  199. synth_ai/{zyk/lms → lm}/core/vendor_clients.py +12 -10
  200. synth_ai/lm/cost/monitor.py +1 -0
  201. synth_ai/lm/cost/statefulness.py +1 -0
  202. synth_ai/lm/provider_support/__init__.py +8 -0
  203. synth_ai/lm/provider_support/anthropic.py +945 -0
  204. synth_ai/lm/provider_support/openai.py +1115 -0
  205. synth_ai/lm/provider_support/suppress_logging.py +31 -0
  206. synth_ai/{zyk/lms → lm}/structured_outputs/handler.py +58 -80
  207. synth_ai/{zyk/lms → lm}/structured_outputs/inject.py +6 -20
  208. synth_ai/{zyk/lms → lm}/structured_outputs/rehabilitate.py +6 -12
  209. synth_ai/{zyk/lms → lm}/vendors/core/anthropic_api.py +21 -30
  210. synth_ai/{zyk/lms → lm}/vendors/core/gemini_api.py +37 -32
  211. synth_ai/{zyk/lms → lm}/vendors/core/mistral_api.py +19 -28
  212. synth_ai/{zyk/lms → lm}/vendors/core/openai_api.py +26 -36
  213. synth_ai/{zyk/lms → lm}/vendors/openai_standard.py +29 -33
  214. synth_ai/{zyk/lms → lm}/vendors/retries.py +1 -1
  215. synth_ai/lm/vendors/supported/__init__.py +0 -0
  216. synth_ai/{zyk/lms → lm}/vendors/supported/custom_endpoint.py +131 -118
  217. synth_ai/{zyk/lms → lm}/vendors/supported/deepseek.py +4 -8
  218. synth_ai/{zyk/lms → lm}/vendors/supported/grok.py +6 -8
  219. synth_ai/{zyk/lms → lm}/vendors/supported/groq.py +1 -1
  220. synth_ai/{zyk/lms → lm}/vendors/supported/ollama.py +2 -2
  221. synth_ai/{zyk/lms → lm}/vendors/supported/openrouter.py +18 -16
  222. synth_ai/{zyk/lms → lm}/vendors/supported/together.py +1 -1
  223. synth_ai/tracing/__init__.py +0 -0
  224. synth_ai/tracing/abstractions.py +224 -0
  225. synth_ai/tracing/base_client.py +91 -0
  226. synth_ai/tracing/client_manager.py +131 -0
  227. synth_ai/tracing/config.py +140 -0
  228. synth_ai/tracing/context.py +146 -0
  229. synth_ai/tracing/decorators.py +679 -0
  230. synth_ai/tracing/events/__init__.py +0 -0
  231. synth_ai/tracing/events/manage.py +147 -0
  232. synth_ai/tracing/events/scope.py +86 -0
  233. synth_ai/tracing/events/store.py +227 -0
  234. synth_ai/tracing/immediate_client.py +152 -0
  235. synth_ai/tracing/local.py +18 -0
  236. synth_ai/tracing/log_client_base.py +74 -0
  237. synth_ai/tracing/retry_queue.py +187 -0
  238. synth_ai/tracing/trackers.py +515 -0
  239. synth_ai/tracing/upload.py +504 -0
  240. synth_ai/tracing/utils.py +9 -0
  241. synth_ai/zyk/__init__.py +28 -2
  242. synth_ai-0.2.1.dev0.dist-info/METADATA +349 -0
  243. synth_ai-0.2.1.dev0.dist-info/RECORD +261 -0
  244. synth_ai/zyk/lms/caching/constants.py +0 -1
  245. synth_ai/zyk/lms/cost/monitor.py +0 -1
  246. synth_ai/zyk/lms/cost/statefulness.py +0 -1
  247. synth_ai-0.1.9.dist-info/METADATA +0 -37
  248. synth_ai-0.1.9.dist-info/RECORD +0 -50
  249. /synth_ai/{zyk/lms/__init__.py → environments/reproducibility/helpers.py} +0 -0
  250. /synth_ai/{zyk/lms/caching → lm}/__init__.py +0 -0
  251. /synth_ai/{zyk/lms/core → lm/caching}/__init__.py +0 -0
  252. /synth_ai/{zyk/lms → lm}/caching/dbs.py +0 -0
  253. /synth_ai/{zyk/lms/cost → lm/core}/__init__.py +0 -0
  254. /synth_ai/{zyk/lms → lm}/core/exceptions.py +0 -0
  255. /synth_ai/{zyk/lms/structured_outputs → lm/cost}/__init__.py +0 -0
  256. /synth_ai/{zyk/lms/vendors → lm/structured_outputs}/__init__.py +0 -0
  257. /synth_ai/{zyk/lms → lm}/tools/__init__.py +0 -0
  258. /synth_ai/{zyk/lms → lm}/tools/base.py +0 -0
  259. /synth_ai/{zyk/lms/vendors/core → lm/vendors}/__init__.py +0 -0
  260. /synth_ai/{zyk/lms → lm}/vendors/base.py +0 -0
  261. /synth_ai/{zyk/lms/vendors/local → lm/vendors/core}/__init__.py +0 -0
  262. /synth_ai/{zyk/lms/vendors/supported → lm/vendors/local}/__init__.py +0 -0
  263. /synth_ai/{zyk/lms → lm}/vendors/local/ollama.py +0 -0
  264. {synth_ai-0.1.9.dist-info → synth_ai-0.2.1.dev0.dist-info}/WHEEL +0 -0
  265. {synth_ai-0.1.9.dist-info → synth_ai-0.2.1.dev0.dist-info}/licenses/LICENSE +0 -0
  266. {synth_ai-0.1.9.dist-info → synth_ai-0.2.1.dev0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,311 @@
1
+ """
2
+ Puzzle loader for pre-generated verified Sokoban puzzles.
3
+ """
4
+
5
+ import json
6
+ import logging
7
+ import random
8
+ from pathlib import Path
9
+ from typing import Dict, List, Optional, Tuple, Any
10
+ from dataclasses import dataclass
11
+ import numpy as np
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @dataclass
17
+ class SokobanPuzzle:
18
+ """Represents a verified solvable Sokoban puzzle."""
19
+
20
+ id: str
21
+ difficulty: str
22
+ num_boxes: int
23
+ dim_room: Tuple[int, int]
24
+ room_fixed: List[List[int]]
25
+ room_state: List[List[int]]
26
+ box_mapping: Dict[str, Any]
27
+ solution_path: List[int]
28
+ solution_length: int
29
+ generation_seed: int
30
+ max_steps: int
31
+
32
+ def to_numpy(self) -> Tuple[np.ndarray, np.ndarray]:
33
+ """Convert room data to numpy arrays for use with the engine."""
34
+ return np.array(self.room_fixed), np.array(self.room_state)
35
+
36
+ def to_engine_snapshot(self) -> Dict[str, Any]:
37
+ """Convert puzzle to engine snapshot format."""
38
+ return {
39
+ "dim_room": list(self.dim_room),
40
+ "room_fixed": self.room_fixed,
41
+ "room_state": self.room_state,
42
+ "box_mapping": self.box_mapping,
43
+ "boxes_on_target": self._count_boxes_on_target(),
44
+ "max_steps": self.max_steps,
45
+ "num_boxes": self.num_boxes,
46
+ }
47
+
48
+ def _count_boxes_on_target(self) -> int:
49
+ """Count boxes currently on targets (value 3)."""
50
+ room_state = np.array(self.room_state)
51
+ return int(np.sum(room_state == 3))
52
+
53
+
54
+ class SokobanPuzzleLoader:
55
+ """Manages loading and accessing pre-generated Sokoban puzzles."""
56
+
57
+ def __init__(self, puzzle_file_path: Optional[Path] = None):
58
+ """
59
+ Initialize the puzzle loader.
60
+
61
+ Args:
62
+ puzzle_file_path: Path to the JSON file containing puzzles.
63
+ If None, uses default path.
64
+ """
65
+ if puzzle_file_path is None:
66
+ puzzle_file_path = Path(__file__).parent / "verified_puzzles.json"
67
+
68
+ self.puzzle_file_path = puzzle_file_path
69
+ self.puzzles: Dict[str, List[SokobanPuzzle]] = {}
70
+ self.metadata: Dict[str, Any] = {}
71
+ self._loaded = False
72
+
73
+ def load_puzzles(self) -> None:
74
+ """Load puzzles from the JSON file."""
75
+ if self._loaded:
76
+ return
77
+
78
+ if not self.puzzle_file_path.exists():
79
+ raise FileNotFoundError(f"Puzzle file not found: {self.puzzle_file_path}")
80
+
81
+ try:
82
+ with open(self.puzzle_file_path, "r") as f:
83
+ data = json.load(f)
84
+
85
+ self.metadata = data.get("metadata", {})
86
+ puzzle_data = data.get("puzzles", {})
87
+
88
+ # Convert to SokobanPuzzle objects
89
+ for difficulty, puzzle_list in puzzle_data.items():
90
+ self.puzzles[difficulty] = []
91
+ for puzzle_dict in puzzle_list:
92
+ puzzle = SokobanPuzzle(
93
+ id=puzzle_dict["id"],
94
+ difficulty=puzzle_dict["difficulty"],
95
+ num_boxes=puzzle_dict["num_boxes"],
96
+ dim_room=tuple(puzzle_dict["dim_room"]),
97
+ room_fixed=puzzle_dict["room_fixed"],
98
+ room_state=puzzle_dict["room_state"],
99
+ box_mapping=puzzle_dict["box_mapping"],
100
+ solution_path=puzzle_dict["solution_path"],
101
+ solution_length=puzzle_dict["solution_length"],
102
+ generation_seed=puzzle_dict["generation_seed"],
103
+ max_steps=puzzle_dict["max_steps"],
104
+ )
105
+ self.puzzles[difficulty].append(puzzle)
106
+
107
+ self._loaded = True
108
+ logger.info(
109
+ f"Loaded {self.get_total_puzzle_count()} puzzles from {self.puzzle_file_path}"
110
+ )
111
+
112
+ except Exception as e:
113
+ logger.error(f"Error loading puzzles: {e}")
114
+ raise
115
+
116
+ def get_puzzle_by_id(self, puzzle_id: str) -> Optional[SokobanPuzzle]:
117
+ """Get a specific puzzle by its ID."""
118
+ self.load_puzzles()
119
+
120
+ for difficulty_puzzles in self.puzzles.values():
121
+ for puzzle in difficulty_puzzles:
122
+ if puzzle.id == puzzle_id:
123
+ return puzzle
124
+ return None
125
+
126
+ def get_puzzles_by_difficulty(self, difficulty: str) -> List[SokobanPuzzle]:
127
+ """Get all puzzles for a specific difficulty level."""
128
+ self.load_puzzles()
129
+ return self.puzzles.get(difficulty, [])
130
+
131
+ def get_random_puzzle(self, difficulty: str) -> Optional[SokobanPuzzle]:
132
+ """Get a random puzzle from the specified difficulty level."""
133
+ puzzles = self.get_puzzles_by_difficulty(difficulty)
134
+ if not puzzles:
135
+ return None
136
+ return random.choice(puzzles)
137
+
138
+ def get_puzzle_by_index(self, difficulty: str, index: int) -> Optional[SokobanPuzzle]:
139
+ """Get a puzzle by its index within a difficulty level."""
140
+ puzzles = self.get_puzzles_by_difficulty(difficulty)
141
+ if 0 <= index < len(puzzles):
142
+ return puzzles[index]
143
+ return None
144
+
145
+ def get_puzzle_by_seed(self, difficulty: str, seed: int) -> Optional[SokobanPuzzle]:
146
+ """
147
+ Get a puzzle deterministically using a seed via modular arithmetic.
148
+ Same seed will always return the same puzzle for a given difficulty.
149
+
150
+ Args:
151
+ difficulty: The difficulty level
152
+ seed: Integer seed for deterministic selection
153
+
154
+ Returns:
155
+ SokobanPuzzle or None if no puzzles available
156
+ """
157
+ puzzles = self.get_puzzles_by_difficulty(difficulty)
158
+ if not puzzles:
159
+ return None
160
+
161
+ # Use modular arithmetic to map seed to puzzle index
162
+ index = seed % len(puzzles)
163
+ return puzzles[index]
164
+
165
+ def get_available_difficulties(self) -> List[str]:
166
+ """Get list of available difficulty levels."""
167
+ self.load_puzzles()
168
+ return list(self.puzzles.keys())
169
+
170
+ def get_puzzle_count(self, difficulty: str) -> int:
171
+ """Get the number of puzzles for a specific difficulty."""
172
+ return len(self.get_puzzles_by_difficulty(difficulty))
173
+
174
+ def get_total_puzzle_count(self) -> int:
175
+ """Get the total number of puzzles across all difficulties."""
176
+ self.load_puzzles()
177
+ return sum(len(puzzles) for puzzles in self.puzzles.values())
178
+
179
+ def get_puzzles_by_criteria(
180
+ self,
181
+ difficulty: Optional[str] = None,
182
+ num_boxes: Optional[int] = None,
183
+ min_solution_length: Optional[int] = None,
184
+ max_solution_length: Optional[int] = None,
185
+ max_results: Optional[int] = None,
186
+ ) -> List[SokobanPuzzle]:
187
+ """
188
+ Get puzzles matching specific criteria.
189
+
190
+ Args:
191
+ difficulty: Filter by difficulty level
192
+ num_boxes: Filter by number of boxes
193
+ min_solution_length: Minimum solution length
194
+ max_solution_length: Maximum solution length
195
+ max_results: Maximum number of results to return
196
+
197
+ Returns:
198
+ List of matching puzzles
199
+ """
200
+ self.load_puzzles()
201
+
202
+ all_puzzles = []
203
+ if difficulty:
204
+ all_puzzles = self.get_puzzles_by_difficulty(difficulty)
205
+ else:
206
+ for difficulty_puzzles in self.puzzles.values():
207
+ all_puzzles.extend(difficulty_puzzles)
208
+
209
+ # Apply filters
210
+ filtered_puzzles = []
211
+ for puzzle in all_puzzles:
212
+ if num_boxes is not None and puzzle.num_boxes != num_boxes:
213
+ continue
214
+ if min_solution_length is not None and puzzle.solution_length < min_solution_length:
215
+ continue
216
+ if max_solution_length is not None and puzzle.solution_length > max_solution_length:
217
+ continue
218
+ filtered_puzzles.append(puzzle)
219
+
220
+ # Limit results
221
+ if max_results is not None:
222
+ filtered_puzzles = filtered_puzzles[:max_results]
223
+
224
+ return filtered_puzzles
225
+
226
+ def get_metadata(self) -> Dict[str, Any]:
227
+ """Get metadata about the puzzle set."""
228
+ self.load_puzzles()
229
+ return self.metadata
230
+
231
+ def get_metadata_for_filtering(self) -> Dict[str, Any]:
232
+ """Get metadata to help with filtering across environments."""
233
+ self.load_puzzles()
234
+
235
+ return {
236
+ "environment_type": "sokoban",
237
+ "total_puzzles": self.get_total_puzzle_count(),
238
+ "difficulties": self.get_available_difficulties(),
239
+ "difficulty_counts": {
240
+ difficulty: len(puzzles) for difficulty, puzzles in self.puzzles.items()
241
+ },
242
+ "features": {
243
+ "has_boxes": True,
244
+ "has_targets": True,
245
+ "has_player": True,
246
+ "grid_based": True,
247
+ "puzzle_type": "box_pushing",
248
+ },
249
+ "difficulty_ranges": {
250
+ "ultra_easy": {"boxes": 1, "grid_size": (5, 5), "solution_length": (3, 8)},
251
+ "easy": {"boxes": 1, "grid_size": (6, 6), "solution_length": (8, 15)},
252
+ "medium": {"boxes": 2, "grid_size": (7, 7), "solution_length": (15, 30)},
253
+ "hard": {"boxes": 3, "grid_size": (8, 8), "solution_length": (30, 60)},
254
+ },
255
+ }
256
+
257
+ def print_summary(self) -> None:
258
+ """Print a summary of loaded puzzles."""
259
+ self.load_puzzles()
260
+
261
+ print(f"Sokoban Puzzle Summary:")
262
+ print(f"Total puzzles: {self.get_total_puzzle_count()}")
263
+ print(f"Difficulties: {', '.join(self.get_available_difficulties())}")
264
+ print()
265
+
266
+ for difficulty in self.get_available_difficulties():
267
+ puzzles = self.get_puzzles_by_difficulty(difficulty)
268
+ if puzzles:
269
+ avg_solution_length = sum(p.solution_length for p in puzzles) / len(puzzles)
270
+ min_solution_length = min(p.solution_length for p in puzzles)
271
+ max_solution_length = max(p.solution_length for p in puzzles)
272
+
273
+ print(f"{difficulty}:")
274
+ print(f" Count: {len(puzzles)}")
275
+ print(f" Avg solution length: {avg_solution_length:.1f}")
276
+ print(f" Solution length range: {min_solution_length}-{max_solution_length}")
277
+ print(f" Boxes: {puzzles[0].num_boxes}")
278
+ print(f" Room size: {puzzles[0].dim_room}")
279
+ print()
280
+
281
+
282
+ # Global instance for easy access
283
+ _global_loader = None
284
+
285
+
286
+ def get_puzzle_loader() -> SokobanPuzzleLoader:
287
+ """Get the global puzzle loader instance."""
288
+ global _global_loader
289
+ if _global_loader is None:
290
+ _global_loader = SokobanPuzzleLoader()
291
+ return _global_loader
292
+
293
+
294
+ def get_puzzle_by_id(puzzle_id: str) -> Optional[SokobanPuzzle]:
295
+ """Convenience function to get a puzzle by ID."""
296
+ return get_puzzle_loader().get_puzzle_by_id(puzzle_id)
297
+
298
+
299
+ def get_random_puzzle(difficulty: str) -> Optional[SokobanPuzzle]:
300
+ """Convenience function to get a random puzzle."""
301
+ return get_puzzle_loader().get_random_puzzle(difficulty)
302
+
303
+
304
+ def get_puzzle_by_index(difficulty: str, index: int) -> Optional[SokobanPuzzle]:
305
+ """Convenience function to get a puzzle by index."""
306
+ return get_puzzle_loader().get_puzzle_by_index(difficulty, index)
307
+
308
+
309
+ def get_puzzle_by_seed(difficulty: str, seed: int) -> Optional[SokobanPuzzle]:
310
+ """Convenience function to get a puzzle by seed."""
311
+ return get_puzzle_loader().get_puzzle_by_seed(difficulty, seed)