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.
- cogames_agents/__init__.py +0 -0
- cogames_agents/evals/__init__.py +5 -0
- cogames_agents/evals/planky_evals.py +415 -0
- cogames_agents/policy/__init__.py +0 -0
- cogames_agents/policy/evolution/__init__.py +0 -0
- cogames_agents/policy/evolution/cogsguard/__init__.py +0 -0
- cogames_agents/policy/evolution/cogsguard/evolution.py +695 -0
- cogames_agents/policy/evolution/cogsguard/evolutionary_coordinator.py +540 -0
- cogames_agents/policy/nim_agents/__init__.py +20 -0
- cogames_agents/policy/nim_agents/agents.py +98 -0
- cogames_agents/policy/nim_agents/bindings/generated/libnim_agents.dylib +0 -0
- cogames_agents/policy/nim_agents/bindings/generated/nim_agents.py +215 -0
- cogames_agents/policy/nim_agents/cogsguard_agents.nim +555 -0
- cogames_agents/policy/nim_agents/cogsguard_align_all_agents.nim +569 -0
- cogames_agents/policy/nim_agents/common.nim +1054 -0
- cogames_agents/policy/nim_agents/install.sh +1 -0
- cogames_agents/policy/nim_agents/ladybug_agent.nim +954 -0
- cogames_agents/policy/nim_agents/nim_agents.nim +68 -0
- cogames_agents/policy/nim_agents/nim_agents.nims +14 -0
- cogames_agents/policy/nim_agents/nimby.lock +3 -0
- cogames_agents/policy/nim_agents/racecar_agents.nim +844 -0
- cogames_agents/policy/nim_agents/random_agents.nim +68 -0
- cogames_agents/policy/nim_agents/test_agents.py +53 -0
- cogames_agents/policy/nim_agents/thinky_agents.nim +677 -0
- cogames_agents/policy/nim_agents/thinky_eval.py +230 -0
- cogames_agents/policy/scripted_agent/README.md +360 -0
- cogames_agents/policy/scripted_agent/__init__.py +0 -0
- cogames_agents/policy/scripted_agent/baseline_agent.py +1031 -0
- cogames_agents/policy/scripted_agent/cogas/__init__.py +5 -0
- cogames_agents/policy/scripted_agent/cogas/context.py +68 -0
- cogames_agents/policy/scripted_agent/cogas/entity_map.py +152 -0
- cogames_agents/policy/scripted_agent/cogas/goal.py +115 -0
- cogames_agents/policy/scripted_agent/cogas/goals/__init__.py +27 -0
- cogames_agents/policy/scripted_agent/cogas/goals/aligner.py +160 -0
- cogames_agents/policy/scripted_agent/cogas/goals/gear.py +197 -0
- cogames_agents/policy/scripted_agent/cogas/goals/miner.py +441 -0
- cogames_agents/policy/scripted_agent/cogas/goals/scout.py +40 -0
- cogames_agents/policy/scripted_agent/cogas/goals/scrambler.py +174 -0
- cogames_agents/policy/scripted_agent/cogas/goals/shared.py +160 -0
- cogames_agents/policy/scripted_agent/cogas/goals/stem.py +60 -0
- cogames_agents/policy/scripted_agent/cogas/goals/survive.py +100 -0
- cogames_agents/policy/scripted_agent/cogas/navigator.py +401 -0
- cogames_agents/policy/scripted_agent/cogas/obs_parser.py +238 -0
- cogames_agents/policy/scripted_agent/cogas/policy.py +525 -0
- cogames_agents/policy/scripted_agent/cogas/trace.py +69 -0
- cogames_agents/policy/scripted_agent/cogsguard/CLAUDE.md +517 -0
- cogames_agents/policy/scripted_agent/cogsguard/README.md +252 -0
- cogames_agents/policy/scripted_agent/cogsguard/__init__.py +74 -0
- cogames_agents/policy/scripted_agent/cogsguard/aligned_junction_held_investigation.md +152 -0
- cogames_agents/policy/scripted_agent/cogsguard/aligner.py +333 -0
- cogames_agents/policy/scripted_agent/cogsguard/behavior_hooks.py +44 -0
- cogames_agents/policy/scripted_agent/cogsguard/control_agent.py +323 -0
- cogames_agents/policy/scripted_agent/cogsguard/debug_agent.py +533 -0
- cogames_agents/policy/scripted_agent/cogsguard/miner.py +589 -0
- cogames_agents/policy/scripted_agent/cogsguard/options.py +67 -0
- cogames_agents/policy/scripted_agent/cogsguard/parity_metrics.py +36 -0
- cogames_agents/policy/scripted_agent/cogsguard/policy.py +1967 -0
- cogames_agents/policy/scripted_agent/cogsguard/prereq_trace.py +33 -0
- cogames_agents/policy/scripted_agent/cogsguard/role_trace.py +50 -0
- cogames_agents/policy/scripted_agent/cogsguard/roles.py +31 -0
- cogames_agents/policy/scripted_agent/cogsguard/rollout_trace.py +40 -0
- cogames_agents/policy/scripted_agent/cogsguard/scout.py +69 -0
- cogames_agents/policy/scripted_agent/cogsguard/scrambler.py +350 -0
- cogames_agents/policy/scripted_agent/cogsguard/targeted_agent.py +418 -0
- cogames_agents/policy/scripted_agent/cogsguard/teacher.py +224 -0
- cogames_agents/policy/scripted_agent/cogsguard/types.py +381 -0
- cogames_agents/policy/scripted_agent/cogsguard/v2_agent.py +49 -0
- cogames_agents/policy/scripted_agent/common/__init__.py +0 -0
- cogames_agents/policy/scripted_agent/common/geometry.py +24 -0
- cogames_agents/policy/scripted_agent/common/roles.py +34 -0
- cogames_agents/policy/scripted_agent/common/tag_utils.py +48 -0
- cogames_agents/policy/scripted_agent/demo_policy.py +242 -0
- cogames_agents/policy/scripted_agent/pathfinding.py +126 -0
- cogames_agents/policy/scripted_agent/pinky/DESIGN.md +317 -0
- cogames_agents/policy/scripted_agent/pinky/__init__.py +5 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/__init__.py +17 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/aligner.py +400 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/base.py +119 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/miner.py +632 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/scout.py +138 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/scrambler.py +433 -0
- cogames_agents/policy/scripted_agent/pinky/policy.py +570 -0
- cogames_agents/policy/scripted_agent/pinky/services/__init__.py +7 -0
- cogames_agents/policy/scripted_agent/pinky/services/map_tracker.py +808 -0
- cogames_agents/policy/scripted_agent/pinky/services/navigator.py +864 -0
- cogames_agents/policy/scripted_agent/pinky/services/safety.py +189 -0
- cogames_agents/policy/scripted_agent/pinky/state.py +299 -0
- cogames_agents/policy/scripted_agent/pinky/types.py +138 -0
- cogames_agents/policy/scripted_agent/planky/CLAUDE.md +124 -0
- cogames_agents/policy/scripted_agent/planky/IMPROVEMENTS.md +160 -0
- cogames_agents/policy/scripted_agent/planky/NOTES.md +153 -0
- cogames_agents/policy/scripted_agent/planky/PLAN.md +254 -0
- cogames_agents/policy/scripted_agent/planky/README.md +214 -0
- cogames_agents/policy/scripted_agent/planky/STRATEGY.md +100 -0
- cogames_agents/policy/scripted_agent/planky/__init__.py +5 -0
- cogames_agents/policy/scripted_agent/planky/context.py +68 -0
- cogames_agents/policy/scripted_agent/planky/entity_map.py +152 -0
- cogames_agents/policy/scripted_agent/planky/goal.py +107 -0
- cogames_agents/policy/scripted_agent/planky/goals/__init__.py +27 -0
- cogames_agents/policy/scripted_agent/planky/goals/aligner.py +168 -0
- cogames_agents/policy/scripted_agent/planky/goals/gear.py +179 -0
- cogames_agents/policy/scripted_agent/planky/goals/miner.py +416 -0
- cogames_agents/policy/scripted_agent/planky/goals/scout.py +40 -0
- cogames_agents/policy/scripted_agent/planky/goals/scrambler.py +174 -0
- cogames_agents/policy/scripted_agent/planky/goals/shared.py +160 -0
- cogames_agents/policy/scripted_agent/planky/goals/stem.py +49 -0
- cogames_agents/policy/scripted_agent/planky/goals/survive.py +96 -0
- cogames_agents/policy/scripted_agent/planky/navigator.py +388 -0
- cogames_agents/policy/scripted_agent/planky/obs_parser.py +238 -0
- cogames_agents/policy/scripted_agent/planky/policy.py +485 -0
- cogames_agents/policy/scripted_agent/planky/tests/__init__.py +0 -0
- cogames_agents/policy/scripted_agent/planky/tests/conftest.py +66 -0
- cogames_agents/policy/scripted_agent/planky/tests/helpers.py +152 -0
- cogames_agents/policy/scripted_agent/planky/tests/test_aligner.py +24 -0
- cogames_agents/policy/scripted_agent/planky/tests/test_miner.py +30 -0
- cogames_agents/policy/scripted_agent/planky/tests/test_scout.py +15 -0
- cogames_agents/policy/scripted_agent/planky/tests/test_scrambler.py +29 -0
- cogames_agents/policy/scripted_agent/planky/tests/test_stem.py +36 -0
- cogames_agents/policy/scripted_agent/planky/trace.py +69 -0
- cogames_agents/policy/scripted_agent/types.py +239 -0
- cogames_agents/policy/scripted_agent/unclipping_agent.py +461 -0
- cogames_agents/policy/scripted_agent/utils.py +381 -0
- cogames_agents/policy/scripted_registry.py +80 -0
- cogames_agents/py.typed +0 -0
- cogames_agents-0.0.0.7.dist-info/METADATA +98 -0
- cogames_agents-0.0.0.7.dist-info/RECORD +128 -0
- cogames_agents-0.0.0.7.dist-info/WHEEL +6 -0
- 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
|
+
)
|
|
Binary file
|