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,540 @@
1
+ """
2
+ Evolutionary role coordinator for CogsGuard agents.
3
+
4
+ This module extends the smart-role coordination with evolutionary capabilities,
5
+ allowing roles to evolve based on game performance.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import random
11
+ from dataclasses import dataclass, field
12
+ from typing import TYPE_CHECKING, Any, Callable, Optional
13
+
14
+ from cogames_agents.policy.evolution.cogsguard.evolution import (
15
+ BehaviorDef,
16
+ BehaviorSource,
17
+ EvolutionConfig,
18
+ RoleCatalog,
19
+ RoleDef,
20
+ RoleTier,
21
+ TierSelection,
22
+ lock_role_name_if_fit,
23
+ materialize_role_behaviors,
24
+ mutate_role,
25
+ pick_role_id_weighted,
26
+ recombine_roles,
27
+ record_role_score,
28
+ sample_role,
29
+ )
30
+ from mettagrid.simulator import Action
31
+
32
+ if TYPE_CHECKING:
33
+ from cogames_agents.policy.scripted_agent.cogsguard.types import CogsguardAgentState
34
+
35
+
36
+ # Default behaviors for each role that will be registered in the catalog
37
+ def _always_true(_: CogsguardAgentState) -> bool:
38
+ return True
39
+
40
+
41
+ def _always_false(_: CogsguardAgentState) -> bool:
42
+ return False
43
+
44
+
45
+ def _noop_action(_: CogsguardAgentState) -> Action:
46
+ return Action(name="noop")
47
+
48
+
49
+ if TYPE_CHECKING:
50
+ BehaviorHook = Callable[[CogsguardAgentState], Action]
51
+ else:
52
+ BehaviorHook = Callable[[Any], Action]
53
+
54
+
55
+ @dataclass
56
+ class AgentRoleAssignment:
57
+ """Tracks a role assignment for an agent."""
58
+
59
+ agent_id: int
60
+ role_id: int
61
+ role_name: str
62
+ assigned_step: int = 0
63
+ score_contributions: list[float] = field(default_factory=list)
64
+
65
+
66
+ @dataclass
67
+ class EvolutionaryRoleCoordinator:
68
+ """Coordinates evolutionary role selection across agents.
69
+
70
+ This coordinator maintains a catalog of behaviors and roles, assigns
71
+ roles to agents, tracks performance, and evolves new roles based on
72
+ fitness.
73
+
74
+ Attributes:
75
+ num_agents: Total number of agents to coordinate
76
+ catalog: Registry of behaviors and roles
77
+ config: Evolution configuration
78
+ agent_assignments: Current role assignments per agent
79
+ generation: Current evolutionary generation
80
+ games_per_generation: Games to play before evolving new roles
81
+ games_this_generation: Games played in current generation
82
+ rng: Random number generator for reproducibility
83
+ """
84
+
85
+ num_agents: int
86
+ catalog: RoleCatalog = field(default_factory=RoleCatalog)
87
+ config: EvolutionConfig = field(default_factory=EvolutionConfig)
88
+ agent_assignments: dict[int, AgentRoleAssignment] = field(default_factory=dict)
89
+ generation: int = 0
90
+ games_per_generation: int = 10
91
+ games_this_generation: int = 0
92
+ rng: Optional[random.Random] = None
93
+ behavior_hooks: dict[str, BehaviorHook] = field(default_factory=dict)
94
+
95
+ def __post_init__(self) -> None:
96
+ """Initialize the behavior catalog with default behaviors."""
97
+ if not self.catalog.behaviors:
98
+ self._seed_default_behaviors()
99
+ if not self.catalog.roles:
100
+ self._seed_initial_roles()
101
+
102
+ def _seed_default_behaviors(self) -> None:
103
+ """Register default behaviors for each role."""
104
+ # These are placeholder behaviors that will be connected to actual
105
+ # role implementations via the behavior functions
106
+
107
+ # Common behaviors
108
+ self.catalog.add_behavior(
109
+ name="explore",
110
+ source=BehaviorSource.COMMON,
111
+ can_start=_always_true,
112
+ act=self._behavior_act("explore"),
113
+ should_terminate=_always_false,
114
+ interruptible=True,
115
+ )
116
+ self.catalog.add_behavior(
117
+ name="recharge",
118
+ source=BehaviorSource.COMMON,
119
+ can_start=_always_true,
120
+ act=self._behavior_act("recharge"),
121
+ should_terminate=_always_false,
122
+ interruptible=True,
123
+ )
124
+
125
+ # Miner behaviors
126
+ self.catalog.add_behavior(
127
+ name="mine_resource",
128
+ source=BehaviorSource.MINER,
129
+ can_start=_always_true,
130
+ act=self._behavior_act("mine_resource"),
131
+ should_terminate=_always_false,
132
+ interruptible=True,
133
+ )
134
+ self.catalog.add_behavior(
135
+ name="deposit_resource",
136
+ source=BehaviorSource.MINER,
137
+ can_start=_always_true,
138
+ act=self._behavior_act("deposit_resource"),
139
+ should_terminate=_always_false,
140
+ interruptible=True,
141
+ )
142
+ self.catalog.add_behavior(
143
+ name="find_extractor",
144
+ source=BehaviorSource.MINER,
145
+ can_start=_always_true,
146
+ act=self._behavior_act("find_extractor"),
147
+ should_terminate=_always_false,
148
+ interruptible=True,
149
+ )
150
+
151
+ # Scout behaviors
152
+ self.catalog.add_behavior(
153
+ name="discover_stations",
154
+ source=BehaviorSource.SCOUT,
155
+ can_start=_always_true,
156
+ act=self._behavior_act("discover_stations"),
157
+ should_terminate=_always_false,
158
+ interruptible=True,
159
+ )
160
+ self.catalog.add_behavior(
161
+ name="discover_extractors",
162
+ source=BehaviorSource.SCOUT,
163
+ can_start=_always_true,
164
+ act=self._behavior_act("discover_extractors"),
165
+ should_terminate=_always_false,
166
+ interruptible=True,
167
+ )
168
+ self.catalog.add_behavior(
169
+ name="discover_junctions",
170
+ source=BehaviorSource.SCOUT,
171
+ can_start=_always_true,
172
+ act=self._behavior_act("discover_junctions"),
173
+ should_terminate=_always_false,
174
+ interruptible=True,
175
+ )
176
+
177
+ # Aligner behaviors
178
+ self.catalog.add_behavior(
179
+ name="get_hearts",
180
+ source=BehaviorSource.ALIGNER,
181
+ can_start=_always_true,
182
+ act=self._behavior_act("get_hearts"),
183
+ should_terminate=_always_false,
184
+ interruptible=True,
185
+ )
186
+ self.catalog.add_behavior(
187
+ name="get_influence",
188
+ source=BehaviorSource.ALIGNER,
189
+ can_start=_always_true,
190
+ act=self._behavior_act("get_influence"),
191
+ should_terminate=_always_false,
192
+ interruptible=True,
193
+ )
194
+ self.catalog.add_behavior(
195
+ name="align_junction",
196
+ source=BehaviorSource.ALIGNER,
197
+ can_start=_always_true,
198
+ act=self._behavior_act("align_junction"),
199
+ should_terminate=_always_false,
200
+ interruptible=True,
201
+ )
202
+
203
+ # Scrambler behaviors
204
+ self.catalog.add_behavior(
205
+ name="scramble_junction",
206
+ source=BehaviorSource.SCRAMBLER,
207
+ can_start=_always_true,
208
+ act=self._behavior_act("scramble_junction"),
209
+ should_terminate=_always_false,
210
+ interruptible=True,
211
+ )
212
+ self.catalog.add_behavior(
213
+ name="find_enemy_junction",
214
+ source=BehaviorSource.SCRAMBLER,
215
+ can_start=_always_true,
216
+ act=self._behavior_act("find_enemy_junction"),
217
+ should_terminate=_always_false,
218
+ interruptible=True,
219
+ )
220
+
221
+ def _behavior_act(self, name: str) -> BehaviorHook:
222
+ def _act(s: CogsguardAgentState) -> Action:
223
+ hook = self.behavior_hooks.get(name)
224
+ if hook is None:
225
+ return _noop_action(s)
226
+ return hook(s)
227
+
228
+ return _act
229
+
230
+ def _seed_initial_roles(self) -> None:
231
+ """Create initial role definitions for each base role type."""
232
+ # Miner role: deposit -> mine -> find_extractor -> explore
233
+ miner_tiers = [
234
+ RoleTier(
235
+ behavior_ids=[self.catalog.find_behavior_id("deposit_resource")],
236
+ selection=TierSelection.FIXED,
237
+ ),
238
+ RoleTier(
239
+ behavior_ids=[self.catalog.find_behavior_id("mine_resource")],
240
+ selection=TierSelection.FIXED,
241
+ ),
242
+ RoleTier(
243
+ behavior_ids=[self.catalog.find_behavior_id("find_extractor")],
244
+ selection=TierSelection.FIXED,
245
+ ),
246
+ RoleTier(
247
+ behavior_ids=[self.catalog.find_behavior_id("explore")],
248
+ selection=TierSelection.FIXED,
249
+ ),
250
+ ]
251
+ miner_role = RoleDef(
252
+ id=-1,
253
+ name="BaseMiner",
254
+ tiers=miner_tiers,
255
+ origin="manual",
256
+ )
257
+ self.catalog.register_role(miner_role)
258
+
259
+ # Scout role: discover_stations -> discover_extractors -> discover_junctions -> explore
260
+ scout_tiers = [
261
+ RoleTier(
262
+ behavior_ids=[self.catalog.find_behavior_id("discover_stations")],
263
+ selection=TierSelection.FIXED,
264
+ ),
265
+ RoleTier(
266
+ behavior_ids=[self.catalog.find_behavior_id("discover_extractors")],
267
+ selection=TierSelection.FIXED,
268
+ ),
269
+ RoleTier(
270
+ behavior_ids=[self.catalog.find_behavior_id("discover_junctions")],
271
+ selection=TierSelection.FIXED,
272
+ ),
273
+ RoleTier(
274
+ behavior_ids=[self.catalog.find_behavior_id("explore")],
275
+ selection=TierSelection.FIXED,
276
+ ),
277
+ ]
278
+ scout_role = RoleDef(
279
+ id=-1,
280
+ name="BaseScout",
281
+ tiers=scout_tiers,
282
+ origin="manual",
283
+ )
284
+ self.catalog.register_role(scout_role)
285
+
286
+ # Aligner role: get_hearts -> get_influence -> align_junction -> explore
287
+ aligner_tiers = [
288
+ RoleTier(
289
+ behavior_ids=[
290
+ self.catalog.find_behavior_id("get_hearts"),
291
+ self.catalog.find_behavior_id("get_influence"),
292
+ ],
293
+ selection=TierSelection.FIXED,
294
+ ),
295
+ RoleTier(
296
+ behavior_ids=[self.catalog.find_behavior_id("align_junction")],
297
+ selection=TierSelection.FIXED,
298
+ ),
299
+ RoleTier(
300
+ behavior_ids=[self.catalog.find_behavior_id("explore")],
301
+ selection=TierSelection.FIXED,
302
+ ),
303
+ ]
304
+ aligner_role = RoleDef(
305
+ id=-1,
306
+ name="BaseAligner",
307
+ tiers=aligner_tiers,
308
+ origin="manual",
309
+ )
310
+ self.catalog.register_role(aligner_role)
311
+
312
+ # Scrambler role: get_hearts -> find_enemy_junction -> scramble_junction -> explore
313
+ scrambler_tiers = [
314
+ RoleTier(
315
+ behavior_ids=[self.catalog.find_behavior_id("get_hearts")],
316
+ selection=TierSelection.FIXED,
317
+ ),
318
+ RoleTier(
319
+ behavior_ids=[self.catalog.find_behavior_id("find_enemy_junction")],
320
+ selection=TierSelection.FIXED,
321
+ ),
322
+ RoleTier(
323
+ behavior_ids=[self.catalog.find_behavior_id("scramble_junction")],
324
+ selection=TierSelection.FIXED,
325
+ ),
326
+ RoleTier(
327
+ behavior_ids=[self.catalog.find_behavior_id("explore")],
328
+ selection=TierSelection.FIXED,
329
+ ),
330
+ ]
331
+ scrambler_role = RoleDef(
332
+ id=-1,
333
+ name="BaseScrambler",
334
+ tiers=scrambler_tiers,
335
+ origin="manual",
336
+ )
337
+ self.catalog.register_role(scrambler_role)
338
+
339
+ def _reset_roles(self, roles: list[RoleDef]) -> None:
340
+ """Replace the catalog roles with a new set and reassign role IDs."""
341
+ self.catalog.roles = []
342
+ self.catalog.next_role_id = 0
343
+ for role in roles:
344
+ self.catalog.register_role(role)
345
+
346
+ def assign_role(self, agent_id: int, step: int = 0) -> RoleDef:
347
+ """Assign a role to an agent using fitness-weighted selection.
348
+
349
+ Args:
350
+ agent_id: The agent to assign a role to
351
+ step: Current simulation step
352
+
353
+ Returns:
354
+ The assigned RoleDef
355
+ """
356
+ if not self.catalog.roles:
357
+ # Fallback: sample a new role
358
+ new_role = sample_role(self.catalog, self.config, self.rng)
359
+ self.catalog.register_role(new_role)
360
+
361
+ # Select role weighted by fitness
362
+ role_ids = list(range(len(self.catalog.roles)))
363
+ selected_id = pick_role_id_weighted(self.catalog, role_ids, self.rng)
364
+
365
+ if selected_id < 0:
366
+ selected_id = 0
367
+
368
+ role = self.catalog.roles[selected_id]
369
+
370
+ self.agent_assignments[agent_id] = AgentRoleAssignment(
371
+ agent_id=agent_id,
372
+ role_id=selected_id,
373
+ role_name=role.name,
374
+ assigned_step=step,
375
+ )
376
+
377
+ return role
378
+
379
+ def get_agent_role(self, agent_id: int) -> Optional[RoleDef]:
380
+ """Get the currently assigned role for an agent."""
381
+ assignment = self.agent_assignments.get(agent_id)
382
+ if assignment is None:
383
+ return None
384
+ if 0 <= assignment.role_id < len(self.catalog.roles):
385
+ return self.catalog.roles[assignment.role_id]
386
+ return None
387
+
388
+ def get_role_behaviors(self, agent_id: int) -> list[BehaviorDef]:
389
+ """Get the materialized behaviors for an agent's current role."""
390
+ role = self.get_agent_role(agent_id)
391
+ if role is None:
392
+ return []
393
+ return materialize_role_behaviors(self.catalog, role, self.rng)
394
+
395
+ def record_agent_performance(self, agent_id: int, score: float, won: bool = False) -> None:
396
+ """Record performance for an agent's current role.
397
+
398
+ Args:
399
+ agent_id: The agent whose performance to record
400
+ score: Performance score (0.0 to 1.0)
401
+ won: Whether the game was won
402
+ """
403
+ assignment = self.agent_assignments.get(agent_id)
404
+ if assignment is None:
405
+ return
406
+
407
+ assignment.score_contributions.append(score)
408
+
409
+ if 0 <= assignment.role_id < len(self.catalog.roles):
410
+ role = self.catalog.roles[assignment.role_id]
411
+ record_role_score(role, score, won, self.config.fitness_alpha)
412
+ lock_role_name_if_fit(role, self.config.lock_fitness_threshold)
413
+
414
+ def end_game(self, won: bool = False) -> None:
415
+ """Signal end of a game for evolutionary bookkeeping.
416
+
417
+ Args:
418
+ won: Whether the game was won
419
+ """
420
+ self.games_this_generation += 1
421
+
422
+ # Check if we should evolve new roles
423
+ if self.games_this_generation >= self.games_per_generation:
424
+ self._evolve_generation()
425
+
426
+ def _evolve_generation(self) -> None:
427
+ """Evolve new roles based on fitness of current generation."""
428
+ self.generation += 1
429
+ self.games_this_generation = 0
430
+
431
+ target_population = len(self.catalog.roles)
432
+ if target_population < 2:
433
+ # Not enough roles to recombine, just sample new ones
434
+ for _ in range(2):
435
+ new_role = sample_role(self.catalog, self.config, self.rng)
436
+ self.catalog.register_role(new_role)
437
+ return
438
+
439
+ # Select top performers for reproduction
440
+ sorted_roles = sorted(
441
+ self.catalog.roles,
442
+ key=lambda r: r.fitness,
443
+ reverse=True,
444
+ )
445
+
446
+ # Keep top 50% of roles
447
+ survivors = sorted_roles[: max(2, len(sorted_roles) // 2)]
448
+ self._reset_roles(survivors)
449
+
450
+ # Create offspring through recombination
451
+ num_offspring = max(0, target_population - len(survivors))
452
+ for _ in range(num_offspring):
453
+ # Select parents weighted by fitness
454
+ parent_ids = list(range(len(self.catalog.roles)))
455
+ parent1_id = pick_role_id_weighted(self.catalog, parent_ids, self.rng)
456
+ parent2_id = pick_role_id_weighted(self.catalog, parent_ids, self.rng)
457
+
458
+ parent1 = self.catalog.roles[parent1_id] if 0 <= parent1_id < len(self.catalog.roles) else survivors[0]
459
+ parent2 = self.catalog.roles[parent2_id] if 0 <= parent2_id < len(self.catalog.roles) else survivors[-1]
460
+
461
+ # Recombine and mutate
462
+ child = recombine_roles(self.catalog, parent1, parent2, self.rng)
463
+ child = mutate_role(self.catalog, child, self.config.mutation_rate, self.rng)
464
+ self.catalog.register_role(child)
465
+
466
+ # Occasionally sample a completely new role for diversity
467
+ if self.rng:
468
+ if self.rng.random() < 0.1 and len(self.catalog.roles) < target_population:
469
+ new_role = sample_role(self.catalog, self.config, self.rng)
470
+ self.catalog.register_role(new_role)
471
+ elif random.random() < 0.1 and len(self.catalog.roles) < target_population:
472
+ new_role = sample_role(self.catalog, self.config, self.rng)
473
+ self.catalog.register_role(new_role)
474
+
475
+ self.agent_assignments.clear()
476
+
477
+ def map_role_to_vibe(self, role: RoleDef) -> str:
478
+ """Map a role definition to a vibe name for the existing system.
479
+
480
+ This bridges the evolutionary roles to the existing vibe-based system.
481
+ """
482
+ # Check which behaviors dominate the role
483
+ behavior_sources: dict[BehaviorSource, int] = {}
484
+ for tier in role.tiers:
485
+ for behavior_id in tier.behavior_ids:
486
+ if 0 <= behavior_id < len(self.catalog.behaviors):
487
+ source = self.catalog.behaviors[behavior_id].source
488
+ behavior_sources[source] = behavior_sources.get(source, 0) + 1
489
+
490
+ # Find dominant source
491
+ if not behavior_sources:
492
+ return "gear" # Fallback to smart role selection
493
+
494
+ dominant = max(behavior_sources.items(), key=lambda x: x[1])[0]
495
+
496
+ source_to_vibe = {
497
+ BehaviorSource.MINER: "miner",
498
+ BehaviorSource.SCOUT: "scout",
499
+ BehaviorSource.ALIGNER: "aligner",
500
+ BehaviorSource.SCRAMBLER: "scrambler",
501
+ BehaviorSource.COMMON: "gear",
502
+ }
503
+
504
+ return source_to_vibe.get(dominant, "gear")
505
+
506
+ def choose_vibe(self, agent_id: int, current_step: int = 0) -> str:
507
+ """Choose a vibe for an agent using evolutionary selection.
508
+
509
+ This method is the main interface for the existing policy system.
510
+ It assigns a role and returns the corresponding vibe name.
511
+
512
+ Args:
513
+ agent_id: The agent to choose a vibe for
514
+ current_step: Current simulation step
515
+
516
+ Returns:
517
+ Vibe name (miner, scout, aligner, scrambler, or gear)
518
+ """
519
+ role = self.assign_role(agent_id, current_step)
520
+ return self.map_role_to_vibe(role)
521
+
522
+ def get_catalog_summary(self) -> dict:
523
+ """Get a summary of the current catalog state for debugging."""
524
+ return {
525
+ "generation": self.generation,
526
+ "games_this_generation": self.games_this_generation,
527
+ "num_behaviors": len(self.catalog.behaviors),
528
+ "num_roles": len(self.catalog.roles),
529
+ "roles": [
530
+ {
531
+ "name": r.name,
532
+ "origin": r.origin,
533
+ "fitness": r.fitness,
534
+ "games": r.games,
535
+ "wins": r.wins,
536
+ "locked": r.locked_name,
537
+ }
538
+ for r in self.catalog.roles
539
+ ],
540
+ }
@@ -0,0 +1,20 @@
1
+ """Nim-based agent policies for CoGames."""
2
+
3
+ from cogames_agents.policy.nim_agents import agents # noqa: F401
4
+
5
+ __all__ = [
6
+ "RandomAgentsMultiPolicy",
7
+ "ThinkyAgentsMultiPolicy",
8
+ "RaceCarAgentsMultiPolicy",
9
+ "LadyBugAgentsMultiPolicy",
10
+ "CogsguardAlignAllAgentsMultiPolicy",
11
+ ]
12
+
13
+ # Re-export the policy classes for convenience
14
+ from cogames_agents.policy.nim_agents.agents import ( # noqa: F401
15
+ CogsguardAlignAllAgentsMultiPolicy,
16
+ LadyBugAgentsMultiPolicy,
17
+ RaceCarAgentsMultiPolicy,
18
+ RandomAgentsMultiPolicy,
19
+ ThinkyAgentsMultiPolicy,
20
+ )
@@ -0,0 +1,98 @@
1
+ import importlib
2
+ import os
3
+ import sys
4
+ from typing import Sequence
5
+
6
+ from mettagrid.policy.policy import NimMultiAgentPolicy
7
+ from mettagrid.policy.policy_env_interface import PolicyEnvInterface
8
+
9
+ current_dir = os.path.dirname(os.path.abspath(__file__))
10
+ bindings_dir = os.path.join(current_dir, "bindings/generated")
11
+ if bindings_dir not in sys.path:
12
+ sys.path.append(bindings_dir)
13
+
14
+ na = importlib.import_module("nim_agents")
15
+
16
+
17
+ def start_measure():
18
+ na.start_measure()
19
+
20
+
21
+ def end_measure():
22
+ na.end_measure()
23
+
24
+
25
+ class ThinkyAgentsMultiPolicy(NimMultiAgentPolicy):
26
+ short_names = ["thinky"]
27
+
28
+ def __init__(self, policy_env_info: PolicyEnvInterface, agent_ids: Sequence[int] | None = None):
29
+ super().__init__(
30
+ policy_env_info,
31
+ nim_policy_factory=na.ThinkyPolicy,
32
+ agent_ids=agent_ids,
33
+ )
34
+
35
+
36
+ class RandomAgentsMultiPolicy(NimMultiAgentPolicy):
37
+ short_names = ["nim_random"]
38
+
39
+ def __init__(self, policy_env_info: PolicyEnvInterface, agent_ids: Sequence[int] | None = None):
40
+ super().__init__(
41
+ policy_env_info,
42
+ nim_policy_factory=na.RandomPolicy,
43
+ agent_ids=agent_ids,
44
+ )
45
+
46
+
47
+ class RaceCarAgentsMultiPolicy(NimMultiAgentPolicy):
48
+ short_names = ["race_car"]
49
+
50
+ def __init__(self, policy_env_info: PolicyEnvInterface, agent_ids: Sequence[int] | None = None):
51
+ super().__init__(
52
+ policy_env_info,
53
+ nim_policy_factory=na.RaceCarPolicy,
54
+ agent_ids=agent_ids,
55
+ )
56
+
57
+
58
+ class LadyBugAgentsMultiPolicy(NimMultiAgentPolicy):
59
+ short_names = ["ladybug"]
60
+
61
+ def __init__(self, policy_env_info: PolicyEnvInterface, agent_ids: Sequence[int] | None = None):
62
+ super().__init__(
63
+ policy_env_info,
64
+ nim_policy_factory=na.LadybugPolicy,
65
+ agent_ids=agent_ids,
66
+ )
67
+
68
+
69
+ class CogsguardAgentsMultiPolicy(NimMultiAgentPolicy):
70
+ short_names = ["role"]
71
+
72
+ def __init__(
73
+ self,
74
+ policy_env_info: PolicyEnvInterface,
75
+ agent_ids: Sequence[int] | None = None,
76
+ **_: object,
77
+ ):
78
+ super().__init__(
79
+ policy_env_info,
80
+ nim_policy_factory=na.CogsguardPolicy,
81
+ agent_ids=agent_ids,
82
+ )
83
+
84
+
85
+ class CogsguardAlignAllAgentsMultiPolicy(NimMultiAgentPolicy):
86
+ short_names = ["alignall"]
87
+
88
+ def __init__(
89
+ self,
90
+ policy_env_info: PolicyEnvInterface,
91
+ agent_ids: Sequence[int] | None = None,
92
+ **_: object,
93
+ ):
94
+ super().__init__(
95
+ policy_env_info,
96
+ nim_policy_factory=na.CogsguardAlignAllPolicy,
97
+ agent_ids=agent_ids,
98
+ )