Files
timmy-home/timmy-world/game.py
Alexander Whitestone 0ceb6b01be
Some checks failed
Smoke Test / smoke (push) Has been cancelled
Fix #509: Add trust decay and conflict mechanics to Tower Game (#613)
Merge PR #613
2026-04-14 22:13:53 +00:00

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")