use crate::board::{Board, Point};
use std::fmt::{Debug, Display, Formatter};
pub mod board;
pub mod minsweeper;
pub mod solver;
pub trait Minsweeper {
fn start(&mut self) -> &GameState;
fn gamestate(&self) -> &GameState;
fn reveal(&mut self, point: Point) -> Result<&GameState, &GameState>;
fn clear_around(&mut self, point: Point) -> Result<&GameState, &GameState>;
fn set_flagged(&mut self, point: Point, flagged: bool) -> Result<&GameState, &GameState>;
fn toggle_flag(&mut self, point: Point) -> Result<&GameState, &GameState> {
self.set_flagged(point, self.gamestate().board[point].cell_state != CellState::Flagged)
}
fn left_click(&mut self, point: Point) -> Result<&GameState, &GameState> {
if check_interact(self, point).is_err() {
return Err(self.gamestate())
}
let cell = self.gamestate().board[point];
match cell {
Cell { cell_type: CellType::Safe(_), cell_state: CellState::Revealed } => self.clear_around(point),
Cell { cell_state: CellState::Unknown, .. } => self.reveal(point),
_ => Err(self.gamestate())
}
}
fn right_click(&mut self, point: Point) -> Result<&GameState, &GameState> {
self.toggle_flag(point)
}
}
fn check_interact(minsweeper: &(impl Minsweeper + ?Sized), point: Point) -> Result<(), ()> {
let state = minsweeper.gamestate();
if state.status == GameStatus::Playing
&& (0..state.board.size().width().into()).contains(&point.0)
&& (0..state.board.size().height().into()).contains(&point.1) {
Ok(())
} else {
Err(())
}
}
impl AsRef<GameState> for Result<&GameState, &GameState> {
fn as_ref(&self) -> &GameState {
match self {
Ok(state) => state,
Err(state) => state
}
}
}
impl<'a> From<Result<&'a GameState, &'a GameState>> for &'a GameState {
fn from(value: Result<&'a GameState, &'a GameState>) -> Self {
value.unwrap_or_else(|state| state)
}
}
pub trait GameStateTrait: Clone + Debug {
fn status(&self) -> GameStatus;
fn board(&self) -> &Board;
fn remaining_mines(&self) -> usize;
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GameState {
pub status: GameStatus,
pub board: Board,
pub remaining_mines: isize
}
impl GameState {
pub const fn new(status: GameStatus, board: Board, remaining_mines: isize) -> Self {
Self {
status,
board,
remaining_mines
}
}
fn hide_mines(&self) -> Self {
Self::new(self.status, self.board.hide_mines(), self.remaining_mines)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Cell {
pub cell_type: CellType,
pub cell_state: CellState
}
impl Cell {
pub const EMPTY: Cell = Cell::new(CellType::EMPTY, CellState::Unknown);
pub const fn new(cell_type: CellType, cell_state: CellState) -> Self {
Self {
cell_type,
cell_state
}
}
}
impl Display for Cell {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match (self.cell_type, self.cell_state) {
(CellType::Safe(0), _) => write!(f, " "),
(CellType::Safe(number), _) => write!(f, "{number}"),
(CellType::Mine, _) => write!(f, "*"),
(CellType::Unknown, CellState::Revealed) => write!(f, "?"),
(CellType::Unknown, CellState::Flagged) => write!(f, "!"),
(CellType::Unknown, CellState::Unknown) => write!(f, "▩")
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CellType {
Safe(u8), Mine, Unknown
}
impl CellType {
pub const EMPTY: CellType = CellType::Safe(0);
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CellState {
Unknown, Revealed, Flagged
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum GameStatus {
Playing, Won, Lost, Never
}
#[cfg(test)]
mod tests {
use super::*;
use crate::board::ConventionalSize;
use crate::minsweeper::MinsweeperGame;
use crate::solver::mia::MiaSolver;
use crate::solver::start::SafeStart;
use crate::solver::GameResult::Lost;
use crate::solver::Solver;
#[test]
fn it_works() {
let mewo = const {
size_of::<GameState>()
};
println!("{mewo}")
}
#[test]
fn mia_solver_works_at_least() {
println!("{:?}", ConventionalSize::Expert.size());
let mut game = MinsweeperGame::new(ConventionalSize::Expert.size(), || {}, || {});
println!("starting");
game.start_with_solver(MiaSolver::default());
println!("revealing");
game.reveal((0, 0))
.expect("shouldn't fail i don't think???");
}
#[test]
fn mia_solver_should_never_die() {
let mut game = MinsweeperGame::new(ConventionalSize::Expert.size(), || {}, || {});
for _ in 0..100 {
game.start_with_solver(SafeStart);
game.reveal((0, 0))
.expect("first click shouldn't fail");
let result = MiaSolver::default().solve_game(&mut game);
if result == Lost {
panic!("mia solver shouldn't lose\n{}", game.gamestate().board)
}
}
}
#[test]
fn mewo() {
println!("{:#x}", 16742399)
}
}