use core::{
ops::{Index, IndexMut},
num::{NonZeroUsize, NonZeroU8}
};
use alloc::{
vec::Vec
};
#[cfg(feature = "serialization")]
use serde::{Serialize, Deserialize};
use crate::{
Tile, Flag, ClickOutcome,
Clearing, ClearingMut,
RowIter, ColumnIter,
FieldRowsIter, FieldColumnsIter
};
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub struct Field {
storage: Vec<Tile>,
dimensions: FieldDimensions
}
pub type FieldDimensions = [NonZeroUsize; 2];
pub type FieldCoordinates = [usize; 2];
pub type ChordOutcome = [ClickOutcome; 8];
pub type RecursiveChordOutcome = (FieldCoordinates, ChordOutcome);
impl Field {
#[inline]
#[must_use = "this performs a memory allocation as big as the area of the field"]
pub fn empty(dimensions: FieldDimensions) -> Self {
let (width, height) = (dimensions[0].get(), dimensions[1].get());
let mut tfield = Self {
storage: Vec::with_capacity(width * height),
dimensions
};
for _ in 0..(width * height) {
tfield.storage.push(Tile::default());
}
tfield
}
#[cfg(feature = "generation")]
pub fn populate(&mut self, mine_percentage: f64, safe_spot: Option<FieldCoordinates>) {
use rand::Rng;
let mut rng = rand::thread_rng();
let (width, height) = (self.dimensions[0].get(), self.dimensions[1].get());
let area = width * height;
let num_mines: usize = (area as f64 * mine_percentage).round() as usize;
let mut mines_left = num_mines;
loop {
let rnum: usize = rng.gen_range(0, area);
let mine_location = [rnum % width, rnum / width];
if let Some(spot) = safe_spot {
if mine_location == spot {
continue;
}
}
self[mine_location] = Tile::Mine(Flag::NotFlagged);
if mines_left == 0 {break};
mines_left -= 1;
}
}
#[inline(always)]
pub const fn dimensions(&self) -> FieldDimensions {
self.dimensions
}
#[must_use = "traversing the entire field is obscenely expensive"]
pub fn solved(&self) -> bool {
self.tiles_to_open() > 0
}
#[must_use = "traversing the entire field is obscenely expensive"]
pub fn count_open_tiles(&self) -> usize {
let mut count = 0_usize;
for column in self.columns() {
for tile in column {
if tile.is_open() {count += 1};
}
}
count
}
#[must_use = "traversing the entire field is obscenely expensive"]
pub fn count_closed_tiles(&self) -> usize {
let mut count = 0_usize;
for column in self.columns() {
for tile in column {
if tile.is_closed() {count += 1};
}
}
count
}
#[must_use = "traversing the entire field is obscenely expensive"]
pub fn tiles_to_open(&self) -> usize {
let mut count = 0_usize;
for column in self.columns() {
for tile in column {
if tile.is_required_to_open() {count += 1};
}
}
count
}
#[must_use = "this is a rather complex lookup with 16 branch points"]
pub fn count_neighboring_mines(&self, location: FieldCoordinates) -> u8 {
let mut count = 0_u8;
if let Some(b) = self.is_mine([location[0] - 1, location[1] + 1]) {if b {count += 1;}};
if let Some(b) = self.is_mine([location[0] , location[1] + 1]) {if b {count += 1;}};
if let Some(b) = self.is_mine([location[0] + 1, location[1] + 1]) {if b {count += 1;}};
if let Some(b) = self.is_mine([location[0] - 1, location[1] ]) {if b {count += 1;}};
if let Some(b) = self.is_mine([location[0] + 1, location[1] ]) {if b {count += 1;}};
if let Some(b) = self.is_mine([location[0] - 1, location[1] - 1]) {if b {count += 1;}};
if let Some(b) = self.is_mine([location[0] , location[1] - 1]) {if b {count += 1;}};
if let Some(b) = self.is_mine([location[0] + 1, location[1] - 1]) {if b {count += 1;}};
count
}
#[inline]
pub fn is_mine(&self, location: FieldCoordinates) -> Option<bool> {
let (width, height) = (self.dimensions[0].get(), self.dimensions[1].get());
if location[0] > width || location[1] > height {
return None;
}
let tile = self[location];
if let Tile::Mine(_) = tile {
Some(true)
} else {Some(false)}
}
#[inline]
pub fn get(&self, coordinates: FieldCoordinates) -> Option<&Tile> {
let (width, height) = (self.dimensions[0].get(), self.dimensions[1].get());
let (x, y) = (coordinates[0], coordinates[1]);
if x > width || y > height {return None};
Some(unsafe{self.storage.get_unchecked(
x
+ y * width
)})
}
#[inline]
pub fn get_mut(&mut self, coordinates: FieldCoordinates) -> Option<&mut Tile> {
let (width, height) = (self.dimensions[0].get(), self.dimensions[1].get());
let (x, y) = (coordinates[0], coordinates[1]);
if x > width || y > height {return None};
Some(unsafe{self.storage.get_unchecked_mut(
x
+ y * width
)})
}
pub fn peek(&self, coordinates: FieldCoordinates) -> Option<ClickOutcome> {
if let Some(tile) = self.get(coordinates) {
if let Some(outcome) = tile.peek_local() {
Some(outcome)
} else {
let neighbors = self.count_neighboring_mines(coordinates);
if neighbors > 0 {
Some(ClickOutcome::OpenNumber( unsafe {
NonZeroU8::new_unchecked(neighbors)
}))
} else {
Some(ClickOutcome::OpenClearing)
}
}
} else {None}
}
pub fn open(&mut self, coordinates: FieldCoordinates) -> Option<ClickOutcome> {
if let Some(outcome) = self.peek(coordinates) {
match outcome {
ClickOutcome::OpenClearing => self[coordinates] = Tile::OpenEmpty,
ClickOutcome::OpenNumber(num) => self[coordinates] = Tile::OpenNumber(num),
_ => {}
};
Some(outcome)
} else {None}
}
#[allow(clippy::redundant_closure_call)]
pub fn chord(&mut self, coordinates: FieldCoordinates) -> ChordOutcome {
let (x, y) = (coordinates[0], coordinates[1]);
let mut result = [ClickOutcome::Nothing; 8];
let num_mines = if let Some(tile) = self.get(coordinates) {
if let Tile::OpenNumber(num_mines) = tile {
num_mines.get()
} else {return result}
} else {return result};
let mut num_flags = 0_u8;
let mut ckflag = |coords: FieldCoordinates| {
if let Some(tile) = self.get(coords) {
if tile.is_flagged() {
num_flags += 1;
}
}
};
ckflag([x - 1, y + 1]);
ckflag([x , y + 1]);
ckflag([x + 1, y + 1]);
ckflag([x + 1, y ]);
ckflag([x + 1, y - 1]);
ckflag([x , y - 1]);
ckflag([x - 1, y - 1]);
ckflag([x - 1, y ]);
if num_flags < num_mines {
return result
};
let calc_result = |coords: FieldCoordinates| {
if let Some(tile) = self.get(coords) {
if !tile.is_flagged() {
self.peek(coords).unwrap_or_default()
} else {
ClickOutcome::Nothing
}
} else {
ClickOutcome::Nothing
}
};
result[0] = calc_result([x , y + 1]);
result[1] = calc_result([x + 1, y + 1]);
result[2] = calc_result([x + 1, y ]);
result[3] = calc_result([x + 1, y - 1]);
result[4] = calc_result([x , y - 1]);
result[5] = calc_result([x - 1, y - 1]);
result[6] = calc_result([x - 1, y ]);
result[7] = calc_result([x - 1, y + 1]);
result
}
pub fn recursive_chord(&mut self, index: FieldCoordinates) -> Vec<RecursiveChordOutcome> {
type StackFrame = (FieldCoordinates, [bool; 8]);
let mut stack = Vec::<StackFrame>::with_capacity(8);
let mut stack_top = (index, [true; 8]);
let mut chord_outcomes = Vec::<RecursiveChordOutcome>::with_capacity(8);
loop {
let chosen_location =
if stack_top.1[0] {stack_top.1[0] = false; 0}
else if stack_top.1[1] {stack_top.1[1] = false; 1}
else if stack_top.1[2] {stack_top.1[2] = false; 2}
else if stack_top.1[3] {stack_top.1[3] = false; 3}
else if stack_top.1[4] {stack_top.1[4] = false; 4}
else if stack_top.1[5] {stack_top.1[5] = false; 5}
else if stack_top.1[6] {stack_top.1[6] = false; 6}
else if stack_top.1[7] {stack_top.1[7] = false; 7}
else if let Some(new_top) = stack.pop() {
stack_top = new_top;
continue;
} else {break};
let location_to_chord = match chosen_location {
0 => [stack_top.0[0] - 1, stack_top.0[1] + 1],
1 => [stack_top.0[0] , stack_top.0[1] + 1],
2 => [stack_top.0[0] + 1, stack_top.0[1] + 1],
3 => [stack_top.0[0] + 1, stack_top.0[1] ],
4 => [stack_top.0[0] + 1, stack_top.0[1] - 1],
5 => [stack_top.0[0] , stack_top.0[1] - 1],
6 => [stack_top.0[0] - 1, stack_top.0[1] - 1],
7 => [stack_top.0[0] - 1, stack_top.0[1] ],
_ => unreachable!()
};
let outcome = self.chord(location_to_chord);
chord_outcomes.push((location_to_chord, outcome));
if !(outcome == [ClickOutcome::Nothing; 8]) {
stack.push(stack_top);
stack_top = (location_to_chord, [true; 8]);
continue;
}
};
chord_outcomes
}
#[inline(always)]
pub fn row(&self, row: usize) -> RowIter<'_> {
RowIter::new(self, row)
}
#[inline(always)]
pub fn column(&self, column: usize) -> ColumnIter<'_> {
ColumnIter::new(self, column)
}
#[inline(always)]
pub fn rows(&self) -> FieldRowsIter<'_> {
FieldRowsIter::new(self)
}
#[inline(always)]
pub fn columns(&self) -> FieldColumnsIter<'_> {
FieldColumnsIter::new(self)
}
#[inline(always)]
pub fn clearing(&self, anchor_location: FieldCoordinates) -> Option<Clearing> {
Clearing::new(self, anchor_location)
}
pub fn clearing_mut(&mut self, anchor_location: FieldCoordinates) -> Option<ClearingMut> {
ClearingMut::new(self, anchor_location)
}
#[must_use = "calculating the 3BV value for any possible field requires traversing the entire field two times and opening clearings"]
pub fn calculate_3bv(mut self) -> usize {
let mut result = 0_usize;
for y in 0..self.dimensions[1].get() {
for x in 0..self.dimensions[0].get() {
match self[[x, y]] {
Tile::OpenEmpty
| Tile::OpenNumber(_) => {
self[[x, y]] = Tile::ClosedEmpty(Flag::NotFlagged)
}
_ => {}
};
}
}
for y in 0..self.dimensions[1].get() {
for x in 0..self.dimensions[0].get() {
match self[[x, y]] {
Tile::ClosedEmpty(_) => {
let outcome = self.open([x, y])
.expect("unexpected out of index error during 3BV calculation");
match outcome {
ClickOutcome::OpenClearing => {
self.clearing_mut([x, y])
.expect("unexpected out of index error during 3BV calculation")
.open(true);
result += 1;
},
ClickOutcome::OpenNumber(_) => result += 1,
_ => {}
};
},
Tile::OpenNumber(_) => result += 1,
_ => {}
};
}
}
result
}
}
impl Index<FieldCoordinates> for Field {
type Output = Tile;
#[inline(always)]
fn index(&self, coordinates: FieldCoordinates) -> &Self::Output {
self.get(coordinates).expect("index out of bounds")
}
}
impl IndexMut<FieldCoordinates> for Field {
#[inline(always)]
#[inline(always)]
fn index_mut(&mut self, coordinates: FieldCoordinates) -> &mut Self::Output {
self.get_mut(coordinates).expect("index out of bounds")
}
}