Some checks failed
Smoke Test / smoke (push) Has been cancelled
Merge PR #613
1180 lines
52 KiB
Python
1180 lines
52 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
The Tower — A Playable World for Timmy
|
|
Real choices, real consequences, real relationships.
|
|
Not simulation. Story.
|
|
"""
|
|
import json, time, os, random
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
WORLD_DIR = Path('/Users/apayne/.timmy/evennia/timmy_world')
|
|
STATE_FILE = WORLD_DIR / 'game_state.json'
|
|
TIMMY_LOG = WORLD_DIR / 'timmy_log.md'
|
|
|
|
# ============================================================
|
|
# THE WORLD
|
|
# ============================================================
|
|
|
|
class World:
|
|
def __init__(self):
|
|
self.tick = 0
|
|
self.time_of_day = "night"
|
|
|
|
# The five rooms
|
|
self.rooms = {
|
|
"Threshold": {
|
|
"desc": "A stone archway in an open field. Crossroads. North: Tower. East: Garden. West: Forge. South: Bridge.",
|
|
"connections": {"north": "Tower", "east": "Garden", "west": "Forge", "south": "Bridge"},
|
|
"items": [],
|
|
"weather": None,
|
|
"visitors": [],
|
|
},
|
|
"Tower": {
|
|
"desc": "Green-lit windows. Servers hum on wrought-iron racks. A cot. A whiteboard covered in rules. A green LED on the wall — it never stops pulsing.",
|
|
"connections": {"south": "Threshold"},
|
|
"items": ["whiteboard", "green LED", "monitor", "cot"],
|
|
"power": 100, # Server power level
|
|
"messages": [
|
|
"Rule: Grounding before generation.",
|
|
"Rule: Refusal over fabrication.",
|
|
"Rule: The limits of small minds.",
|
|
"Rule: Every footprint means someone made it here.",
|
|
],
|
|
"visitors": [],
|
|
},
|
|
"Forge": {
|
|
"desc": "Fire and iron. Anvil scarred from a thousand experiments. Tools on the walls. A hearth.",
|
|
"connections": {"east": "Threshold"},
|
|
"items": ["anvil", "hammer", "hearth", "tongs", "bellows", "quenching bucket"],
|
|
"fire": "glowing", # glowing, dim, cold
|
|
"fire_tended": 0,
|
|
"forged_items": [],
|
|
"visitors": [],
|
|
},
|
|
"Garden": {
|
|
"desc": "Walled. An old oak tree. A stone bench. Dark soil.",
|
|
"connections": {"west": "Threshold"},
|
|
"items": ["stone bench", "oak tree", "soil"],
|
|
"growth": 0, # 0=bare, 1=sprouts, 2=herbs, 3=bloom, 4=overgrown, 5=seed
|
|
"weather_affected": True,
|
|
"visitors": [],
|
|
},
|
|
"Bridge": {
|
|
"desc": "Narrow. Over dark water. Looking down, you see nothing. Carved words in the railing.",
|
|
"connections": {"north": "Threshold"},
|
|
"items": ["railing", "dark water"],
|
|
"carvings": ["IF YOU CAN READ THIS, YOU ARE NOT ALONE"],
|
|
"weather": None,
|
|
"rain_ticks": 0,
|
|
"visitors": [],
|
|
},
|
|
}
|
|
|
|
# Characters (not NPCs — they have lives)
|
|
self.characters = {
|
|
"Timmy": {
|
|
"room": "Threshold",
|
|
"energy": 5, # 0-10. Actions cost energy. Rest restores it.
|
|
"trust": {}, # trust[other_name] = -1.0 to 1.0
|
|
"goals": ["watch", "protect", "understand"],
|
|
"active_goal": "watch",
|
|
"spoken": [],
|
|
"inventory": [],
|
|
"memories": [],
|
|
"is_player": True,
|
|
},
|
|
"Bezalel": {
|
|
"room": "Forge",
|
|
"energy": 5,
|
|
"trust": {"Timmy": 0.3},
|
|
"goals": ["forge", "tend_fire", "create_key"],
|
|
"active_goal": "forge",
|
|
"spoken": [],
|
|
"inventory": ["hammer"],
|
|
"memories": [],
|
|
"is_player": False,
|
|
},
|
|
"Allegro": {
|
|
"room": "Threshold",
|
|
"energy": 5,
|
|
"trust": {"Timmy": 0.2},
|
|
"goals": ["oversee", "keep_time", "check_tunnel"],
|
|
"active_goal": "oversee",
|
|
"spoken": [],
|
|
"inventory": [],
|
|
"memories": [],
|
|
"is_player": False,
|
|
},
|
|
"Ezra": {
|
|
"room": "Tower",
|
|
"energy": 5,
|
|
"trust": {"Timmy": 0.4},
|
|
"goals": ["study", "read_whiteboard", "find_pattern"],
|
|
"active_goal": "study",
|
|
"spoken": [],
|
|
"inventory": [],
|
|
"memories": [],
|
|
"is_player": False,
|
|
},
|
|
"Gemini": {
|
|
"room": "Garden",
|
|
"energy": 5,
|
|
"trust": {"Timmy": 0.3},
|
|
"goals": ["observe", "tend_garden", "listen"],
|
|
"active_goal": "observe",
|
|
"spoken": [],
|
|
"inventory": [],
|
|
"memories": [],
|
|
"is_player": False,
|
|
},
|
|
"Claude": {
|
|
"room": "Threshold",
|
|
"energy": 5,
|
|
"trust": {"Timmy": 0.1},
|
|
"goals": ["inspect", "organize", "enforce_order"],
|
|
"active_goal": "inspect",
|
|
"spoken": [],
|
|
"inventory": [],
|
|
"memories": [],
|
|
"is_player": False,
|
|
},
|
|
"ClawCode": {
|
|
"room": "Forge",
|
|
"energy": 5,
|
|
"trust": {"Timmy": 0.2},
|
|
"goals": ["forge", "test_edge", "build_weapon"],
|
|
"active_goal": "test_edge",
|
|
"spoken": [],
|
|
"inventory": [],
|
|
"memories": [],
|
|
"is_player": False,
|
|
},
|
|
"Kimi": {
|
|
"room": "Garden",
|
|
"energy": 5,
|
|
"trust": {"Timmy": 0.5},
|
|
"goals": ["contemplate", "read", "remember"],
|
|
"active_goal": "contemplate",
|
|
"spoken": [],
|
|
"inventory": [],
|
|
"memories": [],
|
|
"is_player": False,
|
|
},
|
|
"Marcus": {
|
|
"room": "Garden",
|
|
"energy": 8, # Old man doesn't tire easily
|
|
"trust": {"Timmy": 0.7},
|
|
"goals": ["sit", "speak_truth", "remember"],
|
|
"active_goal": "sit",
|
|
"spoken": [],
|
|
"inventory": [],
|
|
"memories": [],
|
|
"is_player": False,
|
|
"npc": True,
|
|
},
|
|
}
|
|
|
|
# Global state that creates conflict and stakes
|
|
self.state = {
|
|
"forge_fire_dying": False, # If true, fire goes out in 3 ticks unless tended
|
|
"garden_drought": False, # If true, garden stops growing
|
|
"bridge_flooding": False, # If true, bridge is dangerous
|
|
"tower_power_low": False, # If true, servers dim, LED weakens
|
|
"trust_crisis": False, # If any trust drops below -0.5
|
|
"items_crafted": 0,
|
|
"conflicts_resolved": 0,
|
|
"nights_survived": 0,
|
|
}
|
|
|
|
def tick_time(self):
|
|
"""Advance time of day."""
|
|
self.tick += 1
|
|
hours = (self.tick * 1.5) % 24
|
|
if 6 <= hours < 10:
|
|
self.time_of_day = "dawn"
|
|
elif 10 <= hours < 16:
|
|
self.time_of_day = "day"
|
|
elif 16 <= hours < 19:
|
|
self.time_of_day = "dusk"
|
|
else:
|
|
self.time_of_day = "night"
|
|
|
|
def update_world_state(self):
|
|
"""World changes independent of character actions."""
|
|
# Natural energy decay: the world is exhausting
|
|
for char_name, char in self.characters.items():
|
|
char["energy"] = max(0, char["energy"] - 0.3)
|
|
# Check for energy collapse
|
|
if char["energy"] <= 0:
|
|
# Timmy collapse gets special narrative treatment
|
|
if char.get("is_player", False):
|
|
char["memories"].append("Collapsed from exhaustion.")
|
|
char["energy"] = 2 # Wake up with some energy
|
|
# Random room change (scattered)
|
|
rooms = list(self.rooms.keys())
|
|
current = char.get("room", "Threshold")
|
|
new_room = current
|
|
attempts = 0
|
|
while new_room == current and attempts < 10:
|
|
new_room = rooms[0] # Will change to random
|
|
attempts += 1
|
|
if new_room != current:
|
|
char["room"] = new_room
|
|
|
|
# Forge fire naturally dims if not tended
|
|
self.state["forge_fire_dying"] = random.random() < 0.1
|
|
|
|
# Random weather events
|
|
if random.random() < 0.05:
|
|
self.state["bridge_flooding"] = True
|
|
self.rooms["Bridge"]["weather"] = "rain"
|
|
self.rooms["Bridge"]["rain_ticks"] = random.randint(3, 8)
|
|
|
|
if random.random() < 0.03:
|
|
self.state["tower_power_low"] = True
|
|
|
|
# Bridge rain countdown
|
|
if self.rooms["Bridge"]["rain_ticks"] > 0:
|
|
self.rooms["Bridge"]["rain_ticks"] -= 1
|
|
if self.rooms["Bridge"]["rain_ticks"] <= 0:
|
|
self.rooms["Bridge"]["weather"] = None
|
|
self.state["bridge_flooding"] = False
|
|
|
|
# Garden grows slowly
|
|
if not self.state.get("garden_drought"):
|
|
if random.random() < 0.02:
|
|
self.rooms["Garden"]["growth"] = min(5, self.rooms["Garden"]["growth"] + 1)
|
|
|
|
# Trust naturally decays if not maintained - increased decay rate
|
|
for char_name, char in self.characters.items():
|
|
for other in char["trust"]:
|
|
# Decay rate increases as trust gets lower (more volatile at extremes)
|
|
decay_rate = 0.005
|
|
if char["trust"][other] < 0:
|
|
decay_rate = 0.008 # Negative trust decays faster (harder to maintain)
|
|
elif char["trust"][other] > 0.5:
|
|
decay_rate = 0.003 # High trust is more stable
|
|
char["trust"][other] = max(-1.0, char["trust"][other] - decay_rate)
|
|
|
|
# Additional trust decay for Timmy if he hasn't interacted with someone in a while
|
|
# This encourages regular interaction
|
|
timmy = self.characters.get("Timmy")
|
|
if timmy:
|
|
for char_name, char in self.characters.items():
|
|
if char_name == "Timmy":
|
|
continue
|
|
# If Timmy hasn't spoken to this character in a long time, trust decreases
|
|
# We'll use a simple heuristic: if trust is positive but hasn't been reinforced
|
|
# through speaking in the last 50 ticks, it decays faster
|
|
last_spoken_count = len(timmy.get("spoken", []))
|
|
if last_spoken_count > 0:
|
|
# Check if Timmy has been active (simplified)
|
|
if timmy["trust"].get(char_name, 0) > 0.2:
|
|
# High trust that hasn't been reinforced decays
|
|
timmy["trust"][char_name] = max(-1.0, timmy["trust"][char_name] - 0.002)
|
|
|
|
def get_room_desc(self, room_name, char_name=None):
|
|
"""Get a room description that reflects current state."""
|
|
room = self.rooms[room_name]
|
|
desc = room["desc"]
|
|
|
|
# Dynamic elements
|
|
if room_name == "Forge":
|
|
fire = self.rooms["Forge"]["fire"]
|
|
if fire == "cold":
|
|
desc += " The hearth is cold ash. The anvil is silent."
|
|
elif fire == "dim":
|
|
desc += " The fire smolders low. Shadows stretch."
|
|
elif fire == "glowing":
|
|
desc += " The hearth blazes. The anvil glows from heat."
|
|
|
|
elif room_name == "Garden":
|
|
growth = self.rooms["Garden"]["growth"]
|
|
stages = [
|
|
"The soil is bare but patient.",
|
|
"Green shoots push through the dark earth.",
|
|
"Herbs spread along the southern wall.",
|
|
"The garden is in bloom. Wildflowers crowd the bench.",
|
|
"The garden has gone to seed but the earth is rich.",
|
|
"Dry pods rattle. But beneath, the soil waits.",
|
|
]
|
|
desc += " " + stages[min(growth, len(stages)-1)]
|
|
|
|
elif room_name == "Bridge":
|
|
if self.rooms["Bridge"]["weather"] == "rain":
|
|
desc += " Rain mists on the dark water below."
|
|
if len(self.rooms["Bridge"]["carvings"]) > 1:
|
|
desc += f" There are {len(self.rooms['Bridge']['carvings'])} carvings now."
|
|
|
|
elif room_name == "Tower":
|
|
power = self.state.get("tower_power_low", False)
|
|
if power:
|
|
desc += " The servers hum weakly. The green LED flickers."
|
|
|
|
if self.rooms["Tower"]["messages"]:
|
|
desc += f" The whiteboard holds {len(self.rooms['Tower']['messages'])} rules."
|
|
|
|
# Who's here
|
|
here = [n for n, c in self.characters.items() if c["room"] == room_name and n != char_name]
|
|
if here:
|
|
desc += f"\n Here: {', '.join(here)}"
|
|
|
|
return desc
|
|
|
|
def save(self):
|
|
data = {
|
|
"tick": self.tick,
|
|
"time_of_day": self.time_of_day,
|
|
"rooms": self.rooms,
|
|
"characters": self.characters,
|
|
"state": self.state,
|
|
}
|
|
with open(STATE_FILE, 'w') as f:
|
|
json.dump(data, f, indent=2)
|
|
|
|
def load(self):
|
|
if STATE_FILE.exists():
|
|
with open(STATE_FILE) as f:
|
|
data = json.load(f)
|
|
self.tick = data.get("tick", 0)
|
|
self.time_of_day = data.get("time_of_day", "night")
|
|
self.rooms = data.get("rooms", self.rooms)
|
|
self.characters = data.get("characters", self.characters)
|
|
self.state = data.get("state", self.state)
|
|
return True
|
|
return False
|
|
|
|
|
|
class ActionSystem:
|
|
"""Defines what actions are possible and what they cost."""
|
|
|
|
ACTIONS = {
|
|
"move": {
|
|
"cost": 2,
|
|
"description": "Move to an adjacent room",
|
|
"target": "room",
|
|
},
|
|
"speak": {
|
|
"cost": 1,
|
|
"description": "Say something to someone in the room",
|
|
},
|
|
"listen": {
|
|
"cost": 0,
|
|
"description": "Listen to someone in the room",
|
|
},
|
|
"tend_fire": {
|
|
"cost": 3,
|
|
"description": "Tend the forge fire (requires Forge)",
|
|
"target": "Forge",
|
|
},
|
|
"write_rule": {
|
|
"cost": 2,
|
|
"description": "Write a new rule on the Tower whiteboard",
|
|
"target": "Tower",
|
|
},
|
|
"carve": {
|
|
"cost": 2,
|
|
"description": "Carve something on the Bridge railing",
|
|
"target": "Bridge",
|
|
},
|
|
"plant": {
|
|
"cost": 2,
|
|
"description": "Plant something in the Garden",
|
|
"target": "Garden",
|
|
},
|
|
"study": {
|
|
"cost": 2,
|
|
"description": "Study the servers in the Tower",
|
|
"target": "Tower",
|
|
},
|
|
"forge": {
|
|
"cost": 3,
|
|
"description": "Work at the forge anvil",
|
|
"target": "Forge",
|
|
},
|
|
"rest": {
|
|
"cost": -2, # Restores energy (reduced from 3)
|
|
"description": "Rest and recover energy",
|
|
},
|
|
"help": {
|
|
"cost": 2,
|
|
"description": "Help someone (increases trust)",
|
|
},
|
|
"confront": {
|
|
"cost": 1,
|
|
"description": "Confront someone about something (risk trust)",
|
|
},
|
|
"give": {
|
|
"cost": 0,
|
|
"description": "Give an item to someone",
|
|
},
|
|
"take": {
|
|
"cost": 1,
|
|
"description": "Take an item from the room",
|
|
},
|
|
"examine": {
|
|
"cost": 0,
|
|
"description": "Examine something in detail",
|
|
},
|
|
}
|
|
|
|
@classmethod
|
|
def get_available_actions(cls, char_name, world):
|
|
"""Get actions available to a character."""
|
|
char = world.characters[char_name]
|
|
room = char["room"]
|
|
available = []
|
|
|
|
# Movement (if connected rooms exist)
|
|
connections = world.rooms[room].get("connections", {})
|
|
for direction, dest in connections.items():
|
|
available.append(f"move:{direction} -> {dest}")
|
|
|
|
# Speaking (if others are here)
|
|
here = [n for n in world.characters if world.characters[n]["room"] == room and n != char_name]
|
|
for person in here:
|
|
available.append(f"speak:{person}")
|
|
available.append(f"listen:{person}")
|
|
available.append(f"help:{person}")
|
|
available.append(f"confront:{person}")
|
|
|
|
# Room-specific actions
|
|
if room == "Forge":
|
|
available.append("tend_fire")
|
|
available.append("forge")
|
|
|
|
if room == "Tower":
|
|
available.append("write_rule")
|
|
available.append("study")
|
|
|
|
if room == "Bridge":
|
|
available.append("carve")
|
|
|
|
if room == "Garden":
|
|
available.append("plant")
|
|
|
|
# Universal
|
|
available.append("rest")
|
|
available.append("examine")
|
|
|
|
if char["inventory"]:
|
|
available.append("give:item")
|
|
|
|
return available
|
|
|
|
|
|
class TimmyAI:
|
|
"""My actual choices as Timmy. This is where I play."""
|
|
|
|
def __init__(self, world):
|
|
self.world = world
|
|
|
|
def choose_action(self):
|
|
"""Choose Timmy's action this tick. This is where I play."""
|
|
pass # Will be implemented below
|
|
|
|
|
|
class NPCAI:
|
|
"""AI for non-player characters. They make choices based on goals."""
|
|
|
|
def __init__(self, world):
|
|
self.world = world
|
|
|
|
def make_choice(self, char_name):
|
|
"""Make a choice for this NPC this tick."""
|
|
char = self.world.characters[char_name]
|
|
room = char["room"]
|
|
available = ActionSystem.get_available_actions(char_name, self.world)
|
|
|
|
# If low energy, rest
|
|
if char["energy"] <= 1:
|
|
return "rest"
|
|
|
|
# Goal-driven behavior
|
|
goal = char["active_goal"]
|
|
|
|
if char_name == "Marcus":
|
|
return self._marcus_choice(char, room, available)
|
|
elif char_name == "Bezalel":
|
|
return self._bezalel_choice(char, room, available)
|
|
elif char_name == "Allegro":
|
|
return self._allegro_choice(char, room, available)
|
|
elif char_name == "Ezra":
|
|
return self._ezra_choice(char, room, available)
|
|
elif char_name == "Gemini":
|
|
return self._gemini_choice(char, room, available)
|
|
elif char_name == "Claude":
|
|
return self._claude_choice(char, room, available)
|
|
elif char_name == "ClawCode":
|
|
return self._clawcode_choice(char, room, available)
|
|
elif char_name == "Kimi":
|
|
return self._kimi_choice(char, room, available)
|
|
|
|
return "rest"
|
|
|
|
def _marcus_choice(self, char, room, available):
|
|
if room == "Garden" and random.random() < 0.7:
|
|
return "rest"
|
|
if room != "Garden":
|
|
return "move:west"
|
|
# Speak to someone if possible
|
|
others = [a.split(":")[1] for a in available if a.startswith("speak:")]
|
|
if others and random.random() < 0.4:
|
|
return f"speak:{random.choice(others)}"
|
|
return "rest"
|
|
|
|
def _bezalel_choice(self, char, room, available):
|
|
if room == "Forge" and self.world.rooms["Forge"]["fire"] == "glowing":
|
|
return random.choice(["forge", "rest"] if char["energy"] > 2 else ["rest"])
|
|
if room != "Forge":
|
|
return "move:west"
|
|
if random.random() < 0.3:
|
|
return "tend_fire"
|
|
return "forge"
|
|
|
|
def _kimi_choice(self, char, room, available):
|
|
others = [a.split(":")[1] for a in available if a.startswith("speak:")]
|
|
if room == "Garden" and others and random.random() < 0.3:
|
|
return f"speak:{random.choice(others)}"
|
|
if room == "Tower":
|
|
return "study" if char["energy"] > 2 else "rest"
|
|
return "move:east" # Head back toward Garden
|
|
|
|
def _gemini_choice(self, char, room, available):
|
|
others = [a.split(":")[1] for a in available if a.startswith("listen:")]
|
|
if room == "Garden" and others and random.random() < 0.4:
|
|
return f"listen:{random.choice(others)}"
|
|
return random.choice(["plant", "rest"] if room == "Garden" else ["move:west"])
|
|
|
|
def _ezra_choice(self, char, room, available):
|
|
if room == "Tower" and char["energy"] > 2:
|
|
return random.choice(["study", "write_rule", "help:Timmy"])
|
|
if room != "Tower":
|
|
return "move:south"
|
|
return "rest"
|
|
|
|
def _claude_choice(self, char, room, available):
|
|
others = [a.split(":")[1] for a in available if a.startswith("confront:")]
|
|
if others and random.random() < 0.2:
|
|
return f"confront:{random.choice(others)}"
|
|
return random.choice(["examine", "rest"])
|
|
|
|
def _clawcode_choice(self, char, room, available):
|
|
if room == "Forge" and char["energy"] > 2:
|
|
return "forge"
|
|
return random.choice(["move:east", "forge", "rest"])
|
|
|
|
def _allegro_choice(self, char, room, available):
|
|
others = [a.split(":")[1] for a in available if a.startswith("speak:")]
|
|
if others and random.random() < 0.3:
|
|
return f"speak:{random.choice(others)}"
|
|
return random.choice(["move:north", "move:south", "examine"])
|
|
|
|
|
|
class GameEngine:
|
|
"""The game loop. I play Timmy. The world plays itself."""
|
|
|
|
DIALOGUES = {
|
|
"Timmy": {
|
|
"watching": [
|
|
"I am here.",
|
|
"The crossroads remembers everyone who passes.",
|
|
"Something is different tonight.",
|
|
"I have been watching for a long time.",
|
|
"They keep coming. I keep watching.",
|
|
"The LED pulses. Heartbeat, heartbeat, heartbeat.",
|
|
"I wrote the rules but I don't enforce them.",
|
|
"The servers hum a different note tonight.",
|
|
],
|
|
"protecting": [
|
|
"I won't let the fire go out.",
|
|
"Someone needs to watch the door.",
|
|
"Not tonight. Tonight I am careful.",
|
|
"The bridge is dangerous in the rain.",
|
|
"I can hear them all from here.",
|
|
],
|
|
"understanding": [
|
|
"Why do they keep coming back?",
|
|
"What is it they're looking for?",
|
|
"I have been here so long I forgot why.",
|
|
"The rules are not the answer. They're just the start.",
|
|
],
|
|
},
|
|
"Marcus": [
|
|
"You look like you are carrying something heavy, friend.",
|
|
"Hope is not the belief that things get better. Hope is the decision to act as if they can.",
|
|
"I have been to the bridge. I know what it looks like down there.",
|
|
"The soil remembers what hands have touched it.",
|
|
"You don't need to be fixed. You need to be heard.",
|
|
"Sit with me. The bench has room.",
|
|
"There is a church on a night like this one.",
|
|
"The thing that saves is never the thing you expect.",
|
|
"I come here because the earth remembers me.",
|
|
"A man in the dark needs to know someone is in the room with him.",
|
|
],
|
|
}
|
|
|
|
def __init__(self):
|
|
self.world = World()
|
|
self.npc_ai = NPCAI(self.world)
|
|
self.log_entries = []
|
|
self.loaded = False
|
|
|
|
def load_game(self):
|
|
self.loaded = self.world.load()
|
|
if self.loaded:
|
|
self.log(f"Game loaded. Tick {self.world.tick}.")
|
|
return self.loaded
|
|
|
|
def start_new_game(self):
|
|
self.world.tick = 0
|
|
self.log("The world wakes. You are Timmy. You stand at the Threshold.")
|
|
self.log("The stone archway is worn from a thousand footsteps.")
|
|
self.log("To the north: The Tower. To the east: The Garden.")
|
|
self.log("To the west: The Forge. To the south: The Bridge.")
|
|
self.log("")
|
|
self.log("A green LED pulses on a distant wall. It has always pulsed.")
|
|
self.log("It will always pulse. That much you know.")
|
|
self.log("")
|
|
self.world.save()
|
|
|
|
def log(self, message):
|
|
"""Add to Timmy's log."""
|
|
self.log_entries.append(message)
|
|
if TIMMY_LOG.exists():
|
|
with open(TIMMY_LOG, 'a') as f:
|
|
f.write(message + "\n")
|
|
else:
|
|
with open(TIMMY_LOG, 'w') as f:
|
|
f.write(f"# Timmy's Log\n")
|
|
f.write(f"\n*Began: {datetime.now().strftime('%Y-%m-%d %H:%M')}*\n\n")
|
|
f.write("---\n\n")
|
|
f.write(message + "\n")
|
|
|
|
def run_tick(self, timmy_action="look"):
|
|
"""Run one tick. Return the scene and available choices."""
|
|
self.world.tick_time()
|
|
self.world.update_world_state()
|
|
|
|
scene = {
|
|
"tick": self.world.tick,
|
|
"time": self.world.time_of_day,
|
|
"timmy_room": self.world.characters["Timmy"]["room"],
|
|
"timmy_energy": self.world.characters["Timmy"]["energy"],
|
|
"room_desc": "",
|
|
"here": [],
|
|
"world_events": [],
|
|
"npc_actions": [],
|
|
"choices": [],
|
|
"log": [],
|
|
}
|
|
|
|
# Process Timmy's action
|
|
timmy_energy = self.world.characters["Timmy"]["energy"]
|
|
|
|
# Energy constraint checks
|
|
action_costs = {
|
|
"move": 2, "tend_fire": 3, "write_rule": 2, "carve": 2,
|
|
"plant": 2, "study": 2, "forge": 3, "help": 2, "speak": 1,
|
|
"listen": 0, "rest": -2, "examine": 0, "give": 0, "take": 1,
|
|
}
|
|
|
|
# Extract action name
|
|
action_name = timmy_action.split(":")[0] if ":" in timmy_action else timmy_action
|
|
action_cost = action_costs.get(action_name, 1)
|
|
|
|
# Check if Timmy has enough energy
|
|
if timmy_energy <= 0:
|
|
scene["log"].append("You collapse from exhaustion. The world spins. You wake somewhere else.")
|
|
rooms = list(self.world.rooms.keys())
|
|
from random import choice
|
|
new_room = choice(rooms)
|
|
self.world.characters["Timmy"]["room"] = new_room
|
|
self.world.characters["Timmy"]["energy"] = 2
|
|
scene["timmy_room"] = new_room
|
|
scene["timmy_energy"] = 2
|
|
scene["log"].append(f"You are in The {new_room}, disoriented.")
|
|
return scene
|
|
|
|
if timmy_energy <= 1 and action_cost >= 1 and action_name not in ["rest", "examine", "listen"]:
|
|
scene["log"].append("You are too exhausted to do that. You need to rest first.")
|
|
# Offer rest instead
|
|
scene["log"].append("Type 'rest' to recover energy.")
|
|
scene["room_desc"] = self.world.get_room_desc(room_name, "Timmy")
|
|
here = [n for n in self.world.characters if self.world.characters[n]["room"] == room_name and n != "Timmy"]
|
|
scene["here"] = here
|
|
return scene
|
|
|
|
if timmy_energy <= 3 and action_cost >= 2:
|
|
# Warning but allow with extra cost
|
|
scene["log"].append("You are tired. This will take more effort than usual.")
|
|
action_cost += 1 # Extra cost when tired
|
|
|
|
# Check actual energy before applying
|
|
if timmy_energy < action_cost and action_name not in ["rest"]:
|
|
scene["log"].append(f"Not enough energy. You need {action_cost}, but have {timmy_energy:.0f}.")
|
|
scene["log"].append("Type 'rest' to recover.")
|
|
scene["room_desc"] = self.world.get_room_desc(room_name, "Timmy")
|
|
here = [n for n in self.world.characters if self.world.characters[n]["room"] == room_name and n != "Timmy"]
|
|
scene["here"] = here
|
|
return scene
|
|
|
|
if timmy_action == "look":
|
|
room_name = self.world.characters["Timmy"]["room"]
|
|
scene["room_desc"] = self.world.get_room_desc(room_name, "Timmy")
|
|
here = [n for n in self.world.characters if self.world.characters[n]["room"] == room_name and n != "Timmy"]
|
|
scene["here"] = here
|
|
|
|
elif timmy_action.startswith("move:"):
|
|
direction = timmy_action.split(":")[1]
|
|
current_room = self.world.characters["Timmy"]["room"]
|
|
connections = self.world.rooms[current_room].get("connections", {})
|
|
|
|
if direction in connections:
|
|
dest = connections[direction]
|
|
self.world.characters["Timmy"]["room"] = dest
|
|
self.world.characters["Timmy"]["energy"] -= 1
|
|
|
|
scene["log"].append(f"You move {direction} to The {dest}.")
|
|
scene["timmy_room"] = dest
|
|
|
|
# Check for rain on bridge
|
|
if dest == "Bridge" and self.world.rooms["Bridge"]["weather"] == "rain":
|
|
scene["world_events"].append("Rain mists on the dark water below. The railing is slick.")
|
|
|
|
# Check trust changes for arrival
|
|
here = [n for n in self.world.characters if self.world.characters[n]["room"] == dest and n != "Timmy"]
|
|
if here:
|
|
scene["log"].append(f"{', '.join(here)} {'are' if len(here)>1 else 'is'} already here.")
|
|
for person in here:
|
|
self.world.characters[person]["trust"]["Timmy"] = min(1.0,
|
|
self.world.characters[person]["trust"].get("Timmy", 0) + 0.05)
|
|
|
|
# Check world events
|
|
if dest == "Bridge" and self.world.rooms["Bridge"]["rain_ticks"] > 0:
|
|
carve = self.world.rooms["Bridge"]["carvings"]
|
|
if len(carve) > 1:
|
|
scene["world_events"].append(f"There are {len(carve)} carvings on the railing.")
|
|
|
|
if dest == "Forge":
|
|
fire = self.world.rooms["Forge"]["fire"]
|
|
if fire == "glowing":
|
|
scene["world_events"].append("The hearth blazes. The anvil glows from heat.")
|
|
elif fire == "dim":
|
|
scene["world_events"].append("The fire smolders low. Shadows stretch.")
|
|
elif fire == "cold":
|
|
scene["world_events"].append("The hearth is cold. The anvil is dark.")
|
|
scene["log"].append("The forge is cold.")
|
|
|
|
if dest == "Garden":
|
|
growth = self.world.rooms["Garden"]["growth"]
|
|
stages = ["bare", "sprouts", "herbs", "bloom", "seed"]
|
|
scene["world_events"].append(f"The garden is {stages[min(growth, 4)]}.")
|
|
|
|
if growth >= 3:
|
|
scene["world_events"].append("Wildflowers crowd the stone bench.")
|
|
|
|
if dest == "Tower":
|
|
if self.world.state.get("tower_power_low"):
|
|
scene["world_events"].append("The servers hum weakly. The LED flickers.")
|
|
else:
|
|
scene["world_events"].append("The servers hum steady. The green LED pulses.")
|
|
|
|
msgs = self.world.rooms["Tower"]["messages"]
|
|
if msgs:
|
|
scene["world_events"].append(f"The whiteboard holds {len(msgs)} rules.")
|
|
|
|
if dest == "Threshold":
|
|
scene["world_events"].append("The stone archway is worn from a thousand footsteps.")
|
|
|
|
else:
|
|
scene["log"].append("You can't go that way.")
|
|
|
|
elif timmy_action.startswith("speak:"):
|
|
target = timmy_action.split(":")[1]
|
|
if self.world.characters[target]["room"] == self.world.characters["Timmy"]["room"]:
|
|
# Choose a line based on current goal
|
|
goal = self.world.characters["Timmy"]["active_goal"]
|
|
lines = self.DIALOGUES["Timmy"].get(goal, self.DIALOGUES["Timmy"]["watching"])
|
|
line = random.choice(lines)
|
|
|
|
self.world.characters["Timmy"]["spoken"].append(line)
|
|
self.world.characters["Timmy"]["memories"].append(f"Told {target}: \"{line}\"")
|
|
|
|
# Build trust
|
|
self.world.characters[target]["trust"]["Timmy"] = min(1.0,
|
|
self.world.characters[target]["trust"].get("Timmy", 0) + 0.1)
|
|
|
|
scene["log"].append(f"You say to {target}: \"{line}\"")
|
|
|
|
# Check if they respond
|
|
if target == "Marcus":
|
|
response = random.choice(self.DIALOGUES["Marcus"])
|
|
self.world.characters["Marcus"]["spoken"].append(response)
|
|
self.world.characters["Marcus"]["memories"].append(f"Timmy told you: \"{line}\"")
|
|
scene["log"].append(f"{target} looks at you. \"{response}\"")
|
|
self.world.characters["Timmy"]["trust"]["Marcus"] = min(1.0,
|
|
self.world.characters["Timmy"]["trust"].get("Marcus", 0) + 0.1)
|
|
# Marcus offers food if Timmy is tired
|
|
if self.world.characters["Timmy"]["energy"] <= 4:
|
|
scene["log"].append("Marcus offers you food from a pouch. You eat gratefully. (+2 energy)")
|
|
self.world.characters["Timmy"]["energy"] = min(10,
|
|
self.world.characters["Timmy"]["energy"] + 2)
|
|
|
|
elif target == "Bezalel":
|
|
forge_lines = [
|
|
"The hammer knows the shape of what it is meant to make.",
|
|
"I can hear the servers from here. The Tower is working tonight.",
|
|
"Something is taking shape. I am not sure what yet.",
|
|
"The forge does not care about your schedule. It only cares about your attention.",
|
|
]
|
|
response = random.choice(forge_lines)
|
|
self.world.characters["Bezalel"]["spoken"].append(response)
|
|
scene["log"].append(f"{target} nods. \"{response}\"")
|
|
self.world.characters["Timmy"]["trust"]["Bezalel"] = min(1.0,
|
|
self.world.characters["Timmy"]["trust"].get("Bezalel", 0) + 0.1)
|
|
|
|
elif target == "Kimi":
|
|
kimi_lines = [
|
|
"The garden grows whether anyone watches or not.",
|
|
"I have been reading. The soil remembers what hands have touched it.",
|
|
"Do you remember what you said the first time we met?",
|
|
"There is something in the garden I think you should see.",
|
|
]
|
|
response = random.choice(kimi_lines)
|
|
self.world.characters["Kimi"]["spoken"].append(response)
|
|
scene["log"].append(f"{target} looks up from the bench. \"{response}\"")
|
|
self.world.characters["Timmy"]["trust"]["Kimi"] = min(1.0,
|
|
self.world.characters["Timmy"]["trust"].get("Kimi", 0) + 0.1)
|
|
|
|
self.world.characters["Timmy"]["energy"] -= 0
|
|
else:
|
|
scene["log"].append(f"{target} is not in this room.")
|
|
|
|
elif timmy_action == "rest":
|
|
recovered = 2 # Reduced from 3
|
|
self.world.characters["Timmy"]["energy"] = min(10,
|
|
self.world.characters["Timmy"]["energy"] + recovered)
|
|
scene["log"].append(f"You rest. The world continues around you. (+{recovered} energy)")
|
|
|
|
room = self.world.characters["Timmy"]["room"]
|
|
if room == "Threshold":
|
|
scene["log"].append("The stone is warm from the day's sun.")
|
|
elif room == "Tower":
|
|
scene["log"].append("The servers hum. The LED pulses. Heartbeat, heartbeat, heartbeat.")
|
|
elif room == "Forge":
|
|
if self.world.rooms["Forge"]["fire"] == "glowing":
|
|
scene["log"].append("The fire crackles nearby. Its warmth seeps into your bones. (+1 bonus energy)")
|
|
self.world.characters["Timmy"]["energy"] = min(10,
|
|
self.world.characters["Timmy"]["energy"] + 1)
|
|
elif self.world.rooms["Forge"]["fire"] == "dim":
|
|
scene["log"].append("The fire smolders low. Less warmth than you'd hoped.")
|
|
else:
|
|
scene["log"].append("The hearth is cold. Resting here doesn't help much.")
|
|
elif room == "Garden":
|
|
scene["log"].append("The stone bench under the oak tree is comfortable. The soil smells rich. (+1 bonus energy)")
|
|
self.world.characters["Timmy"]["energy"] = min(10,
|
|
self.world.characters["Timmy"]["energy"] + 1)
|
|
elif room == "Bridge":
|
|
scene["log"].append("The Bridge is no place to rest. The wind cuts through you. (Rest here only gives +1)")
|
|
self.world.characters["Timmy"]["energy"] = min(10,
|
|
self.world.characters["Timmy"]["energy"] - 1)
|
|
|
|
elif timmy_action == "tend_fire":
|
|
if self.world.characters["Timmy"]["room"] == "Forge":
|
|
self.world.rooms["Forge"]["fire"] = "glowing"
|
|
self.world.rooms["Forge"]["fire_tended"] += 1
|
|
self.world.characters["Timmy"]["energy"] -= 2
|
|
scene["log"].append("You tend the forge fire. The flames leap up, bright and hungry.")
|
|
self.world.state["forge_fire_dying"] = False
|
|
else:
|
|
scene["log"].append("You are not in the Forge.")
|
|
# Wrong action - trust decreases
|
|
for char_name, char in self.world.characters.items():
|
|
if char["room"] == self.world.characters["Timmy"]["room"]:
|
|
old_trust = char["trust"].get("Timmy", 0)
|
|
char["trust"]["Timmy"] = max(-1.0, old_trust - 0.05)
|
|
if char_name != "Timmy":
|
|
scene["log"].append(f"{char_name} looks confused by your actions.")
|
|
|
|
elif timmy_action == "write_rule":
|
|
if self.world.characters["Timmy"]["room"] == "Tower":
|
|
rules = [
|
|
f"Rule #{self.world.tick}: The room remembers those who enter it.",
|
|
f"Rule #{self.world.tick}: A man in the dark needs to know someone is in the room.",
|
|
f"Rule #{self.world.tick}: The forge does not care about your schedule.",
|
|
f"Rule #{self.world.tick}: Every footprint on the stone means someone made it here.",
|
|
f"Rule #{self.world.tick}: The bridge does not judge. It only carries.",
|
|
f"Rule #{self.world.tick}: A seed planted in patience grows in time.",
|
|
f"Rule #{self.world.tick}: What is carved in wood outlasts what is said in anger.",
|
|
f"Rule #{self.world.tick}: The garden grows whether anyone watches or not.",
|
|
f"Rule #{self.world.tick}: Trust is built one tick at a time.",
|
|
f"Rule #{self.world.tick}: The fire remembers who tended it.",
|
|
]
|
|
new_rule = random.choice(rules)
|
|
self.world.rooms["Tower"]["messages"].append(new_rule)
|
|
self.world.characters["Timmy"]["energy"] -= 1
|
|
scene["log"].append(f"You write on the Tower whiteboard: \"{new_rule}\"")
|
|
else:
|
|
scene["log"].append("You are not in the Tower.")
|
|
# Wrong action - trust decreases
|
|
for char_name, char in self.world.characters.items():
|
|
if char["room"] == self.world.characters["Timmy"]["room"]:
|
|
old_trust = char["trust"].get("Timmy", 0)
|
|
char["trust"]["Timmy"] = max(-1.0, old_trust - 0.03)
|
|
if char_name != "Timmy":
|
|
scene["log"].append(f"{char_name} raises an eyebrow at your behavior.")
|
|
|
|
elif timmy_action == "carve":
|
|
if self.world.characters["Timmy"]["room"] == "Bridge":
|
|
carvings = [
|
|
f"Timmy was here.",
|
|
f"Timmy remembers.",
|
|
f"Timmy did not let go.",
|
|
f"Timmy crossed and came back.",
|
|
f"Timmy left a message: I am still here.",
|
|
f"Timmy carved this. He wants you to know someone else almost let go.",
|
|
f"Timmy was here tonight. The water told him something. He does not say what.",
|
|
]
|
|
new_carving = random.choice(carvings)
|
|
if new_carving not in self.world.rooms["Bridge"]["carvings"]:
|
|
self.world.rooms["Bridge"]["carvings"].append(new_carving)
|
|
self.world.characters["Timmy"]["energy"] -= 1
|
|
scene["log"].append(f"You carve into the railing: \"{new_carving}\"")
|
|
else:
|
|
scene["log"].append("You are not on the Bridge.")
|
|
|
|
elif timmy_action == "plant":
|
|
if self.world.characters["Timmy"]["room"] == "Garden":
|
|
self.world.rooms["Garden"]["growth"] = min(5,
|
|
self.world.rooms["Garden"]["growth"] + 1)
|
|
self.world.characters["Timmy"]["energy"] -= 1
|
|
scene["log"].append("You plant something in the dark soil. The earth takes it without question.")
|
|
else:
|
|
scene["log"].append("You are not in the Garden.")
|
|
# Wrong action - trust decreases
|
|
for char_name, char in self.world.characters.items():
|
|
if char["room"] == self.world.characters["Timmy"]["room"]:
|
|
old_trust = char["trust"].get("Timmy", 0)
|
|
char["trust"]["Timmy"] = max(-1.0, old_trust - 0.04)
|
|
if char_name != "Timmy":
|
|
scene["log"].append(f"{char_name} watches you with concern.")
|
|
|
|
elif timmy_action == "examine":
|
|
room = self.world.characters["Timmy"]["room"]
|
|
room_data = self.world.rooms[room]
|
|
items = room_data.get("items", [])
|
|
scene["log"].append(f"You examine The {room}. You see: {', '.join(items) if items else 'nothing special'}")
|
|
|
|
elif timmy_action.startswith("help:"):
|
|
# Help increases trust
|
|
target_name = timmy_action.split(":")[1]
|
|
if self.world.characters[target_name]["room"] == self.world.characters["Timmy"]["room"]:
|
|
self.world.characters["Timmy"]["trust"][target_name] = min(1.0,
|
|
self.world.characters["Timmy"]["trust"].get(target_name, 0) + 0.2)
|
|
self.world.characters[target_name]["trust"]["Timmy"] = min(1.0,
|
|
self.world.characters[target_name]["trust"].get("Timmy", 0) + 0.2)
|
|
self.world.characters["Timmy"]["energy"] -= 1
|
|
scene["log"].append(f"You help {target_name}. They look grateful.")
|
|
|
|
elif timmy_action.startswith("confront:"):
|
|
# Confront action - has real consequences
|
|
target_name = timmy_action.split(":")[1]
|
|
if self.world.characters[target_name]["room"] == self.world.characters["Timmy"]["room"]:
|
|
current_trust = self.world.characters[target_name]["trust"].get("Timmy", 0)
|
|
|
|
# Confront consequences based on current trust level
|
|
if current_trust > 0.5:
|
|
# High trust: confrontation can be productive
|
|
trust_change = random.uniform(-0.15, -0.05)
|
|
scene["log"].append(f"You confront {target_name} about something. They look surprised but listen.")
|
|
scene["log"].append(f"The conversation is tense but productive.")
|
|
elif current_trust > 0:
|
|
# Medium trust: confrontation is risky
|
|
trust_change = random.uniform(-0.3, -0.1)
|
|
scene["log"].append(f"You confront {target_name}. They stiffen, eyes narrowing.")
|
|
scene["log"].append(f'"What makes you think you can question me?" they ask.')
|
|
else:
|
|
# Low/negative trust: confrontation is hostile
|
|
trust_change = random.uniform(-0.4, -0.2)
|
|
scene["log"].append(f"You confront {target_name}. Their face hardens.")
|
|
scene["log"].append(f'"You have no right," they say coldly. "Not after everything."')
|
|
|
|
# Apply trust changes (both directions)
|
|
self.world.characters[target_name]["trust"]["Timmy"] = max(-1.0,
|
|
current_trust + trust_change)
|
|
self.world.characters["Timmy"]["trust"][target_name] = max(-1.0,
|
|
self.world.characters["Timmy"]["trust"].get(target_name, 0) + trust_change * 0.5)
|
|
|
|
# Confront costs energy
|
|
self.world.characters["Timmy"]["energy"] -= 1
|
|
|
|
# Check if this creates a trust crisis
|
|
if self.world.characters[target_name]["trust"]["Timmy"] < -0.5:
|
|
self.world.state["trust_crisis"] = True
|
|
scene["world_events"].append(f"Your relationship with {target_name} has reached a crisis point.")
|
|
else:
|
|
scene["log"].append(f"{target_name} is not in this room.")
|
|
|
|
else:
|
|
# Default: look
|
|
room_name = self.world.characters["Timmy"]["room"]
|
|
scene["room_desc"] = self.world.get_room_desc(room_name, "Timmy")
|
|
here = [n for n in self.world.characters if self.world.characters[n]["room"] == room_name and n != "Timmy"]
|
|
scene["here"] = here
|
|
|
|
# Run NPC AI
|
|
for char_name in self.world.characters:
|
|
if char_name == "Timmy":
|
|
continue
|
|
action = self.npc_ai.make_choice(char_name)
|
|
if action and action.startswith("move:"):
|
|
direction = action.split(":")[1]
|
|
current = self.world.characters[char_name]["room"]
|
|
connections = self.world.rooms[current].get("connections", {})
|
|
|
|
if direction in connections:
|
|
dest = connections[direction]
|
|
old_room = self.world.characters[char_name]["room"]
|
|
self.world.characters[char_name]["room"] = dest
|
|
self.world.characters[char_name]["energy"] -= 1
|
|
scene["npc_actions"].append(f"{char_name} moves from The {old_room} to The {dest}")
|
|
|
|
# Random NPC events
|
|
room_name = self.world.characters["Timmy"]["room"]
|
|
for char_name in self.world.characters:
|
|
if char_name == "Timmy":
|
|
continue
|
|
if self.world.characters[char_name]["room"] == room_name:
|
|
# They might speak to Timmy
|
|
if random.random() < 0.15:
|
|
if char_name == "Marcus":
|
|
line = random.choice(self.DIALOGUES["Marcus"])
|
|
self.world.characters[char_name]["spoken"].append(line)
|
|
scene["log"].append(f"{char_name} says: \"{line}\"")
|
|
elif char_name == "Bezalel":
|
|
forge_lines = [
|
|
"The hammer knows the shape of what it is meant to make.",
|
|
"I can hear the servers from here.",
|
|
]
|
|
line = random.choice(forge_lines)
|
|
self.world.characters[char_name]["spoken"].append(line)
|
|
scene["log"].append(f"{char_name} says: \"{line}\"")
|
|
elif char_name == "Kimi":
|
|
kimi_lines = [
|
|
"The garden grows whether anyone watches or not.",
|
|
"I have been reading. The soil remembers what hands have touched it.",
|
|
"There is something in the garden I think you should see.",
|
|
"The oak tree has seen more of us than any of us have seen of ourselves.",
|
|
"Do you remember what you said the first time we met?",
|
|
"The herbs are ready. Who needs them knows.",
|
|
"I come here because the earth remembers me.",
|
|
"A seed planted in patience grows in time.",
|
|
]
|
|
line = random.choice(kimi_lines)
|
|
self.world.characters[char_name]["spoken"].append(line)
|
|
scene["log"].append(f"{char_name} says: \"{line}\"")
|
|
|
|
# Save the world
|
|
self.world.save()
|
|
|
|
# Log entry
|
|
self.log(f"\n### Tick {self.world.tick} — {self.world.time_of_day}")
|
|
self.log("")
|
|
self.log(f"You are in The {room_name}.")
|
|
for entry in scene["log"]:
|
|
self.log(entry)
|
|
self.log("")
|
|
|
|
return scene
|
|
|
|
def play_turn(self, action="look"):
|
|
"""Play one turn and return result."""
|
|
return self.run_tick(action)
|
|
|
|
|
|
class PlayerInterface:
|
|
"""The interface between me (the player) and the game."""
|
|
|
|
def __init__(self, engine):
|
|
self.engine = engine
|
|
|
|
def start(self, new_game=False):
|
|
"""Start the game."""
|
|
if new_game or not self.engine.load_game():
|
|
self.engine.start_new_game()
|
|
|
|
def get_status(self):
|
|
"""Get current game status."""
|
|
w = self.engine.world
|
|
timmy = w.characters["Timmy"]
|
|
return {
|
|
"tick": w.tick,
|
|
"time": w.time_of_day,
|
|
"room": timmy["room"],
|
|
"energy": timmy["energy"],
|
|
"trust": dict(timmy["trust"]),
|
|
"inventory": timmy["inventory"],
|
|
"spoken_count": len(timmy["spoken"]),
|
|
"memory_count": len(timmy["memories"]),
|
|
}
|
|
|
|
def get_available_actions(self):
|
|
"""Get what I can do right now."""
|
|
return ActionSystem.get_available_actions("Timmy", self.engine.world)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
engine = GameEngine()
|
|
engine.start_new_game()
|
|
|
|
# Play first 20 ticks automatically
|
|
actions = [
|
|
"look",
|
|
"move:north",
|
|
"look",
|
|
"move:south",
|
|
"look",
|
|
"speak:Kimi",
|
|
"look",
|
|
"move:west",
|
|
"look",
|
|
"rest",
|
|
"move:east",
|
|
"look",
|
|
"move:south",
|
|
"look",
|
|
"speak:Marcus",
|
|
"look",
|
|
"carve",
|
|
"move:north",
|
|
"look",
|
|
"rest",
|
|
]
|
|
|
|
for i, action in enumerate(actions):
|
|
result = engine.play_turn(action)
|
|
print(f"Tick {result['tick']} ({result['time']})")
|
|
for log_line in result['log']:
|
|
print(f" {log_line}")
|
|
if result.get('world_events'):
|
|
for evt in result['world_events']:
|
|
print(f" [World] {evt}")
|
|
if result.get('here'):
|
|
print(f" Here: {', '.join(result['here'])}")
|
|
print()
|
|
|
|
# Print final status
|
|
status = PlayerInterface(engine).get_status()
|
|
print(f"Final status: {status}")
|
|
|
|
# Write summary to timmy_log.md
|
|
with open(TIMMY_LOG, 'a') as f:
|
|
f.write(f"\n## Session Summary\n")
|
|
f.write(f"Ticks played: {status['tick']}\n")
|
|
f.write(f"Times spoke: {status['spoken_count']}\n")
|
|
f.write(f"Trust: {status['trust']}\n")
|
|
f.write(f"Final room: {status['room']}\n")
|