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 points: Vec<Point> = board_size.points().collect();
200    fastrand::shuffle(&mut points);
201    for point in &points[..board_size.mines().get()] {
202        board[*point] = mine;
203    }
204
205    generate_nmbers(&mut board);
206
207    GameState::new(GameStatus::Playing, board, usize::from(board_size.mines()).try_into().unwrap())
208}
209
210fn generate_nmbers(board: &mut Board) {
211    let empty_unknown = Cell::new(CellType::EMPTY, CellState::Unknown);
212    for point in board.size().points() {
213        let cell = &mut board[point];
214
215        if matches!(cell.cell_type, CellType::Safe(_)) {
216            *cell = empty_unknown;
217        }
218    }
219    for point in board.size().points() {
220        if board[point].cell_type == CellType::Mine {
221            for point in board.size().neighbours(point) {
222                if let CellType::Safe(number) = board[point].cell_type {
223                    board[point] = Cell::new(CellType::Safe(number + 1), CellState::Unknown);
224                }
225            }
226        }
227    }
228}
229
230pub struct MinsweeperGame<
231    S: Solver = Box<dyn Solver>,
232    OnWin: Fn() = Box<dyn Fn()>,
233    OnLose: Fn() = Box<dyn Fn()>,
234> {
235    board_size: BoardSize,
236    game_state: GameState,
237    player_game_state: GameState,
238    on_win: OnWin,
239    on_lose: OnLose,
240    first: bool,
241    solver: Option<S>
242}
243
244impl<S: Solver, OnWin: Fn(), OnLose: Fn()> MinsweeperGame<S, OnWin, OnLose> {
245
246    pub fn new(board_size: BoardSize, on_win: OnWin, on_lose: OnLose) -> Self {
247        Self {
248            board_size,
249            game_state: GameState::new(GameStatus::Never, Board::empty(board_size), 0),
250            player_game_state: GameState::new(GameStatus::Never, Board::empty(board_size), 0),
251            on_win,
252            on_lose,
253            first: true,
254            solver: None
255        }
256    }
257
258    fn internal_start(&mut self, solver: Option<S>) -> &GameState {
259        *self.gamestate_mut() = GameState::new(GameStatus::Playing, Board::empty(self.board_size),
260                                         usize::from(self.board_size.mines()).try_into().unwrap());
261
262        self.first = true;
263        self.solver = solver;
264
265        self.player_gamestate()
266    }
267
268    pub fn start_with_solver(&mut self, solver: S) -> &GameState {
269        self.internal_start(solver.into())
270    }
271}
272
273impl<S: Solver, OnWin: Fn(), OnLose: Fn()> InternalMinsweeper for MinsweeperGame<S, OnWin, OnLose> {
274    fn start(&mut self) -> &GameState {
275        self.internal_start(None)
276    }
277
278    fn on_win(&self) {
279        (self.on_win)()
280    }
281
282    fn on_lose(&self) {
283        (self.on_lose)()
284    }
285
286    fn player_gamestate(&self) -> &GameState {
287        if self.game_state.status == GameStatus::Playing {
288            &self.player_game_state
289        } else {
290            &self.game_state
291        }
292    }
293
294    fn gamestate_mut(&mut self) -> impl DerefMut<Target = GameState> {
295        GameStateHandle {
296            game_state: &mut self.game_state,
297            obfuscated_game_state: &mut self.player_game_state
298        }
299    }
300
301    fn reveal(&mut self, point: Point) -> Result<&GameState, &GameState> {
302        if check_interact(self, point).is_err() {
303            return Err(self.player_gamestate())
304        }
305
306        if self.first {
307            self.first = false;
308
309            if let Some(solver) = &self.solver {
310                *self.gamestate_mut() = generate_solvable_game(self.board_size, solver, point);
311            } else {
312                *self.gamestate_mut() = generate_game(self.board_size);
313            }
314        }
315
316
317        let success = self.internal_reveal(point);
318
319        if !success {
320            self.gamestate_mut().status = GameStatus::Lost;
321
322            self.on_lose();
323
324            return Ok(self.player_gamestate())
325        }
326
327        if self.gamestate_mut().board.has_won() {
328            self.gamestate_mut().status = GameStatus::Won;
329
330            self.on_win();
331
332            return Ok(self.player_gamestate())
333        }
334
335        Ok(self.player_gamestate())
336    }
337
338    fn set_flagged(&mut self, point: Point, flagged: bool) -> Result<&GameState, &GameState> {
339        if check_interact(self, point).is_err() || self.first {
340            return Err(self.player_gamestate())
341        }
342
343        let mut mewo = self.gamestate_mut();
344        let state = mewo.deref_mut();
345        let cell = &mut state.board[point];
346
347        if cell.cell_state == CellState::Revealed {
348            drop(mewo);
349            return Err(self.player_gamestate())
350        }
351
352
353        if flagged != (cell.cell_state == CellState::Flagged) {
354            if flagged { state.remaining_mines -= 1 } else { state.remaining_mines += 1 }
355        }
356
357        cell.cell_state = if flagged { CellState::Flagged } else { CellState::Unknown };
358
359        drop(mewo);
360        Ok(self.player_gamestate())
361    }
362}
363
364#[cfg(feature = "async")]
365pub mod nonblocking {
366    use crate::board::{BoardSize, Point};
367    use crate::minsweeper::{generate_game, generate_solvable_game_async, InternalMinsweeper, MinsweeperGame};
368    use crate::solver::Solver;
369    use crate::{check_interact, Cell, CellState, CellType, GameState, Minsweeper};
370    use tokio::sync::{Mutex, RwLock};
371
372    pub struct AsyncMinsweeperGame<S: Solver + Send + Sync, OnWin: Fn() + Send + Sync, OnLose: Fn() + Send + Sync> {
373        minsweeper_game: RwLock<MinsweeperGame<S, OnWin, OnLose>>,
374        generate_lock: Mutex<()>
375
376    }
377
378    impl<S: Solver + Send + Sync + Clone, OnWin: Fn() + Send + Sync, OnLose: Fn() + Send + Sync> AsyncMinsweeperGame<S, OnWin, OnLose> {
379
380        pub fn new(board_size: BoardSize, on_win: OnWin, on_lose: OnLose) -> Self {
381            Self {
382                minsweeper_game: MinsweeperGame::new(board_size, on_win, on_lose).into(),
383                generate_lock: Default::default(),
384            }
385        }
386
387        pub async fn start(&self) -> GameState {
388            drop(self.generate_lock.lock().await);
389            Minsweeper::start(&mut *self.minsweeper_game.write().await)
390                    .clone()
391        }
392
393        pub async fn start_with_solver(&self, solver: S) -> GameState {
394            drop(self.generate_lock.lock().await);
395            self.minsweeper_game.write()
396                    .await
397                    .start_with_solver(solver)
398                    .clone()
399        }
400
401        pub async fn gamestate(&self) -> GameState {
402            self.minsweeper_game.read()
403                    .await
404                    .player_gamestate()
405                    .clone()
406        }
407
408        pub fn blocking_gamestate(&self) -> GameState {
409            self.minsweeper_game.blocking_read()
410                    .player_gamestate()
411                    .clone()
412        }
413
414
415        pub async fn reveal(&self, point: Point) -> Result<GameState, GameState> {
416            drop(self.generate_lock.lock().await);
417            let mut game = self.minsweeper_game.write().await;
418            if check_interact(&*game, point).is_err() {
419                return Err(game.player_gamestate().clone())
420            }
421
422            if game.first {
423                game.first = false;
424
425
426                let solver = game.solver.clone();
427                let size = game.board_size;
428                drop(game);
429
430                let generate_guard = self.generate_lock.lock();
431                let gamestate = if let Some(solver) = solver {
432                    generate_solvable_game_async(size, &solver, point).await
433                } else {
434                    generate_game(size)
435                };
436                *self.minsweeper_game.write().await.gamestate_mut() = gamestate;
437                drop(generate_guard);
438
439                game = self.minsweeper_game.write().await;
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            drop(self.generate_lock.lock().await);
451            Minsweeper::clear_around(&mut *self.minsweeper_game.write().await, point)
452                    .cloned()
453                    .map_err(Clone::clone)
454        }
455
456        pub async fn set_flagged(&self, point: Point, flagged: bool) -> Result<GameState, GameState> {
457            drop(self.generate_lock.lock().await);
458            Minsweeper::set_flagged(&mut *self.minsweeper_game.write().await, point, flagged)
459                    .cloned()
460                    .map_err(Clone::clone)
461        }
462
463        pub async fn toggle_flag(&self, point: Point) -> Result<GameState, GameState> {
464            drop(self.generate_lock.lock().await);
465            Minsweeper::toggle_flag(&mut *self.minsweeper_game.write().await, point)
466                    .cloned()
467                    .map_err(Clone::clone)
468        }
469
470        pub async fn left_click(&self, point: Point) -> Result<GameState, GameState> {
471            let game = self.minsweeper_game.read().await;
472            if check_interact(&*game, point).is_err() {
473                return Err(game.gamestate().clone())
474            }
475
476            let cell = game.gamestate().board[point];
477
478            match cell {
479                Cell { cell_type: CellType::Safe(_), cell_state: CellState::Revealed } => {
480                    drop(game);
481                    self.clear_around(point).await
482                },
483                Cell { cell_state: CellState::Unknown, .. } => {
484                    drop(game);
485                    self.reveal(point).await
486                },
487                _ => Err(game.gamestate().clone())
488            }
489        }
490
491        pub async fn right_click(&self, point: Point) -> Result<GameState, GameState> {
492            self.toggle_flag(point).await
493        }
494    }
495}
496
497pub fn generate_solvable_game(board_size: BoardSize, solver: &dyn Solver, point: Point) -> GameState {
498    loop {
499        let state = generate_game(board_size);
500
501        let mut game = SetMinsweeperGame::new(state.clone());
502        Minsweeper::reveal(&mut game, point)
503                .expect("should always be able to successfully reveal");
504
505        let result = solver.solve_game(&mut game);
506
507        if result == GameResult::Won {
508            return state;
509        }
510    }
511}
512
513pub async fn generate_solvable_game_async<S: Solver + Send + Sync>(board_size: BoardSize, solver: &S, point: Point) -> GameState {
514    loop {
515        let Some(state) = try_generate_solvable_game_async(board_size, solver, point).await else {
516            #[cfg(feature = "tokio")]
517            tokio::task::yield_now().await;
518            continue
519        };
520        return state
521    }
522}
523async fn try_generate_solvable_game_async<S: Solver + Send + Sync>(board_size: BoardSize, solver: &S, point: Point) -> Option<GameState> {
524    let state = generate_game(board_size);
525
526    let mut game = SetMinsweeperGame::new(state.clone());
527    Minsweeper::reveal(&mut game, point)
528            .expect("should always be able to successfully reveal");
529
530    let result = solver.solve_game(&mut game);
531
532    if result == GameResult::Won {
533        Some(state)
534    } else {
535        None
536    }
537}
538
539#[derive(Clone, Debug)]
540pub struct SetMinsweeperGame {
541    game_state: GameState,
542    player_game_state: GameState
543}
544
545impl SetMinsweeperGame {
546    pub fn new(game_state: GameState) -> Self {
547        Self { player_game_state: game_state.hide_mines(), game_state }
548    }
549}
550
551impl InternalMinsweeper for SetMinsweeperGame {
552    fn start(&mut self) -> &GameState {
553        unimplemented!()
554    }
555
556    fn on_win(&self) {
557
558    }
559
560    fn on_lose(&self) {
561
562    }
563
564    fn player_gamestate(&self) -> &GameState {
565        &self.player_game_state
566    }
567
568    fn gamestate_mut(&mut self) -> impl DerefMut<Target = GameState> {
569        GameStateHandle {
570            game_state: &mut self.game_state,
571            obfuscated_game_state: &mut self.player_game_state,
572        }
573    }
574}
575
576struct GameStateHandle<'a> {
577    game_state: &'a mut GameState,
578    obfuscated_game_state: &'a mut GameState
579}
580
581impl AsMut<GameState> for GameStateHandle<'_> {
582    fn as_mut(&mut self) -> &mut GameState {
583        self.game_state
584    }
585}
586
587impl Deref for GameStateHandle<'_> {
588    type Target = GameState;
589
590    fn deref(&self) -> &Self::Target {
591        self.game_state
592    }
593}
594
595impl DerefMut for GameStateHandle<'_> {
596    fn deref_mut(&mut self) -> &mut Self::Target {
597        self.game_state
598    }
599}
600
601impl Drop for GameStateHandle<'_> {
602    fn drop(&mut self) {
603        *self.obfuscated_game_state = self.game_state.hide_mines()
604    }
605}
606