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)]
78pub struct GameState {
79    pub status: GameStatus,
80    pub board: Board,
81    pub remaining_mines: isize
82}
83
84impl GameState {
85    pub const fn new(status: GameStatus, board: Board, remaining_mines: isize) -> Self {
86        Self {
87            status,
88            board,
89            remaining_mines
90        }
91    }
92
93    fn hide_mines(&self) -> Self {
94
95        Self::new(self.status, self.board.hide_mines(), self.remaining_mines)
96    }
97}
98
99#[derive(Copy, Clone, Debug, Eq, PartialEq)]
100pub struct Cell {
101    pub cell_type: CellType,
102    pub cell_state: CellState
103}
104
105impl Cell {
106    pub const EMPTY: Cell = Cell::new(CellType::EMPTY, CellState::Unknown);
107
108    pub const fn new(cell_type: CellType, cell_state: CellState) -> Self {
109        Self {
110            cell_type,
111            cell_state
112        }
113    }
114}
115
116impl Display for Cell {
117    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
118        match (self.cell_type, self.cell_state) {
119            (CellType::Safe(0), _) => write!(f, " "),
120            (CellType::Safe(number), _) => write!(f, "{number}"),
121            (CellType::Mine, _) => write!(f, "*"),
122            (CellType::Unknown, CellState::Revealed) => write!(f, "?"),
123            (CellType::Unknown, CellState::Flagged) => write!(f, "!"),
124            (CellType::Unknown, CellState::Unknown) => write!(f, "▩")
125        }
126    }
127}
128
129#[derive(Copy, Clone, Debug, Eq, PartialEq)]
130pub enum CellType {
131    Safe(u8), Mine, Unknown
132}
133impl CellType {
134    pub const EMPTY: CellType = CellType::Safe(0);
135}
136
137#[derive(Copy, Clone, Debug, Eq, PartialEq)]
138pub enum CellState {
139    Unknown, Revealed, Flagged
140}
141
142#[derive(Copy, Clone, Debug, Eq, PartialEq)]
143pub enum GameStatus {
144    Playing, Won, Lost, Never
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150    use crate::board::ConventionalSize;
151    use crate::minsweeper::MinsweeperGame;
152    use crate::solver::mia::MiaSolver;
153    use crate::solver::start::SafeStart;
154    use crate::solver::GameResult::Lost;
155    use crate::solver::Solver;
156    use std::sync::Arc;
157
158    #[test]
159    fn it_works() {
160        let mewo = const {
161            size_of::<GameState>()
162        };
163        println!("{mewo}")
164    }
165
166    #[test]
167    fn mia_solver_works_at_least() {
168        println!("{:?}", ConventionalSize::Expert.size());
169        let mut game = MinsweeperGame::new(ConventionalSize::Expert.size(), Arc::new(|| {}), Arc::new(|| {}));
170        println!("starting");
171        game.start_with_solver(Box::new(MiaSolver));
172
173        println!("revealing");
174        game.reveal((0, 0))
175                .expect("shouldn't fail i don't think???");
176    }
177
178    #[test]
179    fn mia_solver_should_never_die() {
180        let mut game = MinsweeperGame::new(ConventionalSize::Expert.size(), Arc::new(|| {}), Arc::new(|| {}));
181
182        for _ in 0..100 {
183            game.start_with_solver(Box::new(SafeStart));
184
185            game.reveal((0, 0))
186                    .expect("first click shouldn't fail");
187
188            let result = MiaSolver.solve_game(&mut game);
189            dbg!("mewo");
190
191            if result == Lost {
192                panic!("mia solver shouldn't lose\n{}", game.gamestate().board)
193            }
194        }
195    }
196
197    #[test]
198    fn mewo() {
199        println!("{:#x}", 16742399)
200    }
201}