Ollin

WaveFunctionCollapse

a pipe network solved by Wave Function Collapse: min-entropy observation, weighted collapse, and constraint propagation over edge sockets, repeated until every neighbor connects legally (wfc, drawWFC)

Run
swift run --package-path Examples Example-Patterns-WaveFunctionCollapse
Source
Examples/Patterns/WaveFunctionCollapse/Sketch.swift
import Ollin

/// Wave Function Collapse: fill a grid from a handful of tiles so every neighbor
/// connects legally. Each cell starts holding all tiles at once; the solver keeps
/// collapsing the most-constrained cell to one tile and propagating that choice
/// to its neighbors, until the whole grid is a single continuous pipe network.
///
/// The tiles here are a blank plus straight, elbow, tee, and cross connectors
/// (built from one drawn shape each via `rotations()`); their edge sockets say
/// which sides carry a pipe, so the solver only ever places tiles whose pipes
/// line up. The solve is a pure function of the seed, so it re-rolls to a fresh
/// but always-legal layout every few seconds.
@main
final class WaveFunctionCollapseSketch: Sketch {
    private let tiles: [WFCTile] = {
        let blank = WFCTile([0, 0, 0, 0], weight: 1.1)
        let line  = WFCTile([1, 0, 1, 0], weight: 1.6).rotations(2)   // vertical + horizontal
        let elbow = WFCTile([1, 1, 0, 0], weight: 1.3).rotations(4)   // four corners
        let tee   = WFCTile([1, 1, 1, 0], weight: 0.5).rotations(4)
        let cross = WFCTile([1, 1, 1, 1], weight: 0.3)
        return [blank] + line + elbow + tee + [cross]
    }()

    private var grid: [[Int]] = []
    private var roll = -1

    override func draw() {
        // Re-roll to a new (still legal) layout every few seconds.
        let currentRoll = frameCount / 170
        if currentRoll != roll {
            roll = currentRoll
            seed(currentRoll + 1)
            grid = wfc(tiles: tiles, columns: 18, rows: 18) ?? grid
        }
        guard !grid.isEmpty else { return }

        background(Color(hex: 0x0E1116))
        strokeCap(.round)
        strokeJoin(.round)
        strokeWeight(6 * scale)
        noFill()

        drawWFC(grid, padding: .all(40 * scale)) { index, cell in
            let sockets = tiles[index].sockets
            guard sockets.contains(where: { $0 != 0 }) else { return }
            // Tint each tile by position so the network reads with some depth.
            let hue = (cell.center.x + cell.center.y) / (width + height)
            stroke(Color(hue: 0.5 + hue * 0.25, saturation: 0.55, brightness: 0.95))

            let c = cell.center
            let mids = [Vector2(cell.center.x, cell.y),               // top
                        Vector2(cell.x + cell.width, cell.center.y),  // right
                        Vector2(cell.center.x, cell.y + cell.height), // bottom
                        Vector2(cell.x, cell.center.y)]               // left
            for edge in 0 ..< 4 where sockets[edge] != 0 {
                drawLine(c, mids[edge])
            }
        }
    }
}

↑↓ move ↩ open