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}