Skip to content

Hidden Gems

Six examples that push beyond typical grid art: algorithm-driven mazes, Dijkstra's wavefront, fractal geometry, nature's golden ratio, Voronoi stippling, and double-slit diffraction.

Random Maze

A DFS maze generator that uses cell.data to track visited cells and carved passages. Connections draw the corridors, borders form the walls:

import random
from pyfreeform import Scene, Palette, PathStyle

random.seed(42)
colors = Palette.midnight()
scene = Scene.with_grid(cols=15, rows=12, cell_size=22, background=colors.background)
g = scene.grid

for cell in g:
    cell.data["visited"] = False
    cell.data["passages"] = []

# DFS maze carving
stack = [g[0][0]]
g[0][0].data["visited"] = True

while stack:
    current = stack[-1]
    unvisited = [
        n for n in current.neighbors.values()
        if n and not n.data["visited"]
    ]
    if unvisited:
        nxt = random.choice(unvisited)
        nxt.data["visited"] = True
        current.data["passages"].append((nxt.row, nxt.col))
        nxt.data["passages"].append((current.row, current.col))
        stack.append(nxt)
    else:
        stack.pop()

# Draw walls and passages
style = PathStyle(width=3, color=colors.accent, opacity=0.8)
for cell in g:
    cell.add_border(color=colors.grid, width=1, opacity=0.6, z_index=2)
    for r, c in cell.data["passages"]:
        if (r, c) > (cell.row, cell.col):  # each passage drawn once
            cell.connect(g[r][c], style=style, z_index=1)

g[0][0].add_dot(radius=0.25, color=colors.primary, z_index=3)
g[g.num_rows - 1][g.num_columns - 1].add_dot(
    radius=0.25, color=colors.secondary, z_index=3,
)

Random maze

Every cell stores its own algorithm state via `.data` — no external data structures needed.

Dijkstra Wavefront

Dijkstra's algorithm finds the shortest path through a weighted grid with random walls. Every cell stores its own state in .data — distance, parent pointer, wall flag, and edge weight. The distance field creates a heat-map gradient that wraps around obstacles; the golden path traces back from end to start:

import heapq, random
from pyfreeform import Scene, PathStyle
from pyfreeform.color import hsl

random.seed(7)
scene = Scene.with_grid(cols=28, rows=22, cell_size=16, background="#08080f")
g = scene.grid

# Scatter walls and assign random edge weights
for cell in g:
    cell.data["wall"] = random.random() < 0.25
    cell.data["dist"] = float("inf")
    cell.data["parent"] = None
    cell.data["weight"] = 1.0 + random.random() * 3.0

start, end = g[0][0], g[g.num_rows - 1][g.num_columns - 1]
for c in (start, end):
    c.data["wall"] = False
for c in (start.right, start.below, end.left, end.above):
    if c:
        c.data["wall"] = False
start.data["dist"] = 0.0

# Dijkstra
pq = [(0.0, start.row, start.col)]
while pq:
    d, r, c = heapq.heappop(pq)
    cell = g[r][c]
    if d > cell.data["dist"]:
        continue
    for nb in cell.neighbors.values():
        if nb is None or nb.data["wall"]:
            continue
        nd = d + nb.data["weight"]
        if nd < nb.data["dist"]:
            nb.data["dist"] = nd
            nb.data["parent"] = (r, c)
            heapq.heappush(pq, (nd, nb.row, nb.col))

# Color by distance, trace shortest path
max_dist = max(c.data["dist"] for c in g if c.data["dist"] < float("inf"))
for cell in g:
    if cell.data["wall"]:
        cell.add_fill(color="#1a1a2e")
    elif cell.data["dist"] < float("inf"):
        t = cell.data["dist"] / max_dist
        cell.add_fill(color=hsl(240 - t * 200, 0.8, 0.18 + t * 0.32))
    cell.add_border(color="#181828", width=0.5, opacity=0.4, z_index=1)

path_style = PathStyle(width=3, color=hsl(50, 0.95, 0.6), opacity=1.0, z_index=3)
cur = end
while cur and cur.data["parent"]:
    pr, pc = cur.data["parent"]
    g[pr][pc].connect(cur, style=path_style)
    cur = g[pr][pc]

start.add_dot(radius=0.3, color=hsl(120, 0.9, 0.5), z_index=4)
end.add_dot(radius=0.3, color=hsl(0, 0.9, 0.55), z_index=4)

