minsweeper_rs/
minsweeper.rs

1use crate::board::{Board, BoardSize, Point};
2use crate::solver::{GameResult, Solver};
3use crate::{check_interact, Cell, CellState, CellType, GameState, GameStatus, Minsweeper};
4use rand::Rng;
5use std::collections::HashSet;
6use std::ops::{Deref, DerefMut};
7
8trait InternalMinsweeper {
9
10    fn start(&mut self) -> &GameState;
11
12    fn on_win(&self);
13    fn on_lose(&self);
14
15    fn player_gamestate(&self) -> &GameState;
16    fn gamestate_mut(&mut self) -> impl DerefMut<Target = GameState>;
17
18    fn reveal(&mut self, point: Point) -> Result<&GameState, &GameState> {
19        if check_interact(self, point).is_err() {
20            return Err(self.player_gamestate())
21        }
22
23
24        let success = self.internal_reveal(point);
25
26        if !success {
27            self.gamestate_mut().status = GameStatus::Lost;
28
29            self.on_lose();
30
31            return Ok(self.player_gamestate())
32        }
33
34        if self.gamestate_mut().board.has_won() {
35            self.gamestate_mut().status = GameStatus::Won;
36
37            self.on_win();
38
39            return Ok(self.player_gamestate())
40        }
41
42        Ok(self.player_gamestate())
43
44    }
45
46    fn reveal_empty(board: &mut Board, point: Point) {
47        if !matches!(board[point], Cell { cell_type: CellType::EMPTY, cell_state: state } if state != CellState::Revealed) {
48            return
49        }
50
51        let empty_cell = Cell::new(CellType::EMPTY, CellState::Revealed);
52        board[point] = empty_cell;
53
54        let mut flood = HashSet::new();
55
56        flood.insert(point);
57
58        while !flood.is_empty() {
59            let point = *flood.iter().next().unwrap();
60            flood.remove(&point);
61
62            for point in board.size().neighbours(point) {
63                if let Cell { cell_type: CellType::Safe(number), cell_state: state } = board[point]
64                        && state != CellState::Revealed {
65                    board[point] = Cell::new(CellType::Safe(number), CellState::Revealed);
66
67                    if number == 0 {
68                        flood.insert(point);
69                    }
70                }
71            }
72        }
73
74    }
75
76    fn internal_reveal(&mut self, point: Point) -> bool {
77        let mut state = self.gamestate_mut();
78        // let state = state.as_mut();
79        let board = &mut state.board;
80        if board[point].cell_state != CellState::Unknown {
81            return true
82        }
83
84        match board[point].cell_type {
85            CellType::Safe(number) => {
86                if number == 0 {
87                    Self::reveal_empty(board, point)
88                } else {
89                    board[point] = Cell::new(CellType::Safe(number), CellState::Revealed)
90                }
91                true
92            }
93            CellType::Mine => {
94                board[point] = Cell::new(CellType::Mine, CellState::Revealed);
95                false
96            }
97            _ => unreachable!()
98        }
99    }
100
101    fn clear_around(&mut self, point: Point) -> Result<&GameState, &GameState> {
102        if check_interact(self, point).is_err() {
103            return Err(self.player_gamestate())
104        }
105
106        let Cell { cell_type: CellType::Safe(number), cell_state: CellState::Revealed } = self.player_gamestate().board[point] else {
107            return Err(self.player_gamestate())
108        };
109
110        let flags = self.count_flags(point);
111
112        if flags != number as usize {
113            return Err(self.player_gamestate())
114        }
115
116        let mut success = true;
117
118        for point in self.player_gamestate().board.size().neighbours(point) {
119            success &= self.internal_reveal(point);
120        }
121
122        if !success {
123            self.gamestate_mut().status = GameStatus::Lost;
124
125            self.on_lose();
126
127            return Ok(self.player_gamestate())
128        }
129
130        if self.gamestate_mut().board.has_won() {
131            self.gamestate_mut().status = GameStatus::Won;
132
133            self.on_win();
134
135            return Ok(self.player_gamestate())
136        }
137
138        Ok(self.player_gamestate())
139    }
140
141    fn set_flagged(&mut self, point: Point, flagged: bool) -> Result<&GameState, &GameState> {
142        if check_interact(self, point).is_err() {
143            return Err(self.player_gamestate())
144        }
145
146        let mut mewo = self.gamestate_mut();
147        let state = mewo.deref_mut();
148        let cell = &mut state.board[point];
149
150        if cell.cell_state == CellState::Revealed {
151            drop(mewo);
152            return Err(self.player_gamestate())
153        }
154
155
156        if flagged != (cell.cell_state == CellState::Flagged) {
157            if flagged { state.remaining_mines -= 1 } else { state.remaining_mines += 1 }
158        }
159
160        cell.cell_state = if flagged { CellState::Flagged } else { CellState::Unknown };
161
162        drop(mewo);
163        Ok(self.player_gamestate())
164    }
165
166    fn count_flags(&self, point: Point) -> usize {
167        self.player_gamestate().board.size().neighbours(point)
168                .filter(|e| self.player_gamestate().board[*e].cell_state == CellState::Flagged)
169                .count()
170    }
171}
172
173impl<T: InternalMinsweeper + ?Sized> Minsweeper for T {
174    fn start(&mut self) -> &GameState {
175        self.start()
176    }
177
178    fn gamestate(&self) -> &GameState {
179        self.player_gamestate()
180    }
181
182    fn reveal(&mut self, point: Point) -> Result<&GameState, &GameState> {
183        self.reveal(point)
184    }
185
186    fn clear_around(&mut self, point: Point) -> Result<&GameState, &GameState> {
187        self.clear_around(point)
188    }
189
190    fn set_flagged(&mut self, point: Point, flagged: bool) -> Result<&GameState, &GameState> {
191        self.set_flagged(point, flagged)
192    }
193}
194
195
196pub fn generate_game(board_size: BoardSize) -> GameState {
197    let mut board = Board::empty(board_size);
198
199    let mine = Cell::new(CellType::Mine, CellState::Unknown);
200    let mut rng = rand::rng();
201    let mut mines = 0usize;
202    while mines < board_size.mines().into() {
203        let point = (rng.random_range(0..board_size.width().into()),
204                     rng.random_range(0..board_size.height().into()));
205
206        if matches!(board[point].cell_type, CellType::Safe(_)) {
207            board[point] = mine;
208            mines += 1;
209        }
210    };
211
212    generate_nmbers(&mut board);
213
214    GameState::new(GameStatus::Playing, board, usize::from(board_size.mines()).try_into().unwrap())
215}
216
217fn generate_nmbers(board: &mut Board) {
218    let empty_unknown = Cell::new(CellType::EMPTY, CellState::Unknown);
219    for point in board.size().points() {
220        let cell = &mut board[point];
221
222        if matches!(cell.cell_type, CellType::Safe(_)) {
223            *cell = empty_unknown;
224        }
225    }
226    for point in board.size().points() {
227        if board[point].cell_type == CellType::Mine {
228            for point in board.size().neighbours(point) {
229                if let CellType::Safe(number) = board[point].cell_type {
230                    board[point] = Cell::new(CellType::Safe(number + 1), CellState::Unknown);
231                }
232            }
233        }
234    }
235}
236
237pub struct MinsweeperGame {
238    board_size: BoardSize,
239    game_state: GameState,
240    player_game_state: GameState,
241    on_win: Box<dyn Fn()>,
242    on_lose: Box<dyn Fn()>,
243    first: bool,
244    solver: Option<Box<dyn Solver>>
245}
246
247impl MinsweeperGame {
248
249    pub fn new(board_size: BoardSize, on_win: Box<dyn Fn()>, on_lose: Box<dyn Fn()>) -> Self {
250        Self {
251            board_size,
252            game_state: GameState::new(GameStatus::Never, Board::empty(board_size), 0),
253            player_game_state: GameState::new(GameStatus::Never, Board::empty(board_size), 0),
254            on_win,
255            on_lose,
256            first: true,
257            solver: None
258        }
259    }
260
261    fn internal_start(&mut self, solver: Option<Box<dyn Solver>>) -> &GameState {
262        *self.gamestate_mut() = GameState::new(GameStatus::Playing, Board::empty(self.board_size),
263                                         usize::from(self.board_size.mines()).try_into().unwrap());
264
265        self.first = true;
266        self.solver = solver;
267
268        self.player_gamestate()
269    }
270
271    pub fn start_with_solver(&mut self, solver: Box<dyn Solver>) -> &GameState {
272        self.internal_start(solver.into())
273    }
274}
275
276impl InternalMinsweeper for MinsweeperGame {
277    fn start(&mut self) -> &GameState {
278        self.internal_start(None)
279    }
280
281    fn on_win(&self) {
282        (self.on_win)()
283    }
284
285    fn on_lose(&self) {
286        (self.on_lose)()
287    }
288
289    fn player_gamestate(&self) -> &GameState {
290        if self.game_state.status == GameStatus::Playing {
291            &self.player_game_state
292        } else {
293            &self.game_state
294        }
295    }
296
297    fn gamestate_mut(&mut self) -> impl DerefMut<Target = GameState> {
298        GameStateHandle {
299            game_state: &mut self.game_state,
300            obfuscated_game_state: &mut self.player_game_state
301        }
302    }
303
304    fn reveal(&mut self, point: Point) -> Result<&GameState, &GameState> {
305        if check_interact(self, point).is_err() {
306            return Err(self.player_gamestate())
307        }
308
309        if self.first {
310            self.first = false;
311
312            if let Some(solver) = &self.solver {
313                *self.gamestate_mut() = generate_solvable_game(self.board_size, solver.as_ref(), point);
314            } else {
315                *self.gamestate_mut() = generate_game(self.board_size);
316            }
317        }
318
319
320        let success = self.internal_reveal(point);
321
322        if !success {
323            self.gamestate_mut().status = GameStatus::Lost;
324
325            self.on_lose();
326
327            return Ok(self.player_gamestate())
328        }
329
330        if self.gamestate_mut().board.has_won() {
331            self.gamestate_mut().status = GameStatus::Won;
332
333            self.on_win();
334
335            return Ok(self.player_gamestate())
336        }
337
338        Ok(self.player_gamestate())
339    }
340
341    fn set_flagged(&mut self, point: Point, flagged: bool) -> Result<&GameState, &GameState> {
342        if check_interact(self, point).is_err() || self.first {
343            return Err(self.player_gamestate())
344        }
345
346        let mut mewo = self.gamestate_mut();
347        let state = mewo.deref_mut();
348        let cell = &mut state.board[point];
349
350        if cell.cell_state == CellState::Revealed {
351            drop(mewo);
352            return Err(self.player_gamestate())
353        }
354
355
356        if flagged != (cell.cell_state == CellState::Flagged) {
357            if flagged { state.remaining_mines -= 1 } else { state.remaining_mines += 1 }
358        }
359
360        cell.cell_state = if flagged { CellState::Flagged } else { CellState::Unknown };
361
362        drop(mewo);
363        Ok(self.player_gamestate())
364    }
365}
366
367pub fn generate_solvable_game(board_size: BoardSize, solver: &dyn Solver, point: Point) -> GameState {
368    loop {
369        let state = generate_game(board_size);
370
371        let mut game = SetMinsweeperGame::new(state.clone());
372        Minsweeper::reveal(&mut game, point)
373                .expect("should always be able to successfully reveal");
374
375        let result = solver.solve_game(&mut game);
376
377        if result == GameResult::Won {
378            return state;
379        }
380    }
381}
382
383#[derive(Clone, Debug)]
384pub struct SetMinsweeperGame {
385    game_state: GameState,
386    player_game_state: GameState
387}
388
389impl SetMinsweeperGame {
390    pub fn new(game_state: GameState) -> Self {
391        Self { player_game_state: game_state.hide_mines(), game_state }
392    }
393}
394
395impl InternalMinsweeper for SetMinsweeperGame {
396    fn start(&mut self) -> &GameState {
397        unimplemented!()
398    }
399
400    fn on_win(&self) {
401
402    }
403
404    fn on_lose(&self) {
405
406    }
407
408    fn player_gamestate(&self) -> &GameState {
409        &self.player_game_state
410    }
411
412    fn gamestate_mut(&mut self) -> impl DerefMut<Target = GameState> {
413        GameStateHandle {
414            game_state: &mut self.game_state,
415            obfuscated_game_state: &mut self.player_game_state,
416        }
417    }
418}
419
420struct GameStateHandle<'a> {
421    game_state: &'a mut GameState,
422    obfuscated_game_state: &'a mut GameState
423}
424
425impl AsMut<GameState> for GameStateHandle<'_> {
426    fn as_mut(&mut self) -> &mut GameState {
427        self.game_state
428    }
429}
430
431impl Deref for GameStateHandle<'_> {
432    type Target = GameState;
433
434    fn deref(&self) -> &Self::Target {
435        self.game_state
436    }
437}
438
439impl DerefMut for GameStateHandle<'_> {
440    fn deref_mut(&mut self) -> &mut Self::Target {
441        self.game_state
442    }
443}
444
445impl Drop for GameStateHandle<'_> {
446    fn drop(&mut self) {
447        *self.obfuscated_game_state = self.game_state.hide_mines()
448    }
449}
450