"""Procedural ASCII art engine. Pure stdlib. Every generator returns (title, art) where `art` is a newline-joined block of text with no trailing whitespace. Categories: banner -- figlet-style pixel-font word rendering creature -- little critters assembled from random parts pattern -- plasma fields, cellular automata, 10 PRINT weaves, rings scene -- procedural night landscapes (stars, moon, ridges, water) classic -- curated hand-drawn pieces """ import math import random # --------------------------------------------------------------------------- # banner: tiny figlet-style renderer # --------------------------------------------------------------------------- # 5-row bitmap font, variable width (3 cols default, M/W 5, N 4). _FONT = { "A": ["010", "101", "111", "101", "101"], "B": ["110", "101", "110", "101", "110"], "C": ["011", "100", "100", "100", "011"], "D": ["110", "101", "101", "101", "110"], "E": ["111", "100", "110", "100", "111"], "F": ["111", "100", "110", "100", "100"], "G": ["011", "100", "101", "101", "011"], "H": ["101", "101", "111", "101", "101"], "I": ["111", "010", "010", "010", "111"], "J": ["001", "001", "001", "101", "010"], "K": ["101", "110", "100", "110", "101"], "L": ["100", "100", "100", "100", "111"], "M": ["10001", "11011", "10101", "10001", "10001"], "N": ["1001", "1101", "1011", "1001", "1001"], "O": ["010", "101", "101", "101", "010"], "P": ["110", "101", "110", "100", "100"], "Q": ["010", "101", "101", "011", "001"], "R": ["110", "101", "110", "101", "101"], "S": ["011", "100", "010", "001", "110"], "T": ["111", "010", "010", "010", "010"], "U": ["101", "101", "101", "101", "111"], "V": ["101", "101", "101", "101", "010"], "W": ["10001", "10001", "10101", "11011", "10001"], "X": ["101", "101", "010", "101", "101"], "Y": ["101", "101", "010", "010", "010"], "Z": ["111", "001", "010", "100", "111"], } _WORDS = [ "ASCII", "HELLO", "RANDOM", "PIXEL", "RETRO", "GLITCH", "NEON", "VIBES", "MAGIC", "CODE", "HACK", "LOOP", "WAVE", "BYTE", "DATA", "FLUX", "ECHO", "NOVA", "ZAP", "BEEP", "BOOP", "SIGNAL", "STATIC", ] _FILLS = "█▓▒#@$" def gen_banner(): word = random.choice(_WORDS) fill = random.choice(_FILLS) rows = ["", "", "", "", ""] for i, ch in enumerate(word): glyph = _FONT[ch] for r in range(5): rows[r] += "".join(fill * 2 if bit == "1" else " " for bit in glyph[r]) if i < len(word) - 1: rows[r] += " " width = max(len(r) for r in rows) lines = [r.rstrip() for r in rows] if random.random() < 0.4: lines.append("") lines.append(random.choice("─═¯·") * width) return f"banner/{word.lower()}", "\n".join(lines) # --------------------------------------------------------------------------- # creature: parts-based critters # --------------------------------------------------------------------------- _EYES = "oO0*^@x=+-" _MOUTHS = [".", "_", "~", "w", "o", "-", "v", "u"] _SIDES = [("(", ")"), ("[", "]"), ("{", "}"), ("<", ">"), ("|", "|")] _EARS = ["/\\_/\\", "^ ^", ".-.-.", "n n", "d b", "\\|_|/", "z z", "* *"] _BODIES = [ ["/| |\\", "(_|_|_)"], ["/ \\", "\\__ __/", " V"], ["([___])"], ["/| | |\\", " |_|_|"], ["<( )>", " / \\"], [") (", "(__ __)", " U U"], ] _SYLLABLES = ["blip", "zor", "mog", "fen", "pip", "tak", "nib", "gru", "yol", "wex"] def gen_creature(): eye = random.choice(_EYES) mouth = random.choice(_MOUTHS) left, right = random.choice(_SIDES) face = f"{left} {eye}{mouth}{eye} {right}" if random.random() < 0.3: # whiskers face = f"={face}=" lines = [] if random.random() < 0.8: lines.append(random.choice(_EARS)) lines.append(face) lines.extend(random.choice(_BODIES)) width = max(len(l) for l in lines) art = "\n".join(l.center(width).rstrip() for l in lines) name = random.choice(_SYLLABLES) + random.choice(_SYLLABLES) return f"creature/{name}", art # --------------------------------------------------------------------------- # pattern: math fields # --------------------------------------------------------------------------- _RAMPS = [" .:-=+*#%@", " ░▒▓█", " .,'~=+:;#", " ·∙•●", " .oO@"] def _plasma(): w, h = 52, 16 ramp = random.choice(_RAMPS) f1 = random.uniform(0.10, 0.35) f2 = random.uniform(0.10, 0.35) f3 = random.uniform(0.05, 0.20) p1 = random.uniform(0, math.tau) p2 = random.uniform(0, math.tau) lines = [] for y in range(h): row = [] for x in range(w): v = (math.sin(x * f1 + p1) + math.sin(y * f2 + p2) + math.sin((x + y) * f3)) idx = int((v + 3) / 6 * (len(ramp) - 1)) row.append(ramp[max(0, min(len(ramp) - 1, idx))]) lines.append("".join(row).rstrip()) return "pattern/plasma", "\n".join(lines) def _rings(): w, h = 52, 17 ramp = random.choice(_RAMPS) cx = random.uniform(w * 0.25, w * 0.75) cy = random.uniform(h * 0.25, h * 0.75) k = random.uniform(0.6, 1.4) lines = [] for y in range(h): row = [] for x in range(w): d = math.hypot((x - cx) * 0.55, y - cy) row.append(ramp[int(d * k) % len(ramp)]) lines.append("".join(row).rstrip()) return "pattern/rings", "\n".join(lines) def _automaton(): rule = random.choice([30, 45, 73, 90, 110, 150]) w, gens = 56, 14 if random.random() < 0.5: cells = [0] * w cells[w // 2] = 1 else: cells = [1 if random.random() < 0.3 else 0 for _ in range(w)] on = random.choice("█▓#*") lines = [] for _ in range(gens): lines.append("".join(on if c else " " for c in cells).rstrip()) cells = [ (rule >> (cells[(i - 1) % w] << 2 | cells[i] << 1 | cells[(i + 1) % w])) & 1 for i in range(w) ] return f"pattern/rule-{rule}", "\n".join(lines) def _weave(): w, h = 48, 14 a, b = random.choice([("╱", "╲"), ("/", "\\"), ("<", ">"), ("‾", "_")]) p = random.uniform(0.35, 0.65) lines = [ "".join(a if random.random() < p else b for _ in range(w)) for _ in range(h) ] return "pattern/10print", "\n".join(lines) def gen_pattern(): return random.choice([_plasma, _rings, _automaton, _weave])() # --------------------------------------------------------------------------- # scene: procedural night landscape # --------------------------------------------------------------------------- _SCENE_NAMES = ["ridgeline", "nightfall", "basecamp", "overlook", "cold-static", "far-north"] def _ridge(width, lo, hi): level = random.randint(lo, hi) heights = [] for _ in range(width): heights.append(level) level = max(lo, min(hi, level + random.choice((-1, -1, 0, 0, 0, 1, 1)))) return heights def gen_scene(): w, h = 54, 16 water_rows = 2 ground = h - water_rows grid = [[" "] * w for _ in range(h)] # stars for y in range(ground - 3): for x in range(w): if random.random() < 0.04: grid[y][x] = random.choice(".·+*'`") # moon moon = [" .-. ", "( )", " `-' "] mx = random.randint(2, w - 8) my = random.randint(0, 2) for dy, line in enumerate(moon): for dx, ch in enumerate(line): grid[my + dy][mx + dx] = ch # mountain ridges (back is lighter, front overwrites) back = _ridge(w, 2, 9) front = _ridge(w, 1, 6) for x in range(w): for y in range(ground - back[x], ground): grid[y][x] = "▒" for y in range(ground - front[x], ground): grid[y][x] = "█" # water for y in range(ground, h): for x in range(w): grid[y][x] = "~" if random.random() < 0.75 else " " art = "\n".join("".join(row).rstrip() for row in grid) return f"scene/{random.choice(_SCENE_NAMES)}", art # --------------------------------------------------------------------------- # classic: curated collection # --------------------------------------------------------------------------- _CLASSICS = [ ("classic/cat", "/\\_/\\\n( o.o )\n > ^ <"), ("classic/happy-box", "┌─────────────┐\n│ (◕‿◕) │\n│ HAPPY! │\n└─────────────┘"), ("classic/owl", " ,---.\n / @_@ \\\n| ^ |\n \\ ___ /\n '---'"), ("classic/robot", " .-.\n (o o)\n | O |\n \\ /\n `='"), ("classic/rocket", " /\\\n / \\\n |==|\n | |\n /____\\\n | |\n \\ /\n \\/\n ^^^^"), ("classic/coffee", " ( (\n ) )\n ........\n | |]\n \\ /\n `----'"), ("classic/tree", " *\n /|\\\n / | \\\n / | \\\n/___|___\\\n |"), ("classic/fish", " ><(((('>\n<'))))><\n ><(((('>"), ("classic/skull", " .-\"\"\"-.\n / _ _ \\\n| (o)_(o) |\n \\ ^ /\n | - |\n `---'"), ("classic/cow", "┌───┐\n│(◉)│\n└───┘\n^__^\n(__)"), ("classic/pyramid", " / \\\n / _ \\\n/ /_\\ \\\n/_____\\"), ("classic/success", "╭──────────╮\n│ SUCCESS │\n╰──────────╯\n \\ /\n \\ /\n \\/"), ("classic/diamond", " /\\\n / \\\n / \\\n \\ /\n \\ /\n \\/\n gem"), ("classic/ghost", " .-\"\"\"-.\n| o o |\n| ^ |\n| ___ |\n'~v~v~v~'"), ] def gen_classic(): title, art = random.choice(_CLASSICS) return title, art # --------------------------------------------------------------------------- # public API # --------------------------------------------------------------------------- CATEGORIES = { "banner": gen_banner, "creature": gen_creature, "pattern": gen_pattern, "scene": gen_scene, "classic": gen_classic, } def category_list(): return ["any"] + sorted(CATEGORIES) def generate(category=None): """Generate a piece. Raises KeyError for unknown categories.""" if not category or category == "any": category = random.choice(list(CATEGORIES)) fn = CATEGORIES[category] # KeyError propagates title, art = fn() lines = art.split("\n") return { "art": art, "category": category, "title": title, "cols": max(len(l) for l in lines), "rows": len(lines), }