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,844 @@
1
+ import
2
+ std/[strformat, tables, random, sets, options, json, deques],
3
+ fidget2/measure,
4
+ common
5
+
6
+ const
7
+ MaxEnergy = 100
8
+ MaxResourceInventory = 100
9
+ MaxToolInventory = 100
10
+ MaxStepsDefault = 10000
11
+
12
+ PutCarbonAmount = 10
13
+ PutOxygenAmount = 10
14
+ PutGermaniumAmount = 1
15
+ PutSiliconAmount = 25
16
+
17
+ type
18
+ RaceCarAgent* = ref object
19
+ agentId*: int
20
+
21
+ maxSteps: int
22
+ steps: int
23
+ map: Table[Location, seq[FeatureValue]]
24
+ seen: HashSet[Location]
25
+ unreachable: HashSet[Location]
26
+ depleted: HashSet[Location] # extractors/junctions with remainingUses == 0
27
+ cfg: Config
28
+ random: Rand
29
+ location: Location
30
+ lastActions: seq[int]
31
+ recipes: seq[RecipeInfo]
32
+
33
+ carbonTarget: int
34
+ oxygenTarget: int
35
+ germaniumTarget: int
36
+ siliconTarget: int
37
+
38
+ assignedVibe: Option[int] # desired vibe for vibe-check recipes
39
+ needsPatternReapply: bool # if we changed vibe away from pattern for a side-task
40
+
41
+ bump: bool
42
+ offsets4: seq[Location] # 4 cardinal but random for each agent
43
+ seenHub: bool
44
+ seenChest: bool
45
+ hubHome: Option[Location]
46
+ chestHome: Option[Location]
47
+ exploreLocations: seq[Location]
48
+
49
+ RaceCarPolicy* = ref object
50
+ agents*: seq[RaceCarAgent]
51
+
52
+ proc log(message: string) =
53
+ when defined(debug):
54
+ echo message
55
+
56
+ const Offsets4 = [
57
+ Location(x: -1, y: +0),
58
+ Location(x: +0, y: +1),
59
+ Location(x: +0, y: -1),
60
+ Location(x: +1, y: +0),
61
+ ]
62
+
63
+ const Offsets8 = [
64
+ Location(x: +0, y: +0),
65
+ Location(x: +0, y: +1),
66
+ Location(x: +0, y: -1),
67
+ Location(x: +1, y: +0),
68
+ Location(x: +1, y: +1),
69
+ Location(x: +1, y: -1),
70
+ Location(x: -1, y: +0),
71
+ Location(x: -1, y: +1),
72
+ Location(x: -1, y: -1),
73
+ ]
74
+
75
+ proc getActiveRecipe(agent: RaceCarAgent): RecipeInfo {.measure.} =
76
+ ## Get the recipes form the hub protocol inputs.
77
+ let hubLocation = agent.cfg.getNearby(agent.location, agent.map, agent.cfg.tags.hub)
78
+ if hubLocation.isSome():
79
+ let location = hubLocation.get()
80
+ # Get the vibe key.
81
+ result.pattern = @[]
82
+ for offsets in Offsets8:
83
+ let vibeKey = agent.cfg.getVibe(agent.map, location + offsets)
84
+ if vibeKey != -1:
85
+ result.pattern.add(vibeKey)
86
+
87
+ # Get the required resources.
88
+ let hubFeatures = agent.map[location]
89
+ for feature in hubFeatures:
90
+ updateRecipeFromProtocol(agent.cfg, feature, result)
91
+
92
+ proc newRaceCarAgent*(agentId: int, environmentConfig: string): RaceCarAgent =
93
+ ## Create a new racecar agent, the fastest and the smartest agent.
94
+
95
+ var config = parseConfig(environmentConfig)
96
+ result = RaceCarAgent(agentId: agentId, cfg: config)
97
+ result.maxSteps = MaxStepsDefault
98
+ # Try to read max_steps from the environment config; fall back to default.
99
+ try:
100
+ let env = environmentConfig.parseJson()
101
+ if env.hasKey("game") and env["game"].hasKey("max_steps"):
102
+ result.maxSteps = env["game"]["max_steps"].getInt()
103
+ elif env.hasKey("max_steps"):
104
+ result.maxSteps = env["max_steps"].getInt()
105
+ except CatchableError:
106
+ discard
107
+ result.random = initRand(agentId)
108
+ result.map = initTable[Location, seq[FeatureValue]]()
109
+ result.seen = initHashSet[Location]()
110
+ result.unreachable = initHashSet[Location]()
111
+ result.depleted = initHashSet[Location]()
112
+ result.location = Location(x: 0, y: 0)
113
+ result.assignedVibe = none(int)
114
+ result.needsPatternReapply = false
115
+ result.steps = 0
116
+ result.hubHome = none(Location)
117
+ result.chestHome = none(Location)
118
+ result.lastActions = @[]
119
+ # Randomize the offsets4 for each agent, so they take different directions.
120
+ var offsets4 = Offsets4
121
+ result.random.shuffle(offsets4)
122
+ result.offsets4 = @[]
123
+ for o in offsets4:
124
+ result.offsets4.add(o)
125
+
126
+ result.exploreLocations = @[
127
+ Location(x: -7, y: 0),
128
+ Location(x: 0, y: +7),
129
+ Location(x: +7, y: 0),
130
+ Location(x: 0, y: -7),
131
+ ]
132
+ result.random.shuffle(result.exploreLocations)
133
+
134
+ proc updateMap(agent: RaceCarAgent, visible: Table[Location, seq[FeatureValue]]) {.measure.} =
135
+ ## Update the big map with the small visible map.
136
+
137
+ if agent.map.len == 0:
138
+ # First time we're called, just copy the visible map to the big map.
139
+ agent.map = visible
140
+ agent.location = Location(x: 0, y: 0)
141
+ for x in -5 .. 5:
142
+ for y in -5 .. 5:
143
+ let visibleLocation = Location(x: x, y: y)
144
+ agent.seen.incl(visibleLocation)
145
+ return
146
+
147
+ var newLocation = agent.location
148
+ let lastAction = agent.cfg.getLastAction(visible)
149
+ if lastAction == agent.cfg.actions.moveNorth:
150
+ newLocation.y -= 1
151
+ elif lastAction == agent.cfg.actions.moveSouth:
152
+ newLocation.y += 1
153
+ elif lastAction == agent.cfg.actions.moveWest:
154
+ newLocation.x -= 1
155
+ elif lastAction == agent.cfg.actions.moveEast:
156
+ newLocation.x += 1
157
+
158
+ # Does the new location have force any tags to no be there?
159
+ # If so we did a bump instead of a move.
160
+ block bumpCheck:
161
+ agent.bump = false
162
+ for x in -5 .. 5:
163
+ for y in -5 .. 5:
164
+ let visibleLocation = Location(x: x, y: y)
165
+ let mapLocation = Location(x: x + newLocation.x, y: y + newLocation.y)
166
+ if mapLocation notin agent.seen:
167
+ continue
168
+ var visibleTag = agent.cfg.getTag(visible, visibleLocation)
169
+ if visibleTag == agent.cfg.tags.agent:
170
+ # Ignore agents.
171
+ visibleTag = -1
172
+ var mapTag = agent.cfg.getTag(agent.map, mapLocation)
173
+ if mapTag == agent.cfg.tags.agent:
174
+ # Ignore agents.
175
+ mapTag = -1
176
+ if visibleTag != mapTag:
177
+ newLocation = agent.location
178
+ agent.bump = true
179
+ break bumpCheck
180
+
181
+ # Update the seen set.
182
+ agent.location = newLocation
183
+ for x in -5 .. 5:
184
+ for y in -5 .. 5:
185
+ let visibleLocation = Location(x: x, y: y)
186
+ let mapLocation = Location(x: x + agent.location.x, y: y + agent.location.y)
187
+ if visibleLocation in visible:
188
+ agent.map[mapLocation] = visible[visibleLocation]
189
+ # Mark depleted sites to avoid revisits.
190
+ for f in visible[visibleLocation]:
191
+ if f.featureId == agent.cfg.features.remainingUses and f.value == 0:
192
+ agent.depleted.incl(mapLocation)
193
+ else:
194
+ agent.map[mapLocation] = @[]
195
+ agent.seen.incl(mapLocation)
196
+
197
+ # agent.cfg.drawMap(agent.map, agent.seen)
198
+
199
+ proc getNumAgentsNearby*(
200
+ cfg: Config,
201
+ location: Location,
202
+ map: Table[Location, seq[FeatureValue]]
203
+ ): int {.measure.} =
204
+ ## Get the number of agents nearby.
205
+ for offset in Offsets8:
206
+ let at = location + offset
207
+ if at in map:
208
+ for featureValue in map[at]:
209
+ if featureValue.featureId == cfg.features.group:
210
+ result += 1
211
+ break
212
+
213
+ proc getNearbyExtractor*(
214
+ cfg: Config,
215
+ currentLocation: Location,
216
+ map: Table[Location, seq[FeatureValue]],
217
+ tagId: int
218
+ ): Option[Location] =
219
+ ## Get if there is a nearby location with the given tag.
220
+ var
221
+ found = false
222
+ closestLocation = Location(x: 0, y: 0)
223
+ closestDistance = 9999
224
+ for location, featureValues in map:
225
+ for featureValue in featureValues:
226
+ if featureValue.featureId == cfg.features.tag and featureValue.value == tagId:
227
+ let agentsNearby = cfg.getNumAgentsNearby(location, map)
228
+ if agentsNearby > 1:
229
+ continue
230
+ var skip = false
231
+ for f in map[location]:
232
+ if f.featureId == cfg.features.remainingUses and f.value == 0:
233
+ skip = true
234
+ break
235
+ # if f.featureId == cfg.features.cooldownRemaining and f.value > 50:
236
+ # skip = true
237
+ # break
238
+ if skip:
239
+ continue
240
+ let distance = manhattan(location, currentLocation)
241
+ if distance < closestDistance:
242
+ closestDistance = distance
243
+ closestLocation = location
244
+ found = true
245
+ if found:
246
+ return some(closestLocation)
247
+ return none(Location)
248
+
249
+ proc step*(
250
+ agent: RaceCarAgent,
251
+ numAgents: int,
252
+ numTokens: int,
253
+ sizeToken: int,
254
+ rawObservation: pointer,
255
+ numActions: int,
256
+ agentAction: ptr int32
257
+ ) {.measure.} =
258
+ try:
259
+
260
+ let observations = cast[ptr UncheckedArray[uint8]](rawObservation)
261
+
262
+ # Parse the tokens into a vision map.
263
+ var visible: Table[Location, seq[FeatureValue]]
264
+ for token in 0 ..< numTokens:
265
+ let locationPacked = observations[token * sizeToken]
266
+ let featureId = observations[token * sizeToken + 1]
267
+ let value = observations[token * sizeToken + 2]
268
+ if locationPacked == 255 and featureId == 255 and value == 255:
269
+ break
270
+ var location: Location
271
+ if locationPacked != 0xFF:
272
+ location.y = (locationPacked shr 4).int - 5
273
+ location.x = (locationPacked and 0x0F).int - 5
274
+ if location notin visible:
275
+ visible[location] = @[]
276
+ visible[location].add(FeatureValue(featureId: featureId.int, value: value.int))
277
+
278
+ proc doAction(action: int) {.measure.} =
279
+
280
+ # Get last action from observations, in case something else moved us.
281
+ agent.lastActions.add(agent.cfg.getLastAction(visible))
282
+
283
+ # Helper to set action without modifying history again.
284
+ proc setAction(actionToSet: int32) =
285
+ agentAction[] = actionToSet
286
+
287
+ # Stuck prevention: if last 2 actions are oscillating, sometimes noop.
288
+ if agent.lastActions.len >= 2:
289
+ if action == agent.cfg.actions.moveWest and
290
+ agent.lastActions[^1] == agent.cfg.actions.moveEast and
291
+ agent.lastActions[^2] == agent.cfg.actions.moveWest:
292
+ if agent.random.rand(1 .. 2) == 1:
293
+ log "Stuck prevention: left, right, left"
294
+ setAction(agent.cfg.actions.noop.int32)
295
+ return
296
+ elif action == agent.cfg.actions.moveEast and
297
+ agent.lastActions[^1] == agent.cfg.actions.moveWest and
298
+ agent.lastActions[^2] == agent.cfg.actions.moveEast:
299
+ if agent.random.rand(1 .. 2) == 1:
300
+ log "Stuck prevention: right, left, right"
301
+ setAction(agent.cfg.actions.noop.int32)
302
+ return
303
+ elif action == agent.cfg.actions.moveNorth and
304
+ agent.lastActions[^1] == agent.cfg.actions.moveSouth and
305
+ agent.lastActions[^2] == agent.cfg.actions.moveNorth:
306
+ if agent.random.rand(1 .. 2) == 1:
307
+ log "Stuck prevention: north, south, north"
308
+ setAction(agent.cfg.actions.noop.int32)
309
+ return
310
+ elif action == agent.cfg.actions.moveSouth and
311
+ agent.lastActions[^1] == agent.cfg.actions.moveNorth and
312
+ agent.lastActions[^2] == agent.cfg.actions.moveSouth:
313
+ if agent.random.rand(1 .. 2) == 1:
314
+ log "Stuck prevention: south, north, south"
315
+ setAction(agent.cfg.actions.noop.int32)
316
+ return
317
+
318
+ setAction(action.int32)
319
+
320
+ updateMap(agent, visible)
321
+ agent.steps += 1
322
+
323
+ # Track base infrastructure as soon as we see it and seed exploration around it
324
+ let hubNearbyNow = agent.cfg.getNearby(agent.location, agent.map, agent.cfg.tags.hub)
325
+ if hubNearbyNow.isSome():
326
+ let asmLoc = hubNearbyNow.get()
327
+ agent.hubHome = some(asmLoc)
328
+ if not agent.seenHub:
329
+ agent.seenHub = true
330
+ let keyLocations = [
331
+ Location(x: -10, y: -10),
332
+ Location(x: -10, y: +10),
333
+ Location(x: +10, y: -10),
334
+ Location(x: +10, y: +10),
335
+ Location(x: -20, y: 0),
336
+ Location(x: +20, y: 0),
337
+ Location(x: 0, y: -20),
338
+ Location(x: 0, y: +20),
339
+ ]
340
+ for keyLocation in keyLocations:
341
+ let location = asmLoc + keyLocation
342
+ if location notin agent.exploreLocations:
343
+ agent.exploreLocations.add(location)
344
+
345
+ let chestNearbyNow = agent.cfg.getNearby(agent.location, agent.map, agent.cfg.tags.chest)
346
+ if chestNearbyNow.isSome():
347
+ let chestLoc = chestNearbyNow.get()
348
+ agent.chestHome = some(chestLoc)
349
+ if not agent.seenChest:
350
+ agent.seenChest = true
351
+ let keyLocations = [
352
+ Location(x: -3, y: 0),
353
+ Location(x: 0, y: +3),
354
+ Location(x: +3, y: 0),
355
+ Location(x: 0, y: -3),
356
+ Location(x: -6, y: 0),
357
+ Location(x: 0, y: +6),
358
+ Location(x: +6, y: 0),
359
+ Location(x: 0, y: -6),
360
+ ]
361
+ for keyLocation in keyLocations:
362
+ let location = chestLoc + keyLocation
363
+ if location notin agent.exploreLocations:
364
+ agent.exploreLocations.add(location)
365
+
366
+ let
367
+ vibe = agent.cfg.getVibe(visible, Location(x: 0, y: 0))
368
+ invEnergy = agent.cfg.getInventory(visible, agent.cfg.features.invEnergy)
369
+ invCarbon = agent.cfg.getInventory(visible, agent.cfg.features.invCarbon)
370
+ invOxygen = agent.cfg.getInventory(visible, agent.cfg.features.invOxygen)
371
+ invGermanium = agent.cfg.getInventory(visible, agent.cfg.features.invGermanium)
372
+ invSilicon = agent.cfg.getInventory(visible, agent.cfg.features.invSilicon)
373
+ invHeart = agent.cfg.getInventory(visible, agent.cfg.features.invHeart)
374
+ invDecoder = agent.cfg.getInventory(visible, agent.cfg.features.invDecoder)
375
+ invModulator = agent.cfg.getInventory(visible, agent.cfg.features.invModulator)
376
+ invResonator = agent.cfg.getInventory(visible, agent.cfg.features.invResonator)
377
+ invScrambler = agent.cfg.getInventory(visible, agent.cfg.features.invScrambler)
378
+ completionPct = agent.cfg.getInventory(visible, agent.cfg.features.episodeCompletionPct)
379
+
380
+ var stepsRemaining = max(0, agent.maxSteps - agent.steps)
381
+ if completionPct > 0 and completionPct < 100:
382
+ # Estimate total steps from observed completion percentage.
383
+ let estTotal = int((agent.steps * 100) div completionPct)
384
+ stepsRemaining = max(0, estTotal - agent.steps)
385
+
386
+ log &"vibe:{vibe} H:{invHeart} E:{invEnergy} C:{invCarbon} O2:{invOxygen} Ge:{invGermanium} Si:{invSilicon} D:{invDecoder} M:{invModulator} R:{invResonator} S:{invScrambler}"
387
+
388
+ let activeRecipe = agent.getActiveRecipe()
389
+ log "active recipe: " & $activeRecipe
390
+
391
+ # If only one resource is needed, make it the sole target to avoid wasted trips.
392
+ var numNeeded = 0
393
+ for cost in [activeRecipe.carbonCost, activeRecipe.oxygenCost, activeRecipe.germaniumCost, activeRecipe.siliconCost]:
394
+ if cost > 0:
395
+ inc numNeeded
396
+
397
+ if activeRecipe.pattern.len > 0:
398
+ # Split the cost evenly across the agents.
399
+ proc divUp(a, b: int): int =
400
+ ## Like div, but rounds up instead of down.
401
+ let extra = if a mod b > 0: 1 else: 0
402
+ return a div b + extra
403
+ agent.carbonTarget = max(agent.carbonTarget, activeRecipe.carbonCost.divUp(activeRecipe.pattern.len))
404
+ agent.oxygenTarget = max(agent.oxygenTarget, activeRecipe.oxygenCost.divUp(activeRecipe.pattern.len))
405
+ agent.germaniumTarget = max(agent.germaniumTarget, activeRecipe.germaniumCost.divUp(activeRecipe.pattern.len))
406
+ agent.siliconTarget = max(agent.siliconTarget, activeRecipe.siliconCost.divUp(activeRecipe.pattern.len))
407
+ else:
408
+ agent.carbonTarget = max(agent.carbonTarget, activeRecipe.carbonCost)
409
+ agent.oxygenTarget = max(agent.oxygenTarget, activeRecipe.oxygenCost)
410
+ agent.germaniumTarget = max(agent.germaniumTarget, activeRecipe.germaniumCost)
411
+ agent.siliconTarget = max(agent.siliconTarget, activeRecipe.siliconCost)
412
+
413
+ if numNeeded == 1:
414
+ # Only keep the required resource; zero out others so we don't waste time or dump it.
415
+ if activeRecipe.carbonCost > 0:
416
+ agent.oxygenTarget = 0
417
+ agent.germaniumTarget = 0
418
+ agent.siliconTarget = 0
419
+ elif activeRecipe.oxygenCost > 0:
420
+ agent.carbonTarget = 0
421
+ agent.germaniumTarget = 0
422
+ agent.siliconTarget = 0
423
+ elif activeRecipe.germaniumCost > 0:
424
+ agent.carbonTarget = 0
425
+ agent.oxygenTarget = 0
426
+ agent.siliconTarget = 0
427
+ elif activeRecipe.siliconCost > 0:
428
+ agent.carbonTarget = 0
429
+ agent.oxygenTarget = 0
430
+ agent.germaniumTarget = 0
431
+
432
+ # Vibe-check support: assign and enforce a required vibe from the hub pattern.
433
+ proc actionForVibe(cfg: Config, v: int): Option[int] =
434
+ if v == cfg.vibes.default: return some(cfg.actions.vibeDefault)
435
+ if v == cfg.vibes.junction: return some(cfg.actions.vibeCharger)
436
+ if v == cfg.vibes.carbonA: return some(cfg.actions.vibeCarbonA)
437
+ if v == cfg.vibes.carbonB: return some(cfg.actions.vibeCarbonB)
438
+ if v == cfg.vibes.oxygenA: return some(cfg.actions.vibeOxygenA)
439
+ if v == cfg.vibes.oxygenB: return some(cfg.actions.vibeOxygenB)
440
+ if v == cfg.vibes.germaniumA: return some(cfg.actions.vibeGermaniumA)
441
+ if v == cfg.vibes.germaniumB: return some(cfg.actions.vibeGermaniumB)
442
+ if v == cfg.vibes.siliconA: return some(cfg.actions.vibeSiliconA)
443
+ if v == cfg.vibes.siliconB: return some(cfg.actions.vibeSiliconB)
444
+ if v == cfg.vibes.heartA: return some(cfg.actions.vibeHeartA)
445
+ if v == cfg.vibes.heartB: return some(cfg.actions.vibeHeartB)
446
+ if v == cfg.vibes.gear: return some(cfg.actions.vibeGear)
447
+ if v == cfg.vibes.hub: return some(cfg.actions.vibeHub)
448
+ if v == cfg.vibes.chest: return some(cfg.actions.vibeChest)
449
+ if v == cfg.vibes.wall: return some(cfg.actions.vibeWall)
450
+ return none(int)
451
+
452
+ template markPatternReapply(v: int) =
453
+ if activeRecipe.pattern.len > 0 and agent.assignedVibe.isSome() and v != agent.assignedVibe.get():
454
+ agent.needsPatternReapply = true
455
+
456
+ if activeRecipe.pattern.len > 0:
457
+ # Assign required vibe: if more agents than vibes, duplicate the first vibe to keep full coverage.
458
+ var haveAssigned = false
459
+ if agent.assignedVibe.isSome():
460
+ for v in activeRecipe.pattern:
461
+ if v == agent.assignedVibe.get():
462
+ haveAssigned = true
463
+ break
464
+ if not haveAssigned:
465
+ let idx = if agent.agentId < activeRecipe.pattern.len: agent.agentId else: 0
466
+ agent.assignedVibe = some(activeRecipe.pattern[idx])
467
+
468
+ proc patternSatisfied(agent: RaceCarAgent): bool =
469
+ # Check if all required vibes are present around the hub location we know.
470
+ if agent.hubHome.isNone():
471
+ return false
472
+ let asmLoc = agent.hubHome.get()
473
+ var seenVibes: HashSet[int]
474
+ for offset in Offsets8:
475
+ let loc = asmLoc + offset
476
+ let v = agent.cfg.getVibe(agent.map, loc)
477
+ if v != -1:
478
+ seenVibes.incl(v)
479
+ for v in activeRecipe.pattern:
480
+ if v notin seenVibes:
481
+ return false
482
+ return true
483
+
484
+ let desiredVibe = agent.assignedVibe.get()
485
+ if not patternSatisfied(agent) and (vibe != desiredVibe or agent.needsPatternReapply):
486
+ let vibeAction = actionForVibe(agent.cfg, desiredVibe)
487
+ if vibeAction.isSome():
488
+ doAction(vibeAction.get().int32)
489
+ log "setting vibe to match hub pattern: " & $desiredVibe
490
+ agent.needsPatternReapply = false
491
+ return
492
+
493
+ # Are we running low on energy?
494
+ if invEnergy < MaxEnergy div 4:
495
+ let junctionNearby = agent.cfg.getNearbyExtractor(agent.location, agent.map, agent.cfg.tags.junction)
496
+ if junctionNearby.isSome():
497
+ measurePush("junction nearby")
498
+ let action = agent.cfg.aStar(agent.location, junctionNearby.get(), agent.map)
499
+ measurePop()
500
+ if action.isSome():
501
+ doAction(action.get().int32)
502
+ log "going to junction"
503
+ return
504
+
505
+ # Charge opportunistically, with a slightly higher margin for hard energy maps.
506
+ let opportunisticMargin = if stepsRemaining < 200: MaxEnergy - 30 else: MaxEnergy - 20
507
+ if invEnergy < opportunisticMargin:
508
+ let junctionNearby = agent.cfg.getNearbyExtractor(agent.location, agent.map, agent.cfg.tags.junction)
509
+ if junctionNearby.isSome():
510
+ if manhattan(agent.location, junctionNearby.get()) < 2:
511
+ measurePush("charge nearby")
512
+ let action = agent.cfg.aStar(agent.location, junctionNearby.get(), agent.map)
513
+ measurePop()
514
+ if action.isSome():
515
+ doAction(action.get().int32)
516
+ log "charge nearby might as well charge"
517
+ return
518
+
519
+ # Deposit heart into the chest.
520
+ if invHeart > 0:
521
+
522
+ # Reset the targets when we deposit hearts.
523
+ log "depositing hearts"
524
+ agent.carbonTarget = PutCarbonAmount
525
+ agent.oxygenTarget = PutOxygenAmount
526
+ agent.germaniumTarget = PutGermaniumAmount
527
+ agent.siliconTarget = PutSiliconAmount
528
+
529
+ let depositAction = agent.cfg.actions.vibeHeartB
530
+ let depositVibe = agent.cfg.vibes.heartB
531
+ if depositAction != 0 and vibe != depositVibe:
532
+ doAction(depositAction.int32)
533
+ markPatternReapply(depositVibe)
534
+ return
535
+ let chestNearby = agent.cfg.getNearby(agent.location, agent.map, agent.cfg.tags.chest)
536
+ if chestNearby.isSome():
537
+ measurePush("chest nearby")
538
+ let action = agent.cfg.aStar(agent.location, chestNearby.get(), agent.map)
539
+ measurePop()
540
+ if action.isSome():
541
+ doAction(action.get().int32)
542
+ log "going to chest"
543
+ return
544
+
545
+ # Build a heart when we have the full recipe.
546
+ if invCarbon >= agent.carbonTarget and invOxygen >= agent.oxygenTarget and invGermanium >= agent.germaniumTarget and invSilicon >= agent.siliconTarget:
547
+ log "trying to build a heart"
548
+
549
+ if vibe != agent.cfg.vibes.heartA:
550
+ doAction(agent.cfg.actions.vibeHeartA.int32)
551
+ markPatternReapply(agent.cfg.vibes.heartA)
552
+ log "vibing heart for hub"
553
+ return
554
+
555
+ let hubNearby = agent.cfg.getNearby(agent.location, agent.map, agent.cfg.tags.hub)
556
+ if hubNearby.isSome():
557
+ measurePush("hub nearby")
558
+ let action = agent.cfg.aStar(agent.location, hubNearby.get(), agent.map)
559
+ measurePop()
560
+ if action.isSome():
561
+ doAction(action.get().int32)
562
+ log "going to hub to build heart"
563
+ return
564
+
565
+ # Dump excess resources.
566
+ let atMaxInventory = invCarbon + invOxygen + invGermanium + invSilicon >= MaxResourceInventory
567
+ let avgResource = (invCarbon + invOxygen + invGermanium + invSilicon) div 4
568
+ if atMaxInventory:
569
+ log "at max inventory"
570
+
571
+ if atMaxInventory and invCarbon > avgResource and invCarbon > agent.carbonTarget + PutCarbonAmount and agent.carbonTarget == 0:
572
+ let chestNearby = agent.cfg.getNearby(agent.location, agent.map, agent.cfg.tags.chest)
573
+ if chestNearby.isSome():
574
+ if vibe != agent.cfg.vibes.carbonB:
575
+ doAction(agent.cfg.actions.vibeCarbonB.int32)
576
+ markPatternReapply(agent.cfg.vibes.carbonB)
577
+ log "vibing carbon B to dump excess carbon"
578
+ return
579
+ measurePush("chest nearby excess carbon")
580
+ let action = agent.cfg.aStar(agent.location, chestNearby.get(), agent.map)
581
+ measurePop()
582
+ if action.isSome():
583
+ doAction(action.get().int32)
584
+ log "going to chest to dump excess carbon"
585
+ return
586
+
587
+ if atMaxInventory and invSilicon > avgResource and invSilicon > agent.siliconTarget + PutSiliconAmount and agent.siliconTarget == 0:
588
+ let chestNearby = agent.cfg.getNearby(agent.location, agent.map, agent.cfg.tags.chest)
589
+ if chestNearby.isSome():
590
+ if vibe != agent.cfg.vibes.siliconB:
591
+ doAction(agent.cfg.actions.vibeSiliconB.int32)
592
+ markPatternReapply(agent.cfg.vibes.siliconB)
593
+ log "vibing silicon B to dump excess silicon"
594
+ return
595
+ let action = agent.cfg.aStar(agent.location, chestNearby.get(), agent.map)
596
+ if action.isSome():
597
+ doAction(action.get().int32)
598
+ log "going to chest to dump excess silicon"
599
+ return
600
+
601
+ if atMaxInventory and invOxygen > avgResource and invOxygen > agent.oxygenTarget + PutOxygenAmount and agent.oxygenTarget == 0:
602
+ let chestNearby = agent.cfg.getNearby(agent.location, agent.map, agent.cfg.tags.chest)
603
+ if chestNearby.isSome():
604
+ if vibe != agent.cfg.vibes.oxygenB:
605
+ doAction(agent.cfg.actions.vibeOxygenB.int32)
606
+ markPatternReapply(agent.cfg.vibes.oxygenB)
607
+ log "vibing oxygen B to dump excess oxygen"
608
+ return
609
+ measurePush("chest nearby excess oxygen")
610
+ let action = agent.cfg.aStar(agent.location, chestNearby.get(), agent.map)
611
+ measurePop()
612
+ if action.isSome():
613
+ doAction(action.get().int32)
614
+ log "going to chest to dump excess oxygen"
615
+ return
616
+
617
+ if atMaxInventory and invGermanium > avgResource and invGermanium > agent.germaniumTarget + PutGermaniumAmount and agent.germaniumTarget == 0:
618
+ let chestNearby = agent.cfg.getNearby(agent.location, agent.map, agent.cfg.tags.chest)
619
+ if chestNearby.isSome():
620
+ if vibe != agent.cfg.vibes.germaniumB:
621
+ doAction(agent.cfg.actions.vibeGermaniumB.int32)
622
+ markPatternReapply(agent.cfg.vibes.germaniumB)
623
+ log "vibing germanium B to dump excess germanium"
624
+ return
625
+ measurePush("chest nearby excess germanium")
626
+ let action = agent.cfg.aStar(agent.location, chestNearby.get(), agent.map)
627
+ measurePop()
628
+ if action.isSome():
629
+ doAction(action.get().int32)
630
+ log "going to chest to dump excess germanium"
631
+ return
632
+
633
+ # Distance-aware late return: aim to arrive before the episode ends.
634
+ if agent.hubHome.isSome():
635
+ let distAsm = manhattan(agent.location, agent.hubHome.get())
636
+ let buffer = 30
637
+ if stepsRemaining <= distAsm + buffer:
638
+ if invHeart > 0:
639
+ let target = if agent.chestHome.isSome(): agent.chestHome.get() else: agent.hubHome.get()
640
+ let action = agent.cfg.aStar(agent.location, target, agent.map)
641
+ if action.isSome():
642
+ doAction(action.get().int32)
643
+ log "late return: delivering hearts"
644
+ return
645
+ elif invCarbon + invOxygen + invGermanium + invSilicon > 0:
646
+ let action = agent.cfg.aStar(agent.location, agent.hubHome.get(), agent.map)
647
+ if action.isSome():
648
+ doAction(action.get().int32)
649
+ log "late return: heading to hub with resources"
650
+ return
651
+
652
+ proc findAndTakeResource(
653
+ agent: RaceCarAgent,
654
+ vibe: int,
655
+ resource: int,
656
+ target: int,
657
+ inventory: int,
658
+ vibeGetResource: int,
659
+ vibeAction: int,
660
+ extractorTag: int,
661
+ name: string
662
+ ): bool {.measure.} =
663
+ # Check the chest.
664
+ var closeChest = agent.cfg.getNearby(agent.location, agent.map, agent.cfg.tags.chest)
665
+ if closeChest.isSome():
666
+ # Does it have the resources we need?
667
+ let chestInventory = agent.cfg.getInventory(agent.map, resource, closeChest.get())
668
+ if chestInventory > 0:
669
+ # Vibe the right resource to take from the chest.
670
+ if vibe != vibeGetResource:
671
+ doAction(vibeAction.int32)
672
+ log "vibing " & name & " to take from chest"
673
+ return true
674
+ measurePush("chest nearby to take " & name)
675
+ let action = agent.cfg.aStar(agent.location, closeChest.get(), agent.map)
676
+ measurePop()
677
+ if action.isSome():
678
+ doAction(action.get().int32)
679
+ log "going to chest to take " & name & " from chest"
680
+ return true
681
+
682
+ # Check the carbon extractor.
683
+ let extractorNearby = agent.cfg.getNearbyExtractor(agent.location, agent.map, extractorTag)
684
+ if extractorNearby.isSome() and extractorNearby.get() notin agent.unreachable and extractorNearby.get() notin agent.depleted:
685
+ measurePush("extractor nearby to take " & name)
686
+ let action = agent.cfg.aStar(agent.location, extractorNearby.get(), agent.map)
687
+ measurePop()
688
+ if action.isSome():
689
+ doAction(action.get().int32)
690
+ log "going to " & name & ", need: " & $target & " have: " & $inventory
691
+ markPatternReapply(vibe)
692
+ # If we arrive and see it depleted, mark it.
693
+ if extractorNearby.get() in agent.depleted:
694
+ agent.unreachable.incl(extractorNearby.get())
695
+ return true
696
+ else:
697
+ agent.unreachable.incl(extractorNearby.get())
698
+
699
+ # Is there carbon nearby?
700
+ if agent.carbonTarget > 0 and invCarbon < agent.carbonTarget:
701
+ if agent.findAndTakeResource(
702
+ vibe,
703
+ agent.cfg.features.invCarbon,
704
+ agent.carbonTarget,
705
+ invCarbon,
706
+ agent.cfg.vibes.carbonA,
707
+ agent.cfg.actions.vibeCarbonA,
708
+ agent.cfg.tags.carbonExtractor,
709
+ "carbon"
710
+ ):
711
+ return
712
+
713
+ # Is there silicon nearby?
714
+ if agent.siliconTarget > 0 and invSilicon < agent.siliconTarget:
715
+ if agent.findAndTakeResource(
716
+ vibe,
717
+ agent.cfg.features.invSilicon,
718
+ agent.siliconTarget,
719
+ invSilicon,
720
+ agent.cfg.vibes.siliconA,
721
+ agent.cfg.actions.vibeSiliconA,
722
+ agent.cfg.tags.siliconExtractor,
723
+ "silicon"
724
+ ):
725
+ return
726
+
727
+ # Is there oxygen nearby?
728
+ if agent.oxygenTarget > 0 and invOxygen < agent.oxygenTarget:
729
+ if agent.findAndTakeResource(
730
+ vibe,
731
+ agent.cfg.features.invOxygen,
732
+ agent.oxygenTarget,
733
+ invOxygen,
734
+ agent.cfg.vibes.oxygenA,
735
+ agent.cfg.actions.vibeOxygenA,
736
+ agent.cfg.tags.oxygenExtractor,
737
+ "oxygen"
738
+ ):
739
+ return
740
+
741
+ # Is there germanium nearby?
742
+ if agent.germaniumTarget > 0 and invGermanium < agent.germaniumTarget:
743
+ if agent.findAndTakeResource(
744
+ vibe,
745
+ agent.cfg.features.invGermanium,
746
+ agent.germaniumTarget,
747
+ invGermanium,
748
+ agent.cfg.vibes.germaniumA,
749
+ agent.cfg.actions.vibeGermaniumA,
750
+ agent.cfg.tags.germaniumExtractor,
751
+ "germanium"
752
+ ):
753
+ return
754
+
755
+ # Do Exploration.
756
+ if agent.exploreLocations.len == 0:
757
+ measurePush("exploration")
758
+ var locationFound = false
759
+ var unexploredLocation: Location
760
+ var visited: HashSet[Location]
761
+ block exploration:
762
+ var seedLocation = agent.location
763
+ let hubNearby = agent.cfg.getNearby(agent.location, agent.map, agent.cfg.tags.hub)
764
+ if hubNearby.isSome():
765
+ seedLocation = hubNearby.get()
766
+ var queue: Deque[Location]
767
+ queue.addLast(seedLocation)
768
+ visited.incl(seedLocation)
769
+ while queue.len > 0:
770
+ let location = queue.popLast()
771
+ if location notin agent.seen:
772
+ locationFound = true
773
+ unexploredLocation = location
774
+ break exploration
775
+ for i, offset in Offsets4:
776
+ let neighbor = location + offset
777
+ # passable check
778
+ if agent.cfg.isWalkable(agent.map, neighbor):
779
+ if neighbor notin visited:
780
+ visited.incl(neighbor)
781
+ queue.addLast(neighbor)
782
+ if locationFound:
783
+ agent.exploreLocations.add(unexploredLocation)
784
+ else:
785
+ log "no unseen location found"
786
+ # agent.cfg.drawMap(agent.map, visited)
787
+ measurePop()
788
+
789
+ measurePush("explore locations")
790
+ log "explore locations: " & $agent.exploreLocations
791
+ if agent.exploreLocations.len > 0:
792
+ for location in agent.exploreLocations:
793
+ if location notin agent.seen:
794
+ let action = agent.cfg.aStar(agent.location, location, agent.map)
795
+ if action.isSome():
796
+ doAction(action.get().int32)
797
+ log "going to explore location: " & $location
798
+ return
799
+ else:
800
+ agent.exploreLocations.remove(location)
801
+ break
802
+ else:
803
+ agent.exploreLocations.remove(location)
804
+ break
805
+ measurePop()
806
+
807
+ # If all else fails, take a random move to explore the map or get unstuck.
808
+ let action = agent.random.rand(1 .. 4).int32
809
+ log "taking random action " & $action
810
+ doAction(action.int32)
811
+
812
+ except:
813
+ echo getCurrentException().getStackTrace()
814
+ echo getCurrentExceptionMsg()
815
+ quit()
816
+
817
+ proc newRaceCarPolicy*(environmentConfig: string): RaceCarPolicy =
818
+ let cfg = parseConfig(environmentConfig)
819
+ var agents: seq[RaceCarAgent] = @[]
820
+ for id in 0 ..< cfg.config.numAgents:
821
+ agents.add(newRaceCarAgent(id, environmentConfig))
822
+ return RaceCarPolicy(agents: agents)
823
+
824
+ proc stepBatch*(
825
+ policy: RaceCarPolicy,
826
+ agentIds: pointer,
827
+ numAgentIds: int,
828
+ numAgents: int,
829
+ numTokens: int,
830
+ sizeToken: int,
831
+ rawObservations: pointer,
832
+ numActions: int,
833
+ rawActions: pointer
834
+ ) {.measure.} =
835
+ let ids = cast[ptr UncheckedArray[int32]](agentIds)
836
+ let obsArray = cast[ptr UncheckedArray[uint8]](rawObservations)
837
+ let actionArray = cast[ptr UncheckedArray[int32]](rawActions)
838
+ let obsStride = numTokens * sizeToken
839
+
840
+ for i in 0 ..< numAgentIds:
841
+ let idx = int(ids[i])
842
+ let obsPtr = cast[pointer](obsArray[idx * obsStride].addr)
843
+ let actPtr = cast[ptr int32](actionArray[idx].addr)
844
+ step(policy.agents[idx], numAgents, numTokens, sizeToken, obsPtr, numActions, actPtr)