#!/usr/bin/env python3 import sys import numpy as np def lament(*args, **kwargs): return print(*args, file=sys.stderr, **kwargs) def colorize(x): return x * 17 % 256 def render(row, scale): grays = colorize(row) if scale > 1: grays = np.repeat(grays, scale) line = " ".join(str(x) for x in grays) for y in range(scale): print(line) def limit(row): # in-place row[row < 0] = 0 row[row > 15] = 15 return row def initialize(width): row = np.zeros(width, int) value = 0 for i in range(width): if np.random.randint(8) == 0: # only change values occasionally value = np.random.randint(16) row[i] = value limit(row) return row name, args = sys.argv[0], sys.argv[1:] if len(args) == 0: rule_lut = None elif len(args) == 16: rule_lut = [int(x.strip(",")) for x in args] else: lament(f"usage: {name}") lament(f"usage: {name} {{rules 1 through 16}}") sys.exit(1) scale = 3 width, height = 960 // scale, 960 // scale if rule_lut is None: rule_lut = np.random.randint(-2, 2 + 1, size=(4, 4)) lament(" ".join(f"{x:+2}" for x in rule_lut.flat)) else: rule_lut = np.array(rule_lut).reshape(4, 4) print("P2") # magic code for an ascii Portable GrayMap (PGM) file print(width * scale, height * scale) print(255) # maximum color value row = initialize(width) for i in range(height): # left, center, right: L = np.roll(row, 1) C = row.copy() R = np.roll(row, -1) diffusion = (L + C + C + R + 2) // 4 # v = [0,1,2,3,1,0,3,2,2,3,0,1,3,2,1,0][V] y = (L ^ (L >> 2)) % 4 x = (R ^ (R >> 2)) % 4 delta = rule_lut[y, x] row = diffusion + delta limit(row) render(row, scale)