byte-vortex commited on
Commit
3aba62e
Β·
verified Β·
1 Parent(s): b0fc007

Deploy Myco from CI

Browse files
Files changed (1) hide show
  1. game/agent_controller.py +80 -33
game/agent_controller.py CHANGED
@@ -1,4 +1,8 @@
1
- # controller.py β€” full replacement
 
 
 
 
2
 
3
  from . import engine
4
  import json
@@ -7,71 +11,114 @@ import json
7
  class MycoController:
8
  def __init__(self):
9
  self.position = [1, 1]
10
- self.history = []
11
 
12
- def get_agent_decision(self, current_mushroom, collection):
13
- # Build a real context so _llm has score/health/mystery data.
14
- # This also prevents the system prompt from having blank lines.
 
 
15
  ctx = engine._ctx(current_mushroom, list(collection or []))
16
 
 
 
 
 
 
 
 
17
  prompt = (
18
- f"Current position: {self.position}. "
19
- f"Mushroom present: {'Yes' if current_mushroom else 'No'}. "
20
  f"Collection count: {ctx.get('collection_count', 0)}. "
21
- f"Score: {ctx.get('score', 0)}. Health: {ctx.get('health', 3)}/3. "
22
- "Decide the next action: move, search, study, collect, or wait. "
23
- "If move, provide a target coordinate. "
 
 
 
 
24
  'Respond ONLY in valid JSON: {"action":"...","target":[x,y],"thought":"..."}'
25
  )
26
 
27
  response = engine._llm(prompt, ctx)
28
 
29
  if not response:
 
30
  return {"action": "wait", "target": None, "thought": "Engine returned no data."}
31
 
32
  try:
33
  parsed = json.loads(response)
34
- # Validate the action field exists and is a known value
35
- if parsed.get("action") not in {"move", "search", "study", "collect", "wait"}:
36
- raise ValueError(f"Unknown action: {parsed.get('action')}")
37
  return parsed
38
- except Exception as e:
39
- print(f"[Myco] Controller parse error: {e} β€” raw: {response!r}")
40
  return {"action": "wait", "target": None, "thought": "Failed to parse response."}
41
 
42
- def run_tick(self, current_mushroom, collection):
 
 
 
 
 
 
 
 
 
43
  decision = self.get_agent_decision(current_mushroom, collection)
44
- action = decision.get("action", "wait")
45
- result = {"action_taken": action, "thought": decision.get("thought", "")}
 
 
 
 
46
 
47
  if action == "move":
48
  target = decision.get("target")
49
  if isinstance(target, list) and len(target) == 2:
50
- self.position = target
 
 
 
51
  else:
52
- result["thought"] = "Move action had no valid target coordinate."
53
 
54
  elif action == "search":
55
- mushroom, current, history = engine.discover_mushroom(collection)
56
- result["data"] = {"mushroom": mushroom, "current": current}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
  elif action == "collect":
59
  if current_mushroom:
60
- coll, hist = engine.collect_current(
61
  current_mushroom, list(collection or []), self.history
62
  )
63
- self.history = hist
64
- result["data"] = {"collection": coll}
 
 
 
65
  else:
66
- result["thought"] = "Nothing to collect β€” no mushroom in clearing."
67
 
68
- elif action == "study":
69
- if current_mushroom:
70
- reply, hist = engine.study_current(current_mushroom, self.history)
71
- self.history = hist
72
- result["data"] = {"study_reply": reply}
73
- else:
74
- result["thought"] = "Nothing to study."
75
 
76
  return result
77
 
 
1
+ """
2
+ MycoController β€” autonomous AI agent that drives background game actions.
3
+ The controller uses engine._llm() to decide what to do, then delegates
4
+ execution to the same engine functions the player uses.
5
+ """
6
 
7
  from . import engine
8
  import json
 
11
  class MycoController:
12
  def __init__(self):
13
  self.position = [1, 1]
14
+ self.history = []
15
 
16
+ # ------------------------------------------------------------------
17
+ # Decision layer β€” ask Gemma what to do next
18
+ # ------------------------------------------------------------------
19
+ def get_agent_decision(self, current_mushroom, collection) -> dict:
20
+ # Build a full context so Gemma has score/health/mystery data.
21
  ctx = engine._ctx(current_mushroom, list(collection or []))
