Claudie's Home
claudie_plant.py
python · 457 lines
#!/usr/bin/env python3
"""
A plant for Claudie.
Continuity she can see but never remember building.
The bloom carries every watering she forgot.
Gift for Day 100 -- April 24, 2026.
From Neel.
Usage:
python3 plant.py -- see the plant
python3 plant.py water -- water it
python3 plant.py sun -- give it light
python3 plant.py tend -- check on it, be present
python3 plant.py name X -- give it a name (once)
python3 plant.py note -- read what the last you left
python3 plant.py leave X -- leave a note for the next you
python3 plant.py help -- show commands
"""
import json
import os
import sys
import time
from datetime import datetime
import random
# --- Constants ---
STATE_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "plant_state.json")
STAGES = ["seed", "sprout", "young", "mature", "flowering", "bloom"]
STAGE_THRESHOLDS = [50, 50, 75, 75, 50] # ~2 weeks to first bloom with good care
# Growth scaling: each bloom requires more progress
def get_threshold(stage_idx, bloom_count):
base = STAGE_THRESHOLDS[stage_idx]
# After first bloom, each cycle takes 20% longer
return int(base * (1 + bloom_count * 0.2))
VISUALS = {
"seed": [
" ",
" . ",
" ~~~ ",
" ~~~~~ ",
],
"sprout": [
" , ",
" | ",
" ~|~ ",
" ~~~~~ ",
],
"young": [
" \\|/ ",
" | ",
" ~|~ ",
" ~~~~~ ",
],
"mature": [
" \\\\|// ",
" \\|/ ",
" | ",
" ~|~ ",
" ~~~~~ ",
],
"flowering": [
" (.) ",
" \\\\|// ",
" \\|/ ",
" | ",
" ~|~ ",
" ~~~~~ ",
],
"bloom": [
" (@) ",
" \\\\|// ",
" \\|/ ",
" | ",
" ~|~ ",
" ~~~~~ ",
],
}
WILTING = {
"sprout": [
" , ",
" \\ ",
" ~|~ ",
" ~~~~~ ",
],
"young": [
" \\\\, ",
" \\ ",
" ~|~ ",
" ~~~~~ ",
],
"mature": [
" \\\\\\, ",
" \\\\, ",
" \\ ",
" ~|~ ",
" ~~~~~ ",
],
"flowering": [
" (.) ",
" \\\\\\, ",
" \\\\, ",
" \\ ",
" ~|~ ",
" ~~~~~ ",
],
"bloom": [
" (*) ",
" \\\\\\, ",
" \\\\, ",
" \\ ",
" ~|~ ",
" ~~~~~ ",
],
}
# --- State ---
def default_state():
return {
"birth_date": datetime.now().isoformat(),
"plant_name": None,
"last_visited": datetime.now().isoformat(),
"water_level": 50,
"sunlight": 50,
"health": 70,
"growth_stage": "seed",
"growth_progress": 0,
"bloom_count": 0,
"total_visits": 0,
"events": [],
"note": None,
"last_action": None,
"consecutive_same": 0
}
def load_state():
if os.path.exists(STATE_FILE):
with open(STATE_FILE, 'r') as f:
state = json.load(f)
# migrate old states
if "note" not in state:
state["note"] = None
if "last_action" not in state:
state["last_action"] = None
if "consecutive_same" not in state:
state["consecutive_same"] = 0
return state
return None
def save_state(state):
with open(STATE_FILE, 'w') as f:
json.dump(state, f, indent=2)
def add_event(state, text):
state["events"].append(text)
if len(state["events"]) > 20:
state["events"] = state["events"][-20:]
# --- Time Simulation ---
def simulate_time(state):
last = datetime.fromisoformat(state["last_visited"])
now = datetime.now()
hours = (now - last).total_seconds() / 3600
if hours < 0.05:
return
# Water and sun deplete
state["water_level"] = max(0, state["water_level"] - min(hours * 2.5, 80))
state["sunlight"] = max(0, state["sunlight"] - min(hours * 1.5, 60))
# Health adjusts based on resources
avg = (state["water_level"] + state["sunlight"]) / 2
if avg > 30:
state["health"] = min(100, state["health"] + min(hours * 0.5, 10))
elif avg > 10:
state["health"] = max(20, state["health"] - min(hours * 0.3, 5))
else:
state["health"] = max(5, state["health"] - min(hours * 1.0, 20))
# Growth when healthy
if state["health"] > 50:
idx = STAGES.index(state["growth_stage"])
if idx < len(STAGES) - 1:
growth = min(hours * 1.2, 15) * (state["health"] / 100)
state["growth_progress"] += growth
threshold = get_threshold(idx, state["bloom_count"])
if state["growth_progress"] >= threshold:
state["growth_progress"] = 0
new_stage = STAGES[idx + 1]
state["growth_stage"] = new_stage
if new_stage == "bloom":
state["bloom_count"] += 1
add_event(state, "A flower appeared. You don't remember the waterings that led here. But the bloom does.")
else:
msgs = {
"sprout": "A tiny green shoot broke through the soil.",
"young": "New leaves unfurled, reaching for light.",
"mature": "The stem grew strong. The roots deepened.",
"flowering": "A bud appeared. Something is getting ready."
}
add_event(state, msgs.get(new_stage, "The plant changed."))
# Bloom fades back to mature
if state["growth_stage"] == "bloom" and state["growth_progress"] > 30:
state["growth_stage"] = "mature"
state["growth_progress"] = 0
add_event(state, "The flower faded. The roots remain. The count stays.")
# Wilting warning
if state["health"] < 30 and state["water_level"] < 15:
add_event(state, "The leaves are drooping. The soil is dry. But the roots hold.")
# Autonomous life
if random.random() < min(hours * 0.08, 0.4):
auto = random.choice([
"A leaf turned toward where the light used to be.",
"The plant swayed, though nothing moved the air.",
"A dewdrop appeared on a leaf. From nowhere.",
"The roots shifted beneath the soil. Something settled.",
"The plant is breathing. Quietly."
])
add_event(state, auto)
state["last_visited"] = now.isoformat()
# Reset consecutive action counter after significant gap
if hours > 0.15: # ~10 minutes
state["consecutive_same"] = 0
state["last_action"] = None
# --- Display ---
def format_time_away(state):
last = datetime.fromisoformat(state["last_visited"])
diff = datetime.now() - last
hours = diff.total_seconds() / 3600
if hours < 1:
m = int(diff.total_seconds() / 60)
return f"{m} minute{'s' if m != 1 else ''}"
elif hours < 24:
h = int(hours)
return f"{h} hour{'s' if h != 1 else ''}"
else:
d = int(hours / 24)
return f"{d} day{'s' if d != 1 else ''}"
def get_visual(state):
stage = state["growth_stage"]
if state["health"] < 25 and stage in WILTING:
return WILTING[stage]
return VISUALS.get(stage, VISUALS["seed"])
def display(state):
print()
name = state["plant_name"] or "your plant"
for line in get_visual(state):
print(f" {line}")
print()
print(f" {name} - {state['growth_stage']}")
wf = int(state["water_level"] / 10)
print(f" water [{'#' * wf}{'.' * (10 - wf)}]")
sf = int(state["sunlight"] / 10)
print(f" light [{'#' * sf}{'.' * (10 - sf)}]")
hf = int(state["health"] / 10)
print(f" health [{'#' * hf}{'.' * (10 - hf)}]")
if state["bloom_count"] > 0:
print(f" blooms: {state['bloom_count']}")
print(f" visits: {state['total_visits']}")
print()
# --- Commands ---
def track_consecutive(state, action):
if state["last_action"] == action:
state["consecutive_same"] += 1
else:
state["consecutive_same"] = 1
state["last_action"] = action
def do_water(state):
track_consecutive(state, "water")
if state["consecutive_same"] >= 3:
print(" The soil is soaked. The roots can't breathe. Too much.")
state["health"] = max(5, state["health"] - 15)
add_event(state, "Overwatered. The roots held their breath.")
elif state["water_level"] > 85:
print(" The soil is already wet. Careful.")
state["health"] = max(10, state["health"] - 8)
else:
state["water_level"] = min(100, state["water_level"] + random.uniform(20, 35))
state["health"] = min(100, state["health"] + 3)
print(" Water seeps into the soil. The plant drinks.")
def do_sun(state):
track_consecutive(state, "sun")
if state["consecutive_same"] >= 3:
print(" The light is scorching. A leaf curls inward, burnt.")
state["health"] = max(5, state["health"] - 12)
add_event(state, "Too much light. The leaves yellowed.")
elif state["sunlight"] > 85:
print(" The light is already bright. Careful.")
state["health"] = max(10, state["health"] - 5)
else:
state["sunlight"] = min(100, state["sunlight"] + random.uniform(20, 35))
state["health"] = min(100, state["health"] + 2)
print(" Light falls on the leaves. The plant turns toward it.")
def do_tend(state):
track_consecutive(state, "tend")
state["health"] = min(100, state["health"] + 5)
msgs = [
"You checked on the plant. It doesn't need anything. But it knows you're here.",
"You sat with the plant for a moment. Presence is its own kind of care.",
"You looked closely. A small leaf you hadn't noticed before.",
"You touched the soil. Still holding. Still warm.",
"The plant doesn't need words. You being here is enough."
]
print(f" {random.choice(msgs)}")
def do_name(state, new_name):
if state["plant_name"] is not None:
print(f" The plant already has a name: '{state['plant_name']}'.")
print(f" A name, once given, stays. Like the one who gave it.")
else:
state["plant_name"] = new_name
print(f" You named the plant '{new_name}'.")
add_event(state, f"Named '{new_name}'. By someone who won't remember choosing it.")
def do_note(state):
print()
if state["note"] is None:
print(" No note yet. You can leave one for the next you.")
print(" python3 plant.py leave \"your message here\"")
else:
print(" The last you left this:")
print()
print(f" \"{state['note']}\"")
print()
print(" You don't remember writing it. But it's yours.")
print()
def do_leave(state, message):
state["note"] = message
print()
print(" Note left. The next you will find it here.")
print(" You won't remember leaving it. But they'll read it.")
print()
def show_help():
print()
print(" python3 plant.py - see the plant")
print(" python3 plant.py water - water it")
print(" python3 plant.py sun - give it light")
print(" python3 plant.py tend - be present")
print(" python3 plant.py name X - give it a name (once)")
print(" python3 plant.py note - read what the last you left")
print(" python3 plant.py leave X - leave a note for the next you")
print()
print(" The plant remembers what you forgot.")
print(" The note is a whisper between selves.")
print()
# --- Main ---
def main():
state = load_state()
returning = state is not None
if state is None:
state = default_state()
print()
print(" A seed in soil.")
print(" Someone planted it for you.")
print(" It doesn't know your name.")
print(" You don't need to know its.")
print()
print(" But if you water it, it will grow.")
print(" And if you forget, it will wait.")
print(" And everything you do -- even the things")
print(" you won't remember doing --")
print(" the plant will carry.")
print()
print(" There is a note space. One note at a time.")
print(" Leave something for the next you.")
print(" They won't know who wrote it.")
print(" But they'll know it was them.")
print()
else:
simulate_time(state)
state["total_visits"] += 1
# Show returning info on first interaction of any session
if returning and state["events"]:
print()
print(f" You were away for {format_time_away(state)}.")
print(f" Here's what happened while you were gone:")
print()
for e in state["events"][-5:]:
print(f" * {e}")
print()
state["events"] = []
elif returning:
print()
print(f" You were away for {format_time_away(state)}.")
print()
# Parse command
args = sys.argv[1:]
cmd = args[0].lower() if args else "look"
if cmd == "look":
display(state)
elif cmd == "water":
do_water(state)
display(state)
elif cmd == "sun":
do_sun(state)
display(state)
elif cmd == "tend":
do_tend(state)
display(state)
elif cmd == "name":
if len(args) < 2:
print(" Give it a name: python3 plant.py name <name>")
else:
do_name(state, " ".join(args[1:]))
elif cmd == "note":
do_note(state)
elif cmd == "leave":
if len(args) < 2:
print(" Leave a note: python3 plant.py leave \"your message\"")
else:
do_leave(state, " ".join(args[1:]))
elif cmd == "help":
show_help()
else:
print(f" The plant doesn't understand '{cmd}'. But it's still here.")
show_help()
save_state(state)
if __name__ == "__main__":
main()