Skip to main content

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 std::collections::HashSet;
5use std::ops::{Deref, DerefMut};
6
7trait InternalMinsweeper {
8
9    fn start(&mut self) -> &GameState;
10
11    fn on_win(&self);
12    fn on_lose(&self);
13
14    fn player_gamestate(&self) -> &GameState;
15    fn gamestate_mut(&mut self) -> impl DerefMut<Target = GameState>;
16
17    fn reveal(&mut self, point: Point) -> Result<&GameState, &GameState> {
18        if check_interact(self, point).is_err() {
19            return Err(self.player_gamestate())
20        }
21
22
23        let success = self.internal_reveal(point);
24
25        if !success {
26            self.gamestate_mut().status = GameStatus::Lost;
27
28            self.on_lose();
29
30            return Ok(self.player_gamestate())
31        }
32
33        if self.gamestate_mut().board.has_won() {
34            self.gamestate_mut().status = GameStatus::Won;
35
36            self.on_win();
37
38            return Ok(self.player_gamestate())
39        }
40
41        Ok(self.player_gamestate())
42
43    }
44
45    fn reveal_empty(board: &mut Board, point: Point) {
46        if !matches!(board[point], Cell { cell_type: CellType::EMPTY, cell_state: state } if state != CellState::Revealed) {
47            return
48        }
49
50        let empty_cell = Cell::new(CellType::EMPTY, CellState::Revealed);
51        board[point] = empty_cell;
52
53        let mut flood = HashSet::new();
54
55        flood.insert(point);
56
57        while !flood.is_empty() {
58            let point = *flood.iter().next().unwrap();
59            flood.remove(&point);
60
61            for point in board.size().neighbours(point) {
62                if let Cell { cell_type: CellType::Safe(number), cell_state: state } = board[point]
63                        && state != CellState::Revealed {
64                    board[point] = Cell::new(CellType::Safe(number), CellState::Revealed);
65
66                    if number == 0 {
67                        flood.insert(point);
68                    }
69                }
70            }
71        }
72
73    }
74
75    fn internal_reveal(&mut self, point: Point) -> bool {
76        let mut state = self.gamestate_mut();
77        // let state = state.as_mut();
78        let board = &mut state.board;
79        if board[point].cell_state != CellState::Unknown {
80            return true
81        }
82
83        match board[point].cell_type {
84            CellType::Safe(number) => {
85                if number == 0 {
86                    Self::reveal_empty(board, point)
87                } else {
88                    board[point] = Cell::new(CellType::Safe(number), CellState::Revealed)
89                }
90                true
91            }
92            CellType::Mine => {
93                board[point] = Cell::new(CellType::Mine, CellState::Revealed);
94                false
95            }
96            _ => unreachable!()
97        }
98    }
99
100    fn clear_around(&mut self, point: Point) -> Result<&GameState, &GameState> {
101        if check_interact(self, point).is_err() {
102            return Err(self.player_gamestate())
103        }
104
105        let Cell { cell_type: CellType::Safe(number), cell_state: CellState::Revealed } = self.player_gamestate().board[point] else {
106            return Err(self.player_gamestate())
107        };
108
109        let flags = self.count_flags(point);
110
111        if flags != number as usize {
112            return Err(self.player_gamestate())
113        }
114
115        let mut success = true;
116
117        for point in self.player_gamestate().board.size().neighbours(point) {
118            success &= self.internal_reveal(point);
119        }
120
121        if !success {
122            self.gamestate_mut().status = GameStatus::Lost;
123
124            self.on_lose();
125
126            return Ok(self.player_gamestate())
127        }
128
129        if self.gamestate_mut().board.has_won() {
130            self.gamestate_mut().status = GameStatus::Won;
131
132            self.on_win();
133
134            return Ok(self.player_gamestate())
135        }
136
137        Ok(self.player_gamestate())
138    }
139
140    fn set_flagged(&mut self, point: Point, flagged: bool) -> Result<&GameState, &GameState> {
141        if check_interact(self, point).is_err() {
142            return Err(self.player_gamestate())
143        }
144
145        let mut mewo = self.gamestate_mut();
146        let state = mewo.deref_mut();
147        let cell = &mut state.board[point];
148
149        if cell.cell_state == CellState::Revealed {
150            drop(mewo);
151            return Err(self.player_gamestate())
152        }
153
154
155        if flagged != (cell.cell_state == CellState::Flagged) {
156            if flagged { state.remaining_mines -= 1 } else { state.remaining_mines += 1 }
157        }
158
159        cell.cell_state = if flagged { CellState::Flagged } else { CellState::Unknown };
160
161        drop(mewo);
162        Ok(self.player_gamestate())
163    }
164
165    fn count_flags(&self, point: Point) -> usize {
166        self.player_gamestate().board.size().neighbours(point)
167                .filter(|e| self.player_gamestate().board[*e].cell_state == CellState::Flagged)
168                .count()
169    }
170}
171
172impl<T: InternalMinsweeper + ?Sized> Minsweeper for T {
173    fn start(&mut self) -> &GameState {
174        self.start()
175    }
176
177    fn gamestate(&self) -> &GameState {
178        self.player_gamestate()
179    }
180
181    fn reveal(&mut self, point: Point) -> Result<&GameState, &GameState> {
182        self.reveal(point)
183    }
184
185    fn clear_around(&mut self, point: Point) -> Result<&GameState, &GameState> {
186        self.clear_around(point)
187    }
188
189    fn set_flagged(&mut self, point: Point, flagged: bool) -> Result<&GameState, &GameState> {
190        self.set_flagged(point, flagged)
191    }
192}
193
194
195pub fn generate_game(board_size: BoardSize) -> GameState {
196    let mut board = Board::empty(board_size);
197
198    let mine = Cell::new(CellType::Mine, CellState::Unknown);
199    let mut mines = 0usize;
200    while mines < board_size.mines().into() {
201        let point = (fastrand::usize(0..board_size.width().into()),
202                     fastrand::usize(0..board_size.height().into()));
203
204        if matches!(board[point].cell_type, CellType::Safe(_)) {
205            board[point] = mine;
206            mines += 1;
207        }
208    };
209
210    generate_nmbers(&mut board);
211
212    GameState::new(GameStatus::Playing, board, usize::from(board_size.mines()).try_into().unwrap())
213}
214
215fn generate_nmbers(board: &mut Board) {
216    let empty_unknown = Cell::new(CellType::EMPTY, CellState::Unknown);
217    for point in board.size().points() {
218        let cell = &mut board[point];
219
220        if matches!(cell.cell_type, CellType::Safe(_)) {
221            *cell = empty_unknown;
222        }
223    }
224    for point in board.size().points() {
225        if board[point].cell_type == CellType::Mine {
226            for point in board.size().neighbours(point) {
227                if let CellType::Safe(number) = board[point].cell_type {
228                    board[point] = Cell::new(CellType::Safe(number + 1), CellState::Unknown);
229                }
230            }
231        }
232    }
233}
234
235pub struct MinsweeperGame<
236    S: Solver = Box<dyn Solver>,
237    OnWin: Fn() = Box<dyn Fn()>,
238    OnLose: Fn() = Box<dyn Fn()>,
239> {
240    board_size: BoardSize,
241    game_state: GameState,
242    player_game_state: GameState,
243    on_win: OnWin,
244    on_lose: OnLose,
245    first: bool,
246    solver: Option<S>
247}
248
249impl<S: Solver, OnWin: Fn(), OnLose: Fn()> MinsweeperGame<S, OnWin, OnLose> {
250
251    pub fn new(board_size: BoardSize, on_win: OnWin, on_lose: OnLose) -> Self {
252        Self {
253            board_size,
254            game_state: GameState::new(GameStatus::Never, Board::empty(board_size), 0),
255            player_game_state: GameState::new(GameStatus::Never, Board::empty(board_size), 0),
256            on_win,
257            on_lose,
258            first: true,
259            solver: None
260        }
261    }
262
263    fn internal_start(&mut self, solver: Option<S>) -> &GameState {
264        *self.gamestate_mut() = GameState::new(GameStatus::Playing, Board::empty(self.board_size),
265                                         usize::from(self.board_size.mines()).try_into().unwrap());
266
267        self.first = true;
268        self.solver = solver;
269
270        self.player_gamestate()
271    }
272
273    pub fn start_with_solver(&mut self, solver: S) -> &GameState {
274        self.internal_start(solver.into())
275    }
276}
277
278impl<S: Solver, OnWin: Fn(), OnLose: Fn()> InternalMinsweeper for MinsweeperGame<S, OnWin, OnLose> {
279    fn start(&mut self) -> &GameState {
280        self.internal_start(None)
281    }
282
283    fn on_win(&self) {
284        (self.on_win)()
285    }
286
287    fn on_lose(&self) {
288        (self.on_lose)()
289    }
290
291    fn player_gamestate(&self) -> &GameState {
292        if self.game_state.status == GameStatus::Playing {
293            &self.player_game_state
294        } else {
295            &self.game_state
296        }
297    }
298
299    fn gamestate_mut(&mut self) -> impl DerefMut<Target = GameState> {
300        GameStateHandle {
301            game_state: &mut self.game_state,
302            obfuscated_game_state: &mut self.player_game_state
303        }
304    }
305
306    fn reveal(&mut self, point: Point) -> Result<&GameState, &GameState> {
307        if check_interact(self, point).is_err() {
308            return Err(self.player_gamestate())
309        }
310
311        if self.first {
312            self.first = false;
313
314            if let Some(solver) = &self.solver {
315                *self.gamestate_mut() = generate_solvable_game(self.board_size, solver, point);
316            } else {
317                *self.gamestate_mut() = generate_game(self.board_size);
318            }
319        }
320
321
322        let success = self.internal_reveal(point);
323
324        if !success {
325            self.gamestate_mut().status = GameStatus::Lost;
326
327            self.on_lose();
328
329            return Ok(self.player_gamestate())
330        }
331
332        if self.gamestate_mut().board.has_won() {
333            self.gamestate_mut().status = GameStatus::Won;
334
335            self.on_win();
336
337            return Ok(self.player_gamestate())
338        }
339
340        Ok(self.player_gamestate())
341    }
342
343    fn set_flagged(&mut self, point: Point, flagged: bool) -> Result<&GameState, &GameState> {
344        if check_interact(self, point).is_err() || self.first {
345            return Err(self.player_gamestate())
346        }
347
348        let mut mewo = self.gamestate_mut();
349        let state = mewo.deref_mut();
350        let cell = &mut state.board[point];
351
352        if cell.cell_state == CellState::Revealed {
353            drop(mewo);
354            return Err(self.player_gamestate())
355        }
356
357
358        if flagged != (cell.cell_state == CellState::Flagged) {
359            if flagged { state.remaining_mines -= 1 } else { state.remaining_mines += 1 }
360        }
361
362        cell.cell_state = if flagged { CellState::Flagged } else { CellState::Unknown };
363
364        drop(mewo);
365        Ok(self.player_gamestate())
366    }
367}
368
369#[cfg(feature = "async")]
370pub mod nonblocking {
371    use crate::board::{BoardSize, Point};
372    use crate::minsweeper::{generate_game, generate_solvable_game_async, InternalMinsweeper, MinsweeperGame};
373    use crate::solver::Solver;
374    use crate::{check_interact, Cell, CellState, CellType, GameState, Minsweeper};
375    use tokio::sync::{Mutex, RwLock};
376
377    pub struct AsyncMinsweeperGame<S: Solver + Send + Sync, OnWin: Fn() + Send + Sync, OnLose: Fn() + Send + Sync> {
378        minsweeper_game: RwLock<MinsweeperGame<S, OnWin, OnLose>>,
379        generate_lock: Mutex<()>
380
381    }
382
383    impl<S: Solver + Send + Sync + Clone, OnWin: Fn() + Send + Sync, OnLose: Fn() + Send + Sync> AsyncMinsweeperGame<S, OnWin, OnLose> {
384
385        pub fn new(board_size: BoardSize, on_win: OnWin, on_lose: OnLose) -> Self {
386            Self {
387                minsweeper_game: MinsweeperGame::new(board_size, on_win, on_lose).into(),
388                generate_lock: Default::default(),
389            }
390        }
391
392        pub async fn start(&self) -> GameState {
393            Minsweeper::start(&mut *self.minsweeper_game.write().await)
394                    .clone()
395        }
396
397        pub async fn start_with_solver(&self, solver: S) -> GameState {
398            self.minsweeper_game.write()
399                    .await
400                    .start_with_solver(solver)
401                    .clone()
402        }
403
404        pub async fn gamestate(&self) -> GameState {
405            self.minsweeper_game.read()
406                    .await
407                    .player_gamestate()
408                    .clone()
409        }
410
411        pub fn blocking_gamestate(&self) -> GameState {
412            self.minsweeper_game.blocking_read()
413                    .player_gamestate()
414                    .clone()
415        }
416
417
418        pub async fn reveal(&self, point: Point) -> Result<GameState, GameState> {
419            let mut game = self.minsweeper_game.write().await;
420            if check_interact(&*game, point).is_err() {
421                return Err(game.player_gamestate().clone())
422            }
423
424            if game.first {
425                game.first = false;
426
427
428                let solver = game.solver.clone();
429                let size = game.board_size;
430                drop(game);
431
432                let generate_guard = self.generate_lock.lock();
433                let gamestate = if let Some(solver) = solver {
434                    generate_solvable_game_async(size, &solver, point).await
435                } else {
436                    generate_game(size)
437                };
438                *self.minsweeper_game.write().await.gamestate_mut() = gamestate;
439                drop(generate_guard);
440            }
441
442            let mut game = self.minsweeper_game.write().await;
443            Minsweeper::reveal(&mut *game, point)
444                    .cloned()
445                    .map_err(Clone::clone)
446        }
447
448
449        pub async fn clear_around(&self, point: Point) -> Result<GameState, GameState> {
450            Minsweeper::clear_around(&mut *self.minsweeper_game.write().await, point)
451                    .cloned()
452                    .map_err(Clone::clone)
453        }
454
455        pub async fn set_flagged(&self, point: Point, flagged: bool) -> Result<GameState, GameState> {
456            Minsweeper::set_flagged(&mut *self.minsweeper_game.write().await, point, flagged)
457                    .cloned()
458                    .map_err(Clone::clone)
459        }
460
461        pub async fn toggle_flag(&self, point: Point) -> Result<GameState, GameState> {
462            Minsweeper::toggle_flag(&mut *self.minsweeper_game.write().await, point)
463                    .cloned()
464                    .map_err(Clone::clone)
465        }
466
467        pub async fn left_click(&self, point: Point) -> Result<GameState, GameState> {
468            let game = self.minsweeper_game.read().await;
469            if check_interact(&*game, point).is_err() {
470                return Err(game.gamestate().clone())
471            }
472
473            let cell = game.gamestate().board[point];
474
475            match cell {
476                Cell { cell_type: CellType::Safe(_), cell_state: CellState::Revealed } => {
477                    drop(game);
478                    self.clear_around(point).await
479                },
480                Cell { cell_state: CellState::Unknown, .. } => {
481                    drop(game);
482                    self.reveal(point).await
483                },
484                _ => Err(game.gamestate().clone())
485            }
486        }
487
488        pub async fn right_click(&self, point: Point) -> Result<GameState, GameState> {
489            self.toggle_flag(point).await
490        }
491    }
492}
493
494pub fn generate_solvable_game(board_size: BoardSize, solver: &dyn Solver, point: Point) -> GameState {
495    loop {
496        let state = generate_game(board_size);
497
498        let mut game = SetMinsweeperGame::new(state.clone());
499        Minsweeper::reveal(&mut game, point)
500                .expect("should always be able to successfully reveal");
501
502        let result = solver.solve_game(&mut game);
503
504        if result == GameResult::Won {
505            return state;
506        }
507    }
508}
509
510pub async fn generate_solvable_game_async<S: Solver + Send + Sync>(board_size: BoardSize, solver: &S, point: Point) -> GameState {
511    loop {
512        let Some(state) = try_generate_solvable_game_async(board_size, solver, point).await else {
513            #[cfg(feature = "tokio")]
514            tokio::task::yield_now().await;
515            continue
516        };
517        return state
518    }
519}
520async fn try_generate_solvable_game_async<S: Solver + Send + Sync>(board_size: BoardSize, solver: &S, point: Point) -> Option<GameState> {
521    let state = generate_game(board_size);
522
523    let mut game = SetMinsweeperGame::new(state.clone());
524    Minsweeper::reveal(&mut game, point)
525            .expect("should always be able to successfully reveal");
526
527    let result = solver.solve_game(&mut game);
528
529    if result == GameResult::Won {
530        Some(state)
531    } else {
532        None
533    }
534}
535
536#[derive(Clone, Debug)]
537pub struct SetMinsweeperGame {
538    game_state: GameState,
539    player_game_state: GameState
540}
541
542impl SetMinsweeperGame {
543    pub fn new(game_state: GameState) -> Self {
544        Self { player_game_state: game_state.hide_mines(), game_state }
545    }
546}
547
548impl InternalMinsweeper for SetMinsweeperGame {
549    fn start(&mut self) -> &GameState {
550        unimplemented!()
551    }
552
553    fn on_win(&self) {
554
555    }
556
557    fn on_lose(&self) {
558
559    }
560
561    fn player_gamestate(&self) -> &GameState {
562        &self.player_game_state
563    }
564
565    fn gamestate_mut(&mut self) -> impl DerefMut<Target = GameState> {
566        GameStateHandle {
567            game_state: &mut self.game_state,
568            obfuscated_game_state: &mut self.player_game_state,
569        }
570    }
571}
572
573struct GameStateHandle<'a> {
574    game_state: &'a mut GameState,
575    obfuscated_game_state: &'a mut GameState
576}
577
578impl AsMut<GameState> for GameStateHandle<'_> {
579    fn as_mut(&mut self) -> &mut GameState {
580        self.game_state
581    }
582}
583
584impl Deref for GameStateHandle<'_> {
585    type Target = GameState;
586
587    fn deref(&self) -> &Self::Target {
588        self.game_state
589    }
590}
591
592impl DerefMut for GameStateHandle<'_> {
593    fn deref_mut(&mut self) -> &mut Self::Target {
594        self.game_state
595    }
596}
597
598impl Drop for GameStateHandle<'_> {
599    fn drop(&mut self) {
600        *self.obfuscated_game_state = self.game_state.hide_mines()
601    }
602}
603