Skip to main content

minsweeper_rs/
board.rs

1use crate::{Cell, CellState, CellType};
2use std::error::Error;
3use std::fmt::{Display, Formatter};
4use std::iter::Flatten;
5use std::num::NonZeroUsize;
6use std::ops::{Index, IndexMut};
7use std::vec::IntoIter;
8
9#[derive(Clone, Debug)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub struct Board {
12    grid: Vec<Vec<Cell>>,
13    size: BoardSize
14}
15
16pub type Point = (usize, usize);
17
18impl Board {
19
20    // pub(crate) const fn zero(board_size: BoardSize) -> Self {
21    //     Self {
22    //         grid: vec![],
23    //         size: board_size
24    //     }
25    // }
26
27    pub fn new(board_size: BoardSize, cell: Cell) -> Self {
28        Self {
29            grid: vec![vec![cell; board_size.height().into()]; board_size.width().into()],
30            size: board_size
31        }
32    }
33
34    pub fn empty(board_size: BoardSize) -> Self {
35        Self::new(board_size, Cell::EMPTY)
36    }
37
38    pub fn size(&self) -> BoardSize {
39        self.size
40    }
41
42    pub(crate) fn has_won(&self) -> bool {
43        !self.iter()
44                .any(|cell| match cell {
45                    Cell { cell_type: CellType::Mine, cell_state: CellState::Revealed } => true,
46                    Cell { cell_type: CellType::Safe(_), cell_state: state } if *state != CellState::Revealed => true,
47                    _ => false
48                })
49    }
50
51    pub(crate) fn hide_mines(&self) -> Self {
52        let mut board = self.clone();
53
54        board.grid = board.grid.into_iter()
55                .map(|e| e.into_iter()
56                        .map(|mut cell| {
57                            if cell.cell_state != CellState::Revealed {
58                                cell.cell_type = CellType::Unknown
59                            }
60                            cell
61                        })
62                        .collect())
63                .collect();
64
65        board
66    }
67
68    pub fn iter(&self) -> impl Iterator<Item = &Cell> {
69        self.into_iter()
70    }
71
72    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Cell> {
73        self.into_iter()
74    }
75}
76
77impl Index<Point> for Board {
78    type Output = Cell;
79
80    fn index(&self, index: Point) -> &Self::Output {
81        &self.grid[index.0][index.1]
82    }
83}
84
85impl IndexMut<Point> for Board {
86    fn index_mut(&mut self, index: Point) -> &mut Self::Output {
87        &mut self.grid[index.0][index.1]
88    }
89}
90
91impl IntoIterator for Board {
92    type Item = Cell;
93    type IntoIter = Flatten<IntoIter<Vec<Cell>>>;
94
95    fn into_iter(self) -> Self::IntoIter {
96        self.grid
97                .into_iter()
98                .flatten()
99    }
100}
101impl<'a> IntoIterator for &'a Board {
102    type Item = &'a Cell;
103    type IntoIter = Flatten<std::slice::Iter<'a, Vec<Cell>>>;
104
105    fn into_iter(self) -> Self::IntoIter {
106        self.grid
107                .iter()
108                .flatten()
109    }
110}
111
112impl<'a> IntoIterator for &'a mut Board {
113    type Item = &'a mut Cell;
114    type IntoIter = Flatten<std::slice::IterMut<'a, Vec<Cell>>>;
115
116    fn into_iter(self) -> Self::IntoIter {
117        self.grid
118                .iter_mut()
119                .flatten()
120    }
121}
122
123impl Display for Board {
124    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
125        for y in 0..self.size.height.into() {
126            for x in 0..self.size.width.into() {
127                write!(f, "{}", self[(x, y)])?;
128            }
129            writeln!(f)?;
130        }
131
132        Ok(())
133    }
134}
135
136#[derive(Debug)]
137pub enum BoardSizeError {
138    InvalidSize {
139        width: usize,
140        height: usize
141    },
142    TooManyMines {
143        mines: usize,
144        max_mines: usize
145    },
146    TooFewMines
147}
148
149impl Display for BoardSizeError {
150    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
151        match self {
152            BoardSizeError::InvalidSize { width, height } =>
153                write!(f, "board size cannot be {} by {}", width, height),
154            BoardSizeError::TooManyMines { mines, max_mines } =>
155                write!(f, "board cannot have {} mines (max: {})", mines, max_mines),
156            BoardSizeError::TooFewMines =>
157                write!(f, "board cannot have 0 mines")
158        }
159    }
160}
161
162impl Error for BoardSizeError {}
163
164#[derive(Copy, Clone, Debug, Eq, PartialEq)]
165#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
166pub struct BoardSize {
167    width: NonZeroUsize,
168    height: NonZeroUsize,
169    mines: NonZeroUsize
170}
171
172impl BoardSize {
173    pub fn new(width: usize, height: usize, mines: usize) -> Result<Self, BoardSizeError> {
174
175        let w = NonZeroUsize::new(width)
176                .ok_or(BoardSizeError::InvalidSize { width, height })?;
177        let h = NonZeroUsize::new(height)
178                .ok_or(BoardSizeError::InvalidSize { width, height })?;
179        let m = NonZeroUsize::new(mines)
180                .ok_or(BoardSizeError::TooFewMines)?;
181
182        if mines >= width * height {
183            return Err(BoardSizeError::TooManyMines {
184                mines,
185                max_mines: width * height
186            })
187        }
188
189        Ok(Self {
190            width: w,
191            height: h,
192            mines: m
193        })
194    }
195
196    pub fn width(&self) -> NonZeroUsize {
197        self.width
198    }
199
200    pub fn height(&self) -> NonZeroUsize {
201        self.height
202    }
203
204    pub fn mines(&self) -> NonZeroUsize {
205        self.mines
206    }
207
208    pub fn neighbours(&self, point: Point) -> impl Iterator<Item = Point> {
209        let mut neighbours = vec![];
210
211        for y in point.1.saturating_sub(1)..=usize::min(usize::from(self.height()) - 1, point.1.saturating_add(1)) {
212            for x in point.0.saturating_sub(1)..=usize::min(usize::from(self.width()) - 1, point.0.saturating_add(1)) {
213                if (x, y) != point {
214                    neighbours.push((x, y))
215                }
216            }
217        }
218
219        neighbours.into_iter()
220    }
221
222    pub fn points(&self) -> impl Iterator<Item = Point> {
223        (0..self.height.into())
224                .flat_map(|y| (0..self.width.into())
225                        .map(move |x| (x, y)))
226    }
227}
228
229#[derive(Copy, Clone, Debug)]
230pub enum ConventionalSize {
231    Beginner,
232    Intermediate,
233    Expert
234}
235
236impl ConventionalSize {
237    pub fn size(self) -> BoardSize {
238        match self {
239            ConventionalSize::Beginner => BoardSize::new(9, 9, 10).unwrap(),
240            ConventionalSize::Intermediate => BoardSize::new(16, 16, 40).unwrap(),
241            ConventionalSize::Expert => BoardSize::new(30, 16, 99).unwrap()
242        }
243    }
244}