mindus/data/
autotile.rs

1use super::renderer::*;
2use super::GridPos;
3use crate::block::{Block, Rotation};
4use bobbin_bits::U4;
5
6#[cfg(test)]
7macro_rules! dir {
8    (^) => {
9        crate::block::Rotation::Up
10    };
11    (v) => {
12        crate::block::Rotation::Down
13    };
14    (<) => {
15        crate::block::Rotation::Left
16    };
17    (>) => {
18        crate::block::Rotation::Right
19    };
20}
21#[cfg(test)]
22macro_rules! conv {
23    (_) => {
24        None
25    };
26    ($dir:tt) => {
27        Some((&crate::block::CONVEYOR, crate::data::autotile::dir!($dir)))
28    };
29}
30#[cfg(test)]
31macro_rules! define {
32    ($a:tt,$b:tt,$c:tt,$d:tt) => {
33        [
34            crate::data::autotile::conv!($a),
35            crate::data::autotile::conv!($b),
36            crate::data::autotile::conv!($c),
37            crate::data::autotile::conv!($d),
38        ]
39    };
40}
41
42#[cfg(test)]
43pub(crate) use conv;
44#[cfg(test)]
45pub(crate) use define;
46#[cfg(test)]
47pub(crate) use dir;
48
49pub type Cross = [Option<(&'static Block, Rotation)>; 4];
50/// holds the 4 bordering blocks
51#[derive(Copy, Clone)]
52pub struct RenderingContext {
53    pub cross: Cross,
54    pub position: PositionContext,
55}
56
57/// holds positions
58#[derive(Copy, Clone, Eq, PartialEq)]
59pub struct PositionContext {
60    pub position: GridPos,
61    pub width: usize,
62    pub height: usize,
63}
64
65impl std::fmt::Debug for PositionContext {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        write!(
68            f,
69            "PC<{:?} ({}/{})>",
70            self.position, self.width, self.height
71        )
72    }
73}
74
75#[cfg(test)]
76fn print_crosses(v: Vec<Cross>, height: usize) -> String {
77    let mut s = String::new();
78    for c in v.chunks(height) {
79        for c in c {
80            s.push(c[0].map_or('_', |(_, r)| r.ch()));
81            for c in &c[1..] {
82                s.push(',');
83                s.push(c.map_or('_', |(_, r)| r.ch()));
84            }
85            s.push(' ');
86        }
87        s.push('\n');
88    }
89    s
90}
91
92pub fn tile(ctx: &RenderingContext, name: &str, rot: Rotation, s: Scale) -> ImageHolder<4> {
93    mask2tile(mask(ctx, rot, name), rot, name, s)
94}
95
96pub fn mask2tile(mask: U4, rot: Rotation, name: &str, scale: Scale) -> ImageHolder<4> {
97    use U4::*;
98    macro_rules! p {
99        ($image:literal) => {
100            load!(concat $image => name which is ["reinforced-conduit" | "armored-duct" | "pulse-conduit" | "plated-conduit" | "conduit" | "conveyor" | "titanium-conveyor" | "armored-conveyor" | "duct"], scale)
101        };
102    }
103
104    match mask {
105        // from left
106        B0001 => match rot {
107            Rotation::Down => p!("1-1-h"), // ┐
108            Rotation::Right => p!("0-0"),  // ─
109            Rotation::Up => p!("1-3"),     // ┘
110            Rotation::Left => unreachable!(),
111        },
112        // from below
113        B0010 => match rot {
114            Rotation::Left => p!("1-2"),    // ┐
115            Rotation::Right => p!("1-2-h"), // ┌
116            Rotation::Up => p!("0-3"),      // │
117            Rotation::Down => unreachable!(),
118        },
119        // from bottom + left
120        B0011 => match rot {
121            Rotation::Right => p!("2-0"), // ┬
122            Rotation::Up => p!("2-3-h"),  // ┤
123            _ => unreachable!(),
124        },
125        // from right
126        B0100 => match rot {
127            Rotation::Left => p!("0-2"), // ─
128            Rotation::Down => p!("1-1"), // ┌
129            Rotation::Up => p!("1-1-v"), // └
130            Rotation::Right => unreachable!(),
131        },
132        // from sides
133        B0101 => match rot {
134            Rotation::Up => p!("4-3"),   // ┴
135            Rotation::Down => p!("4-1"), // ┬
136            _ => unreachable!(),
137        },
138        // from right + down
139        B0110 => match rot {
140            Rotation::Up => p!("2-3"),     // ├,
141            Rotation::Left => p!("2-0-h"), // ┬
142            _ => unreachable!(),
143        },
144        // from right + down + left
145        B0111 => match rot {
146            Rotation::Up => p!("3-3"), // ┼
147            _ => unreachable!(),
148        },
149        // from above
150        B1000 => match rot {
151            Rotation::Down => p!("0-1"),   // │
152            Rotation::Left => p!("1-0-h"), // ┘
153            Rotation::Right => p!("1-0"),  // └
154            Rotation::Up => unreachable!(),
155        },
156        // from top and left
157        B1001 => match rot {
158            Rotation::Right => p!("2-0-v"), // ┴
159            Rotation::Down => p!("2-1"),    // ┤
160            _ => unreachable!(),
161        },
162        // from top sides
163        B1010 => match rot {
164            Rotation::Right => p!("4-0"), // ├
165            Rotation::Left => p!("4-3"),  // ┤
166            _ => unreachable!(),
167        },
168        // from top, left, bottom
169        B1011 => match rot {
170            Rotation::Right => p!("3-0"), // ┼
171            _ => unreachable!(),
172        },
173        // from top and right
174        B1100 => match rot {
175            Rotation::Down => p!("2-1-h"), // ├
176            Rotation::Left => p!("2-2"),   // ┴
177            _ => unreachable!(),
178        },
179        // from top, left, right
180        B1101 => match rot {
181            Rotation::Down => p!("3-1"), // ┼
182            _ => unreachable!(),
183        },
184        // from top, right, bottom
185        B1110 => match rot {
186            Rotation::Left => p!("3-0-h"), // ┼
187            _ => unreachable!(),
188        },
189        B0000 => match rot {
190            Rotation::Left => p!("0-2"),
191            Rotation::Right => p!("0-0"),
192            Rotation::Down => p!("0-1"),
193            Rotation::Up => p!("0-3"),
194        },
195        B1111 => unreachable!(),
196    }
197}
198
199pub fn mask(ctx: &RenderingContext, rot: Rotation, n: &str) -> U4 {
200    macro_rules! c {
201        ($in: expr, $srot: expr, $name: expr, $at: expr) => {{
202            if let Some((b, rot)) = $in {
203                if b.name() == $name {
204                    // if they go down, we must not go up
205                    (rot == $at && rot.mirrored(true, true) != $srot) as u8
206                } else {
207                    0
208                }
209            } else {
210                0
211            }
212        }};
213    }
214    use Rotation::{Down, Left, Right, Up};
215    let mut x = 0b0000;
216
217    x |= 8 * c!(ctx.cross[0], rot, n, Down);
218    x |= 4 * c!(ctx.cross[1], rot, n, Left);
219    x |= 2 * c!(ctx.cross[2], rot, n, Up);
220    x |= c!(ctx.cross[3], rot, n, Right);
221    U4::from(x)
222}
223
224pub trait RotationState {
225    fn get_rotation(&self) -> Option<Rotation>;
226}
227pub trait BlockState {
228    fn get_block(&self) -> Option<&'static Block>;
229}
230pub trait Crossable {
231    fn cross(&self, j: usize, c: &PositionContext) -> Cross;
232}
233
234#[test]
235fn test_cross() {
236    macro_rules! test {
237        ($schem: literal => $($a:tt,$b:tt,$c:tt,$d:tt)*) => {
238            let s = crate::Schematic::deserialize_base64($schem).unwrap();
239            let mut c = vec![];
240            println!("{:#?}", s.blocks);
241            for (position, _) in s.block_iter() {
242                let pctx = PositionContext {
243                    position,
244                    width: s.width,
245                    height: s.height,
246                };
247                c.push(s.cross(&pctx));
248            }
249            let n = s.tags.get("name").map_or("<unknown>", |x| &x);
250            let cc: Vec<Cross> = vec![
251                $(define!($a,$b,$c,$d),)*
252            ];
253            if cc != c {
254                let a = print_crosses(cc, s.height as usize);
255                let b = print_crosses(c, s.height as usize);
256                for diff in diff::lines(&a, &b) {
257                    match diff {
258                        diff::Result::Left(l)    => println!("\x1b[38;5;1m{}", l),
259                        diff::Result::Right(r)   => println!("\x1b[38;5;2m{}", r),
260                        diff::Result::Both(l, _) => println!("\x1b[0m{}", l),
261                    }
262                }
263                print!("\x1b[0m");
264                /*
265                for diff in diff::slice(&c.into_iter().enumerate().collect::<Vec<_>>(), &cc.into_iter().enumerate().collect::<Vec<_>>()) {
266                    match diff {
267                        diff::Result::Left((i, l))    => println!("\x1b[38;5;1m- {l:?} at {i}"),
268                        diff::Result::Right((i, r))   => println!("\x1b[38;5;2m+ {r:?} at {i}"),
269                        diff::Result::Both((i, l), _) => println!("\x1b[0m  {l:?} at {i}"),
270                    }
271                }
272                */
273                panic!("test {n} \x1b[38;5;1mfailed\x1b[0m")
274            }
275            println!("test {n} \x1b[38;5;2mpassed\x1b[0m");
276        };
277    }
278    // crosses go from bottom left -> top left -> bottom left + 1 -> top left + 1...
279    // the symbols are directions (> => Right...), which mean the neighbors pointing direction
280    // _ = no block
281
282    // the basic test
283    // ─┐
284    // ─┤
285    test!("bXNjaAF4nGNgYmBiZmDJS8xNZWBNSizOTGbgTkktTi7KLCjJzM9jYGBgy0lMSs0pZmCNfr9gTSwjA0dyfl5ZamV+EVCOhQEBGGEEM4hiZGAGAOb+EWA=" =>
286    //  (0, 0)  (0, 1)
287    //  n e s w borders (west void for first row)
288        >,v,_,_ _,v,>,_
289    //  (1, 0)  (1, 1)
290        v,_,_,> _,_,v,>
291    );
292    // the loop test
293    // ─│─
294    // ─┼┐
295    // ─└┘
296    test!("bXNjaAF4nDWK4QqAIBCDd6dE0SNGP8zuh2CeaAS9fZk0xvjGBgNjYJM7BDaqy5h3qb6EfAZNAIboNokVvKyE0Wu65NbyDhM+cQv6mTtTM/WFYfqLm6m3lx9MAg7n" =>
297        >,^,_,_ <,>,<,_ _,v,>,_
298        >,<,_,< v,v,^,> _,>,>,<
299        v,_,_,^ >,_,<,> _,_,v,v
300    );
301    // the snek test
302    // └┐
303    // ─┘
304    test!("bXNjaAF4nGNgYmBiZmDJS8xNZWApzkvNZuBOSS1OLsosKMnMz2NgYGDLSUxKzSlmYIqOZWTgSM7PK0utzC8CSrAwIAAjEIIQhGJkYAIARA0Ozg==" =>
305        ^,^,_,_ _,<,>,_
306        <,_,_,> _,_,^,^
307    );
308
309    // the notile test
310    test!("bXNjaAF4nCWJQQqAIBREx69E0Lq994oWph8STEMj6fZpzcDjDQMCSahoDsZsdN1TYB25aucz28uniMlxsdmf3wCGYDYOBbSsAqNN8eYn5XYofJEdAtSB31tfaoIVGw==" =>
311        <,>,_,_ _,^,v,_
312        ^,_,_,v _,_,>,<
313    );
314    // the asymmetrical test
315    // <───
316    // ───>
317    test!("bXNjaAF4nEXJwQqAIBAE0HGVCPrE6GC2B0HdcCPw78MKnMMwj4EFWbjiM8N5bRnLwRpqPK8oBcCU/M5JQetmMAcpNzep/cCIAfX69yv6RF0PFy0O4Q==" =>
318        <,>,_,_ _,<,>,_
319        <,>,_,> _,<,>,<
320        // <,_,_,> _,_,>,<
321        <,_,_,> _,_,>,<
322    );
323
324    // the complex test
325    // ─┬─│││─
326    // ─┤─┘─┘─
327    // ─┤┌─│─┐
328    // ─┼┘─┴─│
329    test!("bXNjaAF4nEWOUQ7CIBBEh2VZTbyCx/A2xg9a+WiC0LTGxNvb7Wjk5wEzb7M4QCO05UdBqj3PF5zuZR2XaX5OvQGwmodSV8j1FnAce3uVd1+24Iz/CYQQ8fcVHYEQIjqEXWEm9LwgX9kR+PLSbm2BMlN6Sk/3LhJnJu6S6CVmxl2MntEzv38AchUPug==" =>
330        >,v,_,_ >,v,>,_ >,v,>,_ _,v,>,_
331        v,<,_,> v,v,v,> v,>,v,> _,<,v,>
332        v,>,_,v >,<,<,v <,^,v,v _,v,>,v
333        <,>,_,< ^,v,>,v v,>,<,> _,^,^,<
334        v,<,_,> >,>,>,< ^,^,v,^ _,^,>,v
335        >,v,_,> ^,v,<,v ^,>,>,> _,>,^,^
336        // <,_,_,< ^,_,>,v v,_,<,> _,_,^,<
337        // v,_,_,> >,_,>,< ^,_,v,^ _,_,>,v
338        // >,_,_,> ^,_,<,v ^,_,>,> _,_,^,^
339        v,_,_,< >,_,v,> >,_,v,^ _,_,>,^
340    );
341}
342
343#[test]
344fn test_mask() {
345    macro_rules! assert {
346        ($a:tt,$b:tt,$c:tt,$d:tt => $rot: tt => $expect: expr) => {
347            assert_eq!(mask!(define!($a, $b, $c, $d), $rot), $expect)
348        };
349    }
350    macro_rules! mask {
351        ($cross:expr, $rot: tt) => {
352            mask(
353                &RenderingContext {
354                    position: PositionContext {
355                        position: GridPos(5, 5),
356                        width: 10,
357                        height: 10,
358                    },
359                    cross: $cross,
360                },
361                dir!($rot),
362                "conveyor",
363            )
364        };
365    }
366    assert!(_,_,_,_ => ^ => U4::B0000);
367    assert!(v,_,_,_ => > => U4::B1000);
368    assert!(v,v,_,_ => v => U4::B1000);
369    assert!(_,v,>,_ => > => U4::B0000);
370    assert!(v,>,<,> => ^ => U4::B0001);
371    assert!(v,>,>,_ => > => U4::B1000);
372}