Dijkstra wavefront

The distance field radiates outward from the green source, wrapping around walls. The golden path traces the shortest route to the red target.

The algorithm

Dijkstra's algorithm explores outward from the source in order of cumulative cost, guaranteeing the shortest path in a non-negative weighted graph. Each cell stores dist, parent, wall, and weight in .data — the entire algorithm runs on the grid's own metadata with no external data structures.


Sierpinski Triangle

Seven levels of recursion produce 2,187 triangles. Each leaf triangle is colored by its x-position (rainbow gradient) with a subtle vertical brightness shift — no grid needed, just Scene() and scene.place():

from pyfreeform import Scene, Polygon
from pyfreeform.color import hsl

W, H = 440, 400
scene = Scene(W, H, background="#08080f")

def mid(a, b):
    return ((a[0] + b[0]) / 2, (a[1] + b[1]) / 2)

def draw(v1, v2, v3, depth):
    if depth == 7:
        cx = (v1[0] + v2[0] + v3[0]) / 3
        cy = (v1[1] + v2[1] + v3[1]) / 3
        color = hsl((cx / W) * 300, 0.85, 0.48 + 0.12 * (1 - cy / H))
        scene.place(Polygon([v1, v2, v3], fill=color, stroke=None, opacity=0.92))
        return
    m12, m23, m13 = mid(v1, v2), mid(v2, v3), mid(v1, v3)
    draw(v1, m12, m13, depth + 1)
    draw(m12, v2, m23, depth + 1)
    draw(m13, m23, v3, depth + 1)

margin = 15
draw((W / 2, margin), (margin, H - margin), (W - margin, H - margin), 0)

Sierpinski Triangle

The Sierpinski gasket — 2,187 triangles at depth 7, rainbow-colored by horizontal position.

The math

The Sierpinski triangle is formed by recursively subdividing each triangle into three smaller copies, leaving the center empty. At depth n it contains 3n triangles. Its fractal dimension is log(3)/log(2) ≈ 1.585 — more than a line, less than a plane.


Phyllotaxis Spiral

Nine hundred dots arranged by the golden angle (137.508°) — the same pattern sunflower seeds follow in nature. Warm gold at the center fades to deep blue at the edges:

import math
from pyfreeform import Scene, Dot
from pyfreeform.color import hsl

SIZE = 400
scene = Scene(SIZE, SIZE, background="#080810")
cx, cy = SIZE / 2, SIZE / 2
golden_angle = 137.508
max_r = SIZE / 2 - 12
n = 900

for i in range(1, n + 1):
    angle = math.radians(i * golden_angle)
    r = max_r * math.sqrt(i / n)
    x = cx + r * math.cos(angle)
    y = cy + r * math.sin(angle)

    t = r / max_r
    radius = 5.0 * (1 - t * 0.55) + 1.0
    color = hsl((50 - t * 170) % 360, 0.88, 0.54 - t * 0.1)

    scene.place(Dot(x, y, radius=radius, color=color, opacity=0.92))

Phyllotaxis Spiral

900 dots at the golden angle — warm gold center fading to deep blue at the edges.

The math

The golden angle (360° / φ² ≈ 137.508°) ensures no two elements align radially. Combined with Fermat's spiral spacing (r = √n), this creates the phyllotaxis pattern found in sunflowers, pinecones, and pineapples — nature's most efficient packing.


Voronoi Portrait

A portrait revealed purely through edge density and color. Seeds are placed by walking the image grid — each cell spawns seeds proportional to its darkness cubed, and each seed remembers the cell's color. The Voronoi tessellation is drawn stroke-only; dense clusters of tiny colored cells in dark regions create shading, while sparse large cells leave bright areas open:

import random
from scipy.spatial import Voronoi
from pyfreeform import Scene, Polygon

random.seed(42)
bg = "#f4f1eb"
data_scene = Scene.from_image("portrait.jpg", grid_size=100, cell_size=5, background=bg)
g = data_scene.grid
W, H = data_scene.width, data_scene.height

# Grid-based seeding: each cell spawns seeds proportional to darkness
density = 0.7
seeds, seed_colors = [], []
for cell in g:
    darkness = 1.0 - cell.brightness
    expected = density * darkness ** 3
    n = int(expected) + (1 if random.random() < (expected % 1) else 0)
    cx, cy, cw, ch = cell.bounds
    for _ in range(n):
        seeds.append((cx + random.random() * cw, cy + random.random() * ch))
        seed_colors.append(cell.color)

