use std::fmt;
use std::num::NonZero;
use std::time::{Duration, Instant};
use action::Action;
use board::field::View;
use board::{Board, MoveResult};
use grid2d::Coordinate;
use outcome::Outcome;
use state::State;
use crate::Error;
pub mod action;
pub mod board;
mod outcome;
pub mod state;
#[derive(Debug)]
pub struct Game {
board: Board,
mines: u8,
duds: u8,
start: Instant,
outcome: Option<Outcome>,
}
impl Game {
pub fn new(
width: NonZero<usize>,
height: NonZero<usize>,
mines: u8,
duds: u8,
) -> Result<Self, Error> {
Board::new(width, height, mines, duds).map(|board| Self {
board,
mines,
duds,
start: Instant::now(),
outcome: None,
})
}
pub fn rows(&self) -> impl Iterator<Item = impl Iterator<Item = View>> {
self.board
.fields()
.rows()
.map(|row| row.map(|field| field.view(self.is_over())))
}
pub fn columns(&self) -> impl Iterator<Item = impl Iterator<Item = View>> {
self.board
.fields()
.columns()
.map(|column| column.map(|field| field.view(self.is_over())))
}
pub fn iter(&self) -> impl Iterator<Item = View> {
self.board
.fields()
.iter()
.map(|field| field.view(self.is_over()))
}
#[must_use]
pub const fn mines(&self) -> u8 {
self.mines
}
#[must_use]
pub const fn duds(&self) -> u8 {
self.duds
}
#[must_use]
pub fn flags(&self) -> usize {
self.board.flags()
}
#[must_use]
pub const fn start(&self) -> Instant {
self.start
}
#[must_use]
pub const fn outcome(&self) -> Option<Outcome> {
self.outcome
}
#[must_use]
pub fn end(&self) -> Option<Instant> {
self.outcome.map(Outcome::end)
}
#[must_use]
pub const fn is_over(&self) -> bool {
self.outcome.is_some()
}
#[must_use]
pub fn duration(&self) -> Duration {
self.end()
.unwrap_or_else(Instant::now)
.duration_since(self.start)
}
#[must_use]
pub fn is_won(&self) -> Option<bool> {
self.outcome.map(Outcome::is_won)
}
pub fn next_round(&mut self, action: Action) -> Option<State> {
if self.is_over() {
return None;
}
Some(match action {
Action::Visit(coordinate) => self.visit(coordinate).into(),
Action::ToggleFlag(coordinate) => self.board.toggle_flag(coordinate).into(),
Action::VisitAllNonFlaggedFields => self.board.visit_non_flagged_fields().into(),
})
}
fn visit(&mut self, coordinate: Coordinate) -> MoveResult {
match self.board.visit(coordinate) {
MoveResult::Lost => {
self.outcome.replace(Outcome::lost());
MoveResult::Lost
}
MoveResult::Won => {
self.outcome.replace(Outcome::won());
MoveResult::Won
}
result => result,
}
}
}
impl fmt::Display for Game {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_over() {
write!(f, "{:#}", self.board)
} else {
writeln!(f, "{}", self.board,)?;
writeln!(f, "\nFlags: {}", self.board.flags())
}
}
}