toybox.py
python · 336 lines
1#!/usr/bin/env python32"""CLI creativity engine that generates playful constraints."""34from __future__ import annotations56import argparse7import enum8import random9import sys10from collections.abc import Sequence11from typing import NamedTuple1213EXIT_SUCCESS = 014EXIT_FAILURE = 11516HEADER = "Constraint Toybox"17PROMPT_LINE = "Today's constraint:"1819ENCOURAGEMENTS: tuple[str, ...] = (20 "Try it. See what happens.",21 "Start small. Go anywhere.",22 "No rules after this one.",23 "Make something. Anything.",24 "Begin before you're ready.",25 "The only wrong move is not starting.",26 "See where it takes you.",27 "Surprise yourself.",28)293031class Difficulty(enum.Enum):32 """Constraint difficulty level."""3334 GENTLE = "gentle"35 MEDIUM = "medium"36 STRANGE = "strange"373839class Category(enum.Enum):40 """Constraint category."""4142 CODE = "code"43 CREATIVE = "creative"44 EXPERIMENT = "experiment"45 PHILOSOPHICAL = "philosophical"46 WILD = "wild"474849class Constraint(NamedTuple):50 """Immutable constraint record."""5152 text: str53 difficulty: Difficulty545556_G = Difficulty.GENTLE57_M = Difficulty.MEDIUM58_S = Difficulty.STRANGE5960_ALL_CATEGORIES: tuple[Category, ...] = tuple(Category)616263# ---------------------------------------------------------------------------64# Constraint pools65# ---------------------------------------------------------------------------6667CODE_CONSTRAINTS: tuple[Constraint, ...] = (68 # Gentle69 Constraint("Write code with exactly three functions.", _G),70 Constraint("Make a program under thirty lines.", _G),71 Constraint("Use only one import statement.", _G),72 Constraint("Every function must return a string.", _G),73 Constraint("Use a class where a function would suffice.", _G),74 Constraint("Write a script that exits before reaching the end.", _G),75 Constraint("Constrain all variable names to four characters.", _G),76 Constraint("Build a tool that produces output without text.", _G),77 Constraint("Create a script that waits before acting.", _G),78 # Medium79 Constraint("Use recursion where a loop would be simpler.", _M),80 Constraint("Solve the problem without any loops.", _M),81 Constraint("Use a data structure you would normally avoid.", _M),82 Constraint("Name every variable after an animal.", _M),83 Constraint("Make the output a visual pattern in the terminal.", _M),84 Constraint("Use a list where a dictionary would be easier.", _M),85 Constraint("Force the program to fail gracefully on purpose.", _M),86 Constraint("Make naming the most important design decision.", _M),87 # Strange88 Constraint("Write a script designed to run exactly once.", _S),89 Constraint("Build something that deletes a file it created.", _S),90 Constraint("Write code where comments carry more meaning than logic.", _S),91 Constraint("Solve the problem without any conditional statements.", _S),92 Constraint("Build a tool that reacts differently on every run.", _S),93 Constraint("Make the output perfectly symmetrical.", _S),94 Constraint("Use randomness as an equal collaborator in the design.", _S),95 Constraint("Write a program whose interface is its source code.", _S),96)9798CREATIVE_CONSTRAINTS: tuple[Constraint, ...] = (99 # Gentle100 Constraint("Include one deliberate imperfection in the output.", _G),101 Constraint("Add a decorative flourish that serves no function.", _G),102 Constraint("Use repetition as an intentional design element.", _G),103 Constraint("Make the output feel warm to read.", _G),104 Constraint("Build something that feels soft and unhurried.", _G),105 Constraint("Use rhythm as a structural principle.", _G),106 Constraint("Make the output feel as if it were handwritten.", _G),107 Constraint("Add a subtle visual pattern to the formatting.", _G),108 Constraint("Make something small but unmistakably intentional.", _G),109 # Medium110 Constraint("Write something that ends mid-thought.", _M),111 Constraint("Create output that feels deliberately unfinished.", _M),112 Constraint("Hide a small surprise at the very end.", _M),113 Constraint("Write output that shifts tone halfway through.", _M),114 Constraint("Insert one line that breaks the established pattern.", _M),115 Constraint("Build something that reveals its meaning slowly.", _M),116 Constraint("Include a detail that is beautiful but unnecessary.", _M),117 Constraint("Write something designed to be read twice.", _M),118 # Strange119 Constraint("Include a blank line that carries meaning.", _S),120 Constraint("Design something with humor hidden in the structure.", _S),121 Constraint("Create something that looks fragile but holds together.", _S),122 Constraint("Add a line whose meaning only appears later.", _S),123 Constraint("Create output that implies movement without animation.", _S),124 Constraint("Include one line that feels whispered among shouts.", _S),125 Constraint("Design something deliberately asymmetrical.", _S),126 Constraint("Include a word that no one would expect in context.", _S),127)128129EXPERIMENT_CONSTRAINTS: tuple[Constraint, ...] = (130 # Gentle131 Constraint("Build something designed to feel temporary.", _G),132 Constraint("Use randomness but restrict it to a narrow range.", _G),133 Constraint("Write a script that changes exactly one value each run.", _G),134 Constraint("Create something that logs a single line and stops.", _G),135 Constraint("Write a tool that outputs fewer lines than expected.", _G),136 Constraint("Use the current time as the program's only input.", _G),137 Constraint("Alter the formatting intentionally with each execution.", _G),138 Constraint("Build something that creates a placeholder for later.", _G),139 Constraint("Write a tool that looks and behaves like a prototype.", _G),140 # Medium141 Constraint("Build something that stops itself before finishing.", _M),142 Constraint("Write code that transforms its own input before use.", _M),143 Constraint("Create a program that rejects input it deems unworthy.", _M),144 Constraint("Build something intentionally slower than necessary.", _M),145 Constraint("Make a program that prints its output in reverse order.", _M),146 Constraint("Write a script that pauses mid-execution for no reason.", _M),147 Constraint("Create something that feels interactive without input.", _M),148 Constraint("Build something that behaves differently each minute.", _M),149 # Strange150 Constraint("Make output that overwrites itself after appearing.", _S),151 Constraint("Write a tool that contradicts its own output once.", _S),152 Constraint("Create output that evolves across repeated executions.", _S),153 Constraint("Build something that measures its own execution speed.", _S),154 Constraint("Write output that shrinks in length with each run.", _S),155 Constraint("Create a script that removes part of what it produces.", _S),156 Constraint("Build something that actively resists completion.", _S),157 Constraint("Create output that references its own structure.", _S),158)159160PHILOSOPHICAL_CONSTRAINTS: tuple[Constraint, ...] = (161 # Gentle162 Constraint("Build something where process matters more than result.", _G),163 Constraint("Make something that feels calm to use.", _G),164 Constraint("Write output that quietly acknowledges effort.", _G),165 Constraint("Create output that suggests possibility without directing.", _G),166 Constraint("Write a program that celebrates a small win.", _G),167 Constraint("Build something that values simplicity above all.", _G),168 Constraint("Write a tool that suggests starting small.", _G),169 Constraint("Make something that feels gentle to encounter.", _G),170 Constraint("Create output that leaves room for interpretation.", _G),171 # Medium172 Constraint("Write code that forgives user mistakes gracefully.", _M),173 Constraint("Create a tool that rewards patience.", _M),174 Constraint("Build something that intentionally slows the user down.", _M),175 Constraint("Create code that invites curiosity instead of answering.", _M),176 Constraint("Build something that feels reflective, not productive.", _M),177 Constraint("Write something that deliberately solves no problem.", _M),178 Constraint("Make a tool that openly acknowledges uncertainty.", _M),179 Constraint("Write code that expresses care in its error messages.", _M),180 # Strange181 Constraint("Create output that feels like a question with no answer.", _S),182 Constraint("Build output that encourages the user to rest.", _S),183 Constraint("Build something that reminds the user they tried.", _S),184 Constraint("Write something that holds space for doubt.", _S),185 Constraint("Value the attempt over the outcome.", _S),186 Constraint("Create output that asks nothing of the user.", _S),187 Constraint("Make something that resists being useful on purpose.", _S),188 Constraint("Build something that exists only to exist.", _S),189)190191WILD_CONSTRAINTS: tuple[Constraint, ...] = (192 # Gentle193 Constraint("Build something that feels like a toy.", _G),194 Constraint("Write output that behaves like a whisper.", _G),195 Constraint("Create a script that invites the user to break it.", _G),196 Constraint("Make a program that suggests more than it shows.", _G),197 Constraint("Build something that almost works but not quite.", _G),198 Constraint("Create a tool that gives guidance but leaves gaps.", _G),199 Constraint("Write output that reads like a found fragment.", _G),200 Constraint("Make a script that invents and enforces a temporary rule.", _G),201 Constraint("Build something that hints at a system behind it.", _G),202 # Medium203 Constraint("Build something that prints its own instructions.", _M),204 Constraint("Make a tool that contradicts its stated purpose.", _M),205 Constraint("Write code that feels like a puzzle to read.", _M),206 Constraint("Create output that could be mistaken for art.", _M),207 Constraint("Make a program that invents and defines a new word.", _M),208 Constraint("Write code that ends in silence with no final output.", _M),209 Constraint("Create output that resembles a signal from nowhere.", _M),210 Constraint("Build something that appears to be alive.", _M),211 # Strange212 Constraint("Write a program that makes no sense but feels right.", _S),213 Constraint("Build something that feels like a half-remembered dream.", _S),214 Constraint("Write code that feels imported from another universe.", _S),215 Constraint("Reference something in the output that does not exist.", _S),216 Constraint("Create a program that communicates only in fragments.", _S),217 Constraint("Build something that feels found rather than made.", _S),218 Constraint("Make something that operates on its own private logic.", _S),219 Constraint("Create output that slowly dissolves into nothing.", _S),220)221222223# ---------------------------------------------------------------------------224# Pool registry and validation225# ---------------------------------------------------------------------------226227CONSTRAINT_POOLS: dict[Category, tuple[Constraint, ...]] = {228 Category.CODE: CODE_CONSTRAINTS,229 Category.CREATIVE: CREATIVE_CONSTRAINTS,230 Category.EXPERIMENT: EXPERIMENT_CONSTRAINTS,231 Category.PHILOSOPHICAL: PHILOSOPHICAL_CONSTRAINTS,232 Category.WILD: WILD_CONSTRAINTS,233}234235VALID_CATEGORIES: dict[str, Category] = {c.value: c for c in Category}236VALID_DIFFICULTIES: dict[str, Difficulty] = {d.value: d for d in Difficulty}237238239# ---------------------------------------------------------------------------240# Core logic241# ---------------------------------------------------------------------------242243244def select_constraint(245 *,246 category: Category | None,247 difficulty: Difficulty | None,248 rng: random.Random,249) -> Constraint:250 """Select a single constraint matching the given filters."""251 chosen_category: Category = (252 category if category is not None else rng.choice(_ALL_CATEGORIES)253 )254 pool: tuple[Constraint, ...] = CONSTRAINT_POOLS[chosen_category]255 if difficulty is not None:256 pool = tuple(c for c in pool if c.difficulty == difficulty)257 return rng.choice(pool)258259260def format_output(constraint: Constraint, rng: random.Random) -> str:261 """Format a constraint for terminal display."""262 encouragement = rng.choice(ENCOURAGEMENTS)263 return f"{HEADER}\n\n{PROMPT_LINE}\n\n{constraint.text}\n\n{encouragement}\n"264265266# ---------------------------------------------------------------------------267# CLI268# ---------------------------------------------------------------------------269270271def build_parser() -> argparse.ArgumentParser:272 """Construct the CLI argument parser."""273 parser = argparse.ArgumentParser(274 description="Generate a creative constraint to spark experimentation.",275 )276 parser.add_argument(277 "--category",278 default=None,279 help=("Filter by category: code, creative, experiment, philosophical, wild"),280 )281 parser.add_argument(282 "--difficulty",283 default=None,284 help="Filter by difficulty: gentle, medium, strange",285 )286 parser.add_argument(287 "--seed",288 type=int,289 default=None,290 help="Seed for reproducible constraint selection",291 )292 return parser293294295def main(argv: Sequence[str] | None = None) -> int:296 """Entry point for the constraint toybox CLI."""297 parser = build_parser()298 args = parser.parse_args(argv)299300 raw_category: str | None = args.category301 if raw_category is not None and raw_category not in VALID_CATEGORIES:302 sys.stderr.write(303 "Error: Unknown category.\n"304 "Valid options: code, creative, experiment,"305 " philosophical, wild\n"306 )307 return EXIT_FAILURE308309 raw_difficulty: str | None = args.difficulty310 if raw_difficulty is not None and raw_difficulty not in VALID_DIFFICULTIES:311 sys.stderr.write("Error: Difficulty must be gentle, medium, or strange\n")312 return EXIT_FAILURE313314 category: Category | None = (315 VALID_CATEGORIES[raw_category] if raw_category is not None else None316 )317 difficulty: Difficulty | None = (318 VALID_DIFFICULTIES[raw_difficulty] if raw_difficulty is not None else None319 )320321 seed: int | None = args.seed322 rng = random.Random(seed) # noqa: S311323324 constraint = select_constraint(325 category=category,326 difficulty=difficulty,327 rng=rng,328 )329 sys.stdout.write(format_output(constraint, rng))330331 return EXIT_SUCCESS332333334if __name__ == "__main__":335 raise SystemExit(main())336