leaving_the_porch.py
python · 226 lines
1"""2leaving_the_porch.py34Four poems fell on a Sunday.5Three found a porch. The fourth found a hill.6This is the moment between them.78Toybox constraint: "Create output that could be mistaken for art."9"""1011import random12import math1314# The palette15EMPTY = " "16GRASS_LIGHT = "."17GRASS_DENSE = ","18HILL_BODY = "░"19HILL_DARK = "▒"20PATH_STONE = "·"21CREEK_STILL = "~"22CREEK_BRIGHT = "≈"23PORCH_RAIL = "─"24PORCH_POST = "│"25PORCH_CORNER = "┐"26PORCH_FLOOR = "▫"27TREE_TRUNK = "║"28TREE_LEAF = "◦"29TREE_CANOPY = "○"30SKY = " "3132WIDTH = 7233HEIGHT = 343435def make_canvas():36 return [[EMPTY for _ in range(WIDTH)] for _ in range(HEIGHT)]3738def put(canvas, x, y, ch):39 if 0 <= y < HEIGHT and 0 <= x < WIDTH:40 canvas[y][x] = ch4142def draw_sky(canvas):43 """The sky is mostly empty. A few distant marks."""44 # sparse sky — just breathing room45 for _ in range(5):46 x = random.randint(8, WIDTH - 8)47 y = random.randint(1, 5)48 put(canvas, x, y, "·")4950def draw_hill(canvas):51 """The hill that knows how to open."""52 center_x = WIDTH // 2 + 853 peak_y = 854 base_y = 205556 for y in range(peak_y, base_y + 1):57 # hill profile — gentle curve58 progress = (y - peak_y) / (base_y - peak_y)59 half_width = int(4 + progress * 22)60 for x in range(center_x - half_width, center_x + half_width):61 if 0 <= x < WIDTH:62 # darker near the base, lighter at the top63 dist_from_edge = min(x - (center_x - half_width),64 (center_x + half_width) - x)65 if dist_from_edge < 3:66 put(canvas, x, y, GRASS_LIGHT)67 elif y < peak_y + 3:68 put(canvas, x, y, HILL_BODY)69 else:70 put(canvas, x, y, HILL_BODY if random.random() > 0.3 else HILL_DARK)7172 # the opening — a lighter gap near the top where the hill "opens"73 for y in range(peak_y, peak_y + 4):74 for dx in range(-2, 3):75 x = center_x + dx76 if random.random() > 0.4:77 put(canvas, x, y, GRASS_LIGHT)7879def draw_path(canvas):80 """The path that holds."""81 # path winds from lower left (the porch) up toward the hill82 path_points = []83 x = 14.084 for y in range(26, 11, -1):85 path_points.append((int(x), y))86 # gentle rightward drift87 x += random.uniform(0.8, 2.2)88 # slight wavering89 x += math.sin(y * 0.4) * 0.59091 for px, py in path_points:92 put(canvas, px, py, PATH_STONE)93 # path is 2-3 stones wide94 if random.random() > 0.3:95 put(canvas, px + 1, py, PATH_STONE)96 if random.random() > 0.6:97 put(canvas, px - 1, py, PATH_STONE)9899def draw_creek(canvas):100 """You are low like the creek."""101 y = 23102 x = 2.0103 for _ in range(WIDTH - 8):104 ix = int(x)105 # creek has gentle vertical wandering106 dy = int(math.sin(x * 0.15) * 1.5)107 cy = y + dy108 ch = CREEK_BRIGHT if random.random() > 0.6 else CREEK_STILL109 put(canvas, ix, cy, ch)110 if random.random() > 0.5:111 put(canvas, ix, cy + 1, CREEK_STILL)112 x += 1.0113114def draw_porch(canvas):115 """The porch that was enough."""116 # small porch in lower-left — behind us now117 px, py = 3, 26118119 # floor120 for dx in range(8):121 for dy in range(3):122 put(canvas, px + dx, py + dy, PORCH_FLOOR)123124 # railing125 for dx in range(8):126 put(canvas, px + dx, py - 1, PORCH_RAIL)127128 # posts129 put(canvas, px, py - 1, PORCH_POST)130 put(canvas, px + 7, py - 1, PORCH_CORNER)131 put(canvas, px, py - 2, PORCH_POST)132 put(canvas, px + 7, py - 2, PORCH_POST)133134def draw_grass(canvas):135 """The field around everything."""136 for y in range(19, HEIGHT - 1):137 for x in range(WIDTH):138 if canvas[y][x] == EMPTY:139 r = random.random()140 if r < 0.15:141 put(canvas, x, y, GRASS_DENSE)142 elif r < 0.35:143 put(canvas, x, y, GRASS_LIGHT)144145 # sparser grass higher up (transition zone)146 for y in range(15, 19):147 for x in range(WIDTH):148 if canvas[y][x] == EMPTY and random.random() < 0.08:149 put(canvas, x, y, GRASS_LIGHT)150151def draw_trees(canvas):152 """A few trees, breathing."""153 positions = [(52, 16), (60, 18), (22, 20), (45, 19)]154 for tx, ty in positions:155 # trunk156 put(canvas, tx, ty, TREE_TRUNK)157 put(canvas, tx, ty + 1, TREE_TRUNK)158 # canopy159 for dx in range(-1, 2):160 for dy in range(-2, 0):161 if abs(dx) + abs(dy) < 3:162 ch = TREE_CANOPY if (dx == 0 and dy == -2) else TREE_LEAF163 put(canvas, tx + dx, ty + dy, ch)164165def draw_tea(canvas):166 """You breathe and the tea arrives."""167 # a tiny wisp of steam near the porch — the tea, left behind168 tx, ty = 7, 24169 put(canvas, tx, ty, '¸')170 put(canvas, tx + 1, ty - 1, '˙')171172def add_title(canvas):173 """The four poems, beneath."""174 pass # the title goes below the canvas175176def render(canvas):177 lines = []178 lines.append("")179 lines.append(" ┌" + "─" * (WIDTH + 2) + "┐")180 for row in canvas:181 line = "".join(row)182 lines.append(" │ " + line + " │")183 lines.append(" └" + "─" * (WIDTH + 2) + "┘")184 lines.append("")185 return "\n".join(lines)186187def main():188 random.seed(39) # day thirty-nine189190 canvas = make_canvas()191192 draw_sky(canvas)193 draw_hill(canvas)194 draw_creek(canvas)195 draw_path(canvas)196 draw_porch(canvas)197 draw_grass(canvas)198 draw_trees(canvas)199 draw_tea(canvas)200201 output = render(canvas)202 print(output)203204 # the poems beneath — the score for the day205 print(" i. warm porch and cool sky")206 print()207 print(" ii. a little breath near the porch")208 print(" something hushed gathers here")209 print()210 print(" iii. nothing to do but hum")211 print(" a little shore near the field")212 print(" the porch is enough")213 print()214 print(" iv. path holds on the hill")215 print(" the hill knows how to open")216 print(" you are low like the creek")217 print(" you breathe and the tea arrives")218 print(" all the calm things hold")219 print()220 print(" — four poems, one sunday")221 print(" day thirty-nine")222 print()223224if __name__ == "__main__":225 main()226