wiki-domino-tilings.rs
5.7 kB · rust · 187 lines
1use figures::{ink, save, Board, Grid};2use mrlyrs::core::error::Result;3use mrlyrs::core::Rng;4use std::collections::{HashMap, HashSet};56const ORDER: i64 = 32;7const SEED: u64 = 5;8const DOMINOES: usize = 1056;910#[derive(Clone, Copy, PartialEq, Eq, Hash)]11struct Domino {12 x: i64,13 y: i64,14 flat: bool,15}1617fn inside(order: i64, x: i64, y: i64) -> bool {18 (2 * x + 1).abs() + (2 * y + 1).abs() <= 2 * order19}2021fn cells(d: Domino) -> [(i64, i64); 2] {22 if d.flat {23 [(d.x, d.y), (d.x + 1, d.y)]24 } else {25 [(d.x, d.y), (d.x, d.y + 1)]26 }27}2829fn leads(order: i64, x: i64, y: i64) -> bool {30 (x + y + order).rem_euclid(2) == 031}3233fn shuffle(order: i64, tiles: &[Domino], rng: &mut Rng) -> Vec<Domino> {34 let laid: HashSet<Domino> = tiles.iter().copied().collect();35 let mut doomed = HashSet::new();36 for &d in tiles {37 if leads(order, d.x, d.y) {38 let facing = if d.flat {39 Domino { y: d.y + 1, ..d }40 } else {41 Domino { x: d.x + 1, ..d }42 };43 if laid.contains(&facing) {44 doomed.insert(d);45 doomed.insert(facing);46 }47 }48 }49 let mut next: Vec<Domino> = tiles50 .iter()51 .filter(|d| !doomed.contains(d))52 .map(|&d| {53 let step = if leads(order, d.x, d.y) { 1 } else { -1 };54 if d.flat {55 Domino { y: d.y + step, ..d }56 } else {57 Domino { x: d.x + step, ..d }58 }59 })60 .collect();61 let grown = order + 1;62 let mut taken = HashSet::new();63 for &d in &next {64 for (x, y) in cells(d) {65 assert!(inside(grown, x, y));66 assert!(taken.insert((x, y)));67 }68 }69 for y in (-grown..grown).rev() {70 for x in -grown..grown {71 if !inside(grown, x, y) || taken.contains(&(x, y)) {72 continue;73 }74 assert!(leads(grown, x, y));75 for (u, v) in [(x, y), (x + 1, y), (x, y - 1), (x + 1, y - 1)] {76 assert!(inside(grown, u, v));77 assert!(taken.insert((u, v)));78 }79 if rng.boolean() {80 next.push(Domino { x, y, flat: true });81 next.push(Domino {82 x,83 y: y - 1,84 flat: true,85 });86 } else {87 next.push(Domino {88 x,89 y: y - 1,90 flat: false,91 });92 next.push(Domino {93 x: x + 1,94 y: y - 1,95 flat: false,96 });97 }98 }99 }100 next101}102103fn count(rows: &[Vec<bool>]) -> u64 {104 let width = rows[0].len();105 let mut states: HashMap<u64, u64> = HashMap::from([(0, 1)]);106 for row in rows {107 let mut next = HashMap::new();108 for (&mask, &ways) in &states {109 let mut stack = vec![(0, mask, 0u64)];110 while let Some((i, held, below)) = stack.pop() {111 if i == width {112 *next.entry(below).or_insert(0) += ways;113 continue;114 }115 let bit = 1u64 << i;116 if held & bit != 0 {117 if row[i] {118 stack.push((i + 1, held, below));119 }120 continue;121 }122 if !row[i] {123 stack.push((i + 1, held, below));124 continue;125 }126 stack.push((i + 1, held, below | bit));127 if i + 1 < width && row[i + 1] && held & (bit << 1) == 0 {128 stack.push((i + 2, held, below));129 }130 }131 }132 states = next;133 }134 states.get(&0).copied().unwrap_or(0)135}136137fn aztec(order: i64) -> Vec<Vec<bool>> {138 (-order..order)139 .map(|y| (-order..order).map(|x| inside(order, x, y)).collect())140 .collect()141}142143fn main() -> Result<()> {144 for order in 1..=6 {145 assert_eq!(count(&aztec(order)), 1 << (order * (order + 1) / 2));146 }147 let fibonacci: Vec<u64> = (1..=10).map(|n| count(&vec![vec![true; 2]; n])).collect();148 assert_eq!(fibonacci, vec![1, 2, 3, 5, 8, 13, 21, 34, 55, 89]);149 assert_eq!(count(&vec![vec![true; 8]; 8]), 12_988_816);150151 let mut rng = Rng::new(SEED);152 let mut tiles = Vec::new();153 for order in 0..ORDER {154 tiles = shuffle(order, &tiles, &mut rng);155 }156 assert_eq!((ORDER * (ORDER + 1)) as usize, DOMINOES);157 assert_eq!(tiles.len(), DOMINOES);158 let region: usize = aztec(ORDER).iter().flatten().filter(|&&on| on).count();159 assert_eq!(region, 2 * DOMINOES);160 let covered: HashSet<(i64, i64)> = tiles.iter().flat_map(|&d| cells(d)).collect();161 assert_eq!(covered.len(), region);162 assert!(tiles.iter().filter(|d| !d.flat).count().is_multiple_of(2));163164 let mut board = Board::square();165 let side = 2 * ORDER as usize;166 let grid = Grid::new(board.frame(0.08), side, side, 0.0);167 let unit = grid.frame.w / side as f64;168 let pad = unit * 0.1;169 for &d in &tiles {170 let [(ax, ay), (bx, by)] = cells(d);171 let corner = |x: i64, y: i64| grid.cell((x + ORDER) as usize, (ORDER - 1 - y) as usize);172 let (x0, y0, _, _) = corner(ax.min(bx), ay.max(by));173 let (x1, y1, w1, h1) = corner(ax.max(bx), ay.min(by));174 let (w, h) = (x1 + w1 - x0, y1 + h1 - y0);175 let color = if d.flat { ink::blue() } else { ink::yellow() };176 board.round_rect(177 x0 + pad,178 y0 + pad,179 w - 2.0 * pad,180 h - 2.0 * pad,181 (unit - 2.0 * pad) * 0.3,182 color,183 );184 }185 save("wiki-domino-tilings", &board)?;186 Ok(())187}