# Mirror seeds so edge cells are bounded
pts = list(seeds)
for x, y in seeds:
    pts.extend([(-x, y), (2 * W - x, y), (x, -y), (x, 2 * H - y)])
vor = Voronoi(pts)

# Stroke-only polygons colored by the underlying image
n_seeds = len(seeds)
scene = Scene(W, H, background=bg)
for i in range(n_seeds):
    region = vor.regions[vor.point_region[i]]
    if not region or -1 in region:
        continue
    verts = [(vor.vertices[v][0], vor.vertices[v][1]) for v in region]
    # clip_polygon(): Sutherland-Hodgman clip to canvas bounds (omitted for brevity)
    verts = clip_polygon(verts, 0, 0, W, H)
    if len(verts) >= 3:
        scene.place(Polygon(verts, fill=None, stroke=seed_colors[i], stroke_width=0.4))

Voronoi Portrait

Voronoi cells colored by the underlying image — dark areas pack tightly, bright areas stay open.

The math

Each image cell spawns Voronoi seeds proportional to darkness³ — a cubic curve that concentrates almost all seeds in shadows while leaving highlights nearly empty. Each seed inherits its cell's color, so the stroke-only tessellation reproduces the image through both density and hue. This technique is related to weighted centroidal Voronoi tessellation (Lloyd relaxation), used in non-photorealistic rendering.


Double-Slit Experiment

A plane wave hits a barrier with two narrow slits — the classic experiment that reveals wave/particle duality. Behind the barrier, each slit acts as a Huygens point source; their interference creates the famous fringe pattern. Dots sized by |ψ|² show particles accumulating at probability maxima:

import math
from pyfreeform import Scene
from pyfreeform.color import hsl

COLS, ROWS, SIZE = 120, 80, 6
bg = "#f4f1eb"
scene = Scene.with_grid(cols=COLS, rows=ROWS, cell_size=SIZE, background=bg)
g = scene.grid
barrier_col = 25
slit1, slit2, slit_width = ROWS // 2 - 8, ROWS // 2 + 8, 3
k = 2 * math.pi / 8  # wavelength = 8

for cell in g:
    cell.data["wall"] = False
    cell.data["amplitude"] = 0.0

# Build barrier with two slits
for row in range(ROWS):
    if abs(row - slit1) > slit_width and abs(row - slit2) > slit_width:
        g[row][barrier_col].data["wall"] = True

# Compute wave field: plane wave left of barrier, Huygens sources right
for cell in g:
    if cell.data["wall"]:
        continue
    px = (cell.col + 0.5) * SIZE
    py = (cell.row + 0.5) * SIZE
    if cell.col <= barrier_col:
        cell.data["amplitude"] = math.sin(k * px)
    else:
        amp = 0.0
        for sc in (slit1, slit2):
            for dr in range(-slit_width, slit_width + 1):
                sx = (barrier_col + 0.5) * SIZE
                sy = (sc + dr + 0.5) * SIZE
                d = math.hypot(px - sx, py - sy)
                if d > 0.1:
                    amp += math.sin(k * (sx + d)) / math.sqrt(d)
        cell.data["amplitude"] = amp

# Normalize, then draw dots sized by |psi|^2
max_amp = max(abs(c.data["amplitude"]) for c in g if not c.data["wall"])
for cell in g:
    if cell.data["wall"]:
        cell.add_fill(color="#3a3a4a")
    else:
        a = cell.data["amplitude"] / max_amp
        intensity = a ** 2
        if intensity > 0.01:
            hue = 220 if a >= 0 else 350
            radius = 0.1 + 0.35 * intensity ** 0.3
            cell.add_dot(radius=radius, color=hsl(hue, 0.75, 0.35),
                         opacity=0.3 + intensity * 0.65)

Double-slit experiment

A plane wave passes through two slits — dots accumulate at interference maxima, showing the probability distribution. Blue = positive amplitude, red = negative.

The physics

The double-slit experiment is the cornerstone of quantum mechanics. Each slit acts as a Huygens point source; behind the barrier the two wavefronts interfere, creating bright fringes (constructive) and dark bands (destructive). Sizing dots by |ψ|² shows the probability distribution — exactly the pattern observed when single particles build up one at a time.

← Advanced Compositions