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}