use std::fmt::Debug;
use rand::prelude::IteratorRandom;
use thiserror::Error;
use super::cell::Cell;
#[derive(PartialEq, Debug)]
pub enum State {
Start,
Ongoing,
Over
}
#[derive(Error, Debug)]
pub enum GridError {
#[error("Index is out of bounds!")]
IndexOutOfBounds,
#[error("A rule of the game has been violated!: {0}")]
RuleViolation(String)
}
pub struct Grid {
pub cells: Vec<Cell>,
pub state: State,
pub rows: usize,
pub columns: usize,
pub mines: usize
}
impl Debug for Grid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut str = String::new();
for i in 0..self.rows {
for j in 0..self.columns {
let index = (i * self.columns) + j;
let ch = if self.cells[index].mine && self.cells[index].excavated {
"💣"
} else if self.cells[index].excavated {
match self.cells[index].mines {
0 => "⬜",
1 => "1️⃣",
2 => "2️⃣",
3 => "3️⃣",
4 => "4️⃣",
5 => "5️⃣",
6 => "6️⃣",
7 => "7️⃣",
8 => "8️⃣",
_ => "⬜"
}
} else if self.cells[index].flagged {
"🚩"
} else {
"⬛"
};
str.push_str(ch);
str.push('\t');
}
str.push_str("\n");
}
write!(f, "{}", str)
}
}
impl Grid {
pub fn new(rows: usize, columns: usize, mines: usize) -> Result<Self, GridError> {
if rows < 2 || columns < 2 {
return Err(GridError::RuleViolation("There must be atleast 2 rows or columns".to_string()));
}
let total_cells = rows*columns;
if mines >= total_cells {
return Err(GridError::RuleViolation(("Can't be more mines than total cells. Atleast one cell must not be a mine.").to_string()));
}
let mut cells: Vec<Cell> = Vec::with_capacity(total_cells);
for i in 0..total_cells {
let cell = Cell::new(false, i);
cells.push(cell);
}
Ok(Grid { cells, state: State::Start, rows, columns, mines })
}
fn in_bounds(&self, index: usize) -> Result<(), GridError> {
if index >= self.rows * self.columns {
Err(GridError::IndexOutOfBounds)
} else {
Ok(())
}
}
fn open(&mut self, cell_index: usize) {
self.cells[cell_index].excavated = true;
self.cells[cell_index].flagged = false;
}
pub fn dig(&mut self, cell_index: usize) -> Result<(), GridError> {
self.in_bounds(cell_index)?;
if self.state == State::Start {
let neighbours = self.cells[cell_index].neighbours(self.rows, self.columns);
let mut banned = neighbours.clone();
banned.push(cell_index);
let picks = (0..self.rows*self.columns)
.filter(|pick| !banned.contains(pick))
.choose_multiple(&mut rand::thread_rng(), self.mines);
for i in picks {
self.cells[i].mine = true;
for cell in self.cells[i].neighbours(self.rows, self.columns) {
self.cells[cell].mines += 1;
}
}
self.open(cell_index);
self.open_neighbours(neighbours);
self.state = State::Ongoing;
} else if self.state == State::Ongoing {
if self.cells[cell_index].excavated {
return Err(GridError::RuleViolation("This cell has already been excavated".to_string()));
}
else if self.cells[cell_index].mine {
self.open(cell_index);
self.state = State::Over;
} else {
self.open(cell_index);
if self.cells[cell_index].mines == 0 {
let nbs = self.cells[cell_index].neighbours(self.rows, self.columns);
self.open_neighbours(nbs);
}
}
}
if self.full() {
self.state = State::Over;
}
Ok(())
}
fn open_neighbours(&mut self, neighbours: Vec<usize>) {
for index in neighbours {
if !self.cells[index].mine && !self.cells[index].excavated {
self.cells[index].excavated = true;
if self.cells[index].mines == 0 {
let nbs = self.cells[index].neighbours(self.rows, self.columns);
self.open_neighbours(nbs);
}
}
}
}
fn full(&self) -> bool {
self.cells
.iter()
.filter(|cell| !cell.excavated)
.all(|cell | cell.mine && cell.flagged)
}
pub fn flag(&mut self, index: usize) -> Result<(), GridError>{
if !self.cells[index].excavated {
self.cells[index].flagged = true;
if self.full() {
self.state = State::Over;
}
Ok(())
} else {
Err(GridError::RuleViolation("Can't place a flag on an already excavated cell.".to_string()))
}
}
}