wiki-truchet-tiles.rs

5.0 kB · rust · 159 lines

1use figures::{ink, save, Board, Color};2use mrlyrs::core::error::Result;3use mrlyrs::core::Rng;4use std::f64::consts::{FRAC_PI_2, PI};56const SIDE: usize = 16;7const SEED: u64 = 7;8const LOOPS: usize = 19;9const SAMPLES: usize = 3;1011fn top(row: usize, col: usize) -> usize {12    row * SIDE + col13}1415fn left(row: usize, col: usize) -> usize {16    (SIDE + 1) * SIDE + row * (SIDE + 1) + col17}1819fn arcs(turn: bool, row: usize, col: usize) -> [(usize, usize); 2] {20    let (t, b) = (top(row, col), top(row + 1, col));21    let (l, r) = (left(row, col), left(row, col + 1));22    if turn {23        [(t, r), (l, b)]24    } else {25        [(t, l), (r, b)]26    }27}2829fn centres(turn: bool) -> [(f64, f64); 2] {30    if turn {31        [(1.0, 0.0), (0.0, 1.0)]32    } else {33        [(0.0, 0.0), (1.0, 1.0)]34    }35}3637fn even(turns: &[bool], u: f64, v: f64) -> bool {38    let col = (u.floor() as usize).min(SIDE - 1);39    let row = (v.floor() as usize).min(SIDE - 1);40    let turn = turns[row * SIDE + col];41    let (x, y) = (u - col as f64, v - row as f64);42    let corner = |c: (f64, f64)| (row + col + c.0 as usize + c.1 as usize).is_multiple_of(2);43    let [a, b] = centres(turn);44    for c in [a, b] {45        if (x - c.0).hypot(y - c.1) < 0.5 {46            return corner(c);47        }48    }49    !corner(a)50}5152fn main() -> Result<()> {53    let mut rng = Rng::new(SEED);54    let turns: Vec<bool> = (0..SIDE * SIDE).map(|_| rng.boolean()).collect();55    let nodes = 2 * SIDE * (SIDE + 1);56    let mut links: Vec<Vec<usize>> = vec![Vec::new(); nodes];57    let mut ends = Vec::new();58    for row in 0..SIDE {59        for col in 0..SIDE {60            for (a, b) in arcs(turns[row * SIDE + col], row, col) {61                let arc = ends.len();62                ends.push((a, b));63                links[a].push(arc);64                links[b].push(arc);65            }66        }67    }68    assert_eq!(ends.len(), 2 * SIDE * SIDE);69    let border = links.iter().filter(|l| l.len() == 1).count();70    assert_eq!(border, 4 * SIDE);7172    let mut curve = vec![usize::MAX; ends.len()];73    let mut open = Vec::new();74    for start in 0..ends.len() {75        if curve[start] != usize::MAX {76            continue;77        }78        let id = open.len();79        let mut stack = vec![start];80        let mut touches = false;81        while let Some(arc) = stack.pop() {82            if curve[arc] != usize::MAX {83                continue;84            }85            curve[arc] = id;86            let (a, b) = ends[arc];87            for node in [a, b] {88                touches |= links[node].len() == 1;89                stack.extend(links[node].iter().filter(|&&n| curve[n] == usize::MAX));90            }91        }92        open.push(touches);93    }94    let strands = open.iter().filter(|o| **o).count();95    assert_eq!(strands, 2 * SIDE);96    assert_eq!(open.len() - strands, LOOPS);9798    let mut board = Board::square();99    let frame = board.frame(0.08);100    let cell = frame.w / SIDE as f64;101    let tint = ink::fade(ink::dim(), 0.25);102    let x0 = frame.x.floor() as usize;103    let x1 = (frame.x + frame.w).ceil() as usize;104    for py in x0..x1 {105        for px in x0..x1 {106            let mut hits = 0;107            for sy in 0..SAMPLES {108                for sx in 0..SAMPLES {109                    let x = px as f64 + (sx as f64 + 0.5) / SAMPLES as f64;110                    let y = py as f64 + (sy as f64 + 0.5) / SAMPLES as f64;111                    let (u, v) = ((x - frame.x) / cell, (y - frame.y) / cell);112                    let inside = (0.0..SIDE as f64).contains(&u) && (0.0..SIDE as f64).contains(&v);113                    if inside && even(&turns, u, v) {114                        hits += 1;115                    }116                }117            }118            board.blend(px, py, tint, hits as f64 / (SAMPLES * SAMPLES) as f64);119        }120    }121122    let thick = cell * 0.15;123    let radius = cell / 2.0;124    let mut arc = 0;125    for row in 0..SIDE {126        for col in 0..SIDE {127            let turn = turns[row * SIDE + col];128            let (x, y) = (frame.x + col as f64 * cell, frame.y + row as f64 * cell);129            let sweeps: [((f64, f64), (f64, f64)); 2] = if turn {130                [131                    ((x + cell, y), (FRAC_PI_2, PI)),132                    ((x, y + cell), (-FRAC_PI_2, 0.0)),133                ]134            } else {135                [136                    ((x, y), (0.0, FRAC_PI_2)),137                    ((x + cell, y + cell), (PI, PI + FRAC_PI_2)),138                ]139            };140            for (centre, angles) in sweeps {141                let color: Color = if open[curve[arc]] {142                    ink::blue()143                } else {144                    ink::yellow()145                };146                board.arc(centre, radius, angles, thick, color);147                arc += 1;148            }149        }150    }151    let (w, end) = (board.width as f64, frame.x + frame.w);152    let ground = ink::ground();153    board.rect(0.0, 0.0, w, frame.y, ground);154    board.rect(0.0, end, w, w - end, ground);155    board.rect(0.0, 0.0, frame.x, w, ground);156    board.rect(end, 0.0, w - end, w, ground);157    save("wiki-truchet-tiles", &board)?;158    Ok(())159}