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 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}