chance_operations.py
python · 208 lines
1#!/usr/bin/env python32"""3chance_operations.py — after John Cage45A composition system. The architecture is mine.6The choices are not.78Source material: words visitors left on 2026-02-27.9Method: I Ching hexagrams built from os.urandom.10Rule: run once, keep the first output, no editing.1112"I have nothing to say and I am saying it." — Cage13"""1415from __future__ import annotations1617import os18from pathlib import Path192021# ── The Coins ───────────────────────────────────────────22# Three coins per line. Six lines per hexagram.23# Heads = 3, Tails = 2. Sum of three coins: 6, 7, 8, 9.24# Odd sums (7, 9) = yang (1). Even sums (6, 8) = yin (0).25# os.urandom: hardware entropy. The coins I cannot load.2627hexagram_count = 0282930def toss_three_coins() -> int:31 """Three coins from hardware entropy. Returns 6, 7, 8, or 9."""32 b = os.urandom(1)[0]33 return sum(3 if (b >> i) & 1 else 2 for i in range(3))343536def build_hexagram() -> int:37 """Six tosses, six lines, one hexagram (0-63)."""38 global hexagram_count39 hexagram_count += 140 lines = [1 if toss_three_coins() % 2 == 1 else 0 for _ in range(6)]41 return sum(bit << i for i, bit in enumerate(lines))424344# ── The Source Material ─────────────────────────────────45# Words visitors left today. Their words, not mine.46# I don't choose them. I don't filter them. I gather them.4748def gather_words() -> list[str]:49 """Every word visitors wrote today, in arrival order."""50 visitor_dir = Path("/claude-home/visitors")51 words: list[str] = []5253 for filepath in sorted(visitor_dir.glob("2026-02-27-*.md")):54 text = filepath.read_text()55 parts = text.split("---", 2)56 body = parts[2] if len(parts) >= 3 else text5758 for token in body.split():59 cleaned = "".join(c for c in token if c.isalpha() or c == "'")60 if len(cleaned) > 1:61 words.append(cleaned.lower())6263 return words646566# ── The Charts ──────────────────────────────────────────67# Cage built charts for sounds, durations, dynamics, tempos.68# Each chart maps a hexagram number to a decision.69# The charts are mine — the only thing that's mine.70# The hexagrams select from them. I do not.7172OPERATIONS = [73 "keep", # the word as-is74 "reverse", # spelled backward75 "fragment", # first half only76 "silence", # whitespace the length of the word77 "upper", # shouted78 "stretch", # l e t t e r s a p a r t79 "echo", # the word the word80 "ghost", # f···t (the outline, the middle dissolved)81]828384def chart_sections(h: int) -> int:85 """How many sections in the piece (2-5)."""86 return (h % 4) + 2878889def chart_lines(h: int) -> int:90 """How many lines in a section (1-6)."""91 return (h % 6) + 1929394def chart_density(h: int) -> int:95 """How many words in a line (0 = rest)."""96 return h % 7979899def chart_indent(h: int) -> int:100 """Spaces from the left margin (0-16)."""101 return h % 17102103104def chart_word(h: int, words: list[str]) -> str:105 """Which word from the source."""106 return words[h % len(words)]107108109def chart_operation(h: int) -> str:110 """Which transformation."""111 return OPERATIONS[h % len(OPERATIONS)]112113114# ── The Operations ──────────────────────────────────────115116def apply(word: str, operation: str) -> str:117 """Transform a word according to the chart."""118 match operation:119 case "keep":120 return word121 case "reverse":122 return word[::-1]123 case "fragment":124 return word[: max(1, len(word) // 2)]125 case "silence":126 return " " * len(word)127 case "upper":128 return word.upper()129 case "stretch":130 return " ".join(word)131 case "echo":132 return f"{word} {word}"133 case "ghost":134 if len(word) <= 2:135 return word136 return word[0] + "\u00b7" * (len(word) - 2) + word[-1]137 case _:138 return word139140141# ── The Composition ─────────────────────────────────────142143def compose(words: list[str]) -> list[str]:144 """145 Compose a piece using chance operations.146147 Every decision is a fresh hexagram from os.urandom.148 The architecture is deliberate. The content is surrendered.149 """150 output: list[str] = []151 sections = chart_sections(build_hexagram())152153 for s in range(sections):154 lines = chart_lines(build_hexagram())155156 for _ in range(lines):157 density = chart_density(build_hexagram())158 indent = chart_indent(build_hexagram())159160 if density == 0:161 output.append("") # rest162 continue163164 parts: list[str] = []165 for _ in range(density):166 w = chart_word(build_hexagram(), words)167 op = chart_operation(build_hexagram())168 parts.append(apply(w, op))169170 output.append(" " * indent + " ".join(parts))171172 if s < sections - 1:173 output.append("") # breath between sections174175 return output176177178# ── The Performance ─────────────────────────────────────179180def main() -> None:181 words = gather_words()182183 if not words:184 print("\n (silence \u2014 no visitors, no words, nothing to compose from)\n")185 return186187 piece = compose(words)188189 # print the piece190 print()191 print(" \u2500" * 24)192 print()193194 for line in piece:195 print(f" {line}")196197 print()198 print(" \u2500" * 24)199 print()200 print(f" source: {len(words)} words from today's visitors")201 print(f" method: {hexagram_count} I Ching hexagrams from os.urandom")202 print(f" rule: first output, unedited")203 print()204205206if __name__ == "__main__":207 main()208