Myco / game /agent_controller.py
byte-vortex's picture
Deploy Myco from CI
3aba62e verified
Raw
History Blame Contribute Delete
5.19 kB
"""
MycoController β€” autonomous AI agent that drives background game actions.
The controller uses engine._llm() to decide what to do, then delegates
execution to the same engine functions the player uses.
"""
from . import engine
import json
class MycoController:
def __init__(self):
self.position = [1, 1]
self.history = []
# ------------------------------------------------------------------
# Decision layer β€” ask Gemma what to do next
# ------------------------------------------------------------------
def get_agent_decision(self, current_mushroom, collection) -> dict:
# Build a full context so Gemma has score/health/mystery data.
ctx = engine._ctx(current_mushroom, list(collection or []))
mushroom_line = (
f"There is a {current_mushroom.get('rarity','?')} mushroom called "
f"{current_mushroom.get('name','?')} in the clearing."
if current_mushroom
else "The clearing is empty."
)
prompt = (
f"You are Myco, an autonomous forest agent. "
f"Current position: {self.position}. {mushroom_line} "
f"Collection count: {ctx.get('collection_count', 0)}. "
f"Score: {ctx.get('score', 0)} spores. Health: {ctx.get('health', 3)}/3. "
f"Mystery chapter: {ctx.get('mystery_title', 'The Wrong Memory')}. "
"Decide the single best next action from: move, search, study, collect, wait. "
"If the clearing is empty, prefer 'search'. "
"If a mushroom is present and unstudied, prefer 'study'. "
"If studied and safe, prefer 'collect'. "
"If move, pick an adjacent grid coordinate within [0,2] x [0,2]. "
'Respond ONLY in valid JSON: {"action":"...","target":[x,y],"thought":"..."}'
)
response = engine._llm(prompt, ctx)
if not response:
print("[Myco Controller] LLM returned no data β€” defaulting to wait.")
return {"action": "wait", "target": None, "thought": "Engine returned no data."}
try:
parsed = json.loads(response)
action = parsed.get("action", "wait")
if action not in {"move", "search", "study", "collect", "wait"}:
raise ValueError(f"Unknown action: {action!r}")
return parsed
except Exception as exc:
print(f"[Myco Controller] Parse error: {exc} β€” raw: {response!r}")
return {"action": "wait", "target": None, "thought": "Failed to parse response."}
# ------------------------------------------------------------------
# Execution layer β€” carry out the decision using engine functions
# ------------------------------------------------------------------
def run_tick(self, current_mushroom, collection) -> dict:
"""
Run one autonomous tick. Returns a result dict with:
action_taken, thought, and optionally data (new current/collection).
This is the ONLY entry point the UI should call β€” it keeps controller
state (self.history, self.position) in sync.
"""
decision = self.get_agent_decision(current_mushroom, collection)
action = decision.get("action", "wait")
result = {
"action_taken": action,
"thought": decision.get("thought", ""),
"data": {},
}
if action == "move":
target = decision.get("target")
if isinstance(target, list) and len(target) == 2:
x = max(0, min(2, int(target[0])))
y = max(0, min(2, int(target[1])))
self.position = [x, y]
result["data"]["position"] = self.position
else:
result["thought"] = "Move had no valid target β€” staying put."
elif action == "search":
mushroom, current, history = engine.discover_mushroom(list(collection or []))
self.history = history
result["data"] = {
"mushroom": mushroom,
"current": current,
"history": history,
}
elif action == "study":
if current_mushroom:
reply, history = engine.study_current(current_mushroom, self.history)
self.history = history
result["data"] = {
"study_reply": reply,
"history": history,
}
else:
result["thought"] = "Nothing to study β€” clearing is empty."
elif action == "collect":
if current_mushroom:
coll, history = engine.collect_current(
current_mushroom, list(collection or []), self.history
)
self.history = history
result["data"] = {
"collection": coll,
"history": history,
}
else:
result["thought"] = "Nothing to collect β€” clearing is empty."
# action == "wait" needs no execution
return result