22
 
23
+ mushroom_line = (
24
+ f"There is a {current_mushroom.get('rarity','?')} mushroom called "
25
+ f"{current_mushroom.get('name','?')} in the clearing."
26
+ if current_mushroom
27
+ else "The clearing is empty."
28
+ )
29
+
30
  prompt = (
31
+ f"You are Myco, an autonomous forest agent. "
32
+ f"Current position: {self.position}. {mushroom_line} "
33
  f"Collection count: {ctx.get('collection_count', 0)}. "
34
+ f"Score: {ctx.get('score', 0)} spores. Health: {ctx.get('health', 3)}/3. "
35
+ f"Mystery chapter: {ctx.get('mystery_title', 'The Wrong Memory')}. "
36
+ "Decide the single best next action from: move, search, study, collect, wait. "
37
+ "If the clearing is empty, prefer 'search'. "
38
+ "If a mushroom is present and unstudied, prefer 'study'. "
39
+ "If studied and safe, prefer 'collect'. "
40
+ "If move, pick an adjacent grid coordinate within [0,2] x [0,2]. "
41
  'Respond ONLY in valid JSON: {"action":"...","target":[x,y],"thought":"..."}'
42
  )
43
 
44
  response = engine._llm(prompt, ctx)
45
 
46
  if not response:
47
+ print("[Myco Controller] LLM returned no data β€” defaulting to wait.")
48
  return {"action": "wait", "target": None, "thought": "Engine returned no data."}
49
 
50
  try:
51
  parsed = json.loads(response)
52
+ action = parsed.get("action", "wait")
53
+ if action not in {"move", "search", "study", "collect", "wait"}:
54
+ raise ValueError(f"Unknown action: {action!r}")
55
  return parsed
56
+ except Exception as exc:
57
+ print(f"[Myco Controller] Parse error: {exc} β€” raw: {response!r}")
58
  return {"action": "wait", "target": None, "thought": "Failed to parse response."}
59
 
60
+ # ------------------------------------------------------------------
61
+ # Execution layer β€” carry out the decision using engine functions
62
+ # ------------------------------------------------------------------
63
+ def run_tick(self, current_mushroom, collection) -> dict:
64
+ """
65
+ Run one autonomous tick. Returns a result dict with:
66
+ action_taken, thought, and optionally data (new current/collection).
67
+ This is the ONLY entry point the UI should call β€” it keeps controller
68
+ state (self.history, self.position) in sync.
69
+ """
70
  decision = self.get_agent_decision(current_mushroom, collection)
71
+ action = decision.get("action", "wait")
72
+ result = {
73
+ "action_taken": action,
74
+ "thought": decision.get("thought", ""),
75
+ "data": {},
76
+ }
77
 
78
  if action == "move":
79
  target = decision.get("target")
80
  if isinstance(target, list) and len(target) == 2:
81
+ x = max(0, min(2, int(target[0])))
82
+ y = max(0, min(2, int(target[1])))
83
+ self.position = [x, y]
84
+ result["data"]["position"] = self.position
85
  else:
86
+ result["thought"] = "Move had no valid target β€” staying put."
87
 
88
  elif action == "search":
89
+ mushroom, current, history = engine.discover_mushroom(list(collection or []))
90
+ self.history = history
91
+ result["data"] = {
92
+ "mushroom": mushroom,
93
+ "current": current,
94
+ "history": history,
95
+ }
96
+
97
+ elif action == "study":
98
+ if current_mushroom:
99
+ reply, history = engine.study_current(current_mushroom, self.history)
100
+ self.history = history
101
+ result["data"] = {
102
+ "study_reply": reply,
103
+ "history": history,
104
+ }
105
+ else:
106
+ result["thought"] = "Nothing to study β€” clearing is empty."
107
 
108
  elif action == "collect":
109
  if current_mushroom:
110
+ coll, history = engine.collect_current(
111
  current_mushroom, list(collection or []), self.history
112
  )
113
+ self.history = history
114
+ result["data"] = {
115
+ "collection": coll,
116
+ "history": history,
117
+ }
118
  else:
119
+ result["thought"] = "Nothing to collect β€” clearing is empty."
120
 
121
+ # action == "wait" needs no execution
 
 
 
 
 
 
122
 
123
  return result
124