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}