world_map_gen/
board.rs

1//! This module provides a board struct representing world map.
2//!
3//! Coordinates which are used in this library is,
4//! - Origin is at left top corner as (0, 0)
5//! - Right direction represents X and width, left direction represents Y and height
6//!
7//! ```text,ignore
8//!       width
9//!  O--------------> x
10//! h|
11//! e|
12//! i|
13//! g|
14//! h|
15//! t|
16//!  V
17//!  y
18//! ```
19
20use serde;
21
22
23use crate::land::Land;
24use std::collections::HashMap;
25use std::ops::{Index, IndexMut};
26use std::slice;
27
28/// Specific (x, y) position on board.
29#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
30pub struct Pos {
31    /// X coordinate in number of cells on board
32    pub x: usize,
33    /// Y coordinate in number of cells on board
34    pub y: usize,
35}
36
37// This warning is raised only on wasm32 target since Pos contains usize fields.
38// usize field have 64bits length on x86_64 but have 32bits on wasm32.
39// So using `self` argument is efficient on wasm32, but not on x86_64.
40#[allow(clippy::trivially_copy_pass_by_ref)]
41impl Pos {
42    /// Calculate how much cost it takes to reach other position from self position. Only 4
43    /// directions (up, down, left, right) are allowed to move.
44    #[inline]
45    pub fn move_cost(&self, other: &Pos) -> usize {
46        use std::cmp::{max, min};
47        let dx = max(self.x, other.x) - min(self.x, other.x);
48        let dy = max(self.y, other.y) - min(self.y, other.y);
49        dx + dy
50    }
51}
52
53/// A struct to represent a one world map. It is generally created by `gen` module's random map
54/// generator. This struct is JSON serializable with `serde_json`.
55#[derive(Debug, PartialEq)]
56pub struct Board<'a> {
57    width: usize,
58    height: usize,
59    cells: Vec<Land<'a>>,
60}
61
62impl<'a> Board<'a> {
63    /// Builds a board with given `width * height` cells. The `builder` generates a cell at the
64    /// given position (x, y) by returning `land::Land` instance.
65    /// Note that you can use `land::LandKind::preset()` to utilize a preset land instance easily.
66    pub fn build<F>(width: usize, height: usize, mut builder: F) -> Board<'a>
67    where
68        F: FnMut(usize, usize) -> Land<'a>,
69    {
70        let mut cells = Vec::with_capacity(width * height);
71        for y in 0..height {
72            for x in 0..width {
73                cells.push(builder(x, y));
74            }
75        }
76        Board {
77            cells,
78            width,
79            height,
80        }
81    }
82
83    /// Returns number of cells per row
84    #[inline]
85    pub fn width(&self) -> usize {
86        self.width
87    }
88
89    /// Returns number of rows
90    #[inline]
91    pub fn height(&self) -> usize {
92        self.height
93    }
94
95    #[inline]
96    fn index_at(&self, x: usize, y: usize) -> usize {
97        y * self.width + x
98    }
99
100    /// Returns a reference to cell at given (x, y) position
101    #[inline]
102    pub fn at(&self, x: usize, y: usize) -> &Land<'_> {
103        &self.cells[self.index_at(x, y)]
104    }
105
106    /// Returns a mutable reference to cell at given (x, y) position
107    #[inline]
108    pub fn at_mut(&mut self, x: usize, y: usize) -> &'a mut Land<'_> {
109        let idx = self.index_at(x, y);
110        &mut self.cells[idx]
111    }
112
113    /// Returns an iterator which iterates all cells from left-top corner to right-bottom corner
114    #[inline]
115    pub fn iter<'b>(&'b self) -> slice::Iter<'b, Land<'a>> {
116        self.cells.iter()
117    }
118
119    /// Returns a mutable reference iterator which iterates all cells from left-top corner to right
120    /// bottom corner
121    #[inline]
122    pub fn iter_mut<'b>(&'b mut self) -> slice::IterMut<'b, Land<'a>> {
123        self.cells.iter_mut()
124    }
125
126    /// Returns an iterator which iterates each row as slice from top to bottom
127    #[inline]
128    pub fn rows<'b>(&'b self) -> slice::Chunks<'b, Land<'a>> {
129        self.cells.chunks(self.width)
130    }
131
132    /// Returns a mutable reference iterator which iterates each row as slice from top to bottom
133    #[inline]
134    pub fn rows_mut<'b>(&'b mut self) -> slice::ChunksMut<'b, Land<'a>> {
135        self.cells.chunks_mut(self.width)
136    }
137}
138
139impl<'a> Index<Pos> for Board<'a> {
140    type Output = Land<'a>;
141
142    /// Returns a reference to cell at given position
143    #[inline]
144    fn index(&self, p: Pos) -> &Land<'a> {
145        &self.cells[self.index_at(p.x, p.y)]
146    }
147}
148
149impl<'a> IndexMut<Pos> for Board<'a> {
150    /// Returns a mutable reference to cell at given position
151    #[inline]
152    fn index_mut(&mut self, p: Pos) -> &mut Land<'a> {
153        let idx = self.index_at(p.x, p.y);
154        &mut self.cells[idx]
155    }
156}
157
158impl<'a> serde::Serialize for Board<'a> {
159    /// Serialize board in a map which contain width, height, cells as array, and legends for each
160    /// land kind as map. By `serde_json`, the struct can be serialized to JSON object.
161    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
162        use serde::ser::SerializeMap;
163
164        struct Cells<'a, 'b: 'a> {
165            w: usize,
166            h: usize,
167            vec: &'a Vec<Land<'b>>,
168        }
169        impl<'a, 'b> serde::Serialize for Cells<'a, 'b> {
170            fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
171                use serde::ser::SerializeSeq;
172                let mut seq = serializer.serialize_seq(Some(self.h))?;
173                for row in self.vec.chunks(self.w) {
174                    seq.serialize_element(row)?;
175                }
176                seq.end()
177            }
178        }
179
180        let mut map = serializer.serialize_map(Some(4))?;
181        map.serialize_entry("width", &self.width)?;
182        map.serialize_entry("height", &self.height)?;
183        map.serialize_entry(
184            "cells",
185            &Cells {
186                w: self.width,
187                h: self.height,
188                vec: &self.cells,
189            },
190        )?;
191
192        // Do not derive Serialize trait since legends should be contained in serialized JSON output
193        let legends = self
194            .iter()
195            .map(|cell| (cell.kind, cell.kind.legend()))
196            .collect::<HashMap<_, _>>();
197        map.serialize_entry("legends", &legends)?;
198
199        map.end()
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206    use crate::land::LandKind;
207    use termcolor::ColorSpec;
208
209    #[test]
210    fn build_board() {
211        let board = Board::build(2, 3, |x, y| Land {
212            kind: LandKind::Town,
213            char: "hi",
214            color: ColorSpec::default(),
215            altitude: (x + 2 * y) as u8,
216        });
217        assert_eq!(board.width, 2);
218        assert_eq!(board.height, 3);
219        for y in 0..board.height {
220            for x in 0..board.width {
221                let land = &board[Pos { x, y }];
222                assert_eq!(land.kind, LandKind::Town);
223                assert_eq!(land.altitude, (x + 2 * y) as u8);
224                let land2 = board.at(x, y);
225                assert_eq!(land, land2);
226            }
227        }
228    }
229
230    #[test]
231    fn iter_board() {
232        let mut idx = 0;
233        let board = Board::build(2, 3, |_, _| {
234            idx += 1;
235            Land {
236                kind: LandKind::Town,
237                char: "hi",
238                color: ColorSpec::default(),
239                altitude: idx,
240            }
241        });
242        for (idx, land) in board.iter().enumerate() {
243            assert_eq!(land.kind, LandKind::Town);
244            assert_eq!(land.altitude, (idx as u8) + 1);
245        }
246    }
247
248    #[test]
249    fn iter_empty_board() {
250        let board = Board::build(0, 0, |_, _| Land::default());
251        for _ in board.iter() {
252            assert!(false);
253        }
254    }
255
256    #[test]
257    fn iter_rows() {
258        let board = Board::build(2, 3, |x, y| Land {
259            kind: LandKind::Town,
260            char: "hi",
261            color: ColorSpec::default(),
262            altitude: (x + 2 * y) as u8,
263        });
264        for row in board.rows() {
265            assert_eq!(row.len(), 2);
266        }
267        assert_eq!(board.rows().count(), 3);
268
269        let mut board = Board::build(2, 3, |x, y| Land {
270            kind: LandKind::Town,
271            char: "hi",
272            color: ColorSpec::default(),
273            altitude: (x + 2 * y) as u8,
274        });
275        for row in board.rows_mut() {
276            assert_eq!(row.len(), 2);
277        }
278        assert_eq!(board.rows_mut().count(), 3);
279    }
280}