Skip to main content

minsweeper_rs/
lib.rs

1use crate::board::{Board, Point};
2use std::fmt::{Debug, Display, Formatter};
3
4pub mod board;
5pub mod minsweeper;
6pub mod solver;
7
8pub trait Minsweeper {
9
10    fn start(&mut self) -> &GameState;
11
12    fn gamestate(&self) -> &GameState;
13
14    fn reveal(&mut self, point: Point) -> Result<&GameState, &GameState>;
15
16    fn clear_around(&mut self, point: Point) -> Result<&GameState, &GameState>;
17
18    fn set_flagged(&mut self, point: Point, flagged: bool) -> Result<&GameState, &GameState>;
19
20    fn toggle_flag(&mut self, point: Point) -> Result<&GameState, &GameState> {
21        self.set_flagged(point, self.gamestate().board[point].cell_state != CellState::Flagged)
22    }
23
24    fn left_click(&mut self, point: Point) -> Result<&GameState, &GameState> {
25
26        if check_interact(self, point).is_err() {
27            return Err(self.gamestate())
28        }
29
30        let cell = self.gamestate().board[point];
31
32        match cell {
33            Cell { cell_type: CellType::Safe(_), cell_state: CellState::Revealed } => self.clear_around(point),
34            Cell { cell_state: CellState::Unknown, .. } => self.reveal(point),
35            _ => Err(self.gamestate())
36        }
37    }
38
39    fn right_click(&mut self, point: Point) -> Result<&GameState, &GameState> {
40        self.toggle_flag(point)
41    }
42
43}
44
45fn check_interact(minsweeper: &(impl Minsweeper + ?Sized), point: Point) -> Result<(), ()> {
46    let state = minsweeper.gamestate();
47    if state.status == GameStatus::Playing
48            && (0..state.board.size().width().into()).contains(&point.0)
49            && (0..state.board.size().height().into()).contains(&point.1) {
50        Ok(())
51    } else {
52        Err(())
53    }
54}
55
56impl AsRef<GameState> for Result<&GameState, &GameState> {
57    fn as_ref(&self) -> &GameState {
58        match self {
59            Ok(state) => state,
60            Err(state) => state
61        }
62    }
63}
64
65impl<'a> From<Result<&'a GameState, &'a GameState>> for &'a GameState {
66    fn from(value: Result<&'a GameState, &'a GameState>) -> Self {
67        value.unwrap_or_else(|state| state)
68    }
69}
70
71pub trait GameStateTrait: Clone + Debug {
72    fn status(&self) -> GameStatus;
73    fn board(&self) -> &Board;
74    fn remaining_mines(&self) -> usize;
75}
76
77#[derive(Clone, Debug)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
79pub struct GameState {
80    pub status: GameStatus,
81    pub board: Board,
82    pub remaining_mines: isize
83}
84
85impl GameState {
86    pub const fn new(status: GameStatus, board: Board, remaining_mines: isize) -> Self {
87        Self {
88            status,
89            board,
90            remaining_mines
91        }
92    }
93
94    fn hide_mines(&self) -> Self {
95
96        Self::new(self.status, self.board.hide_mines(), self.remaining_mines)
97    }
98}
99
100#[derive(Copy, Clone, Debug, Eq, PartialEq)]
101#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
102pub struct Cell {
103    pub cell_type: CellType,
104    pub cell_state: CellState
105}
106
107impl Cell {
108    pub const EMPTY: Cell = Cell::new(CellType::EMPTY, CellState::Unknown);
109
110    pub const fn new(cell_type: CellType, cell_state: CellState) -> Self {
111        Self {
112            cell_type,
113            cell_state
114        }
115    }
116}
117
118impl Display for Cell {
119    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
120        match (self.cell_type, self.cell_state) {
121            (CellType::Safe(0), _) => write!(f, " "),
122            (CellType::Safe(number), _) => write!(f, "{number}"),
123            (CellType::Mine, _) => write!(f, "*"),
124            (CellType::Unknown, CellState::Revealed) => write!(f, "?"),
125            (CellType::Unknown, CellState::Flagged) => write!(f, "!"),
126            (CellType::Unknown, CellState::Unknown) => write!(f, "▩")
127        }
128    }
129}
130
131#[derive(Copy, Clone, Debug, Eq, PartialEq)]
132#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
133pub enum CellType {
134    Safe(u8), Mine, Unknown
135}
136impl CellType {
137    pub const EMPTY: CellType = CellType::Safe(0);
138}
139
140#[derive(Copy, Clone, Debug, Eq, PartialEq)]
141#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
142pub enum CellState {
143    Unknown, Revealed, Flagged
144}
145
146#[derive(Copy, Clone, Debug, Eq, PartialEq)]
147#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
148pub enum GameStatus {
149    Playing, Won, Lost, Never
150}
151
152#[cfg(test)]
153mod tests {
154
155    use super::*;
156    use crate::board::ConventionalSize;
157    use crate::minsweeper::MinsweeperGame;
158    use crate::solver::mia::MiaSolver;
159    use crate::solver::start::SafeStart;
160    use crate::solver::GameResult::Lost;
161    use crate::solver::Solver;
162
163    #[test]
164    fn it_works() {
165        let mewo = const {
166            size_of::<GameState>()
167        };
168        println!("{mewo}")
169    }
170
171    #[test]
172    fn mia_solver_works_at_least() {
173        println!("{:?}", ConventionalSize::Expert.size());
174        let mut game = MinsweeperGame::new(ConventionalSize::Expert.size(), || {}, || {});
175        println!("starting");
176        game.start_with_solver(MiaSolver::default());
177
178        println!("revealing");
179        game.reveal((0, 0))
180                .expect("shouldn't fail i don't think???");
181    }
182
183    #[test]
184    fn mia_solver_should_never_die() {
185        let mut game = MinsweeperGame::new(ConventionalSize::Expert.size(), || {}, || {});
186
187        for _ in 0..100 {
188            game.start_with_solver(SafeStart);
189
190            game.reveal((0, 0))
191                    .expect("first click shouldn't fail");
192
193            let result = MiaSolver::default().solve_game(&mut game);
194
195            if result == Lost {
196                panic!("mia solver shouldn't lose\n{}", game.gamestate().board)
197            }
198        }
199    }
200
201    #[test]
202    fn mewo() {
203        println!("{:#x}", 16742399)
204    }
205}