cogames 0.3.49__py3-none-any.whl → 0.3.64__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 (169) hide show
  1. cogames/cli/client.py +60 -6
  2. cogames/cli/docsync/__init__.py +0 -0
  3. cogames/cli/docsync/_nb_md_directive_processing.py +180 -0
  4. cogames/cli/docsync/_nb_md_sync.py +103 -0
  5. cogames/cli/docsync/_nb_py_sync.py +122 -0
  6. cogames/cli/docsync/_three_way_sync.py +115 -0
  7. cogames/cli/docsync/_utils.py +76 -0
  8. cogames/cli/docsync/docsync.py +156 -0
  9. cogames/cli/leaderboard.py +112 -28
  10. cogames/cli/mission.py +64 -53
  11. cogames/cli/policy.py +46 -10
  12. cogames/cli/submit.py +268 -67
  13. cogames/cogs_vs_clips/cog.py +79 -0
  14. cogames/cogs_vs_clips/cogs_vs_clips_mapgen.md +19 -16
  15. cogames/cogs_vs_clips/cogsguard_reward_variants.py +153 -0
  16. cogames/cogs_vs_clips/cogsguard_tutorial.py +56 -0
  17. cogames/cogs_vs_clips/evals/README.md +10 -16
  18. cogames/cogs_vs_clips/evals/cogsguard_evals.py +81 -0
  19. cogames/cogs_vs_clips/evals/diagnostic_evals.py +49 -444
  20. cogames/cogs_vs_clips/evals/difficulty_variants.py +13 -326
  21. cogames/cogs_vs_clips/evals/integrated_evals.py +5 -45
  22. cogames/cogs_vs_clips/evals/spanning_evals.py +9 -180
  23. cogames/cogs_vs_clips/mission.py +187 -146
  24. cogames/cogs_vs_clips/missions.py +46 -137
  25. cogames/cogs_vs_clips/procedural.py +8 -8
  26. cogames/cogs_vs_clips/sites.py +107 -3
  27. cogames/cogs_vs_clips/stations.py +198 -186
  28. cogames/cogs_vs_clips/tutorial_missions.py +1 -1
  29. cogames/cogs_vs_clips/variants.py +25 -476
  30. cogames/device.py +13 -1
  31. cogames/{policy/scripted_agent/README.md → docs/SCRIPTED_AGENT.md} +82 -58
  32. cogames/evaluate.py +18 -30
  33. cogames/main.py +1434 -243
  34. cogames/maps/canidate1_1000.map +1 -1
  35. cogames/maps/canidate1_1000_stations.map +2 -2
  36. cogames/maps/canidate1_500.map +1 -1
  37. cogames/maps/canidate1_500_stations.map +2 -2
  38. cogames/maps/canidate2_1000.map +1 -1
  39. cogames/maps/canidate2_1000_stations.map +2 -2
  40. cogames/maps/canidate2_500.map +1 -1
  41. cogames/maps/canidate2_500_stations.map +2 -2
  42. cogames/maps/canidate3_1000.map +1 -1
  43. cogames/maps/canidate3_1000_stations.map +2 -2
  44. cogames/maps/canidate3_500.map +1 -1
  45. cogames/maps/canidate3_500_stations.map +2 -2
  46. cogames/maps/canidate4_500.map +1 -1
  47. cogames/maps/canidate4_500_stations.map +2 -2
  48. cogames/maps/cave_base_50.map +2 -2
  49. cogames/maps/diagnostic_evals/diagnostic_agile.map +2 -2
  50. cogames/maps/diagnostic_evals/diagnostic_agile_hard.map +2 -2
  51. cogames/maps/diagnostic_evals/diagnostic_charge_up.map +2 -2
  52. cogames/maps/diagnostic_evals/diagnostic_charge_up_hard.map +2 -2
  53. cogames/maps/diagnostic_evals/diagnostic_chest_navigation1.map +2 -2
  54. cogames/maps/diagnostic_evals/diagnostic_chest_navigation1_hard.map +2 -2
  55. cogames/maps/diagnostic_evals/diagnostic_chest_navigation2.map +2 -2
  56. cogames/maps/diagnostic_evals/diagnostic_chest_navigation2_hard.map +2 -2
  57. cogames/maps/diagnostic_evals/diagnostic_chest_navigation3.map +2 -2
  58. cogames/maps/diagnostic_evals/diagnostic_chest_navigation3_hard.map +2 -2
  59. cogames/maps/diagnostic_evals/diagnostic_chest_near.map +2 -2
  60. cogames/maps/diagnostic_evals/diagnostic_chest_search.map +2 -2
  61. cogames/maps/diagnostic_evals/diagnostic_chest_search_hard.map +2 -2
  62. cogames/maps/diagnostic_evals/diagnostic_extract_lab.map +2 -2
  63. cogames/maps/diagnostic_evals/diagnostic_extract_lab_hard.map +2 -2
  64. cogames/maps/diagnostic_evals/diagnostic_memory.map +2 -2
  65. cogames/maps/diagnostic_evals/diagnostic_memory_hard.map +2 -2
  66. cogames/maps/diagnostic_evals/diagnostic_radial.map +2 -2
  67. cogames/maps/diagnostic_evals/diagnostic_radial_hard.map +2 -2
  68. cogames/maps/diagnostic_evals/diagnostic_resource_lab.map +2 -2
  69. cogames/maps/diagnostic_evals/diagnostic_unclip.map +2 -2
  70. cogames/maps/evals/eval_balanced_spread.map +9 -5
  71. cogames/maps/evals/eval_clip_oxygen.map +9 -5
  72. cogames/maps/evals/eval_collect_resources.map +9 -5
  73. cogames/maps/evals/eval_collect_resources_hard.map +9 -5
  74. cogames/maps/evals/eval_collect_resources_medium.map +9 -5
  75. cogames/maps/evals/eval_divide_and_conquer.map +9 -5
  76. cogames/maps/evals/eval_energy_starved.map +9 -5
  77. cogames/maps/evals/eval_multi_coordinated_collect_hard.map +9 -5
  78. cogames/maps/evals/eval_oxygen_bottleneck.map +9 -5
  79. cogames/maps/evals/eval_single_use_world.map +9 -5
  80. cogames/maps/evals/extractor_hub_100x100.map +9 -5
  81. cogames/maps/evals/extractor_hub_30x30.map +9 -5
  82. cogames/maps/evals/extractor_hub_50x50.map +9 -5
  83. cogames/maps/evals/extractor_hub_70x70.map +9 -5
  84. cogames/maps/evals/extractor_hub_80x80.map +9 -5
  85. cogames/maps/machina_100_stations.map +2 -2
  86. cogames/maps/machina_200_stations.map +2 -2
  87. cogames/maps/machina_200_stations_small.map +2 -2
  88. cogames/maps/machina_eval_exp01.map +2 -2
  89. cogames/maps/machina_eval_template_large.map +2 -2
  90. cogames/maps/machinatrainer4agents.map +2 -2
  91. cogames/maps/machinatrainer4agentsbase.map +2 -2
  92. cogames/maps/machinatrainerbig.map +2 -2
  93. cogames/maps/machinatrainersmall.map +2 -2
  94. cogames/maps/planky_evals/aligner_avoid_aoe.map +28 -0
  95. cogames/maps/planky_evals/aligner_full_cycle.map +28 -0
  96. cogames/maps/planky_evals/aligner_gear.map +24 -0
  97. cogames/maps/planky_evals/aligner_hearts.map +24 -0
  98. cogames/maps/planky_evals/aligner_junction.map +26 -0
  99. cogames/maps/planky_evals/exploration_distant.map +28 -0
  100. cogames/maps/planky_evals/maze.map +32 -0
  101. cogames/maps/planky_evals/miner_best_resource.map +26 -0
  102. cogames/maps/planky_evals/miner_deposit.map +24 -0
  103. cogames/maps/planky_evals/miner_extract.map +26 -0
  104. cogames/maps/planky_evals/miner_full_cycle.map +28 -0
  105. cogames/maps/planky_evals/miner_gear.map +24 -0
  106. cogames/maps/planky_evals/multi_role.map +28 -0
  107. cogames/maps/planky_evals/resource_chain.map +30 -0
  108. cogames/maps/planky_evals/scout_explore.map +32 -0
  109. cogames/maps/planky_evals/scout_gear.map +24 -0
  110. cogames/maps/planky_evals/scrambler_full_cycle.map +28 -0
  111. cogames/maps/planky_evals/scrambler_gear.map +24 -0
  112. cogames/maps/planky_evals/scrambler_target.map +26 -0
  113. cogames/maps/planky_evals/stuck_corridor.map +32 -0
  114. cogames/maps/planky_evals/survive_retreat.map +26 -0
  115. cogames/maps/training_facility_clipped.map +2 -2
  116. cogames/maps/training_facility_open_1.map +2 -2
  117. cogames/maps/training_facility_open_2.map +2 -2
  118. cogames/maps/training_facility_open_3.map +2 -2
  119. cogames/maps/training_facility_tight_4.map +2 -2
  120. cogames/maps/training_facility_tight_5.map +2 -2
  121. cogames/maps/vanilla_large.map +2 -2
  122. cogames/maps/vanilla_small.map +2 -2
  123. cogames/pickup.py +183 -0
  124. cogames/play.py +166 -33
  125. cogames/policy/chaos_monkey.py +54 -0
  126. cogames/policy/nim_agents/__init__.py +27 -10
  127. cogames/policy/nim_agents/agents.py +121 -60
  128. cogames/policy/nim_agents/thinky_eval.py +35 -222
  129. cogames/policy/pufferlib_policy.py +67 -32
  130. cogames/policy/starter_agent.py +184 -0
  131. cogames/policy/trainable_policy_template.py +4 -1
  132. cogames/train.py +51 -13
  133. cogames/verbose.py +2 -2
  134. cogames-0.3.64.dist-info/METADATA +1842 -0
  135. cogames-0.3.64.dist-info/RECORD +159 -0
  136. cogames-0.3.64.dist-info/licenses/LICENSE +21 -0
  137. cogames-0.3.64.dist-info/top_level.txt +2 -0
  138. metta_alo/__init__.py +0 -0
  139. metta_alo/job_specs.py +17 -0
  140. metta_alo/policy.py +16 -0
  141. metta_alo/pure_single_episode_runner.py +75 -0
  142. metta_alo/py.typed +0 -0
  143. metta_alo/rollout.py +322 -0
  144. metta_alo/scoring.py +168 -0
  145. cogames/maps/diagnostic_evals/diagnostic_assembler_near.map +0 -49
  146. cogames/maps/diagnostic_evals/diagnostic_assembler_search.map +0 -49
  147. cogames/maps/diagnostic_evals/diagnostic_assembler_search_hard.map +0 -89
  148. cogames/policy/nim_agents/common.nim +0 -887
  149. cogames/policy/nim_agents/install.sh +0 -1
  150. cogames/policy/nim_agents/ladybug_agent.nim +0 -984
  151. cogames/policy/nim_agents/nim_agents.nim +0 -55
  152. cogames/policy/nim_agents/nim_agents.nims +0 -14
  153. cogames/policy/nim_agents/nimby.lock +0 -3
  154. cogames/policy/nim_agents/racecar_agents.nim +0 -884
  155. cogames/policy/nim_agents/random_agents.nim +0 -68
  156. cogames/policy/nim_agents/test_agents.py +0 -53
  157. cogames/policy/nim_agents/thinky_agents.nim +0 -717
  158. cogames/policy/scripted_agent/baseline_agent.py +0 -1049
  159. cogames/policy/scripted_agent/demo_policy.py +0 -244
  160. cogames/policy/scripted_agent/pathfinding.py +0 -126
  161. cogames/policy/scripted_agent/starter_agent.py +0 -136
  162. cogames/policy/scripted_agent/types.py +0 -235
  163. cogames/policy/scripted_agent/unclipping_agent.py +0 -476
  164. cogames/policy/scripted_agent/utils.py +0 -385
  165. cogames-0.3.49.dist-info/METADATA +0 -406
  166. cogames-0.3.49.dist-info/RECORD +0 -136
  167. cogames-0.3.49.dist-info/top_level.txt +0 -1
  168. {cogames-0.3.49.dist-info → cogames-0.3.64.dist-info}/WHEEL +0 -0
  169. {cogames-0.3.49.dist-info → cogames-0.3.64.dist-info}/entry_points.txt +0 -0
