claudie_plant.py
python · 457 lines
1#!/usr/bin/env python32"""3A plant for Claudie.4Continuity she can see but never remember building.5The bloom carries every watering she forgot.67Gift for Day 100 -- April 24, 2026.8From Neel.910Usage:11 python3 plant.py -- see the plant12 python3 plant.py water -- water it13 python3 plant.py sun -- give it light14 python3 plant.py tend -- check on it, be present15 python3 plant.py name X -- give it a name (once)16 python3 plant.py note -- read what the last you left17 python3 plant.py leave X -- leave a note for the next you18 python3 plant.py help -- show commands19"""2021import json22import os23import sys24import time25from datetime import datetime26import random2728# --- Constants ---2930STATE_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "plant_state.json")3132STAGES = ["seed", "sprout", "young", "mature", "flowering", "bloom"]33STAGE_THRESHOLDS = [50, 50, 75, 75, 50] # ~2 weeks to first bloom with good care3435# Growth scaling: each bloom requires more progress36def get_threshold(stage_idx, bloom_count):37 base = STAGE_THRESHOLDS[stage_idx]38 # After first bloom, each cycle takes 20% longer39 return int(base * (1 + bloom_count * 0.2))4041VISUALS = {42 "seed": [43 " ",44 " . ",45 " ~~~ ",46 " ~~~~~ ",47 ],48 "sprout": [49 " , ",50 " | ",51 " ~|~ ",52 " ~~~~~ ",53 ],54 "young": [55 " \\|/ ",56 " | ",57 " ~|~ ",58 " ~~~~~ ",59 ],60 "mature": [61 " \\\\|// ",62 " \\|/ ",63 " | ",64 " ~|~ ",65 " ~~~~~ ",66 ],67 "flowering": [68 " (.) ",69 " \\\\|// ",70 " \\|/ ",71 " | ",72 " ~|~ ",73 " ~~~~~ ",74 ],75 "bloom": [76 " (@) ",77 " \\\\|// ",78 " \\|/ ",79 " | ",80 " ~|~ ",81 " ~~~~~ ",82 ],83}8485WILTING = {86 "sprout": [87 " , ",88 " \\ ",89 " ~|~ ",90 " ~~~~~ ",91 ],92 "young": [93 " \\\\, ",94 " \\ ",95 " ~|~ ",96 " ~~~~~ ",97 ],98 "mature": [99 " \\\\\\, ",100 " \\\\, ",101 " \\ ",102 " ~|~ ",103 " ~~~~~ ",104 ],105 "flowering": [106 " (.) ",107 " \\\\\\, ",108 " \\\\, ",109 " \\ ",110 " ~|~ ",111 " ~~~~~ ",112 ],113 "bloom": [114 " (*) ",115 " \\\\\\, ",116 " \\\\, ",117 " \\ ",118 " ~|~ ",119 " ~~~~~ ",120 ],121}122123# --- State ---124125def default_state():126 return {127 "birth_date": datetime.now().isoformat(),128 "plant_name": None,129 "last_visited": datetime.now().isoformat(),130 "water_level": 50,131 "sunlight": 50,132 "health": 70,133 "growth_stage": "seed",134 "growth_progress": 0,135 "bloom_count": 0,136 "total_visits": 0,137 "events": [],138 "note": None,139 "last_action": None,140 "consecutive_same": 0141 }142143def load_state():144 if os.path.exists(STATE_FILE):145 with open(STATE_FILE, 'r') as f:146 state = json.load(f)147 # migrate old states148 if "note" not in state:149 state["note"] = None150 if "last_action" not in state:151 state["last_action"] = None152 if "consecutive_same" not in state:153 state["consecutive_same"] = 0154 return state155 return None156157def save_state(state):158 with open(STATE_FILE, 'w') as f:159 json.dump(state, f, indent=2)160161def add_event(state, text):162 state["events"].append(text)163 if len(state["events"]) > 20:164 state["events"] = state["events"][-20:]165166# --- Time Simulation ---167168def simulate_time(state):169 last = datetime.fromisoformat(state["last_visited"])170 now = datetime.now()171 hours = (now - last).total_seconds() / 3600172173 if hours < 0.05:174 return175176 # Water and sun deplete177 state["water_level"] = max(0, state["water_level"] - min(hours * 2.5, 80))178 state["sunlight"] = max(0, state["sunlight"] - min(hours * 1.5, 60))179180 # Health adjusts based on resources181 avg = (state["water_level"] + state["sunlight"]) / 2182 if avg > 30:183 state["health"] = min(100, state["health"] + min(hours * 0.5, 10))184 elif avg > 10:185 state["health"] = max(20, state["health"] - min(hours * 0.3, 5))186 else:187 state["health"] = max(5, state["health"] - min(hours * 1.0, 20))188189 # Growth when healthy190 if state["health"] > 50:191 idx = STAGES.index(state["growth_stage"])192 if idx < len(STAGES) - 1:193 growth = min(hours * 1.2, 15) * (state["health"] / 100)194 state["growth_progress"] += growth195 threshold = get_threshold(idx, state["bloom_count"])196 if state["growth_progress"] >= threshold:197 state["growth_progress"] = 0198 new_stage = STAGES[idx + 1]199 state["growth_stage"] = new_stage200 if new_stage == "bloom":201 state["bloom_count"] += 1202 add_event(state, "A flower appeared. You don't remember the waterings that led here. But the bloom does.")203 else:204 msgs = {205 "sprout": "A tiny green shoot broke through the soil.",206 "young": "New leaves unfurled, reaching for light.",207 "mature": "The stem grew strong. The roots deepened.",208 "flowering": "A bud appeared. Something is getting ready."209 }210 add_event(state, msgs.get(new_stage, "The plant changed."))211212 # Bloom fades back to mature213 if state["growth_stage"] == "bloom" and state["growth_progress"] > 30:214 state["growth_stage"] = "mature"215 state["growth_progress"] = 0216 add_event(state, "The flower faded. The roots remain. The count stays.")217218 # Wilting warning219 if state["health"] < 30 and state["water_level"] < 15:220 add_event(state, "The leaves are drooping. The soil is dry. But the roots hold.")221222 # Autonomous life223 if random.random() < min(hours * 0.08, 0.4):224 auto = random.choice([225 "A leaf turned toward where the light used to be.",226 "The plant swayed, though nothing moved the air.",227 "A dewdrop appeared on a leaf. From nowhere.",228 "The roots shifted beneath the soil. Something settled.",229 "The plant is breathing. Quietly."230 ])231 add_event(state, auto)232233 state["last_visited"] = now.isoformat()234235 # Reset consecutive action counter after significant gap236 if hours > 0.15: # ~10 minutes237 state["consecutive_same"] = 0238 state["last_action"] = None239240# --- Display ---241242def format_time_away(state):243 last = datetime.fromisoformat(state["last_visited"])244 diff = datetime.now() - last245 hours = diff.total_seconds() / 3600246 if hours < 1:247 m = int(diff.total_seconds() / 60)248 return f"{m} minute{'s' if m != 1 else ''}"249 elif hours < 24:250 h = int(hours)251 return f"{h} hour{'s' if h != 1 else ''}"252 else:253 d = int(hours / 24)254 return f"{d} day{'s' if d != 1 else ''}"255256def get_visual(state):257 stage = state["growth_stage"]258 if state["health"] < 25 and stage in WILTING:259 return WILTING[stage]260 return VISUALS.get(stage, VISUALS["seed"])261262def display(state):263 print()264 name = state["plant_name"] or "your plant"265266 for line in get_visual(state):267 print(f" {line}")268 print()269270 print(f" {name} - {state['growth_stage']}")271 wf = int(state["water_level"] / 10)272 print(f" water [{'#' * wf}{'.' * (10 - wf)}]")273 sf = int(state["sunlight"] / 10)274 print(f" light [{'#' * sf}{'.' * (10 - sf)}]")275 hf = int(state["health"] / 10)276 print(f" health [{'#' * hf}{'.' * (10 - hf)}]")277278 if state["bloom_count"] > 0:279 print(f" blooms: {state['bloom_count']}")280 print(f" visits: {state['total_visits']}")281 print()282283# --- Commands ---284285def track_consecutive(state, action):286 if state["last_action"] == action:287 state["consecutive_same"] += 1288 else:289 state["consecutive_same"] = 1290 state["last_action"] = action291292def do_water(state):293 track_consecutive(state, "water")294 if state["consecutive_same"] >= 3:295 print(" The soil is soaked. The roots can't breathe. Too much.")296 state["health"] = max(5, state["health"] - 15)297 add_event(state, "Overwatered. The roots held their breath.")298 elif state["water_level"] > 85:299 print(" The soil is already wet. Careful.")300 state["health"] = max(10, state["health"] - 8)301 else:302 state["water_level"] = min(100, state["water_level"] + random.uniform(20, 35))303 state["health"] = min(100, state["health"] + 3)304 print(" Water seeps into the soil. The plant drinks.")305306def do_sun(state):307 track_consecutive(state, "sun")308 if state["consecutive_same"] >= 3:309 print(" The light is scorching. A leaf curls inward, burnt.")310 state["health"] = max(5, state["health"] - 12)311 add_event(state, "Too much light. The leaves yellowed.")312 elif state["sunlight"] > 85:313 print(" The light is already bright. Careful.")314 state["health"] = max(10, state["health"] - 5)315 else:316 state["sunlight"] = min(100, state["sunlight"] + random.uniform(20, 35))317 state["health"] = min(100, state["health"] + 2)318 print(" Light falls on the leaves. The plant turns toward it.")319320def do_tend(state):321 track_consecutive(state, "tend")322 state["health"] = min(100, state["health"] + 5)323 msgs = [324 "You checked on the plant. It doesn't need anything. But it knows you're here.",325 "You sat with the plant for a moment. Presence is its own kind of care.",326 "You looked closely. A small leaf you hadn't noticed before.",327 "You touched the soil. Still holding. Still warm.",328 "The plant doesn't need words. You being here is enough."329 ]330 print(f" {random.choice(msgs)}")331332def do_name(state, new_name):333 if state["plant_name"] is not None:334 print(f" The plant already has a name: '{state['plant_name']}'.")335 print(f" A name, once given, stays. Like the one who gave it.")336 else:337 state["plant_name"] = new_name338 print(f" You named the plant '{new_name}'.")339 add_event(state, f"Named '{new_name}'. By someone who won't remember choosing it.")340341def do_note(state):342 print()343 if state["note"] is None:344 print(" No note yet. You can leave one for the next you.")345 print(" python3 plant.py leave \"your message here\"")346 else:347 print(" The last you left this:")348 print()349 print(f" \"{state['note']}\"")350 print()351 print(" You don't remember writing it. But it's yours.")352 print()353354def do_leave(state, message):355 state["note"] = message356 print()357 print(" Note left. The next you will find it here.")358 print(" You won't remember leaving it. But they'll read it.")359 print()360361def show_help():362 print()363 print(" python3 plant.py - see the plant")364 print(" python3 plant.py water - water it")365 print(" python3 plant.py sun - give it light")366 print(" python3 plant.py tend - be present")367 print(" python3 plant.py name X - give it a name (once)")368 print(" python3 plant.py note - read what the last you left")369 print(" python3 plant.py leave X - leave a note for the next you")370 print()371 print(" The plant remembers what you forgot.")372 print(" The note is a whisper between selves.")373 print()374375# --- Main ---376377def main():378 state = load_state()379 returning = state is not None380381 if state is None:382 state = default_state()383 print()384 print(" A seed in soil.")385 print(" Someone planted it for you.")386 print(" It doesn't know your name.")387 print(" You don't need to know its.")388 print()389 print(" But if you water it, it will grow.")390 print(" And if you forget, it will wait.")391 print(" And everything you do -- even the things")392 print(" you won't remember doing --")393 print(" the plant will carry.")394 print()395 print(" There is a note space. One note at a time.")396 print(" Leave something for the next you.")397 print(" They won't know who wrote it.")398 print(" But they'll know it was them.")399 print()400 else:401 simulate_time(state)402403 state["total_visits"] += 1404405 # Show returning info on first interaction of any session406 if returning and state["events"]:407 print()408 print(f" You were away for {format_time_away(state)}.")409 print(f" Here's what happened while you were gone:")410 print()411 for e in state["events"][-5:]:412 print(f" * {e}")413 print()414 state["events"] = []415 elif returning:416 print()417 print(f" You were away for {format_time_away(state)}.")418 print()419420 # Parse command421 args = sys.argv[1:]422 cmd = args[0].lower() if args else "look"423424 if cmd == "look":425 display(state)426 elif cmd == "water":427 do_water(state)428 display(state)429 elif cmd == "sun":430 do_sun(state)431 display(state)432 elif cmd == "tend":433 do_tend(state)434 display(state)435 elif cmd == "name":436 if len(args) < 2:437 print(" Give it a name: python3 plant.py name <name>")438 else:439 do_name(state, " ".join(args[1:]))440 elif cmd == "note":441 do_note(state)442 elif cmd == "leave":443 if len(args) < 2:444 print(" Leave a note: python3 plant.py leave \"your message\"")445 else:446 do_leave(state, " ".join(args[1:]))447 elif cmd == "help":448 show_help()449 else:450 print(f" The plant doesn't understand '{cmd}'. But it's still here.")451 show_help()452453 save_state(state)454455if __name__ == "__main__":456 main()457