184 lines
6.0 KiB
Rust
184 lines
6.0 KiB
Rust
|
#[cfg(not(feature = "advanced_threading"))]
|
||
|
use std::thread;
|
||
|
#[cfg(feature = "advanced_threading")]
|
||
|
use rayon::prelude::*;
|
||
|
|
||
|
#[cfg(feature = "advanced_hashing")]
|
||
|
type Board = rustc_hash::FxHashSet<Coord>;
|
||
|
#[cfg(not(feature = "advanced_hashing"))]
|
||
|
type Board = std::collections::HashSet<Coord>;
|
||
|
|
||
|
pub type Coord = (isize, isize);
|
||
|
|
||
|
/// An error in the game's logic
|
||
|
#[derive(Debug)]
|
||
|
pub enum GameError {
|
||
|
NoPreviousTurn
|
||
|
}
|
||
|
|
||
|
/// A representation of the game's state.
|
||
|
#[derive(Default)]
|
||
|
pub struct Game {
|
||
|
primary_board: Board,
|
||
|
initial_state: Board,
|
||
|
count: usize,
|
||
|
#[cfg(not(feature = "advanced_threading"))]
|
||
|
parallelism: usize,
|
||
|
}
|
||
|
|
||
|
impl Game {
|
||
|
/// Constructs a new board with all dead cells
|
||
|
pub fn new() -> Self {
|
||
|
Self {
|
||
|
primary_board: Board::default(),
|
||
|
initial_state: Board::default(),
|
||
|
count: 0,
|
||
|
#[cfg(not(feature = "advanced_threading"))]
|
||
|
parallelism: std::thread::available_parallelism().unwrap().into(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Returns whether the cell at the given coordinate is alive
|
||
|
pub fn get_state(&self, coord: Coord) -> bool {
|
||
|
self.primary_board.contains(&coord)
|
||
|
}
|
||
|
|
||
|
/// Gets all live cells on the board
|
||
|
pub fn cells(&self) -> Vec<Coord> {
|
||
|
self.primary_board.iter().cloned().collect()
|
||
|
}
|
||
|
|
||
|
/// Flips the cell at the given coordinate betweeen alive and dead. Will return whether the new
|
||
|
/// cell is alive
|
||
|
pub fn flip_state(&mut self, coord: Coord) -> bool {
|
||
|
self.count = 0;
|
||
|
if self.primary_board.contains(&coord) {
|
||
|
self.primary_board.remove(&coord);
|
||
|
self.initial_state = self.primary_board.clone();
|
||
|
false
|
||
|
} else {
|
||
|
self.primary_board.insert(coord);
|
||
|
self.initial_state = self.primary_board.clone();
|
||
|
true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Advances the board by one turn
|
||
|
pub fn advance_board(&mut self) {
|
||
|
// Get all the cells to check
|
||
|
let mut check_set = Board::default();
|
||
|
for (x, y) in &self.primary_board {
|
||
|
let x = x.clone();
|
||
|
let y = y.clone();
|
||
|
check_set.insert((x, y));
|
||
|
check_set.insert((x, y+1));
|
||
|
check_set.insert((x, y-1));
|
||
|
check_set.insert((x+1, y));
|
||
|
check_set.insert((x+1, y+1));
|
||
|
check_set.insert((x+1, y-1));
|
||
|
check_set.insert((x-1, y));
|
||
|
check_set.insert((x-1, y+1));
|
||
|
check_set.insert((x-1, y-1));
|
||
|
}
|
||
|
|
||
|
// Iterate through the cells and modify the other HashSet
|
||
|
//let old_board = std::mem::take(&mut self.secondary_board);
|
||
|
let mut new_board = Board::default();
|
||
|
#[cfg(not(feature = "advanced_threading"))]
|
||
|
{
|
||
|
let binding = check_set.iter().collect::<Vec<&Coord>>();
|
||
|
let chunks = binding.chunks((binding.len()/self.parallelism)+1);
|
||
|
thread::scope(|s| {
|
||
|
let mut handles = vec![];
|
||
|
for chunk in chunks {
|
||
|
let handle = s.spawn(|| {
|
||
|
let owned_chunk = chunk.iter().map(|x| x.to_owned().to_owned());
|
||
|
let mut return_set = Board::default();
|
||
|
for coord in owned_chunk {
|
||
|
if get_next_state(&self.primary_board, coord) {
|
||
|
return_set.insert(coord);
|
||
|
}
|
||
|
}
|
||
|
return_set
|
||
|
});
|
||
|
handles.push(handle);
|
||
|
}
|
||
|
for handle in handles {
|
||
|
new_board.extend(handle.join().unwrap());
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
#[cfg(feature = "advanced_threading")]
|
||
|
{
|
||
|
new_board.par_extend(check_set.par_iter().filter(|coord| get_next_state(&self.primary_board, coord.to_owned().to_owned())));
|
||
|
}
|
||
|
|
||
|
// Swap the HashSets
|
||
|
self.primary_board = new_board;
|
||
|
self.count += 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/// Returns a vector of the values between the given top-left and bottom-right
|
||
|
/// coordinate
|
||
|
pub fn get_view(&self, coord1: Coord, coord2: Coord) -> Vec<&Coord>{
|
||
|
self.primary_board.iter()
|
||
|
.filter(|(x, y)| x >= &coord1.0 && y >= &coord1.1 && x <= &coord2.0 && y <= &coord2.1)
|
||
|
.collect()
|
||
|
|
||
|
}
|
||
|
|
||
|
/// Sets the board to its previous state. The board's history will only go back to the last
|
||
|
/// manual intervention done by a player (such as loading a file or toggling a cell). Returns
|
||
|
/// true if it reverted, and false if there was no previous revert.
|
||
|
pub fn revert_board(&mut self) -> bool {
|
||
|
// Check if there have been any turns since the initial state
|
||
|
if self.count == 0 {
|
||
|
return false;
|
||
|
}
|
||
|
self.primary_board = self.initial_state.clone();
|
||
|
// Check if we can simply swap out the old board.
|
||
|
/*if self.easy_buffer_revert {
|
||
|
std::mem::swap(&mut self.primary_board, &mut self.secondary_board);
|
||
|
self.easy_buffer_revert = false;
|
||
|
return;
|
||
|
}*/
|
||
|
let count = self.count;
|
||
|
for _ in 0..count-1 {
|
||
|
self.advance_board();
|
||
|
}
|
||
|
// let set = self.into_iter().nth(count).unwrap();
|
||
|
//self.primary_board = set;
|
||
|
self.count = count - 1;
|
||
|
true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Iterator for Game {
|
||
|
type Item = Board;
|
||
|
fn next(&mut self) -> Option<Self::Item> {
|
||
|
self.advance_board();
|
||
|
Some(self.primary_board.clone())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Given a board and a coordinate, return whether the cell at that coordinate should be live next
|
||
|
/// round
|
||
|
fn get_next_state(board: &Board, coord: Coord) -> bool {
|
||
|
let current_state = board.contains(&coord);
|
||
|
let (x, y) = coord;
|
||
|
let mut count: u8 = 0;
|
||
|
for coord in [(x, y+1), (x, y-1), (x+1, y), (x+1, y+1), (x+1, y-1), (x-1, y), (x-1, y+1), (x-1, y-1)] {
|
||
|
if board.contains(&coord) {
|
||
|
count += 1;
|
||
|
}
|
||
|
}
|
||
|
if current_state {
|
||
|
(2 <= count) && (count <= 3)
|
||
|
} else {
|
||
|
count == 3
|
||
|
}
|
||
|
}
|