Spaces:
Sleeping
Sleeping
Deploy Myco from CI
Browse files- game/agent_controller.py +80 -33
game/agent_controller.py
CHANGED
|
@@ -1,4 +1,8 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
| 15 |
ctx = engine._ctx(current_mushroom, list(collection or []))
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
prompt = (
|
| 18 |
-
f"
|
| 19 |
-
f"
|
| 20 |
f"Collection count: {ctx.get('collection_count', 0)}. "
|
| 21 |
-
f"Score: {ctx.get('score', 0)}. Health: {ctx.get('health', 3)}/3. "
|
| 22 |
-
"
|
| 23 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 35 |
-
if
|
| 36 |
-
raise ValueError(f"Unknown action: {
|
| 37 |
return parsed
|
| 38 |
-
except Exception as
|
| 39 |
-
print(f"[Myco
|
| 40 |
return {"action": "wait", "target": None, "thought": "Failed to parse response."}
|
| 41 |
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
decision = self.get_agent_decision(current_mushroom, collection)
|
| 44 |
-
action
|
| 45 |
-
result
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
if action == "move":
|
| 48 |
target = decision.get("target")
|
| 49 |
if isinstance(target, list) and len(target) == 2:
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
| 51 |
else:
|
| 52 |
-
result["thought"] = "Move
|
| 53 |
|
| 54 |
elif action == "search":
|
| 55 |
-
mushroom, current, history = engine.discover_mushroom(collection)
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
elif action == "collect":
|
| 59 |
if current_mushroom:
|
| 60 |
-
coll,
|
| 61 |
current_mushroom, list(collection or []), self.history
|
| 62 |
)
|
| 63 |
-
self.history
|
| 64 |
-
result["data"] = {
|
|
|
|
|
|
|
|
|
|
| 65 |
else:
|
| 66 |
-
result["thought"] = "Nothing to collect β
|
| 67 |
|
| 68 |
-
|
| 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 |
|