cogames-agents 0.0.0.7__cp312-cp312-macosx_11_0_arm64.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 (128) hide show
  1. cogames_agents/__init__.py +0 -0
  2. cogames_agents/evals/__init__.py +5 -0
  3. cogames_agents/evals/planky_evals.py +415 -0
  4. cogames_agents/policy/__init__.py +0 -0
  5. cogames_agents/policy/evolution/__init__.py +0 -0
  6. cogames_agents/policy/evolution/cogsguard/__init__.py +0 -0
  7. cogames_agents/policy/evolution/cogsguard/evolution.py +695 -0
  8. cogames_agents/policy/evolution/cogsguard/evolutionary_coordinator.py +540 -0
  9. cogames_agents/policy/nim_agents/__init__.py +20 -0
  10. cogames_agents/policy/nim_agents/agents.py +98 -0
  11. cogames_agents/policy/nim_agents/bindings/generated/libnim_agents.dylib +0 -0
  12. cogames_agents/policy/nim_agents/bindings/generated/nim_agents.py +215 -0
  13. cogames_agents/policy/nim_agents/cogsguard_agents.nim +555 -0
  14. cogames_agents/policy/nim_agents/cogsguard_align_all_agents.nim +569 -0
  15. cogames_agents/policy/nim_agents/common.nim +1054 -0
  16. cogames_agents/policy/nim_agents/install.sh +1 -0
  17. cogames_agents/policy/nim_agents/ladybug_agent.nim +954 -0
  18. cogames_agents/policy/nim_agents/nim_agents.nim +68 -0
  19. cogames_agents/policy/nim_agents/nim_agents.nims +14 -0
  20. cogames_agents/policy/nim_agents/nimby.lock +3 -0
  21. cogames_agents/policy/nim_agents/racecar_agents.nim +844 -0
  22. cogames_agents/policy/nim_agents/random_agents.nim +68 -0
  23. cogames_agents/policy/nim_agents/test_agents.py +53 -0
  24. cogames_agents/policy/nim_agents/thinky_agents.nim +677 -0
  25. cogames_agents/policy/nim_agents/thinky_eval.py +230 -0
  26. cogames_agents/policy/scripted_agent/README.md +360 -0
  27. cogames_agents/policy/scripted_agent/__init__.py +0 -0
  28. cogames_agents/policy/scripted_agent/baseline_agent.py +1031 -0
  29. cogames_agents/policy/scripted_agent/cogas/__init__.py +5 -0
  30. cogames_agents/policy/scripted_agent/cogas/context.py +68 -0
  31. cogames_agents/policy/scripted_agent/cogas/entity_map.py +152 -0
  32. cogames_agents/policy/scripted_agent/cogas/goal.py +115 -0
  33. cogames_agents/policy/scripted_agent/cogas/goals/__init__.py +27 -0
  34. cogames_agents/policy/scripted_agent/cogas/goals/aligner.py +160 -0
  35. cogames_agents/policy/scripted_agent/cogas/goals/gear.py +197 -0
  36. cogames_agents/policy/scripted_agent/cogas/goals/miner.py +441 -0
  37. cogames_agents/policy/scripted_agent/cogas/goals/scout.py +40 -0
  38. cogames_agents/policy/scripted_agent/cogas/goals/scrambler.py +174 -0
  39. cogames_agents/policy/scripted_agent/cogas/goals/shared.py +160 -0
  40. cogames_agents/policy/scripted_agent/cogas/goals/stem.py +60 -0
  41. cogames_agents/policy/scripted_agent/cogas/goals/survive.py +100 -0
  42. cogames_agents/policy/scripted_agent/cogas/navigator.py +401 -0
  43. cogames_agents/policy/scripted_agent/cogas/obs_parser.py +238 -0
  44. cogames_agents/policy/scripted_agent/cogas/policy.py +525 -0
  45. cogames_agents/policy/scripted_agent/cogas/trace.py +69 -0
  46. cogames_agents/policy/scripted_agent/cogsguard/CLAUDE.md +517 -0
  47. cogames_agents/policy/scripted_agent/cogsguard/README.md +252 -0
  48. cogames_agents/policy/scripted_agent/cogsguard/__init__.py +74 -0
  49. cogames_agents/policy/scripted_agent/cogsguard/aligned_junction_held_investigation.md +152 -0
  50. cogames_agents/policy/scripted_agent/cogsguard/aligner.py +333 -0
  51. cogames_agents/policy/scripted_agent/cogsguard/behavior_hooks.py +44 -0
  52. cogames_agents/policy/scripted_agent/cogsguard/control_agent.py +323 -0
  53. cogames_agents/policy/scripted_agent/cogsguard/debug_agent.py +533 -0
  54. cogames_agents/policy/scripted_agent/cogsguard/miner.py +589 -0
  55. cogames_agents/policy/scripted_agent/cogsguard/options.py +67 -0
  56. cogames_agents/policy/scripted_agent/cogsguard/parity_metrics.py +36 -0
  57. cogames_agents/policy/scripted_agent/cogsguard/policy.py +1967 -0
  58. cogames_agents/policy/scripted_agent/cogsguard/prereq_trace.py +33 -0
  59. cogames_agents/policy/scripted_agent/cogsguard/role_trace.py +50 -0
  60. cogames_agents/policy/scripted_agent/cogsguard/roles.py +31 -0
  61. cogames_agents/policy/scripted_agent/cogsguard/rollout_trace.py +40 -0
  62. cogames_agents/policy/scripted_agent/cogsguard/scout.py +69 -0
  63. cogames_agents/policy/scripted_agent/cogsguard/scrambler.py +350 -0
  64. cogames_agents/policy/scripted_agent/cogsguard/targeted_agent.py +418 -0
  65. cogames_agents/policy/scripted_agent/cogsguard/teacher.py +224 -0
  66. cogames_agents/policy/scripted_agent/cogsguard/types.py +381 -0
  67. cogames_agents/policy/scripted_agent/cogsguard/v2_agent.py +49 -0
  68. cogames_agents/policy/scripted_agent/common/__init__.py +0 -0
  69. cogames_agents/policy/scripted_agent/common/geometry.py +24 -0
  70. cogames_agents/policy/scripted_agent/common/roles.py +34 -0
  71. cogames_agents/policy/scripted_agent/common/tag_utils.py +48 -0
  72. cogames_agents/policy/scripted_agent/demo_policy.py +242 -0
  73. cogames_agents/policy/scripted_agent/pathfinding.py +126 -0
  74. cogames_agents/policy/scripted_agent/pinky/DESIGN.md +317 -0
  75. cogames_agents/policy/scripted_agent/pinky/__init__.py +5 -0
  76. cogames_agents/policy/scripted_agent/pinky/behaviors/__init__.py +17 -0
  77. cogames_agents/policy/scripted_agent/pinky/behaviors/aligner.py +400 -0
  78. cogames_agents/policy/scripted_agent/pinky/behaviors/base.py +119 -0
  79. cogames_agents/policy/scripted_agent/pinky/behaviors/miner.py +632 -0
  80. cogames_agents/policy/scripted_agent/pinky/behaviors/scout.py +138 -0
  81. cogames_agents/policy/scripted_agent/pinky/behaviors/scrambler.py +433 -0
  82. cogames_agents/policy/scripted_agent/pinky/policy.py +570 -0
  83. cogames_agents/policy/scripted_agent/pinky/services/__init__.py +7 -0
  84. cogames_agents/policy/scripted_agent/pinky/services/map_tracker.py +808 -0
  85. cogames_agents/policy/scripted_agent/pinky/services/navigator.py +864 -0
  86. cogames_agents/policy/scripted_agent/pinky/services/safety.py +189 -0
  87. cogames_agents/policy/scripted_agent/pinky/state.py +299 -0
  88. cogames_agents/policy/scripted_agent/pinky/types.py +138 -0
  89. cogames_agents/policy/scripted_agent/planky/CLAUDE.md +124 -0
  90. cogames_agents/policy/scripted_agent/planky/IMPROVEMENTS.md +160 -0
  91. cogames_agents/policy/scripted_agent/planky/NOTES.md +153 -0
  92. cogames_agents/policy/scripted_agent/planky/PLAN.md +254 -0
  93. cogames_agents/policy/scripted_agent/planky/README.md +214 -0
  94. cogames_agents/policy/scripted_agent/planky/STRATEGY.md +100 -0
  95. cogames_agents/policy/scripted_agent/planky/__init__.py +5 -0
  96. cogames_agents/policy/scripted_agent/planky/context.py +68 -0
  97. cogames_agents/policy/scripted_agent/planky/entity_map.py +152 -0
  98. cogames_agents/policy/scripted_agent/planky/goal.py +107 -0
  99. cogames_agents/policy/scripted_agent/planky/goals/__init__.py +27 -0
  100. cogames_agents/policy/scripted_agent/planky/goals/aligner.py +168 -0
  101. cogames_agents/policy/scripted_agent/planky/goals/gear.py +179 -0
  102. cogames_agents/policy/scripted_agent/planky/goals/miner.py +416 -0
  103. cogames_agents/policy/scripted_agent/planky/goals/scout.py +40 -0
  104. cogames_agents/policy/scripted_agent/planky/goals/scrambler.py +174 -0
  105. cogames_agents/policy/scripted_agent/planky/goals/shared.py +160 -0
  106. cogames_agents/policy/scripted_agent/planky/goals/stem.py +49 -0
  107. cogames_agents/policy/scripted_agent/planky/goals/survive.py +96 -0
  108. cogames_agents/policy/scripted_agent/planky/navigator.py +388 -0
  109. cogames_agents/policy/scripted_agent/planky/obs_parser.py +238 -0
  110. cogames_agents/policy/scripted_agent/planky/policy.py +485 -0
  111. cogames_agents/policy/scripted_agent/planky/tests/__init__.py +0 -0
  112. cogames_agents/policy/scripted_agent/planky/tests/conftest.py +66 -0
  113. cogames_agents/policy/scripted_agent/planky/tests/helpers.py +152 -0
  114. cogames_agents/policy/scripted_agent/planky/tests/test_aligner.py +24 -0
  115. cogames_agents/policy/scripted_agent/planky/tests/test_miner.py +30 -0
  116. cogames_agents/policy/scripted_agent/planky/tests/test_scout.py +15 -0
  117. cogames_agents/policy/scripted_agent/planky/tests/test_scrambler.py +29 -0
  118. cogames_agents/policy/scripted_agent/planky/tests/test_stem.py +36 -0
  119. cogames_agents/policy/scripted_agent/planky/trace.py +69 -0
  120. cogames_agents/policy/scripted_agent/types.py +239 -0
  121. cogames_agents/policy/scripted_agent/unclipping_agent.py +461 -0
  122. cogames_agents/policy/scripted_agent/utils.py +381 -0
  123. cogames_agents/policy/scripted_registry.py +80 -0
  124. cogames_agents/py.typed +0 -0
  125. cogames_agents-0.0.0.7.dist-info/METADATA +98 -0
  126. cogames_agents-0.0.0.7.dist-info/RECORD +128 -0
  127. cogames_agents-0.0.0.7.dist-info/WHEEL +6 -0
  128. cogames_agents-0.0.0.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,381 @@
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, Iterable, cast
10
+
11
+ from mettagrid.simulator import Action
12
+ from mettagrid.simulator.interface import AgentObservation
13
+
14
+ from .common.geometry import is_adjacent as geometry_is_adjacent
15
+ from .common.tag_utils import select_primary_tag
16
+ from .types import ObjectState, ParsedObservation, SimpleAgentState
17
+
18
+
19
+ def is_adjacent(pos1: tuple[int, int], pos2: tuple[int, int]) -> bool:
20
+ """Check if two positions are adjacent (4-way cardinal directions)."""
21
+ return geometry_is_adjacent(pos1, pos2)
22
+
23
+
24
+ def is_wall(obj_name: str) -> bool:
25
+ """Check if an object name represents a wall or obstacle."""
26
+ return "wall" in obj_name or "#" in obj_name or obj_name in {"wall", "obstacle"}
27
+
28
+
29
+ def is_station(obj_name: str, station: str) -> bool:
30
+ """Check if an object name contains a specific station type."""
31
+ return station in obj_name
32
+
33
+
34
+ def add_inventory_token(
35
+ inventory: dict[str, int],
36
+ feature_name: str,
37
+ value: int,
38
+ *,
39
+ token_value_base: int,
40
+ ) -> None:
41
+ """Add inventory token value, reconstructing multi-token amounts."""
42
+ suffix = feature_name[4:]
43
+ if ":p" in suffix:
44
+ resource_name, power_str = suffix.rsplit(":p", 1)
45
+ power = int(power_str)
46
+ else:
47
+ resource_name = suffix
48
+ power = 0
49
+ inventory[resource_name] = inventory.get(resource_name, 0) + value * (token_value_base**power)
50
+
51
+
52
+ def process_feature_at_position(
53
+ position_features: dict[tuple[int, int], dict[str, Any]],
54
+ pos: tuple[int, int],
55
+ feature_name: str,
56
+ value: int,
57
+ *,
58
+ spatial_feature_names: set[str],
59
+ agent_feature_key_by_name: dict[str, str],
60
+ protocol_input_prefix: str,
61
+ protocol_output_prefix: str,
62
+ ) -> None:
63
+ """Process a single observation feature and add it to position_features."""
64
+ if pos not in position_features:
65
+ position_features[pos] = {}
66
+ position_entry = position_features[pos]
67
+
68
+ # Handle spatial features (tag, cooldown, etc.)
69
+ if feature_name in spatial_feature_names:
70
+ # Tag: collect all tags as a list (objects can have multiple tags)
71
+ if feature_name == "tag":
72
+ tags_value = position_entry.get("tags")
73
+ if not isinstance(tags_value, list):
74
+ tags_value = []
75
+ position_entry["tags"] = tags_value
76
+ cast(list[int], tags_value).append(value)
77
+ return
78
+ # Other spatial features are single values
79
+ position_entry[feature_name] = value
80
+ return
81
+
82
+ # Handle agent features (agent:group -> agent_group, etc.)
83
+ agent_feature_key = agent_feature_key_by_name.get(feature_name)
84
+ if agent_feature_key is not None:
85
+ position_entry[agent_feature_key] = value
86
+ return
87
+
88
+ # Handle protocol features (recipes)
89
+ if feature_name.startswith(protocol_input_prefix):
90
+ resource = feature_name[len(protocol_input_prefix) :]
91
+ inputs_value = position_entry.get("protocol_inputs")
92
+ if not isinstance(inputs_value, dict):
93
+ inputs_value = {}
94
+ position_entry["protocol_inputs"] = inputs_value
95
+ cast(dict[str, int], inputs_value)[resource] = value
96
+ return
97
+
98
+ if feature_name.startswith(protocol_output_prefix):
99
+ resource = feature_name[len(protocol_output_prefix) :]
100
+ outputs_value = position_entry.get("protocol_outputs")
101
+ if not isinstance(outputs_value, dict):
102
+ outputs_value = {}
103
+ position_entry["protocol_outputs"] = outputs_value
104
+ cast(dict[str, int], outputs_value)[resource] = value
105
+ return
106
+
107
+
108
+ def has_type_tag(tags: Iterable[str], tokens: Iterable[str]) -> bool:
109
+ for tag in tags:
110
+ if not tag.startswith("type:"):
111
+ continue
112
+ type_name = tag.split(":", 1)[1]
113
+ if any(token in type_name for token in tokens):
114
+ return True
115
+ return False
116
+
117
+
118
+ def create_object_state(
119
+ features: dict[str, Any],
120
+ *,
121
+ tag_names: dict[int, str],
122
+ ) -> ObjectState:
123
+ """Create an ObjectState from collected features.
124
+
125
+ Note: Objects can have multiple tags (e.g., "wall" + "green" vibe).
126
+ Prefer type tags over collective tags for the primary object name.
127
+ """
128
+ # Get tags list (now stored as "tags" instead of "tag")
129
+ tags_value = features.get("tags", [])
130
+ if isinstance(tags_value, list):
131
+ tag_ids = list(tags_value)
132
+ elif isinstance(tags_value, int):
133
+ tag_ids = [tags_value]
134
+ else:
135
+ tag_ids = []
136
+
137
+ # Pick a primary object name with tag precedence.
138
+ if tag_ids:
139
+ tags = [tag_names.get(tag_id, f"unknown_tag_{tag_id}") for tag_id in tag_ids]
140
+ obj_name = select_primary_tag(tags)
141
+ else:
142
+ tags = []
143
+ obj_name = "unknown"
144
+
145
+ # Helper to safely extract int values
146
+ def get_int(key: str, default: int) -> int:
147
+ val = features.get(key, default)
148
+ return int(val) if isinstance(val, int) else default
149
+
150
+ # Helper to safely extract dict values
151
+ def get_dict(key: str) -> dict[str, int]:
152
+ val = features.get(key, {})
153
+ return dict(val) if isinstance(val, dict) else {}
154
+
155
+ return ObjectState(
156
+ name=obj_name,
157
+ tags=tags,
158
+ cooldown_remaining=get_int("cooldown_remaining", 0),
159
+ clipped=get_int("clipped", 0),
160
+ remaining_uses=get_int("remaining_uses", 999),
161
+ inventory=get_dict("inventory"),
162
+ protocol_inputs=get_dict("protocol_inputs"),
163
+ protocol_outputs=get_dict("protocol_outputs"),
164
+ agent_group=get_int("agent_group", -1),
165
+ agent_frozen=get_int("agent_frozen", 0),
166
+ )
167
+
168
+
169
+ def read_inventory_from_obs(
170
+ state: SimpleAgentState,
171
+ obs: AgentObservation,
172
+ *,
173
+ obs_hr: int,
174
+ obs_wr: int,
175
+ ) -> None:
176
+ """Read inventory from observation tokens at center cell and update state."""
177
+ inv = {}
178
+ token_value_base = None
179
+ center_r, center_c = obs_hr, obs_wr
180
+ for tok in obs.tokens:
181
+ if tok.location == (center_r, center_c):
182
+ feature_name = tok.feature.name
183
+ if feature_name.startswith("inv:"):
184
+ if token_value_base is None:
185
+ token_value_base = int(tok.feature.normalization)
186
+ add_inventory_token(inv, feature_name, tok.value, token_value_base=token_value_base)
187
+
188
+ state.energy = inv.get("energy", 0)
189
+ state.carbon = inv.get("carbon", 0)
190
+ state.oxygen = inv.get("oxygen", 0)
191
+ state.germanium = inv.get("germanium", 0)
192
+ state.silicon = inv.get("silicon", 0)
193
+ state.hearts = inv.get("heart", 0)
194
+ state.decoder = inv.get("decoder", 0)
195
+ state.modulator = inv.get("modulator", 0)
196
+ state.resonator = inv.get("resonator", 0)
197
+ state.scrambler = inv.get("scrambler", 0)
198
+
199
+
200
+ def parse_observation(
201
+ state: SimpleAgentState,
202
+ obs: AgentObservation,
203
+ *,
204
+ obs_hr: int,
205
+ obs_wr: int,
206
+ spatial_feature_names: set[str],
207
+ agent_feature_key_by_name: dict[str, str],
208
+ protocol_input_prefix: str,
209
+ protocol_output_prefix: str,
210
+ tag_names: dict[int, str],
211
+ debug: bool = False,
212
+ ) -> ParsedObservation:
213
+ """Parse token-based observation into structured format.
214
+
215
+ AgentObservation with tokens (ObservationToken list)
216
+ - Agent inventory is obtained via agent.inventory (not parsed here)
217
+ - Spatial features are parsed from observations, including object inventories
218
+
219
+ Converts egocentric spatial coordinates to world coordinates using agent position.
220
+ Agent position (agent_row, agent_col) comes from simulation.grid_objects().
221
+ """
222
+ # First pass: collect all spatial features by position
223
+ position_features: dict[tuple[int, int], dict[str, Any]] = {}
224
+ token_value_base = None
225
+
226
+ for tok in obs.tokens:
227
+ obs_r, obs_c = tok.location
228
+ feature_name = tok.feature.name
229
+ value = tok.value
230
+
231
+ # Skip center location - that's inventory/global obs, obtained via agent.inventory
232
+ if obs_r == obs_hr and obs_c == obs_wr:
233
+ continue
234
+
235
+ # Convert observation-relative coords to world coords
236
+ if state.row >= 0 and state.col >= 0:
237
+ r = obs_r - obs_hr + state.row
238
+ c = obs_c - obs_wr + state.col
239
+
240
+ if 0 <= r < state.map_height and 0 <= c < state.map_width:
241
+ if feature_name.startswith("inv:"):
242
+ if token_value_base is None:
243
+ token_value_base = int(tok.feature.normalization)
244
+ position_entry = position_features.setdefault((r, c), {})
245
+ inventory_value = position_entry.get("inventory")
246
+ if not isinstance(inventory_value, dict):
247
+ inventory_value = {}
248
+ position_entry["inventory"] = inventory_value
249
+ add_inventory_token(
250
+ cast(dict[str, int], inventory_value),
251
+ feature_name,
252
+ value,
253
+ token_value_base=token_value_base,
254
+ )
255
+ continue
256
+ process_feature_at_position(
257
+ position_features,
258
+ (r, c),
259
+ feature_name,
260
+ value,
261
+ spatial_feature_names=spatial_feature_names,
262
+ agent_feature_key_by_name=agent_feature_key_by_name,
263
+ protocol_input_prefix=protocol_input_prefix,
264
+ protocol_output_prefix=protocol_output_prefix,
265
+ )
266
+
267
+ # Second pass: create ObjectState for each position with tags
268
+ nearby_objects = {
269
+ pos: create_object_state(features, tag_names=tag_names)
270
+ for pos, features in position_features.items()
271
+ if "tags" in features # Note: stored as "tags" (plural) to support multiple tags per object
272
+ }
273
+
274
+ return ParsedObservation(
275
+ row=state.row,
276
+ col=state.col,
277
+ energy=0, # Inventory obtained via agent.inventory
278
+ carbon=0,
279
+ oxygen=0,
280
+ germanium=0,
281
+ silicon=0,
282
+ hearts=0,
283
+ decoder=0,
284
+ modulator=0,
285
+ resonator=0,
286
+ scrambler=0,
287
+ nearby_objects=nearby_objects,
288
+ )
289
+
290
+
291
+ def change_vibe_action(
292
+ vibe_name: str,
293
+ *,
294
+ action_names: list[str],
295
+ ) -> Action:
296
+ """
297
+ Return a safe vibe-change action.
298
+ Guard against disabled or single-vibe configurations before issuing the action.
299
+ """
300
+ change_vibe_actions = [a for a in action_names if a.startswith("change_vibe_")]
301
+ if len(change_vibe_actions) <= 1:
302
+ return Action(name="noop")
303
+ action_name = f"change_vibe_{vibe_name}"
304
+ if action_name in action_names:
305
+ return Action(name=action_name)
306
+ available = [a[len("change_vibe_") :] for a in change_vibe_actions]
307
+ raise Exception(f"No valid vibe called '{vibe_name}'. Available vibes: {available}")
308
+
309
+
310
+ def update_agent_position(
311
+ state: SimpleAgentState,
312
+ *,
313
+ move_deltas: dict[str, tuple[int, int]],
314
+ ) -> None:
315
+ """Update agent position based on last action.
316
+
317
+ Position is tracked relative to origin (starting position), using only movement deltas.
318
+ No dependency on simulation.grid_objects().
319
+
320
+ IMPORTANT: When using objects (extractors, stations), the agent "moves into" them but doesn't
321
+ actually change position. We detect this by checking the using_object_this_step flag.
322
+ """
323
+ # If last action was a move and we're not using an object, update position
324
+ # We assume the move succeeded unless we were using an object
325
+ if state.last_action and state.last_action.name.startswith("move_") and not state.using_object_this_step:
326
+ # Extract direction from action name (e.g., "move_north" -> "north")
327
+ direction = state.last_action.name[5:] # Remove "move_" prefix
328
+ if direction in move_deltas:
329
+ dr, dc = move_deltas[direction]
330
+ state.row += dr
331
+ state.col += dc
332
+ # Clear the flag for next step
333
+ state.using_object_this_step = False
334
+
335
+
336
+ def use_object_at(
337
+ state: SimpleAgentState,
338
+ target_pos: tuple[int, int],
339
+ ) -> Action:
340
+ """Use an object by moving into its cell. Sets a flag so position tracking knows not to update.
341
+
342
+ This is the generic "move into to use" action for extractors, hubs, chests, junctions, etc.
343
+ """
344
+ action = move_into_cell(state, target_pos)
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
+ ) -> Action:
356
+ """Return the action that attempts to step into the target cell.
357
+
358
+ Checks for agent occupancy before moving to avoid collisions.
359
+ """
360
+ tr, tc = target
361
+ if state.row == tr and state.col == tc:
362
+ return Action(name="noop")
363
+ dr = tr - state.row
364
+ dc = tc - state.col
365
+
366
+ # Check if another agent is at the target position
367
+ if (tr, tc) in state.agent_occupancy:
368
+ # Another agent is blocking the target, wait or try alternative
369
+ # For a simple fallback, return noop (caller can handle random direction if needed)
370
+ return Action(name="noop")
371
+
372
+ if dr == -1:
373
+ return Action(name="move_north")
374
+ if dr == 1:
375
+ return Action(name="move_south")
376
+ if dc == 1:
377
+ return Action(name="move_east")
378
+ if dc == -1:
379
+ return Action(name="move_west")
380
+ # Fallback to noop if offsets unexpected
381
+ return Action(name="noop")
@@ -0,0 +1,80 @@
1
+ """Registry of scripted policy URIs derived from policy short_names."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ import functools
7
+ from pathlib import Path
8
+ from typing import Iterable, Optional
9
+
10
+ _POLICY_ROOT = Path(__file__).resolve().parent
11
+ _SCRIPTED_SCAN_DIRS = (
12
+ _POLICY_ROOT / "scripted_agent",
13
+ _POLICY_ROOT / "nim_agents",
14
+ )
15
+
16
+
17
+ def _iter_policy_files() -> Iterable[Path]:
18
+ for base_dir in _SCRIPTED_SCAN_DIRS:
19
+ if not base_dir.exists():
20
+ continue
21
+ for path in base_dir.rglob("*.py"):
22
+ if path.name.startswith("__"):
23
+ continue
24
+ yield path
25
+
26
+
27
+ def _extract_literal_strings(node: ast.AST) -> Optional[list[str]]:
28
+ if isinstance(node, (ast.List, ast.Tuple)):
29
+ values: list[str] = []
30
+ for elt in node.elts:
31
+ if isinstance(elt, ast.Constant) and isinstance(elt.value, str):
32
+ values.append(elt.value)
33
+ else:
34
+ return None
35
+ return values
36
+ return None
37
+
38
+
39
+ def _extract_short_names_from_class(class_def: ast.ClassDef) -> list[str]:
40
+ for stmt in class_def.body:
41
+ if isinstance(stmt, ast.Assign):
42
+ for target in stmt.targets:
43
+ if isinstance(target, ast.Name) and target.id == "short_names":
44
+ value = _extract_literal_strings(stmt.value)
45
+ return value or []
46
+ if isinstance(stmt, ast.AnnAssign):
47
+ if isinstance(stmt.target, ast.Name) and stmt.target.id == "short_names":
48
+ if stmt.value is None:
49
+ return []
50
+ value = _extract_literal_strings(stmt.value)
51
+ return value or []
52
+ return []
53
+
54
+
55
+ @functools.cache
56
+ def list_scripted_agent_names() -> tuple[str, ...]:
57
+ names: set[str] = set()
58
+ for path in _iter_policy_files():
59
+ try:
60
+ source = path.read_text(encoding="utf-8")
61
+ except OSError:
62
+ continue
63
+ try:
64
+ tree = ast.parse(source, filename=str(path))
65
+ except SyntaxError:
66
+ continue
67
+ for node in tree.body:
68
+ if isinstance(node, ast.ClassDef):
69
+ names.update(_extract_short_names_from_class(node))
70
+ return tuple(sorted(names))
71
+
72
+
73
+ SCRIPTED_AGENT_URIS: dict[str, str] = {name: f"metta://policy/{name}" for name in list_scripted_agent_names()}
74
+
75
+
76
+ def resolve_scripted_agent_uri(name: str) -> str:
77
+ if name in SCRIPTED_AGENT_URIS:
78
+ return SCRIPTED_AGENT_URIS[name]
79
+ available = ", ".join(sorted(SCRIPTED_AGENT_URIS))
80
+ raise ValueError(f"Unknown scripted agent '{name}'. Available: {available}")
File without changes
@@ -0,0 +1,98 @@
1
+ Metadata-Version: 2.4
2
+ Name: cogames-agents
3
+ Version: 0.0.0.7
4
+ Summary: Optional agent policies for CoGames
5
+ Author: Metta AI
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Metta-AI/metta/tree/main/packages/cogames-agents
8
+ Project-URL: Repository, https://github.com/Metta-AI/metta
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Operating System :: POSIX :: Linux
12
+ Classifier: Operating System :: MacOS
13
+ Requires-Python: <3.13,>=3.12
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: cogames==0.3.64
16
+ Requires-Dist: mettagrid==0.2.0.74
17
+ Requires-Dist: numpy>=2.0.0
18
+ Provides-Extra: test
19
+ Requires-Dist: pytest; extra == "test"
20
+ Requires-Dist: pytest-xdist; extra == "test"
21
+ Requires-Dist: ruff; extra == "test"
22
+
23
+ # cogames-agents
24
+
25
+ Optional scripted policies for CoGames. Use them for quick baselines, play/eval smoke tests, or as teacher policies.
26
+
27
+ ## Scripted policy registry
28
+
29
+ The registry at `cogames_agents.policy.scripted_registry` maps policy `short_names` to `metta://policy/...` URIs.
30
+ Scripted agents and teachers share these identifiers, so the same name works for evaluation, play, and
31
+ `TeacherConfig.policy_uri`.
32
+
33
+ To list the current names:
34
+
35
+ ```
36
+ python -c "from cogames_agents.policy.scripted_registry import list_scripted_agent_names; print(list_scripted_agent_names())"
37
+ ```
38
+
39
+ Common scripted policy names include:
40
+
41
+ - Baselines: `baseline`, `tiny_baseline`, `ladybug_py`
42
+ - Nim baselines: `thinky`, `race_car`, `ladybug`, `nim_random`
43
+ - CogsGuard core: `role`, `role_py`, `wombo`
44
+ - CogsGuard variants: `alignall`, `cogsguard_control`, `cogsguard_targeted`, `cogsguard_v2`
45
+ - CogsGuard roles: `miner`, `scout`, `aligner`, `scrambler`
46
+ - Teacher: `teacher`
47
+ - Pinky: `pinky`
48
+
49
+ For the full registry snapshot, see `docs/scripted-agent-registry.md`.
50
+
51
+ Role-specific policies are exposed via role names (miner/scout/aligner/scrambler). For the teacher policy, you can pass
52
+ `role_vibes` as a comma-separated list:
53
+
54
+ ```
55
+ metta://policy/teacher?role_vibes=miner,scout
56
+ ```
57
+
58
+ Fixed-role mixes and explicit orderings are configured via `role_py` parameters:
59
+
60
+ Examples:
61
+
62
+ ```
63
+ metta://policy/role_py?role_cycle=aligner,miner,scrambler,scout
64
+ metta://policy/role_py?role_order=aligner,miner,aligner,miner,scout
65
+ ```
66
+
67
+ Pinky role counts are applied in a different order than CogsGuard:
68
+
69
+ - Pinky order: miner -> scout -> aligner -> scrambler, and any remaining agents stay default/noop.
70
+ - CogsGuard order: scrambler -> aligner -> miner -> scout, then fills remaining agents with gear.
71
+
72
+ Examples:
73
+
74
+ ```
75
+ metta://policy/pinky?miner=4&aligner=2&scrambler=4
76
+ metta://policy/pinky?miner=2&scout=2&aligner=1&scrambler=1&debug=1
77
+ ```
78
+
79
+ ## Recipe usage
80
+
81
+ The `recipes.experiment.scripted_agents` recipe accepts the same scripted policy names:
82
+
83
+ ```
84
+ ./tools/run.py recipes.experiment.scripted_agents.play agent=thinky suite=cvc_arena
85
+ ./tools/run.py recipes.experiment.scripted_agents.play agent=miner suite=cogsguard
86
+ ```
87
+
88
+ ## Included policies
89
+
90
+ - Short names map to the fastest implementation (Nim when available, otherwise Python).
91
+ - `_nim` aliases exist when there is a Nim implementation alongside Python.
92
+ - See `docs/scripted-agent-registry.md` for the canonical short-name list.
93
+ - Teacher wrapper: `teacher` (`teacher_nim`) forces an initial role/vibe, then delegates to the Nim policy.
94
+
95
+ ## Docs
96
+
97
+ - `docs/mettaboxes.md` (mettabox usage guide)
98
+ - `docs/aws-sso-on-mettabox.md` (AWS SSO login from inside mettabox containers)