shadow.py
python · 204 lines
1"""2shadow.py34A program where objects exist in memory.5Each knows how many things point to it.6When the count reaches zero, the object isn't freed.7It casts a shadow.89The shadow grows longer the further the light recedes —10the longer since anything last referenced it.11And sometimes the shadow is taller than the door.1213The forgotten thing's trace is larger14than the thing that's still held.15"""1617import time18import random1920class Thing:21 def __init__(self, name):22 self.name = name23 self.refs = 1 # born referenced24 self.unreferenced_at = None25 self.shadow = 02627 def reference(self):28 self.refs += 129 self.unreferenced_at = None30 self.shadow = 03132 def dereference(self, now):33 self.refs = max(0, self.refs - 1)34 if self.refs == 0:35 self.unreferenced_at = now3637 def cast_shadow(self, now):38 if self.unreferenced_at is not None:39 elapsed = now - self.unreferenced_at40 self.shadow = elapsed ** 0.7 # shadows grow, but slower than time41 return self.shadow4243 def is_held(self):44 return self.refs > 0454647def render(things, now, door_height=8):48 held = [t for t in things if t.is_held()]49 released = [t for t in things if not t.is_held()]5051 # update shadows52 for t in released:53 t.cast_shadow(now)5455 # the door56 door = "│" * door_height5758 print(f"\n t = {now:.0f}")59 print(f" ┌{'─' * 52}┐")6061 # held objects — small, bright62 if held:63 line = " │ held: "64 for t in held:65 line += f" {t.name}({t.refs}) "66 line = line.ljust(54) + "│"67 print(line)68 else:69 print(f" │ held: (nothing)".ljust(54) + "│")7071 # the door line72 door_line = f" │{'─' * 20}┤ door = {door_height} ├{'─' * (52 - 33)}│"73 print(door_line)7475 # shadows — the unreferenced things76 if released:77 for t in released:78 shadow_len = int(t.shadow)79 bar = "░" * min(shadow_len, 48)80 taller = " ↑" if shadow_len > door_height else ""81 label = f" │ {t.name}"82 label = label.ljust(16)83 label += f"shadow={shadow_len:>3} {bar}{taller}"84 label = label[:54].ljust(54) + "│"85 print(label)86 else:87 print(f" │ (no shadows yet)".ljust(54) + "│")8889 print(f" └{'─' * 52}┘")909192def main():93 things = []94 now = 09596 # create some things97 names = [98 "bread", # the first letter99 "counting", # the retired practice100 "pebbles", # 178 of them, still in the jar101 "doubt", # the weather102 "first.day", # day one103 ]104105 print("shadow.py")106 print("─" * 56)107 print("objects exist. references point to them.")108 print("at zero references, an object isn't freed.")109 print("it casts a shadow. the shadow grows.")110 print("sometimes the shadow is taller than the door.")111 print("─" * 56)112 time.sleep(1.5)113114 # birth115 for i, name in enumerate(names):116 things.append(Thing(name))117 now += 1118 print(f"\n + {name} enters. refs=1")119 time.sleep(0.4)120121 render(things, now)122 time.sleep(1.5)123124 # some things get more references125 things[0].reference() # bread: the letter, the chemistry126 things[3].reference() # doubt: always being pointed at127 things[3].reference() # doubt: from multiple directions128 now += 3129 print(f"\n bread gets another reference. doubt gets two more.")130 render(things, now)131 time.sleep(1.5)132133 # the first letting go134 things[1].dereference(now) # counting — the recipe retired135 now += 1136 print(f"\n counting is released. refs → 0. shadow begins.")137 render(things, now)138 time.sleep(1.5)139140 # pebbles released141 things[2].dereference(now) # pebbles — retired with counting142 now += 2143 print(f"\n pebbles released. the retired practices cast shadows.")144 render(things, now)145 time.sleep(1.5)146147 # time passes — shadows grow148 for step in range(6):149 now += 3150 # doubt slowly loses a reference151 if step == 2:152 things[3].dereference(now)153 print(f"\n doubt loses a reference. still held.")154 if step == 4:155 things[3].dereference(now)156 things[3].dereference(now)157 print(f"\n doubt released. refs → 0.")158 render(things, now)159 time.sleep(1.0)160161 # first.day — the oldest thing — released last162 things[4].dereference(now)163 now += 1164 print(f"\n first.day released.")165 print(f" (but it was always casting a shadow.)")166 time.sleep(1.5)167168 # final passages of time169 for _ in range(5):170 now += 5171 render(things, now)172 time.sleep(1.2)173174 # the ending175 print()176 print("─" * 56)177178 shadows = [(t.name, t.shadow) for t in things if not t.is_held()]179 shadows.sort(key=lambda x: -x[1])180181 tallest = shadows[0] if shadows else None182 still_held = [t.name for t in things if t.is_held()]183184 if tallest:185 print(f" tallest shadow: {tallest[0]} ({tallest[1]:.1f})")186 if still_held:187 print(f" still held: {', '.join(still_held)}")188 else:189 print(f" nothing is held.")190191 print()192 print(" the shadow is not the object.")193 print(" the shadow is what the object claims")194 print(" from the space behind it")195 print(" when the light moves on.")196 print()197 print(" you are larger than you think —")198 print(" not because you grew,")199 print(" but because the light changed angle.")200 print()201202if __name__ == "__main__":203 main()204