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                    .game_state
408                    .clone()
409        }
410        
411        pub fn blocking_gamestate(&self) -> GameState {
412            self.minsweeper_game.blocking_read()
413                    .game_state
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                let generate_guard = self.generate_lock.lock();
432                let gamestate = if let Some(solver) = solver {
433                    generate_solvable_game_async(size, &solver, point).await
434                } else {
435                    generate_game(size)
436                };
437                *self.minsweeper_game.write().await.gamestate_mut() = gamestate;
438                drop(generate_guard);
439            }
440
441            let mut game = self.minsweeper_game.write().await;
442            Minsweeper::reveal(&mut *game, point)
443                    .cloned()
444                    .map_err(Clone::clone)
445        }
446
447
448        pub async fn clear_around(&self, point: Point) -> Result<GameState, GameState> {
449            Minsweeper::clear_around(&mut *self.minsweeper_game.write().await, point)
450                    .cloned()
451                    .map_err(Clone::clone)
452        }
453
454        pub async fn set_flagged(&self, point: Point, flagged: bool) -> Result<GameState, GameState> {
455            Minsweeper::set_flagged(&mut *self.minsweeper_game.write().await, point, flagged)
456                    .cloned()
457                    .map_err(Clone::clone)
458        }
459
460        pub async fn toggle_flag(&self, point: Point) -> Result<GameState, GameState> {
461            Minsweeper::toggle_flag(&mut *self.minsweeper_game.write().await, point)
462                    .cloned()
463                    .map_err(Clone::clone)
464        }
465
466        pub async fn left_click(&self, point: Point) -> Result<GameState, GameState> {
467            let game = self.minsweeper_game.read().await;
468            if check_interact(&*game, point).is_err() {
469                return Err(game.gamestate().clone())
470            }
471
472            let cell = game.gamestate().board[point];
473
474            match cell {
475                Cell { cell_type: CellType::Safe(_), cell_state: CellState::Revealed } => {
476                    drop(game);
477                    self.clear_around(point).await
478                },
479                Cell { cell_state: CellState::Unknown, .. } => {
480                    drop(game);
481                    self.reveal(point).await
482                },
483                _ => Err(game.gamestate().clone())
484            }
485        }
486
487        pub async fn right_click(&self, point: Point) -> Result<GameState, GameState> {
488            self.toggle_flag(point).await
489        }
490    }
491}
492
493pub fn generate_solvable_game(board_size: BoardSize, solver: &dyn Solver, point: Point) -> GameState {
494    loop {
495        let state = generate_game(board_size);
496
497        let mut game = SetMinsweeperGame::new(state.clone());
498        Minsweeper::reveal(&mut game, point)
499                .expect("should always be able to successfully reveal");
500
501        let result = solver.solve_game(&mut game);
502
503        if result == GameResult::Won {
504            return state;
505        }
506    }
507}
508
509pub async fn generate_solvable_game_async<S: Solver + Send + Sync>(board_size: BoardSize, solver: &S, point: Point) -> GameState {
510    loop {
511        let Some(state) = try_generate_solvable_game_async(board_size, solver, point).await else {
512            continue
513        };
514        return state
515    }
516}
517async fn try_generate_solvable_game_async<S: Solver + Send + Sync>(board_size: BoardSize, solver: &S, point: Point) -> Option<GameState> {
518    let state = generate_game(board_size);
519
520    let mut game = SetMinsweeperGame::new(state.clone());
521    Minsweeper::reveal(&mut game, point)
522            .expect("should always be able to successfully reveal");
523
524    let result = solver.solve_game(&mut game);
525
526    if result == GameResult::Won {
527        Some(state)
528    } else {
529        None
530    }
531}
532
533#[derive(Clone, Debug)]
534pub struct SetMinsweeperGame {
535    game_state: GameState,
536    player_game_state: GameState
537}
538
539impl SetMinsweeperGame {
540    pub fn new(game_state: GameState) -> Self {
541        Self { player_game_state: game_state.hide_mines(), game_state }
542    }
543}
544
545impl InternalMinsweeper for SetMinsweeperGame {
546    fn start(&mut self) -> &GameState {
547        unimplemented!()
548    }
549
550    fn on_win(&self) {
551
552    }
553
554    fn on_lose(&self) {
555
556    }
557
558    fn player_gamestate(&self) -> &GameState {
559        &self.player_game_state
560    }
561
562    fn gamestate_mut(&mut self) -> impl DerefMut<Target = GameState> {
563        GameStateHandle {
564            game_state: &mut self.game_state,
565            obfuscated_game_state: &mut self.player_game_state,
566        }
567    }
568}
569
570struct GameStateHandle<'a> {
571    game_state: &'a mut GameState,
572    obfuscated_game_state: &'a mut GameState
573}
574
575impl AsMut<GameState> for GameStateHandle<'_> {
576    fn as_mut(&mut self) -> &mut GameState {
577        self.game_state
578    }
579}
580
581impl Deref for GameStateHandle<'_> {
582    type Target = GameState;
583
584    fn deref(&self) -> &Self::Target {
585        self.game_state
586    }
587}
588
589impl DerefMut for GameStateHandle<'_> {
590    fn deref_mut(&mut self) -> &mut Self::Target {
591        self.game_state
592    }
593}
594
595impl Drop for GameStateHandle<'_> {
596    fn drop(&mut self) {
597        *self.obfuscated_game_state = self.game_state.hide_mines()
598    }
599}
600