game-of-life/src/game.rs

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
}
}