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,1054 @@
1
+
2
+ import
3
+ std/[algorithm, strformat, strutils, tables, sets, options],
4
+ fidget2/measure,
5
+ jsony
6
+
7
+ type
8
+ ConfigFeature* = object
9
+ id*: int
10
+ name*: string
11
+ normalization*: float
12
+
13
+ HubProtocol* = object
14
+ inputResources*: Table[string, int]
15
+ outputResources*: Table[string, int]
16
+
17
+ PolicyConfig* = object
18
+ numAgents*: int
19
+ obsWidth*: int
20
+ obsHeight*: int
21
+ actions*: seq[string]
22
+ tags*: seq[string]
23
+ obsFeatures*: seq[ConfigFeature]
24
+ hubProtocols*: seq[HubProtocol]
25
+
26
+ Config* = object
27
+ config*: PolicyConfig
28
+ actions*: Actions
29
+ features*: Features
30
+ tags*: Tags
31
+ vibes*: Vibes
32
+ vibeNames*: seq[string]
33
+ hubProtocols*: seq[HubProtocol]
34
+ inventoryTokenBase*: int
35
+ inventoryPowerFeatures*: Table[int, array[2, int]]
36
+
37
+ FeatureValue* = object
38
+ featureId*: int
39
+ value*: int
40
+
41
+ Location* = object
42
+ x*: int
43
+ y*: int
44
+
45
+ MapBounds* = object
46
+ minX*: int
47
+ maxX*: int
48
+ minY*: int
49
+ maxY*: int
50
+
51
+ Actions* = object
52
+ noop*: int
53
+ moveNorth*: int
54
+ moveSouth*: int
55
+ moveWest*: int
56
+ moveEast*: int
57
+ vibeDefault*: int
58
+ vibeCharger*: int
59
+ vibeCarbonA*: int
60
+ vibeCarbonB*: int
61
+ vibeOxygenA*: int
62
+ vibeOxygenB*: int
63
+ vibeGermaniumA*: int
64
+ vibeGermaniumB*: int
65
+ vibeSiliconA*: int
66
+ vibeSiliconB*: int
67
+ vibeHeartA*: int
68
+ vibeHeartB*: int
69
+ vibeGear*: int
70
+ vibeHub*: int
71
+ vibeChest*: int
72
+ vibeWall*: int
73
+
74
+ Tags* = object
75
+ agent*: int
76
+ hub*: int
77
+ carbonExtractor*: int
78
+ junction*: int
79
+ chest*: int
80
+ germaniumExtractor*: int
81
+ oxygenExtractor*: int
82
+ siliconExtractor*: int
83
+ wall*: int
84
+
85
+ Vibes* = object
86
+ # TODO: Pass with vibes from config.
87
+ default*: int = 0
88
+ junction*: int = 1
89
+ carbonA*: int = 2
90
+ carbonB*: int = 3
91
+ oxygenA*: int = 4
92
+ oxygenB*: int = 5
93
+ germaniumA*: int = 6
94
+ germaniumB*: int = 7
95
+ siliconA*: int = 8
96
+ siliconB*: int = 9
97
+ heartA*: int = 10
98
+ heartB*: int = 11
99
+ gear*: int = 12
100
+ hub*: int = 13
101
+ chest*: int = 14
102
+ wall*: int = 15
103
+ paperclip*: int = 16
104
+
105
+ Features* = object
106
+ group*: int
107
+ frozen*: int
108
+ episodeCompletionPct*: int
109
+ goal*: int
110
+ lastAction*: int
111
+ lastReward*: int
112
+ vibe*: int
113
+ compass*: int
114
+ tag*: int
115
+ cooldownRemaining*: int
116
+ clipped*: int
117
+ remainingUses*: int
118
+ invEnergy*: int
119
+ invCarbon*: int
120
+ invOxygen*: int
121
+ invGermanium*: int
122
+ invSilicon*: int
123
+ invHeart*: int
124
+ invDecoder*: int
125
+ invModulator*: int
126
+ invResonator*: int
127
+ invScrambler*: int
128
+ invMiner*: int
129
+ invScout*: int
130
+ invAligner*: int
131
+ invInfluence*: int
132
+ invHp*: int
133
+
134
+ protocolInputEnergy*: int
135
+ protocolInputCarbon*: int
136
+ protocolInputOxygen*: int
137
+ protocolInputGermanium*: int
138
+ protocolInputSilicon*: int
139
+ protocolInputHeart*: int
140
+ protocolInputHp*: int
141
+ protocolInputDecoder*: int
142
+ protocolInputModulator*: int
143
+ protocolInputResonator*: int
144
+ protocolInputScrambler*: int
145
+ protocolInputMiner*: int
146
+ protocolInputScout*: int
147
+ protocolInputAligner*: int
148
+ protocolInputInfluence*: int
149
+
150
+ protocolOutputEnergy*: int
151
+ protocolOutputCarbon*: int
152
+ protocolOutputOxygen*: int
153
+ protocolOutputGermanium*: int
154
+ protocolOutputSilicon*: int
155
+ protocolOutputHeart*: int
156
+ protocolOutputHp*: int
157
+ protocolOutputDecoder*: int
158
+ protocolOutputModulator*: int
159
+ protocolOutputResonator*: int
160
+ protocolOutputScrambler*: int
161
+ protocolOutputMiner*: int
162
+ protocolOutputScout*: int
163
+ protocolOutputAligner*: int
164
+ protocolOutputInfluence*: int
165
+
166
+ RecipeInfo* = object
167
+ pattern*: seq[int] # In vibe indices
168
+
169
+ energyCost*: int
170
+ carbonCost*: int
171
+ oxygenCost*: int
172
+ germaniumCost*: int
173
+ siliconCost*: int
174
+ heartCost*: int
175
+ decoderCost*: int
176
+ modulatorCost*: int
177
+ resonatorCost*: int
178
+ scramblerCost*: int
179
+
180
+ energyOutput*: int
181
+ carbonOutput*: int
182
+ oxygenOutput*: int
183
+ germaniumOutput*: int
184
+ siliconOutput*: int
185
+ heartOutput*: int
186
+ decoderOutput*: int
187
+ modulatorOutput*: int
188
+ resonatorOutput*: int
189
+ scramblerOutput*: int
190
+ cooldown*: int
191
+
192
+ proc `+`*(location1: Location, location2: Location): Location =
193
+ ## Add two locations.
194
+ result.x = location1.x + location2.x
195
+ result.y = location1.y + location2.y
196
+
197
+ proc `-`*(location1: Location, location2: Location): Location =
198
+ ## Subtract two locations.
199
+ result.x = location1.x - location2.x
200
+ result.y = location1.y - location2.y
201
+
202
+ proc manhattan*(a, b: Location): int =
203
+ ## Get the Manhattan distance between two locations.
204
+ abs(a.x - b.x) + abs(a.y - b.y)
205
+
206
+ proc generateSpiral*(count: int): seq[Location] =
207
+ ## Generate a square spiral starting at (0,0) and spiraling outwards.
208
+ result = @[]
209
+ var
210
+ x = 0
211
+ y = 0
212
+ dx = 1
213
+ dy = 0
214
+ stepSize = 1
215
+ stepsTaken = 0
216
+ directionChanges = 0
217
+ for i in 0 ..< count:
218
+ result.add(Location(x: x, y: y))
219
+ x += dx
220
+ y += dy
221
+ inc stepsTaken
222
+ if stepsTaken == stepSize:
223
+ stepsTaken = 0
224
+ inc(directionChanges)
225
+ # Rotate direction: (dx, dy) -> (-dy, dx)
226
+ let tmp = dx
227
+ dx = -dy
228
+ dy = tmp
229
+ if directionChanges mod 2 == 0:
230
+ inc stepSize
231
+ return result
232
+
233
+ const spiral* = generateSpiral(1000)
234
+
235
+ proc `$`*(recipe: RecipeInfo): string =
236
+ ## Stringify the recipe.
237
+ result = "Recipe(pattern: ["
238
+ for vibe in recipe.pattern:
239
+ case vibe:
240
+ of 0:
241
+ result.add("Default")
242
+ result.add(", ")
243
+ of 1:
244
+ result.add("Charger")
245
+ result.add(", ")
246
+ of 2:
247
+ result.add("CarbonA")
248
+ result.add(", ")
249
+ of 3:
250
+ result.add("CarbonB")
251
+ result.add(", ")
252
+ of 4:
253
+ result.add("OxygenA")
254
+ result.add(", ")
255
+ of 5:
256
+ result.add("OxygenB")
257
+ result.add(", ")
258
+ of 6:
259
+ result.add("GermaniumA")
260
+ result.add(", ")
261
+ of 7:
262
+ result.add("GermaniumB")
263
+ result.add(", ")
264
+ of 8:
265
+ result.add("SiliconA")
266
+ result.add(", ")
267
+ of 9:
268
+ result.add("SiliconB")
269
+ result.add(", ")
270
+ of 10:
271
+ result.add("HeartA")
272
+ result.add(", ")
273
+ of 11:
274
+ result.add("HeartB")
275
+ result.add(", ")
276
+ of 12:
277
+ result.add("Gear")
278
+ result.add(", ")
279
+ of 13:
280
+ result.add("Hub")
281
+ result.add(", ")
282
+ of 14:
283
+ result.add("Chest")
284
+ result.add(", ")
285
+ of 15:
286
+ result.add("Wall")
287
+ result.add(", ")
288
+ of 16:
289
+ result.add("Paperclip")
290
+ result.add(", ")
291
+ else:
292
+ result.add("???")
293
+ result.add(", ")
294
+ result.removeSuffix(", ")
295
+ result.add("]")
296
+ if recipe.energyCost != 0:
297
+ result.add(" E:")
298
+ result.add($recipe.energyCost)
299
+ if recipe.carbonCost != 0:
300
+ result.add(" C:")
301
+ result.add($recipe.carbonCost)
302
+ if recipe.oxygenCost != 0:
303
+ result.add(" O2:")
304
+ result.add($recipe.oxygenCost)
305
+ if recipe.germaniumCost != 0:
306
+ result.add(" Ge:")
307
+ result.add($recipe.germaniumCost)
308
+ if recipe.siliconCost != 0:
309
+ result.add(" Si:")
310
+ result.add($recipe.siliconCost)
311
+ if recipe.heartCost != 0:
312
+ result.add(" Heart:")
313
+ result.add($recipe.heartCost)
314
+ if recipe.decoderCost != 0:
315
+ result.add(" Decoder:")
316
+ result.add($recipe.decoderCost)
317
+ if recipe.modulatorCost != 0:
318
+ result.add(" Modulator:")
319
+ result.add($recipe.modulatorCost)
320
+ if recipe.resonatorCost != 0:
321
+ result.add(" Resonator:")
322
+ result.add($recipe.resonatorCost)
323
+ if recipe.scramblerCost != 0:
324
+ result.add(" Scrambler:")
325
+ result.add($recipe.scramblerCost)
326
+ result.add(" -> ")
327
+ if recipe.energyOutput != 0:
328
+ result.add(" E:")
329
+ result.add($recipe.energyOutput)
330
+ if recipe.carbonOutput != 0:
331
+ result.add(" C:")
332
+ result.add($recipe.carbonOutput)
333
+ if recipe.oxygenOutput != 0:
334
+ result.add(" O2:")
335
+ result.add($recipe.oxygenOutput)
336
+ if recipe.germaniumOutput != 0:
337
+ result.add(" Ge:")
338
+ result.add($recipe.germaniumOutput)
339
+ if recipe.siliconOutput != 0:
340
+ result.add(" Si:")
341
+ result.add($recipe.siliconOutput)
342
+ if recipe.heartOutput != 0:
343
+ result.add(" Heart:")
344
+ result.add($recipe.heartOutput)
345
+ if recipe.decoderOutput != 0:
346
+ result.add(" Decoder:")
347
+ result.add($recipe.decoderOutput)
348
+ if recipe.modulatorOutput != 0:
349
+ result.add(" Modulator:")
350
+ result.add($recipe.modulatorOutput)
351
+ if recipe.resonatorOutput != 0:
352
+ result.add(" Resonator:")
353
+ result.add($recipe.resonatorOutput)
354
+ if recipe.scramblerOutput != 0:
355
+ result.add(" Scrambler:")
356
+ result.add($recipe.scramblerOutput)
357
+ result.add(")")
358
+
359
+ proc registerProtocolFeature(feature: ConfigFeature; prefix: string;
360
+ dest: var Table[string, int]): bool =
361
+ ## Store protocol input/output features keyed by their resource suffix.
362
+ if not feature.name.startsWith(prefix):
363
+ return false
364
+ if feature.name.len <= prefix.len:
365
+ echo "Protocol feature missing resource suffix: ", feature.name
366
+ return true
367
+
368
+ let resource = feature.name[prefix.len .. ^1]
369
+ dest[resource] = feature.id
370
+ return true
371
+
372
+ proc updateRecipeFromProtocol*(cfg: Config; feature: FeatureValue; recipe: var RecipeInfo) =
373
+ let fid = feature.featureId
374
+ let value = feature.value
375
+ if cfg.features.protocolInputEnergy != 0 and fid == cfg.features.protocolInputEnergy:
376
+ recipe.energyCost = value
377
+ elif cfg.features.protocolInputCarbon != 0 and fid == cfg.features.protocolInputCarbon:
378
+ recipe.carbonCost = value
379
+ elif cfg.features.protocolInputOxygen != 0 and fid == cfg.features.protocolInputOxygen:
380
+ recipe.oxygenCost = value
381
+ elif cfg.features.protocolInputGermanium != 0 and fid == cfg.features.protocolInputGermanium:
382
+ recipe.germaniumCost = value
383
+ elif cfg.features.protocolInputSilicon != 0 and fid == cfg.features.protocolInputSilicon:
384
+ recipe.siliconCost = value
385
+ elif cfg.features.protocolInputHeart != 0 and fid == cfg.features.protocolInputHeart:
386
+ recipe.heartCost = value
387
+ elif cfg.features.protocolInputDecoder != 0 and fid == cfg.features.protocolInputDecoder:
388
+ recipe.decoderCost = value
389
+ elif cfg.features.protocolInputModulator != 0 and fid == cfg.features.protocolInputModulator:
390
+ recipe.modulatorCost = value
391
+ elif cfg.features.protocolInputResonator != 0 and fid == cfg.features.protocolInputResonator:
392
+ recipe.resonatorCost = value
393
+ elif cfg.features.protocolInputScrambler != 0 and fid == cfg.features.protocolInputScrambler:
394
+ recipe.scramblerCost = value
395
+ elif cfg.features.protocolOutputEnergy != 0 and fid == cfg.features.protocolOutputEnergy:
396
+ recipe.energyOutput = value
397
+ elif cfg.features.protocolOutputCarbon != 0 and fid == cfg.features.protocolOutputCarbon:
398
+ recipe.carbonOutput = value
399
+ elif cfg.features.protocolOutputOxygen != 0 and fid == cfg.features.protocolOutputOxygen:
400
+ recipe.oxygenOutput = value
401
+ elif cfg.features.protocolOutputGermanium != 0 and fid == cfg.features.protocolOutputGermanium:
402
+ recipe.germaniumOutput = value
403
+ elif cfg.features.protocolOutputSilicon != 0 and fid == cfg.features.protocolOutputSilicon:
404
+ recipe.siliconOutput = value
405
+ elif cfg.features.protocolOutputHeart != 0 and fid == cfg.features.protocolOutputHeart:
406
+ recipe.heartOutput = value
407
+ elif cfg.features.protocolOutputDecoder != 0 and fid == cfg.features.protocolOutputDecoder:
408
+ recipe.decoderOutput = value
409
+ elif cfg.features.protocolOutputModulator != 0 and fid == cfg.features.protocolOutputModulator:
410
+ recipe.modulatorOutput = value
411
+ elif cfg.features.protocolOutputResonator != 0 and fid == cfg.features.protocolOutputResonator:
412
+ recipe.resonatorOutput = value
413
+ elif cfg.features.protocolOutputScrambler != 0 and fid == cfg.features.protocolOutputScrambler:
414
+ recipe.scramblerOutput = value
415
+
416
+ proc assignProtocolTables*(
417
+ cfg: Config,
418
+ feature: FeatureValue,
419
+ inputs: var Table[string, int],
420
+ outputs: var Table[string, int]
421
+ ) =
422
+ let fid = feature.featureId
423
+ let value = feature.value
424
+ if cfg.features.protocolInputEnergy != 0 and fid == cfg.features.protocolInputEnergy:
425
+ inputs["energy"] = value
426
+ elif cfg.features.protocolInputCarbon != 0 and fid == cfg.features.protocolInputCarbon:
427
+ inputs["carbon"] = value
428
+ elif cfg.features.protocolInputOxygen != 0 and fid == cfg.features.protocolInputOxygen:
429
+ inputs["oxygen"] = value
430
+ elif cfg.features.protocolInputGermanium != 0 and fid == cfg.features.protocolInputGermanium:
431
+ inputs["germanium"] = value
432
+ elif cfg.features.protocolInputSilicon != 0 and fid == cfg.features.protocolInputSilicon:
433
+ inputs["silicon"] = value
434
+ elif cfg.features.protocolInputHeart != 0 and fid == cfg.features.protocolInputHeart:
435
+ inputs["heart"] = value
436
+ elif cfg.features.protocolInputHp != 0 and fid == cfg.features.protocolInputHp:
437
+ inputs["hp"] = value
438
+ elif cfg.features.protocolInputDecoder != 0 and fid == cfg.features.protocolInputDecoder:
439
+ inputs["decoder"] = value
440
+ elif cfg.features.protocolInputModulator != 0 and fid == cfg.features.protocolInputModulator:
441
+ inputs["modulator"] = value
442
+ elif cfg.features.protocolInputResonator != 0 and fid == cfg.features.protocolInputResonator:
443
+ inputs["resonator"] = value
444
+ elif cfg.features.protocolInputScrambler != 0 and fid == cfg.features.protocolInputScrambler:
445
+ inputs["scrambler"] = value
446
+ elif cfg.features.protocolInputMiner != 0 and fid == cfg.features.protocolInputMiner:
447
+ inputs["miner"] = value
448
+ elif cfg.features.protocolInputScout != 0 and fid == cfg.features.protocolInputScout:
449
+ inputs["scout"] = value
450
+ elif cfg.features.protocolInputAligner != 0 and fid == cfg.features.protocolInputAligner:
451
+ inputs["aligner"] = value
452
+ elif cfg.features.protocolInputInfluence != 0 and fid == cfg.features.protocolInputInfluence:
453
+ inputs["influence"] = value
454
+ elif cfg.features.protocolOutputEnergy != 0 and fid == cfg.features.protocolOutputEnergy:
455
+ outputs["energy"] = value
456
+ elif cfg.features.protocolOutputCarbon != 0 and fid == cfg.features.protocolOutputCarbon:
457
+ outputs["carbon"] = value
458
+ elif cfg.features.protocolOutputOxygen != 0 and fid == cfg.features.protocolOutputOxygen:
459
+ outputs["oxygen"] = value
460
+ elif cfg.features.protocolOutputGermanium != 0 and fid == cfg.features.protocolOutputGermanium:
461
+ outputs["germanium"] = value
462
+ elif cfg.features.protocolOutputSilicon != 0 and fid == cfg.features.protocolOutputSilicon:
463
+ outputs["silicon"] = value
464
+ elif cfg.features.protocolOutputHeart != 0 and fid == cfg.features.protocolOutputHeart:
465
+ outputs["heart"] = value
466
+ elif cfg.features.protocolOutputHp != 0 and fid == cfg.features.protocolOutputHp:
467
+ outputs["hp"] = value
468
+ elif cfg.features.protocolOutputDecoder != 0 and fid == cfg.features.protocolOutputDecoder:
469
+ outputs["decoder"] = value
470
+ elif cfg.features.protocolOutputModulator != 0 and fid == cfg.features.protocolOutputModulator:
471
+ outputs["modulator"] = value
472
+ elif cfg.features.protocolOutputResonator != 0 and fid == cfg.features.protocolOutputResonator:
473
+ outputs["resonator"] = value
474
+ elif cfg.features.protocolOutputScrambler != 0 and fid == cfg.features.protocolOutputScrambler:
475
+ outputs["scrambler"] = value
476
+ elif cfg.features.protocolOutputMiner != 0 and fid == cfg.features.protocolOutputMiner:
477
+ outputs["miner"] = value
478
+ elif cfg.features.protocolOutputScout != 0 and fid == cfg.features.protocolOutputScout:
479
+ outputs["scout"] = value
480
+ elif cfg.features.protocolOutputAligner != 0 and fid == cfg.features.protocolOutputAligner:
481
+ outputs["aligner"] = value
482
+ elif cfg.features.protocolOutputInfluence != 0 and fid == cfg.features.protocolOutputInfluence:
483
+ outputs["influence"] = value
484
+
485
+ proc ctrlCHandler*() {.noconv.} =
486
+ ## Handle ctrl-c signal to exit cleanly.
487
+ echo "\nNim DLL caught ctrl-c, exiting..."
488
+ quit(0)
489
+
490
+ proc initCHook*() =
491
+ setControlCHook(ctrlCHandler)
492
+ echo "NimAgents initialized"
493
+
494
+ proc parseConfig*(environmentConfig: string): Config {.raises: [].} =
495
+ try:
496
+ var config = environmentConfig.fromJson(PolicyConfig)
497
+ result = Config(config: config)
498
+ result.hubProtocols = config.hubProtocols
499
+ result.inventoryPowerFeatures = initTable[int, array[2, int]]()
500
+ var inventoryBaseIds = initTable[string, int]()
501
+ var inventoryPowerIds = initTable[string, array[2, int]]()
502
+
503
+ for feature in config.obsFeatures:
504
+ if feature.name.startsWith("inv:"):
505
+ if result.inventoryTokenBase == 0:
506
+ result.inventoryTokenBase = int(feature.normalization)
507
+ let suffix = feature.name[4 .. ^1]
508
+ let powerIndex = suffix.rfind(":p")
509
+ if powerIndex != -1:
510
+ let resource = suffix[0 ..< powerIndex]
511
+ let powerStr = suffix[powerIndex + 2 .. ^1]
512
+ if resource.len > 0 and powerStr.len > 0 and powerStr.allCharsInSet({'0' .. '9'}):
513
+ let power = parseInt(powerStr)
514
+ if power > 0:
515
+ var powers = inventoryPowerIds.getOrDefault(resource, [-1, -1])
516
+ if power <= 2:
517
+ powers[power - 1] = feature.id
518
+ inventoryPowerIds[resource] = powers
519
+ continue
520
+ else:
521
+ inventoryBaseIds[suffix] = feature.id
522
+ case feature.name:
523
+ of "agent:group":
524
+ result.features.group = feature.id
525
+ of "agent:frozen":
526
+ result.features.frozen = feature.id
527
+ of "episode_completion_pct":
528
+ result.features.episodeCompletionPct = feature.id
529
+ of "goal":
530
+ result.features.goal = feature.id
531
+ of "last_action":
532
+ result.features.lastAction = feature.id
533
+ of "last_reward":
534
+ result.features.lastReward = feature.id
535
+ of "vibe":
536
+ result.features.vibe = feature.id
537
+ of "agent:compass":
538
+ result.features.compass = feature.id
539
+ of "tag":
540
+ result.features.tag = feature.id
541
+ of "cooldown_remaining":
542
+ result.features.cooldownRemaining = feature.id
543
+ of "clipped":
544
+ result.features.clipped = feature.id
545
+ of "remaining_uses":
546
+ result.features.remainingUses = feature.id
547
+ of "inv:energy":
548
+ result.features.invEnergy = feature.id
549
+ of "inv:carbon":
550
+ result.features.invCarbon = feature.id
551
+ of "inv:oxygen":
552
+ result.features.invOxygen = feature.id
553
+ of "inv:germanium":
554
+ result.features.invGermanium = feature.id
555
+ of "inv:silicon":
556
+ result.features.invSilicon = feature.id
557
+ of "inv:heart":
558
+ result.features.invHeart = feature.id
559
+ of "inv:decoder":
560
+ result.features.invDecoder = feature.id
561
+ of "inv:modulator":
562
+ result.features.invModulator = feature.id
563
+ of "inv:resonator":
564
+ result.features.invResonator = feature.id
565
+ of "inv:scrambler":
566
+ result.features.invScrambler = feature.id
567
+ of "inv:miner":
568
+ result.features.invMiner = feature.id
569
+ of "inv:scout":
570
+ result.features.invScout = feature.id
571
+ of "inv:aligner":
572
+ result.features.invAligner = feature.id
573
+ of "inv:influence":
574
+ result.features.invInfluence = feature.id
575
+ of "inv:hp":
576
+ result.features.invHp = feature.id
577
+ of "protocol_input:energy":
578
+ result.features.protocolInputEnergy = feature.id
579
+ of "protocol_input:carbon":
580
+ result.features.protocolInputCarbon = feature.id
581
+ of "protocol_input:oxygen":
582
+ result.features.protocolInputOxygen = feature.id
583
+ of "protocol_input:germanium":
584
+ result.features.protocolInputGermanium = feature.id
585
+ of "protocol_input:silicon":
586
+ result.features.protocolInputSilicon = feature.id
587
+ of "protocol_input:heart":
588
+ result.features.protocolInputHeart = feature.id
589
+ of "protocol_input:hp":
590
+ result.features.protocolInputHp = feature.id
591
+ of "protocol_input:decoder":
592
+ result.features.protocolInputDecoder = feature.id
593
+ of "protocol_input:modulator":
594
+ result.features.protocolInputModulator = feature.id
595
+ of "protocol_input:resonator":
596
+ result.features.protocolInputResonator = feature.id
597
+ of "protocol_input:scrambler":
598
+ result.features.protocolInputScrambler = feature.id
599
+ of "protocol_input:miner":
600
+ result.features.protocolInputMiner = feature.id
601
+ of "protocol_input:scout":
602
+ result.features.protocolInputScout = feature.id
603
+ of "protocol_input:aligner":
604
+ result.features.protocolInputAligner = feature.id
605
+ of "protocol_input:influence":
606
+ result.features.protocolInputInfluence = feature.id
607
+ of "protocol_output:energy":
608
+ result.features.protocolOutputEnergy = feature.id
609
+ of "protocol_output:carbon":
610
+ result.features.protocolOutputCarbon = feature.id
611
+ of "protocol_output:oxygen":
612
+ result.features.protocolOutputOxygen = feature.id
613
+ of "protocol_output:germanium":
614
+ result.features.protocolOutputGermanium = feature.id
615
+ of "protocol_output:silicon":
616
+ result.features.protocolOutputSilicon = feature.id
617
+ of "protocol_output:heart":
618
+ result.features.protocolOutputHeart = feature.id
619
+ of "protocol_output:hp":
620
+ result.features.protocolOutputHp = feature.id
621
+ of "protocol_output:decoder":
622
+ result.features.protocolOutputDecoder = feature.id
623
+ of "protocol_output:modulator":
624
+ result.features.protocolOutputModulator = feature.id
625
+ of "protocol_output:resonator":
626
+ result.features.protocolOutputResonator = feature.id
627
+ of "protocol_output:scrambler":
628
+ result.features.protocolOutputScrambler = feature.id
629
+ of "protocol_output:miner":
630
+ result.features.protocolOutputMiner = feature.id
631
+ of "protocol_output:scout":
632
+ result.features.protocolOutputScout = feature.id
633
+ of "protocol_output:aligner":
634
+ result.features.protocolOutputAligner = feature.id
635
+ of "protocol_output:influence":
636
+ result.features.protocolOutputInfluence = feature.id
637
+ else:
638
+ echo "Unknown feature: ", feature.name
639
+
640
+ for resource, powers in inventoryPowerIds:
641
+ if resource in inventoryBaseIds:
642
+ result.inventoryPowerFeatures[inventoryBaseIds[resource]] = powers
643
+
644
+ for id, name in config.actions:
645
+ if name.startsWith("change_vibe_"):
646
+ result.vibeNames.add(name[12 .. ^1])
647
+ case name:
648
+ of "noop":
649
+ result.actions.noop = id
650
+ of "move_north":
651
+ result.actions.moveNorth = id
652
+ of "move_south":
653
+ result.actions.moveSouth = id
654
+ of "move_west":
655
+ result.actions.moveWest = id
656
+ of "move_east":
657
+ result.actions.moveEast = id
658
+ of "change_vibe_default":
659
+ result.actions.vibeDefault = id
660
+ of "change_vibe_junction":
661
+ result.actions.vibeCharger = id
662
+ of "change_vibe_carbon_a":
663
+ result.actions.vibeCarbonA = id
664
+ of "change_vibe_carbon_b":
665
+ result.actions.vibeCarbonB = id
666
+ of "change_vibe_oxygen_a":
667
+ result.actions.vibeOxygenA = id
668
+ of "change_vibe_oxygen_b":
669
+ result.actions.vibeOxygenB = id
670
+ of "change_vibe_germanium_a":
671
+ result.actions.vibeGermaniumA = id
672
+ of "change_vibe_germanium_b":
673
+ result.actions.vibeGermaniumB = id
674
+ of "change_vibe_silicon_a":
675
+ result.actions.vibeSiliconA = id
676
+ of "change_vibe_silicon_b":
677
+ result.actions.vibeSiliconB = id
678
+ of "change_vibe_heart_a":
679
+ result.actions.vibeHeartA = id
680
+ of "change_vibe_heart_b":
681
+ result.actions.vibeHeartB = id
682
+ of "change_vibe_carbon":
683
+ result.actions.vibeCarbonA = id
684
+ of "change_vibe_oxygen":
685
+ result.actions.vibeOxygenA = id
686
+ of "change_vibe_germanium":
687
+ result.actions.vibeGermaniumA = id
688
+ of "change_vibe_silicon":
689
+ result.actions.vibeSiliconA = id
690
+ of "change_vibe_heart":
691
+ result.actions.vibeHeartA = id
692
+ of "change_vibe_gear":
693
+ result.actions.vibeGear = id
694
+ of "change_vibe_hub":
695
+ result.actions.vibeHub = id
696
+ of "change_vibe_chest":
697
+ result.actions.vibeChest = id
698
+ of "change_vibe_wall":
699
+ result.actions.vibeWall = id
700
+ else:
701
+ discard
702
+
703
+ for id, name in config.tags:
704
+ case name:
705
+ of "agent":
706
+ result.tags.agent = id
707
+ of "hub":
708
+ result.tags.hub = id
709
+ of "carbon_extractor":
710
+ result.tags.carbonExtractor = id
711
+ of "junction":
712
+ result.tags.junction = id
713
+ of "chest":
714
+ result.tags.chest = id
715
+ of "germanium_extractor":
716
+ result.tags.germaniumExtractor = id
717
+ of "oxygen_extractor":
718
+ result.tags.oxygenExtractor = id
719
+ of "silicon_extractor":
720
+ result.tags.siliconExtractor = id
721
+ of "wall":
722
+ result.tags.wall = id
723
+ else:
724
+ discard
725
+ except JsonError, ValueError:
726
+ echo "Error parsing environment config: ", getCurrentExceptionMsg()
727
+
728
+
729
+ proc computeMapBounds*(map: Table[Location, seq[FeatureValue]]): MapBounds =
730
+ ## Compute the bounds of the map.
731
+ result.minX = -5
732
+ result.maxX = 5
733
+ result.minY = -5
734
+ result.maxY = 5
735
+ for location, featureValues in map:
736
+ if location.x < result.minX:
737
+ result.minX = location.x
738
+ if location.x > result.maxX:
739
+ result.maxX = location.x
740
+ if location.y < result.minY:
741
+ result.minY = location.y
742
+ if location.y > result.maxY:
743
+ result.maxY = location.y
744
+
745
+ proc drawMap*(cfg: Config, map: Table[Location, seq[FeatureValue]], seen: HashSet[Location]) =
746
+ ## Draw the map to the console.
747
+ let bounds = computeMapBounds(map)
748
+ var line = "+"
749
+ for x in bounds.minX .. bounds.maxX:
750
+ line.add "--"
751
+ line.add "+"
752
+ echo line
753
+ for y in bounds.minY .. bounds.maxY:
754
+ line = "|"
755
+ for x in bounds.minX .. bounds.maxX:
756
+ var cell = " "
757
+ let location = Location(x: x, y: y)
758
+ if location notin seen:
759
+ cell = "~~"
760
+ if location in map:
761
+ for featureValue in map[location]:
762
+ if featureValue.featureId == cfg.features.group:
763
+ if featureValue.value == 0:
764
+ cell = "@" & ($featureValue.value)[0]
765
+ if featureValue.featureId == cfg.features.tag:
766
+ if featureValue.value == cfg.tags.agent:
767
+ cell = "@@"
768
+ elif featureValue.value == cfg.tags.hub:
769
+ cell = "As"
770
+ elif featureValue.value == cfg.tags.carbonExtractor:
771
+ cell = "Ca"
772
+ elif featureValue.value == cfg.tags.junction:
773
+ cell = "En"
774
+ elif featureValue.value == cfg.tags.chest:
775
+ cell = "Ch"
776
+ elif featureValue.value == cfg.tags.germaniumExtractor:
777
+ cell = "Ge"
778
+ elif featureValue.value == cfg.tags.oxygenExtractor:
779
+ cell = "O2"
780
+ elif featureValue.value == cfg.tags.siliconExtractor:
781
+ cell = "Si"
782
+ elif featureValue.value == cfg.tags.wall:
783
+ cell = "##"
784
+ else:
785
+ cell = &"{featureValue.value:2d}"
786
+ line.add cell
787
+ line.add "|"
788
+ echo line
789
+ line = "+"
790
+ for x in bounds.minX .. bounds.maxX:
791
+ line.add "--"
792
+ line.add "+"
793
+ echo line
794
+
795
+ proc getTag*(cfg: Config, map: Table[Location, seq[FeatureValue]], location: Location): int =
796
+ ## Get the type id of the location in the map.
797
+ if location in map:
798
+ for featureValue in map[location]:
799
+ if featureValue.featureId == cfg.features.tag:
800
+ return featureValue.value
801
+ return -1
802
+
803
+ proc getFeature*(
804
+ cfg: Config,
805
+ visible: Table[Location,
806
+ seq[FeatureValue]], featureId: int,
807
+ location: Location = Location(x: 0, y: 0)
808
+ ): int =
809
+ ## Get the feature of the visible map.
810
+ if location in visible:
811
+ for featureValue in visible[location]:
812
+ if featureValue.featureId == featureId:
813
+ return featureValue.value
814
+ return -1
815
+
816
+ proc getLastAction*(cfg: Config, visible: Table[Location, seq[FeatureValue]]): int =
817
+ ## Get the last action of the visible map.
818
+ cfg.getFeature(visible, cfg.features.lastAction)
819
+
820
+ proc getEpisodeCompletionPct*(cfg: Config, visible: Table[Location, seq[FeatureValue]]): int =
821
+ ## Get episode completion percent, or -1 when the feature is unavailable.
822
+ if cfg.features.episodeCompletionPct == 0:
823
+ return -1
824
+ cfg.getFeature(visible, cfg.features.episodeCompletionPct)
825
+
826
+ proc getInventory*(
827
+ cfg: Config,
828
+ visible: Table[Location, seq[FeatureValue]],
829
+ inventoryId: int,
830
+ location: Location = Location(x: 0, y: 0)
831
+ ): int =
832
+ ## Get the inventory of the visible map.
833
+ result = cfg.getFeature(visible, inventoryId, location)
834
+ # Missing inventory is 0.
835
+ if result == -1:
836
+ result = 0
837
+ if cfg.inventoryTokenBase > 1 and inventoryId in cfg.inventoryPowerFeatures:
838
+ let powers = cfg.inventoryPowerFeatures[inventoryId]
839
+ if powers[0] > -1:
840
+ let powerValue = cfg.getFeature(visible, powers[0], location)
841
+ if powerValue > -1:
842
+ result += powerValue * cfg.inventoryTokenBase
843
+ if powers[1] > -1:
844
+ let powerValue = cfg.getFeature(visible, powers[1], location)
845
+ if powerValue > -1:
846
+ result += powerValue * cfg.inventoryTokenBase * cfg.inventoryTokenBase
847
+
848
+ proc getOtherInventory*(
849
+ cfg: Config,
850
+ map: Table[Location, seq[FeatureValue]],
851
+ location: Location,
852
+ inventoryId: int
853
+ ): int =
854
+ ## Get the other inventory of the visible map.
855
+ cfg.getInventory(map, inventoryId, location)
856
+
857
+ proc getVibe*(cfg: Config, visible: Table[Location, seq[FeatureValue]], location: Location): int =
858
+ ## Get the vibe of the visible map.
859
+ result = cfg.getFeature(visible, cfg.features.vibe, location)
860
+
861
+ proc getNearby*(
862
+ cfg: Config,
863
+ currentLocation: Location,
864
+ map: Table[Location, seq[FeatureValue]],
865
+ tagId: int
866
+ ): Option[Location] =
867
+ ## Get if there is a nearby location with the given tag.
868
+ var
869
+ found = false
870
+ closestLocation = Location(x: 0, y: 0)
871
+ closestDistance = 9999
872
+ for location, featureValues in map:
873
+ for featureValue in featureValues:
874
+ if featureValue.featureId == cfg.features.tag and featureValue.value == tagId:
875
+ let distance = manhattan(location, currentLocation)
876
+ if distance < closestDistance:
877
+ closestDistance = distance
878
+ closestLocation = location
879
+ found = true
880
+ if found:
881
+ return some(closestLocation)
882
+ return none(Location)
883
+
884
+ proc getNearbyUnseen*(
885
+ cfg: Config,
886
+ currentLocation: Location,
887
+ map: Table[Location, seq[FeatureValue]],
888
+ seen: HashSet[Location],
889
+ unreachables: HashSet[Location]
890
+ ): Option[Location] =
891
+ ## Get if there is a nearby location that is unseen.
892
+ var
893
+ found = false
894
+ closestLocation = Location(x: 0, y: 0)
895
+ closestDistance = 9999
896
+ for spiralLocation in spiral:
897
+ let location = spiralLocation + currentLocation
898
+ if location notin seen and location notin unreachables:
899
+ let distance = manhattan(location, currentLocation)
900
+ if distance < closestDistance:
901
+ closestDistance = distance
902
+ closestLocation = location
903
+ found = true
904
+ if found:
905
+ return some(closestLocation)
906
+ else:
907
+ return none(Location)
908
+
909
+ proc simpleGoTo*(cfg: Config, currentLocation: Location, targetLocation: Location): int =
910
+ ## Navigate to the given location.
911
+ echo "currentLocation: ", currentLocation.x, ", ", currentLocation.y
912
+ echo "targetLocation: ", targetLocation.x, ", ", targetLocation.y
913
+ if currentLocation.x < targetLocation.x:
914
+ echo "moving east"
915
+ return cfg.actions.moveEast
916
+ elif currentLocation.x > targetLocation.x:
917
+ echo "moving west"
918
+ return cfg.actions.moveWest
919
+ elif currentLocation.y < targetLocation.y:
920
+ echo "moving south"
921
+ return cfg.actions.moveSouth
922
+ elif currentLocation.y > targetLocation.y:
923
+ echo "moving north"
924
+ return cfg.actions.moveNorth
925
+ else:
926
+ echo "no action"
927
+ return cfg.actions.noop
928
+
929
+ proc isWalkable*(cfg: Config, map: Table[Location, seq[FeatureValue]], loc: Location): bool =
930
+ # Default: tiles not present are walkable; present tiles are walkable unless you decide otherwise.
931
+ if loc in map:
932
+ for featureValue in map[loc]:
933
+ if featureValue.featureId == cfg.features.tag:
934
+ # Its something that blocks movement.
935
+ return false
936
+ if featureValue.featureId == cfg.features.group:
937
+ # If the group there, then its an agent.
938
+ return false
939
+ return true
940
+
941
+ proc neighbors(loc: Location): array[4, Location] =
942
+ [
943
+ Location(x: loc.x + 1, y: loc.y), # East
944
+ Location(x: loc.x - 1, y: loc.y), # West
945
+ Location(x: loc.x, y: loc.y - 1), # North (assuming y-1 is north)
946
+ Location(x: loc.x, y: loc.y + 1) # South
947
+ ]
948
+
949
+ proc reconstructPath(cameFrom: Table[Location, Location], current: Location): seq[Location] =
950
+ var cur = current
951
+ result = @[cur]
952
+ var cf = cameFrom
953
+ while cf.hasKey(cur):
954
+ cur = cf[cur]
955
+ result.add(cur)
956
+ result.reverse()
957
+
958
+ proc stepToAction(cfg: Config, fromLoc, toLoc: Location): int =
959
+ # Translate the first step along the path into an action id.
960
+ if toLoc.x == fromLoc.x + 1 and toLoc.y == fromLoc.y:
961
+ return cfg.actions.moveEast
962
+ elif toLoc.x == fromLoc.x - 1 and toLoc.y == fromLoc.y:
963
+ return cfg.actions.moveWest
964
+ elif toLoc.y == fromLoc.y - 1 and toLoc.x == fromLoc.x:
965
+ return cfg.actions.moveNorth
966
+ elif toLoc.y == fromLoc.y + 1 and toLoc.x == fromLoc.x:
967
+ return cfg.actions.moveSouth
968
+ else:
969
+ # Not an adjacent cardinal move; noop as a safeguard.
970
+ return cfg.actions.noop
971
+
972
+ proc aStar*(
973
+ cfg: Config,
974
+ currentLocation: Location,
975
+ targetLocation: Location,
976
+ map: Table[Location, seq[FeatureValue]]
977
+ ): Option[int] {.measure.} =
978
+ ## Navigate to the given location using A*. Returns the next action to take.
979
+ if currentLocation == targetLocation:
980
+ return none(int)
981
+
982
+ # Open set: nodes to evaluate
983
+ var openSet = initHashSet[Location]()
984
+ openSet.incl(currentLocation)
985
+
986
+ # For path reconstruction
987
+ var cameFrom = initTable[Location, Location]()
988
+
989
+ # gScore: cost from start
990
+ var gScore = initTable[Location, int]()
991
+ gScore[currentLocation] = 0
992
+
993
+ # fScore: g + heuristic
994
+ var fScore = initTable[Location, int]()
995
+ fScore[currentLocation] = manhattan(currentLocation, targetLocation)
996
+
997
+ # Utility to get fScore with default "infinite"
998
+ proc getF(loc: Location): int =
999
+ if fScore.hasKey(loc): fScore[loc] else: high(int)
1000
+
1001
+ while openSet.len > 0:
1002
+
1003
+ if openSet.len > 100:
1004
+ # Too far... bail out.
1005
+ return none(int)
1006
+
1007
+ # Pick node in openSet with lowest fScore
1008
+ var currentIter = false
1009
+ var current: Location
1010
+ var bestF = high(int)
1011
+ for n in openSet:
1012
+ let f = getF(n)
1013
+ if not currentIter or f < bestF:
1014
+ bestF = f
1015
+ current = n
1016
+ currentIter = true
1017
+
1018
+ # (Optional sanity guard)
1019
+ if not currentIter:
1020
+ # openSet was somehow empty; break out safely
1021
+ return none(int)
1022
+ if current == targetLocation:
1023
+ let path = reconstructPath(cameFrom, current)
1024
+ # path[0] is currentLocation; path[1] is our next step (if exists)
1025
+ if path.len >= 2:
1026
+ return some(stepToAction(cfg, path[0], path[1]))
1027
+ else:
1028
+ return none(int)
1029
+
1030
+ openSet.excl(current)
1031
+
1032
+ # Explore neighbors
1033
+ for nb in neighbors(current).items:
1034
+ # Allow stepping onto the goal even if it's "blocked" (e.g., extractor tile).
1035
+ if nb != targetLocation and not cfg.isWalkable(map, nb):
1036
+ continue
1037
+
1038
+ let tentativeG = (if gScore.hasKey(current): gScore[current] else: high(int)) + 1
1039
+ # If nb has no gScore or this path is better, record it
1040
+ let nbG = (if gScore.hasKey(nb): gScore[nb] else: high(int))
1041
+ if tentativeG < nbG:
1042
+ cameFrom[nb] = current
1043
+ gScore[nb] = tentativeG
1044
+ fScore[nb] = tentativeG + manhattan(nb, targetLocation)
1045
+ if nb notin openSet:
1046
+ openSet.incl(nb)
1047
+
1048
+ # No path found — fall back to greedy single-step
1049
+ return none(int)
1050
+
1051
+ proc remove*[T](seq: var seq[T], item: T) =
1052
+ let index = seq.find(item)
1053
+ if index != -1:
1054
+ seq.delete(index)