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