dcc_tiler/
render.rs

1use crate::board::RectangularBoard;
2use rand::Rng;
3use simplesvg::{Attr, Color, Fig, Svg};
4use std::collections::{HashMap, HashSet};
5
6pub fn render_single_tiling_from_vec(boards: Vec<&RectangularBoard>) -> String {
7    let mut tile_hashmap = HashMap::new();
8
9    for i in (1..boards.len()).rev() {
10        tile_hashmap.insert(boards[i].clone(), vec![boards[i - 1].clone()]);
11    }
12
13    render_single_tiling(boards.last().unwrap(), &tile_hashmap)
14}
15
16pub fn render_single_tiling<S: ::std::hash::BuildHasher>(
17    board: &RectangularBoard,
18    tile_hashmap: &HashMap<RectangularBoard, Vec<RectangularBoard>, S>,
19) -> String {
20    // TODO: maybe remove gap_size now that we've implemented borders
21    let gap_size = 0.0;
22    let box_size = 50.0;
23    let padding = 10.0;
24
25    // TODO: make these configurable
26    let colors = vec![
27        Color(30, 56, 136),
28        Color(71, 115, 170),
29        Color(245, 230, 99),
30        Color(255, 173, 105),
31        Color(156, 56, 72),
32        Color(124, 178, 135),
33        Color(251, 219, 136),
34    ];
35
36    let mut boxes = Vec::new();
37
38    // choose a random initial colour
39    // we do this so that when you render a single tile, it won't always be the first colour in the colors vector
40    let mut color_index = rand::thread_rng().gen_range(0, colors.len());
41    let mut current = board;
42
43    while tile_hashmap.contains_key(current) {
44        // choose a random source for this board state
45        let next = rand::thread_rng().gen_range(0, tile_hashmap[current].len());
46        let next_board = tile_hashmap.get(current).unwrap().get(next).unwrap();
47
48        let mut tiled_positions = HashSet::new();
49
50        // compute the tile that was placed here
51        for y in 0..next_board.height {
52            for x in 0..next_board.width {
53                if next_board.board[y][x] ^ current.board[y][x] {
54                    // we just tiled this position
55                    tiled_positions.insert((x, y));
56                }
57            }
58        }
59
60        for (x, y) in tiled_positions.iter() {
61            // draw the underlying box
62            let rect = Fig::Rect(
63                (*x as f32) * (box_size + gap_size) + padding,
64                (*y as f32) * (box_size + gap_size) + padding,
65                box_size,
66                box_size,
67            )
68            .styled(Attr::default().fill(colors[color_index]));
69
70            boxes.push(rect);
71
72            enum Border {
73                Left,
74                Right,
75                Top,
76                Bottom,
77            };
78
79            // helper function to construct our borders
80            let border = |x: usize, y: usize, b: Border, gray: bool| {
81                let xs = match b {
82                    Border::Right => (x as f32 + 1.0) * (box_size + gap_size) + padding - gap_size,
83                    _ => (x as f32) * (box_size + gap_size) + padding,
84                };
85                let ys = match b {
86                    Border::Top => (y as f32 + 1.0) * (box_size + gap_size) + padding - gap_size,
87                    _ => (y as f32) * (box_size + gap_size) + padding,
88                };
89                let xe = match b {
90                    Border::Left => (x as f32) * (box_size + gap_size) + padding,
91                    _ => (x as f32 + 1.0) * (box_size + gap_size) + padding - gap_size,
92                };
93                let ye = match b {
94                    Border::Bottom => (y as f32) * (box_size + gap_size) + padding,
95                    _ => (y as f32 + 1.0) * (box_size + gap_size) + padding - gap_size,
96                };
97
98                let mut b = Fig::Line(xs, ys, xe, ye);
99                b = b.styled(
100                    Attr::default()
101                        .stroke(if gray {
102                            Color(211, 211, 211)
103                        } else {
104                            Color(0, 0, 0)
105                        })
106                        .stroke_width(0.5),
107                );
108
109                b
110            };
111
112            // left border
113            boxes.push(border(
114                *x,
115                *y,
116                Border::Left,
117                tiled_positions.contains(&(*x - 1, *y)),
118            ));
119            // right border
120            boxes.push(border(
121                *x,
122                *y,
123                Border::Right,
124                tiled_positions.contains(&(*x + 1, *y)),
125            ));
126            // top border
127            boxes.push(border(
128                *x,
129                *y,
130                Border::Top,
131                tiled_positions.contains(&(*x, *y + 1)),
132            ));
133            // bottom border
134            boxes.push(border(
135                *x,
136                *y,
137                Border::Bottom,
138                tiled_positions.contains(&(*x, *y - 1)),
139            ));
140        }
141
142        // increment the color index by 1
143        color_index = (color_index + 1) % colors.len();
144
145        current = next_board;
146    }
147
148    Svg(
149        vec![Fig::Multiple(boxes)],
150        (50 * board.width) as u32 + 2 * (padding as u32),
151        (50 * board.height) as u32 + 2 * (padding as u32),
152    )
153    .to_string()
154}