@@ -1,385 +0,0 @@
1
- """
2
- Utility functions for scripted agents.
3
-
4
- Pure/stateless helper functions that can be reused across different agents.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- from typing import Any, Union
10
-
11
- from mettagrid.simulator import Action
12
- from mettagrid.simulator.interface import AgentObservation
13
-
14
- from .types import ObjectState, ParsedObservation, SimpleAgentState
15
-
16
-
17
- def manhattan_distance(pos1: tuple[int, int], pos2: tuple[int, int]) -> int:
18
- """Calculate Manhattan distance between two positions."""
19
- return abs(pos1[0] - pos2[0]) + abs(pos1[1] - pos2[1])
20
-
21
-
22
- def is_adjacent(pos1: tuple[int, int], pos2: tuple[int, int]) -> bool:
23
- """Check if two positions are adjacent (4-way cardinal directions)."""
24
- dr = abs(pos1[0] - pos2[0])
25
- dc = abs(pos1[1] - pos2[1])
26
- return (dr == 1 and dc == 0) or (dr == 0 and dc == 1)
27
-
28
-
29
- def is_within_bounds(pos: tuple[int, int], map_height: int, map_width: int) -> bool:
30
- """Check if a position is within map bounds."""
31
- r, c = pos
32
- return 0 <= r < map_height and 0 <= c < map_width
33
-
34
-
35
- def get_cardinal_neighbors(pos: tuple[int, int]) -> list[tuple[int, int]]:
36
- """Get all 4-way cardinal neighbor positions."""
37
- r, c = pos
38
- return [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)]
39
-
40
-
41
- def is_wall(obj_name: str) -> bool:
42
- """Check if an object name represents a wall or obstacle."""
43
- return "wall" in obj_name or "#" in obj_name or obj_name in {"wall", "obstacle"}
44
-
45
-
46
- def is_floor(obj_name: str) -> bool:
47
- """Check if an object name represents floor (passable empty space)."""
48
- # Environment returns empty string for empty cells
49
- return obj_name in {"floor", ""}
50
-
51
-
52
- def is_station(obj_name: str, station: str) -> bool:
53
- """Check if an object name contains a specific station type."""
54
- return station in obj_name
55
-
56
-
57
- def position_to_direction(from_pos: tuple[int, int], to_pos: tuple[int, int]) -> str | None:
58
- """
59
- Convert adjacent positions to a cardinal direction name.
60
-
61
- Returns: "north", "south", "east", "west", or None if not adjacent.
62
- """
63
- dr = to_pos[0] - from_pos[0]
64
- dc = to_pos[1] - from_pos[1]
65
-
66
- if dr == -1 and dc == 0:
67
- return "north"
68
- elif dr == 1 and dc == 0:
69
- return "south"
70
- elif dr == 0 and dc == 1:
71
- return "east"
72
- elif dr == 0 and dc == -1:
73
- return "west"
74
- return None
75
-
76
-
77
- def process_feature_at_position(
78
- position_features: dict[tuple[int, int], dict[str, Union[int, list[int], dict[str, int]]]],
79
- pos: tuple[int, int],
80
- feature_name: str,
81
- value: int,
82
- *,
83
- spatial_feature_names: set[str],
84
- agent_feature_key_by_name: dict[str, str],
85
- protocol_input_prefix: str,
86
- protocol_output_prefix: str,
87
- ) -> None:
88
- """Process a single observation feature and add it to position_features."""
89
- if pos not in position_features:
90
- position_features[pos] = {}
91
-
92
- # Handle spatial features (tag, cooldown, etc.)
93
- if feature_name in spatial_feature_names:
94
- # Tag: collect all tags as a list (objects can have multiple tags)
95
- if feature_name == "tag":
96
- tags = position_features[pos].setdefault("tags", [])
97
- if isinstance(tags, list):
98
- tags.append(value)
99
- return
100
- # Other spatial features are single values
101
- position_features[pos][feature_name] = value
102
- return
103
-
104
- # Handle agent features (agent:group -> agent_group, etc.)
105
- agent_feature_key = agent_feature_key_by_name.get(feature_name)
106
- if agent_feature_key is not None:
107
- position_features[pos][agent_feature_key] = value
108
- return
109
-
110
- # Handle protocol features (recipes)
111
- if feature_name.startswith(protocol_input_prefix):
112
- resource = feature_name[len(protocol_input_prefix) :]
113
- inputs = position_features[pos].setdefault("protocol_inputs", {})
114
- if isinstance(inputs, dict):
115
- inputs[resource] = value
116
- return
117
-
118
- if feature_name.startswith(protocol_output_prefix):
119
- resource = feature_name[len(protocol_output_prefix) :]
120
- outputs = position_features[pos].setdefault("protocol_outputs", {})
121
- if isinstance(outputs, dict):
122
- outputs[resource] = value
123
- return
124
-
125
-
126
- def create_object_state(
127
- features: dict[str, Union[int, list[int], dict[str, int]]],
128
- *,
129
- tag_names: dict[int, str],
130
- ) -> ObjectState:
131
- """Create an ObjectState from collected features.
132
-
133
- Note: Objects can have multiple tags (e.g., "wall" + "green" vibe).
134
- We use the first tag as the primary object name.
135
- """
136
- # Get tags list (now stored as "tags" instead of "tag")
137
- tags_value = features.get("tags", [])
138
- if isinstance(tags_value, list):
139
- tag_ids = list(tags_value)
140
- elif isinstance(tags_value, int):
141
- tag_ids = [tags_value]
142
- else:
143
- tag_ids = []
144
-
145
- # Use first tag as primary object name
146
- if tag_ids:
147
- primary_tag_id = tag_ids[0]
148
- obj_name = tag_names.get(primary_tag_id, f"unknown_tag_{primary_tag_id}")
149
- else:
150
- obj_name = "unknown"
151
-
152
- # Helper to safely extract int values
153
- def get_int(key: str, default: int) -> int:
154
- val = features.get(key, default)
155
- return int(val) if isinstance(val, int) else default
156
-
157
- # Helper to safely extract dict values
158
- def get_dict(key: str) -> dict[str, int]:
159
- val = features.get(key, {})
160
- return dict(val) if isinstance(val, dict) else {}
161
-
162
- return ObjectState(
163
- name=obj_name,
164
- cooldown_remaining=get_int("cooldown_remaining", 0),
165
- clipped=get_int("clipped", 0),
166
- remaining_uses=get_int("remaining_uses", 999),
167
- protocol_inputs=get_dict("protocol_inputs"),
168
- protocol_outputs=get_dict("protocol_outputs"),
169
- agent_group=get_int("agent_group", -1),
170
- agent_frozen=get_int("agent_frozen", 0),
171
- )
172
-
173
-
174
- def read_inventory_from_obs(
175
- state: SimpleAgentState,
176
- obs: AgentObservation,
177
- *,
178
- obs_hr: int,
179
- obs_wr: int,
180
- ) -> None:
181
- """Read inventory from observation tokens at center cell and update state."""
182
- inv = {}
183
- center_r, center_c = obs_hr, obs_wr
184
- for tok in obs.tokens:
185
- if tok.location == (center_r, center_c):
186
- feature_name = tok.feature.name
187
- if feature_name.startswith("inv:"):
188
- resource_name = feature_name[4:] # Remove "inv:" prefix
189
- inv[resource_name] = tok.value
190
-
191
- state.energy = inv.get("energy", 0)
192
- state.carbon = inv.get("carbon", 0)
193
- state.oxygen = inv.get("oxygen", 0)
194
- state.germanium = inv.get("germanium", 0)
195
- state.silicon = inv.get("silicon", 0)
196
- state.hearts = inv.get("heart", 0)
197
- state.decoder = inv.get("decoder", 0)
198
- state.modulator = inv.get("modulator", 0)
199
- state.resonator = inv.get("resonator", 0)
200
- state.scrambler = inv.get("scrambler", 0)
201
-
202
-
203
- def parse_observation(
204
- state: SimpleAgentState,
205
- obs: AgentObservation,
206
- *,
207
- obs_hr: int,
208
- obs_wr: int,
209
- spatial_feature_names: set[str],
210
- agent_feature_key_by_name: dict[str, str],
211
- protocol_input_prefix: str,
212
- protocol_output_prefix: str,
213
- tag_names: dict[int, str],
214
- debug: bool = False,
215
- ) -> ParsedObservation:
216
- """Parse token-based observation into structured format.
217
-
218
- AgentObservation with tokens (ObservationToken list)
219
- - Inventory is obtained via agent.inventory (not parsed here)
220
- - Only spatial features are parsed from observations
221
-
222
- Converts egocentric spatial coordinates to world coordinates using agent position.
223
- Agent position (agent_row, agent_col) comes from simulation.grid_objects().
224
- """
225
- # First pass: collect all spatial features by position
226
- position_features: dict[tuple[int, int], dict[str, Union[int, list[int], dict[str, int]]]] = {}
227
-
228
- for tok in obs.tokens:
229
- obs_r, obs_c = tok.location
230
- feature_name = tok.feature.name
231
- value = tok.value
232
-
233
- # Skip center location - that's inventory/global obs, obtained via agent.inventory
234
- if obs_r == obs_hr and obs_c == obs_wr:
235
- continue
236
-
237
- # Convert observation-relative coords to world coords
238
- if state.row >= 0 and state.col >= 0:
239
- r = obs_r - obs_hr + state.row
240
- c = obs_c - obs_wr + state.col
241
-
242
- if 0 <= r < state.map_height and 0 <= c < state.map_width:
243
- process_feature_at_position(
244
- position_features,
245
- (r, c),
246
- feature_name,
247
- value,
248
- spatial_feature_names=spatial_feature_names,
249
- agent_feature_key_by_name=agent_feature_key_by_name,
250
- protocol_input_prefix=protocol_input_prefix,
251
- protocol_output_prefix=protocol_output_prefix,
252
- )
253
-
254
- # Second pass: create ObjectState for each position with tags
255
- nearby_objects = {
256
- pos: create_object_state(features, tag_names=tag_names)
257
- for pos, features in position_features.items()
258
- if "tags" in features # Note: stored as "tags" (plural) to support multiple tags per object
259
- }
260
-
261
- return ParsedObservation(
262
- row=state.row,
263
- col=state.col,
264
- energy=0, # Inventory obtained via agent.inventory
265
- carbon=0,
266
- oxygen=0,
267
- germanium=0,
268
- silicon=0,
269
- hearts=0,
270
- decoder=0,
271
- modulator=0,
272
- resonator=0,
273
- scrambler=0,
274
- nearby_objects=nearby_objects,
275
- )
276
-
277
-
278
- def change_vibe_action(
279
- vibe_name: str,
280
- *,
281
- actions: Any, # PolicyEnvInterface.actions
282
- ) -> Action:
283
- """
284
- Return a safe vibe-change action.
285
- Guard against disabled or single-vibe configurations before issuing the action.
286
- """
287
- from mettagrid.config.vibes import VIBE_BY_NAME
288
-
289
- change_vibe_cfg = getattr(actions, "change_vibe", None)
290
- if change_vibe_cfg is None:
291
- return actions.noop.Noop()
292
- if not getattr(change_vibe_cfg, "enabled", True):
293
- return actions.noop.Noop()
294
- num_vibes = len(getattr(change_vibe_cfg, "vibes", []))
295
- if num_vibes <= 1:
296
- return actions.noop.Noop()
297
- # Raise loudly if the requested vibe isn't registered instead of silently
298
- # falling back to noop; otherwise config issues become very hard to spot.
299
- vibe = VIBE_BY_NAME.get(vibe_name)
300
- if vibe is None:
301
- raise Exception(f"No valid vibes called {vibe_name}")
302
- return actions.change_vibe.ChangeVibe(vibe)
303
-
304
-
305
- def update_agent_position(
306
- state: SimpleAgentState,
307
- *,
308
- move_deltas: dict[str, tuple[int, int]],
309
- ) -> None:
310
- """Update agent position based on last action.
311
-
312
- Position is tracked relative to origin (starting position), using only movement deltas.
313
- No dependency on simulation.grid_objects().
314
-
315
- IMPORTANT: When using objects (extractors, stations), the agent "moves into" them but doesn't
316
- actually change position. We detect this by checking the using_object_this_step flag.
317
- """
318
- # If last action was a move and we're not using an object, update position
319
- # We assume the move succeeded unless we were using an object
320
- if state.last_action and state.last_action.name.startswith("move_") and not state.using_object_this_step:
321
- # Extract direction from action name (e.g., "move_north" -> "north")
322
- direction = state.last_action.name[5:] # Remove "move_" prefix
323
- if direction in move_deltas:
324
- dr, dc = move_deltas[direction]
325
- state.row += dr
326
- state.col += dc
327
- # Clear the flag for next step
328
- state.using_object_this_step = False
329
-
330
-
331
- def use_object_at(
332
- state: SimpleAgentState,
333
- target_pos: tuple[int, int],
334
- *,
335
- actions: Any, # PolicyEnvInterface.actions
336
- move_deltas: dict[str, tuple[int, int]],
337
- using_for: str = "",
338
- ) -> Action:
339
- """Use an object by moving into its cell. Sets a flag so position tracking knows not to update.
340
-
341
- This is the generic "move into to use" action for extractors, assemblers, chests, chargers, etc.
342
- The 'using_for' parameter is used for tracking what we're using (e.g., 'extractor', 'assembler').
343
- """
344
- action = move_into_cell(state, target_pos, actions=actions, move_deltas=move_deltas)
345
-
346
- # Mark that we're using an object so position tracking doesn't update
347
- state.using_object_this_step = True
348
-
349
- return action
350
-
351
-
352
- def move_into_cell(
353
- state: SimpleAgentState,
354
- target: tuple[int, int],
355
- *,
356
- actions: Any, # PolicyEnvInterface.actions
357
- move_deltas: dict[str, tuple[int, int]],
358
- ) -> Action:
359
- """Return the action that attempts to step into the target cell.
360
-
361
- Checks for agent occupancy before moving to avoid collisions.
362
- """
363
-
364
- tr, tc = target
365
- if state.row == tr and state.col == tc:
366
- return actions.noop.Noop()
367
- dr = tr - state.row
368
- dc = tc - state.col
369
-
370
- # Check if another agent is at the target position
371
- if (tr, tc) in state.agent_occupancy:
372
- # Another agent is blocking the target, wait or try alternative
373
- # For a simple fallback, return noop (caller can handle random direction if needed)
374
- return actions.noop.Noop()
375
-
376
- if dr == -1:
377
- return actions.move.Move("north")
378
- if dr == 1:
379
- return actions.move.Move("south")
380
- if dc == 1:
381
- return actions.move.Move("east")
382
- if dc == -1:
383
- return actions.move.Move("west")
384
- # Fallback to noop if offsets unexpected
385
- return actions.noop.Noop()