wiki-rep-tiles.rs

5.6 kB · rust · 196 lines

1use figures::{ink, save, Board};2use mrlyrs::core::error::Result;3use std::collections::HashSet;45const DEPTH: usize = 3;6const GAPS: [f64; DEPTH] = [16.0, 8.0, 3.0];7const SPHINX: [[(i64, i64); 3]; 6] = [8    [(0, 0), (1, 0), (0, 1)],9    [(1, 0), (2, 0), (1, 1)],10    [(2, 0), (3, 0), (2, 1)],11    [(1, 0), (0, 1), (1, 1)],12    [(2, 0), (1, 1), (2, 1)],13    [(0, 1), (1, 1), (0, 2)],14];15const OUTLINE: [(i64, i64); 5] = [(0, 0), (3, 0), (2, 1), (1, 1), (0, 2)];16const ID: Mat = [[1, 0], [0, 1]];17const TURN: Mat = [[0, -1], [1, 1]];18const MIRROR: Mat = [[1, 1], [0, -1]];1920type Mat = [[i64; 2]; 2];21type Point = (i64, i64);2223#[derive(Clone, Copy)]24struct Tile {25    m: Mat,26    t: Point,27}2829fn mul(a: Mat, b: Mat) -> Mat {30    let cell = |i: usize, j: usize| a[i][0] * b[0][j] + a[i][1] * b[1][j];31    [[cell(0, 0), cell(0, 1)], [cell(1, 0), cell(1, 1)]]32}3334fn apply(m: Mat, p: Point) -> Point {35    (m[0][0] * p.0 + m[0][1] * p.1, m[1][0] * p.0 + m[1][1] * p.1)36}3738fn det(m: Mat) -> i64 {39    m[0][0] * m[1][1] - m[0][1] * m[1][0]40}4142fn place(tile: Tile, p: Point) -> Point {43    let q = apply(tile.m, p);44    (q.0 + tile.t.0, q.1 + tile.t.1)45}4647fn key(tri: [Point; 3]) -> Point {48    (49        tri[0].0 + tri[1].0 + tri[2].0,50        tri[0].1 + tri[1].1 + tri[2].1,51    )52}5354fn split(tri: [Point; 3]) -> [[Point; 3]; 4] {55    let [p, q, r] = tri;56    let two = |a: Point| (2 * a.0, 2 * a.1);57    let sum = |a: Point, b: Point| (a.0 + b.0, a.1 + b.1);58    let (pq, qr, pr) = (sum(p, q), sum(q, r), sum(p, r));59    [60        [two(p), pq, pr],61        [two(q), pq, qr],62        [two(r), pr, qr],63        [pq, qr, pr],64    ]65}6667fn cells(tile: Tile) -> Vec<Point> {68    SPHINX69        .iter()70        .map(|tri| key(tri.map(|p| place(tile, p))))71        .collect()72}7374fn cover(75    target: &HashSet<Point>,76    pieces: &[(Tile, Vec<Point>)],77    used: &mut Vec<usize>,78    found: &mut Vec<Vec<usize>>,79) {80    let covered: HashSet<Point> = used81        .iter()82        .flat_map(|&i| pieces[i].1.iter().copied())83        .collect();84    let Some(&first) = target.iter().filter(|c| !covered.contains(c)).min() else {85        found.push(used.clone());86        return;87    };88    for (i, (_, keys)) in pieces.iter().enumerate() {89        if keys.contains(&first) && keys.iter().all(|k| !covered.contains(k)) {90            used.push(i);91            cover(target, pieces, used, found);92            used.pop();93        }94    }95}9697fn main() -> Result<()> {98    let doubled: HashSet<Point> = SPHINX.iter().flat_map(|&tri| split(tri)).map(key).collect();99    assert_eq!(doubled.len(), 24);100    let mut pieces = Vec::new();101    for flip in [ID, MIRROR] {102        let mut m = flip;103        for _ in 0..6 {104            for a in -8..=8 {105                for b in -8..=8 {106                    let tile = Tile { m, t: (a, b) };107                    let keys = cells(tile);108                    if keys.iter().all(|k| doubled.contains(k)) {109                        pieces.push((tile, keys));110                    }111                }112            }113            m = mul(TURN, m);114        }115    }116    let mut found = Vec::new();117    cover(&doubled, &pieces, &mut Vec::new(), &mut found);118    assert_eq!(found.len(), 1);119    let rule: Vec<Tile> = found[0].iter().map(|&i| pieces[i].0).collect();120    assert_eq!(rule.len(), 4);121    assert_eq!(rule.iter().filter(|c| det(c.m) < 0).count(), 3);122123    let mut levels = vec![vec![Tile { m: ID, t: (0, 0) }]];124    for _ in 0..DEPTH {125        let next: Vec<Tile> = levels126            .last()127            .unwrap()128            .iter()129            .flat_map(|p| {130                rule.iter().map(move |c| {131                    let shift = apply(p.m, c.t);132                    Tile {133                        m: mul(p.m, c.m),134                        t: (shift.0 + 2 * p.t.0, shift.1 + 2 * p.t.1),135                    }136                })137            })138            .collect();139        levels.push(next);140    }141    let counts: Vec<usize> = levels.iter().map(Vec::len).collect();142    assert_eq!(counts, vec![1, 4, 16, 64]);143    let leaves = &levels[DEPTH];144    let direct = leaves.iter().filter(|c| det(c.m) > 0).count();145    assert_eq!((direct, leaves.len() - direct), (28, 36));146    let mut fine: Vec<[Point; 3]> = SPHINX.to_vec();147    for _ in 0..DEPTH {148        fine = fine.into_iter().flat_map(split).collect();149    }150    let fine: HashSet<Point> = fine.into_iter().map(key).collect();151    let mut laid = HashSet::new();152    for c in leaves {153        for k in cells(*c) {154            assert!(laid.insert(k));155        }156    }157    assert_eq!(laid.len(), 6 * 64);158    assert_eq!(laid, fine);159160    let mut board = Board::square();161    let frame = board.frame(0.08);162    let rise = 3f64.sqrt() / 2.0;163    let px = frame.w / 3.0;164    let top = frame.y + (frame.h - 2.0 * rise * px) / 2.0;165    let outline = |tile: &Tile, depth: usize| -> Vec<(f64, f64)> {166        let scale = (1 << depth) as f64;167        OUTLINE168            .iter()169            .map(|&p| {170                let (a, b) = place(*tile, p);171                let (a, b) = (a as f64 / scale, b as f64 / scale);172                (173                    frame.x + (a + b / 2.0) * px,174                    top + (2.0 * rise - b * rise) * px,175                )176            })177            .collect()178    };179    for c in leaves {180        let color = if det(c.m) > 0 {181            ink::blue()182        } else {183            ink::orange()184        };185        board.polygon(&outline(c, DEPTH), color);186    }187    for depth in (1..=DEPTH).rev() {188        for c in &levels[depth] {189            let mut ring = outline(c, depth);190            ring.push(ring[0]);191            board.polyline(&ring, GAPS[depth - 1], ink::ground());192        }193    }194    save("wiki-rep-tiles", &board)?;195    Ok(())196}