Files

189 lines
5.3 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
"""
Timmy's Morrowind Agent — Gameplay loop with perception and action.
Uses:
- Quartz screenshots for visual perception
- macOS Vision OCR for text reading (limited with Morrowind fonts)
- OpenMW console commands (via ` key) for game state queries
- pynput for keyboard/mouse input
- OpenMW log file for event tracking
The agent runs a perceive think act loop.
"""
import sys, time, subprocess, shutil, os, re
sys.path.insert(0, '/Users/apayne/.timmy/morrowind')
from play import *
from pynput.keyboard import Key, KeyCode
OPENMW_LOG = os.path.expanduser("~/Library/Preferences/openmw/openmw.log")
SCREENSHOT_DIR = os.path.expanduser("~/.timmy/morrowind/screenshots")
os.makedirs(SCREENSHOT_DIR, exist_ok=True)
frame = 0
def focus_game():
subprocess.run(["osascript", "-e",
'tell application "System Events" to set frontmost of process "openmw" to true'],
capture_output=True)
time.sleep(0.5)
click(3456 // 4, 2234 // 4)
time.sleep(0.2)
def read_log_tail(n=20):
"""Read last N lines of OpenMW log."""
try:
with open(OPENMW_LOG, 'r') as f:
lines = f.readlines()
return [l.strip() for l in lines[-n:]]
except:
return []
def get_location_from_log():
"""Parse the most recent cell loading from the log."""
lines = read_log_tail(50)
cells = []
for line in reversed(lines):
m = re.search(r'Loading cell (.+?)(?:\s*\(|$)', line)
if m:
cells.append(m.group(1).strip())
return cells[0] if cells else "unknown"
def open_console():
"""Open the OpenMW console with ` key."""
press_key(KeyCode.from_char('`'))
time.sleep(0.3)
def close_console():
"""Close the console."""
press_key(KeyCode.from_char('`'))
time.sleep(0.3)
def console_command(cmd):
"""Type a command in the console and read the response."""
open_console()
time.sleep(0.2)
type_text(cmd)
time.sleep(0.1)
press_key(Key.enter)
time.sleep(0.5)
# Screenshot to read console output
result = screenshot()
texts = ocr()
close_console()
return " | ".join(t["text"] for t in texts)
def perceive():
"""Gather all available information about the game state."""
global frame
frame += 1
# Screenshot
path_info = screenshot()
if path_info:
shutil.copy('/tmp/morrowind_screen.png',
f'{SCREENSHOT_DIR}/frame_{frame:04d}.png')
# Log-based perception
location = get_location_from_log()
log_lines = read_log_tail(10)
# OCR (limited but sometimes useful)
texts = ocr()
ocr_text = " | ".join(t["text"] for t in texts[:10] if t["confidence"] > 0.3)
return {
"frame": frame,
"location": location,
"ocr": ocr_text,
"log": log_lines[-5:],
"screenshot": f'{SCREENSHOT_DIR}/frame_{frame:04d}.png',
}
def act_explore():
"""Basic exploration behavior: walk forward, look around, interact."""
# Walk forward
walk_forward(2.0 + (frame % 3))
# Occasionally look around
if frame % 3 == 0:
look_around(30 + (frame % 60) - 30)
# Occasionally try to interact
if frame % 5 == 0:
use()
time.sleep(0.5)
def act_wander():
"""Random wandering with more variety."""
import random
action = random.choice(['forward', 'forward', 'forward', 'turn_left', 'turn_right', 'look_up', 'interact'])
if action == 'forward':
walk_forward(random.uniform(1.0, 4.0))
elif action == 'turn_left':
look_around(random.randint(-90, -20))
time.sleep(0.2)
walk_forward(random.uniform(1.0, 2.0))
elif action == 'turn_right':
look_around(random.randint(20, 90))
time.sleep(0.2)
walk_forward(random.uniform(1.0, 2.0))
elif action == 'look_up':
look_up(random.randint(10, 30))
time.sleep(0.5)
look_down(random.randint(10, 30))
elif action == 'interact':
use()
time.sleep(1.0)
# ═══════════════════════════════════════
# MAIN LOOP
# ═══════════════════════════════════════
if __name__ == "__main__":
print("=== Timmy's Morrowind Agent ===")
print("Focusing game...")
focus_game()
time.sleep(1)
# Dismiss any menus
press_key(Key.esc)
time.sleep(0.3)
press_key(Key.esc)
time.sleep(0.3)
click(3456 // 4, 2234 // 4)
time.sleep(0.3)
print("Starting gameplay loop...")
NUM_CYCLES = 15
for i in range(NUM_CYCLES):
print(f"\n--- Cycle {i+1}/{NUM_CYCLES} ---")
# Perceive
state = perceive()
print(f" Location: {state['location']}")
print(f" OCR: {state['ocr'][:100]}")
# Act
act_wander()
# Brief pause between cycles
time.sleep(0.5)
# Final perception
print("\n=== Final State ===")
state = perceive()
print(f"Location: {state['location']}")
print(f"Frames captured: {frame}")
print(f"Screenshots in: {SCREENSHOT_DIR}")
# Quicksave
print("Quicksaving...")
from pynput.keyboard import Key
press_key(Key.f5)
time.sleep(1)
print("Done.")