use std::{char, cmp, fmt};
use crate::error::{ConwayError, ConwayResult};
use crate::grids::{BitGrid, BitOperation, CharGrid};
use crate::rle::{Pattern, NO_OP_CHAR};
pub struct BigBang {
width: usize,
height: usize,
is_server: bool,
history: usize,
num_players: usize,
player_writable: Vec<Region>,
fog_radius: usize
}
pub struct PlayerBuilder {
writable_region: Region,
}
impl PlayerBuilder {
pub fn new(region: Region) -> PlayerBuilder {
PlayerBuilder {
writable_region: region,
}
}
}
impl BigBang {
pub fn new() -> BigBang {
BigBang {
width: 256,
height: 128,
is_server: true,
history: 16,
num_players: 0,
player_writable: vec![],
fog_radius: 6,
}
}
pub fn width(mut self, new_width: usize) -> BigBang {
self.width = new_width;
self
}
pub fn height(mut self, new_height: usize) -> BigBang {
self.height = new_height;
self
}
pub fn server_mode(mut self, is_server: bool) -> BigBang {
self.is_server = is_server;
self
}
pub fn history(mut self, history_depth: usize) -> BigBang {
self.history = history_depth;
self
}
pub fn add_player(mut self, new_player: PlayerBuilder) -> BigBang {
self.num_players += 1;
self.player_writable.push(new_player.writable_region);
assert_eq!(self.num_players, self.player_writable.len()); self
}
pub fn add_players(mut self, new_player_list: Vec<PlayerBuilder>) -> BigBang {
for player in new_player_list {
self = self.add_player(player);
}
self
}
pub fn fog_radius(mut self, new_radius: usize) -> BigBang {
self.fog_radius = new_radius;
self
}
pub fn birth(&self) -> ConwayResult<Universe> {
let universe = Universe::new(
self.width,
self.height,
self.is_server, self.history,
self.num_players, self.player_writable.clone(), self.fog_radius, );
universe
}
}
pub struct Universe {
width: usize,
height: usize,
width_in_words: usize, generation: usize, num_players: usize, state_index: usize, gen_states: Vec<GenState>, player_writable: Vec<Region>, fog_radius: usize,
fog_circle: BitGrid,
}
#[derive(Debug, Clone, PartialEq)]
pub struct GenState {
gen_or_none: Option<usize>, cells: BitGrid, wall_cells: BitGrid, known: BitGrid, player_states: Vec<PlayerGenState>, }
#[derive(Debug, Clone, PartialEq)]
pub struct GenStateDiff {
pub gen0: usize, pub gen1: usize, pub pattern: Pattern,
}
#[derive(Debug, Clone, PartialEq)]
struct PlayerGenState {
cells: BitGrid, fog: BitGrid, }
#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Debug)]
pub enum CellState {
Dead,
Alive(Option<usize>), Wall,
Fog,
}
impl CellState {
pub fn to_char(self) -> char {
match self {
CellState::Alive(Some(player_id)) => {
if player_id >= 23 {
panic!("Player IDs must be less than 23 to be converted to chars");
}
char::from_u32(player_id as u32 + 65).unwrap()
}
CellState::Alive(None) => 'o',
CellState::Dead => 'b',
CellState::Wall => 'W',
CellState::Fog => '?',
}
}
pub fn from_char(ch: char) -> Option<Self> {
match ch {
'o' => Some(CellState::Alive(None)),
'b' => Some(CellState::Dead),
'W' => Some(CellState::Wall),
'?' => Some(CellState::Fog),
'A'..='V' => {
Some(CellState::Alive(Some(u32::from(ch) as usize - 65)))
}
_ => {
None
}
}
}
}
impl GenState {
pub fn set_unchecked(&mut self, col: usize, row: usize, new_state: CellState) {
let word_col = col/64;
let shift = 63 - (col & (64 - 1)); let mask = 1 << shift;
let known_cell_word = self.known[row][word_col];
if known_cell_word & mask == 0 {
panic!("Tried to set unknown cell at ({}, {})", col, row);
}
{
for player_id in 0 .. self.player_states.len() {
let ref mut grid = self.player_states[player_id].cells;
grid.modify_bits_in_word(row, word_col, mask, BitOperation::Clear);
}
}
let cells = &mut self.cells;
let walls = &mut self.wall_cells;
match new_state {
CellState::Dead => {
cells.modify_bits_in_word(row, word_col, mask, BitOperation::Clear);
walls.modify_bits_in_word(row, word_col, mask, BitOperation::Clear);
}
CellState::Alive(opt_player_id) => {
cells.modify_bits_in_word(row, word_col, mask, BitOperation::Set);
walls.modify_bits_in_word(row, word_col, mask, BitOperation::Clear);
if let Some(player_id) = opt_player_id {
let ref mut player = self.player_states[player_id];
player.cells.modify_bits_in_word(row, word_col, mask, BitOperation::Set);
player.fog.modify_bits_in_word(row, word_col, mask, BitOperation::Clear);
}
}
CellState::Wall => {
cells.modify_bits_in_word(row, word_col, mask, BitOperation::Clear);
walls.modify_bits_in_word(row, word_col, mask, BitOperation::Set);
}
_ => unimplemented!()
}
}
pub fn copy_from_bit_grid(&mut self, src: &BitGrid, dst_region: Region, opt_player_id: Option<usize>) {
BitGrid::copy(src, &mut self.cells, dst_region);
if let Some(player_id) = opt_player_id {
BitGrid::copy(src, &mut self.player_states[player_id].cells, dst_region);
for row in dst_region.top()..=dst_region.bottom() {
let row = row as usize;
for word_col in (dst_region.left()/64) ..= (dst_region.right()/64) {
let word_col = word_col as usize;
self.player_states[player_id].cells[row][word_col] &= !self.wall_cells[row][word_col];
self.player_states[player_id].fog[row][word_col] &= !self.player_states[player_id].cells[row][word_col];
}
}
}
for row in dst_region.top()..=dst_region.bottom() {
let row = row as usize;
for word_col in (dst_region.left()/64) ..= (dst_region.right()/64) {
let word_col = word_col as usize;
self.cells[row][word_col] &= !self.wall_cells[row][word_col];
}
}
}
pub fn diff(&self, new: &GenState, visibility: Option<usize>) -> GenStateDiff {
if self.height() != new.height() || self.width() != new.width() {
panic!("Dimensions do not match: {}x{} vs {}x{}", self.width(), self.height(), new.width(), new.height());
}
let self_gen = self.gen_or_none.unwrap();
let new_gen = new.gen_or_none.unwrap();
if self.player_states.len() != new.player_states.len() {
panic!("Player state vectors do not match");
}
let pair = GenStatePair {
gen_state0: &self,
gen_state1: &new,
};
let pattern = pair.to_pattern(visibility);
GenStateDiff {
gen0: self_gen,
gen1: new_gen,
pattern,
}
}
pub fn clear(&mut self) {
let region = Region::new(0, 0, self.width(), self.height());
self.cells.modify_region(region, BitOperation::Clear);
self.known.modify_region(region, BitOperation::Clear);
self.wall_cells.modify_region(region, BitOperation::Clear);
for player_id in 0..self.player_states.len() {
let p = &mut self.player_states[player_id];
p.cells.modify_region(region, BitOperation::Clear);
p.fog.modify_region(region, BitOperation::Clear);
}
}
pub fn copy(&self, dest: &mut GenState) {
let region = Region::new(0, 0, self.width(), self.height());
BitGrid::copy(&self.cells, &mut dest.cells, region);
BitGrid::copy(&self.known, &mut dest.known, region);
BitGrid::copy(&self.wall_cells, &mut dest.wall_cells, region);
for player_id in 0..dest.player_states.len() {
BitGrid::copy(&self.player_states[player_id].cells, &mut dest.player_states[player_id].cells, region);
BitGrid::copy(&self.player_states[player_id].fog, &mut dest.player_states[player_id].fog, region);
}
}
}
impl CharGrid for GenState {
fn width(&self) -> usize {
self.cells.width()
}
fn height(&self) -> usize {
self.cells.height()
}
#[inline]
fn write_at_position(&mut self, col: usize, row: usize, ch: char, visibility: Option<usize>) {
if !GenState::is_valid(ch) {
panic!(format!("char {:?} is invalid for this CharGrid", ch));
}
let word_col = col/64;
let shift = 63 - (col & (64 - 1));
match ch {
'b' | 'W' | '?' => {
self.cells[row][word_col] &= !(1 << shift)
}
'o' | 'A'..='V' => {
self.cells[row][word_col] |= 1 << shift
}
_ => unreachable!()
}
match ch {
'W' => {
self.wall_cells[row][word_col] |= 1 << shift
}
'b' | 'o' | 'A'..='V' | '?' => {
self.wall_cells[row][word_col] &= !(1 << shift)
}
_ => unreachable!()
}
if ch == '?' {
if visibility.is_none() {
panic!("cannot write fog when no player_id is specified");
}
let player_id = visibility.unwrap();
self.player_states[player_id].fog[row][word_col] |= 1 << shift;
} else {
self.known[row][word_col] |= 1 << shift; if let Some(player_id) = visibility {
self.player_states[player_id].fog[row][word_col] &= !(1 << shift);
} else {
for i in 0 .. self.player_states.len() {
self.player_states[i].fog[row][word_col] &= !(1 << shift);
}
}
for i in 0 .. self.player_states.len() {
self.player_states[i].cells[row][word_col] &= !(1 << shift);
}
if ch >= 'A' && ch <= 'V' {
let p_id = ch as usize - 'A' as usize;
self.player_states[p_id].cells[row][word_col] |= 1 << shift; }
}
}
#[inline]
fn is_valid(ch: char) -> bool {
match ch {
'o' | 'b' | 'A'..='W' | '?' => true,
NO_OP_CHAR => true,
_ => false
}
}
fn get_run(&self, col: usize, row: usize, visibility: Option<usize>) -> (usize, char) {
let mut min_run = self.width() - col;
let (known_run, known_ch) = self.known.get_run(col, row, None);
if known_run < min_run { min_run = known_run; }
if known_ch == 'b' {
return (min_run, CellState::Fog.to_char());
}
if let Some(player_id) = visibility {
let (fog_run, fog_ch) = self.player_states[player_id].fog.get_run(col, row, None);
if fog_run < min_run { min_run = fog_run; }
if fog_ch == 'o' {
return (min_run, CellState::Fog.to_char());
}
}
let (cell_run, cell_ch) = self.cells.get_run(col, row, None);
if cell_run < min_run { min_run = cell_run; }
let (wall_run, wall_ch) = self.wall_cells.get_run(col, row, None);
if wall_run < min_run { min_run = wall_run; }
if cell_ch == 'o' {
for player_id in 0 .. self.player_states.len() {
let (player_cell_run, player_cell_ch) = self.player_states[player_id].cells.get_run(col, row, None);
if player_cell_run < min_run { min_run = player_cell_run; }
if player_cell_ch == 'o' {
let owned_ch = CellState::Alive(Some(player_id)).to_char();
return (min_run, owned_ch);
}
}
return (min_run, CellState::Alive(None).to_char());
}
if wall_ch == 'o' {
return (min_run, CellState::Wall.to_char());
} else {
return (min_run, CellState::Dead.to_char());
}
}
}
struct GenStatePair<'a,'b> {
gen_state0: &'a GenState,
gen_state1: &'b GenState,
}
impl<'a,'b> CharGrid for GenStatePair<'a,'b> {
fn width(&self) -> usize {
self.gen_state0.width()
}
fn height(&self) -> usize {
self.gen_state0.height()
}
fn write_at_position(&mut self, _col: usize, _row: usize, _ch: char, _visibility: Option<usize>) {
unimplemented!("This is a read-only struct!");
}
fn is_valid(ch: char) -> bool {
if ch == NO_OP_CHAR {
return true;
}
GenState::is_valid(ch)
}
fn get_run(&self, mut col: usize, row: usize, visibility: Option<usize>) -> (usize, char) {
let (run0, ch0) = self.gen_state0.get_run(col, row, visibility);
let (run1, ch1) = self.gen_state1.get_run(col, row, visibility);
let ch;
if ch0 == ch1 {
ch = NO_OP_CHAR; } else {
ch = ch1; }
let mut run = cmp::min(run0, run1);
let mut total_run = run;
if ch == NO_OP_CHAR {
loop {
col += run;
if col >= self.width() {
break;
}
let (run0, ch0) = self.gen_state0.get_run(col, row, visibility);
let (run1, ch1) = self.gen_state1.get_run(col, row, visibility);
if ch0 != ch1 {
break;
}
run = cmp::min(run0, run1);
total_run += run;
}
}
(total_run, ch)
}
}
impl fmt::Display for Universe {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let cells = &self.gen_states[self.state_index].cells;
let wall = &self.gen_states[self.state_index].wall_cells;
let known = &self.gen_states[self.state_index].known;
for row_idx in 0 .. self.height {
for col_idx in 0 .. self.width_in_words {
let cell_cen = cells[row_idx][col_idx];
let wall_cen = wall [row_idx][col_idx];
let known_cen = known[row_idx][col_idx];
let mut s = String::with_capacity(64);
for shift in (0..64).rev() {
if (known_cen>>shift)&1 == 0 {
s.push('?');
} else if (cell_cen>>shift)&1 == 1 {
let mut is_player = false;
for player_id in 0 .. self.num_players {
let player_word = self.gen_states[self.state_index].player_states[player_id].cells[row_idx][col_idx];
if (player_word>>shift)&1 == 1 {
s.push(char::from_u32(player_id as u32 + 65).unwrap());
is_player = true;
break;
}
}
if !is_player { s.push('*'); }
} else if (wall_cen>>shift)&1 == 1 {
s.push('W');
} else {
s.push(' ');
}
}
write!(f, "{}", s)?;
}
write!(f, "\n")?;
}
Ok(())
}
}
impl Universe {
pub fn get_cell_state(&mut self, col: usize, row: usize, opt_player_id: Option<usize>) -> CellState {
let gen_state = &mut self.gen_states[self.state_index];
let word_col = col/64;
let shift = 63 - (col & (64 - 1)); let mask = 1 << shift;
if let Some(player_id) = opt_player_id {
let cell = (gen_state.player_states[player_id].cells[row][word_col] & mask) >> shift;
if cell == 1 {CellState::Alive(opt_player_id)} else {CellState::Dead}
}
else {
let cell = (gen_state.cells[row][word_col] & mask) >> shift;
if cell == 1 {CellState::Alive(None)} else {CellState::Dead}
}
}
pub fn set_unchecked(&mut self, col: usize, row: usize, new_state: CellState) {
self.gen_states[self.state_index].set_unchecked(col, row, new_state)
}
pub fn set(&mut self, col: usize, row: usize, new_state: CellState, player_id: usize) {
{
let gen_state = &mut self.gen_states[self.state_index];
let word_col = col/64;
let shift = 63 - (col & (64 - 1));
let mask = 1 << shift;
let cells = &mut gen_state.cells;
let wall = &mut gen_state.wall_cells;
let cells_word = cells[row][word_col];
let walls_word = wall [row][word_col];
if walls_word & mask > 0 {
return;
}
if !self.player_writable[player_id].contains(col as isize, row as isize) { return;
}
if gen_state.player_states[player_id].fog[row][word_col] & mask > 0 {
return;
}
if cells_word & mask > 0 && gen_state.player_states[player_id].cells[row][word_col] & mask == 0 {
return;
}
if let CellState::Alive(Some(new_state_player_id)) = new_state {
if new_state_player_id != player_id {
panic!("A player cannot set the cell state of another player");
}
}
}
self.set_unchecked(col, row, new_state)
}
pub fn toggle_unchecked(&mut self, col: usize, row: usize, opt_player_id: Option<usize>) -> CellState {
let word_col = col/64;
let shift = 63 - (col & (64 - 1));
let mask = 1 << shift;
let word =
{
let cells = &mut self.gen_states[self.state_index].cells;
cells.modify_bits_in_word(row, word_col, mask, BitOperation::Toggle);
cells[row][word_col]
};
let next_cell = (word & mask) > 0;
for player_id in 0 .. self.num_players {
let ref mut player_cells = self.gen_states[self.state_index].player_states[player_id].cells;
player_cells.modify_bits_in_word(row, word_col, mask, BitOperation::Clear);
}
if next_cell {
if let Some(player_id) = opt_player_id {
let ref mut player = self.gen_states[self.state_index].player_states[player_id];
player.cells.modify_bits_in_word(row, word_col, mask, BitOperation::Set);
player.fog.modify_bits_in_word(row, word_col, mask, BitOperation::Clear);
}
CellState::Alive(opt_player_id)
} else {
CellState::Dead
}
}
pub fn toggle(&mut self, col: usize, row: usize, player_id: usize) -> ConwayResult<CellState> {
use ConwayError::*;
if !self.player_writable[player_id].contains(col as isize, row as isize) {
return Err(AccessDenied{reason: format!("outside writable area for player {}: col={}, row={}",
player_id, col, row)});
}
let word_col = col/64;
let shift = 63 - (col & (64 - 1));
{
let wall = &self.gen_states[self.state_index].wall_cells;
let known = &self.gen_states[self.state_index].known;
if (wall[row][word_col] >> shift) & 1 == 1 {
return Err(AccessDenied{reason: format!("cannot write to wall cell: col={}, row={}", col, row)});
}
if (known[row][word_col] >> shift) & 1 == 0 {
return Err(AccessDenied{reason: format!("not a known cell for player {}: col={}, row={}",
player_id, col, row)});
}
}
Ok(self.toggle_unchecked(col, row, Some(player_id)))
}
pub fn new(width: usize,
height: usize,
is_server: bool,
history: usize,
num_players: usize,
player_writable: Vec<Region>,
fog_radius: usize) -> ConwayResult<Universe> {
use ConwayError::*;
if height == 0 {
return Err(InvalidData{reason:"Height must be positive".to_owned()});
}
let width_in_words = width/64;
if width % 64 != 0 {
return Err(InvalidData{reason: "Width must be a multiple of 64".to_owned()});
} else if width == 0 {
return Err(InvalidData{reason: "Width must be positive".to_owned()});
}
if history == 0 {
return Err(InvalidData{reason: "History must be positive".to_owned()});
}
if fog_radius == 0 {
return Err(InvalidData{reason: "Fog radius must be positive".to_owned()});
}
let mut gen_states = Vec::new();
for i in 0 .. history {
let mut player_states = Vec::new();
for player_id in 0 .. num_players {
let mut pgs = PlayerGenState {
cells: BitGrid::new(width_in_words, height),
fog: BitGrid::new(width_in_words, height),
};
pgs.fog.modify_region(Region::new(0, 0, width, height), BitOperation::Set);
pgs.fog.modify_region(player_writable[player_id], BitOperation::Clear);
player_states.push(pgs);
}
let mut known = BitGrid::new(width_in_words, height);
if is_server && i == 0 {
for y in 0 .. height {
for x in 0 .. width_in_words {
known[y][x] = u64::max_value(); }
}
}
gen_states.push(GenState {
gen_or_none: if i == 0 && is_server { Some(1) } else { None },
cells: BitGrid::new(width_in_words, height),
wall_cells: BitGrid::new(width_in_words, height),
known: known,
player_states: player_states,
});
}
let mut uni = Universe {
width: width,
height: height,
width_in_words: width_in_words,
generation: 1,
num_players: num_players,
state_index: 0,
gen_states: gen_states,
player_writable: player_writable,
fog_radius: fog_radius, fog_circle: BitGrid(vec![]), };
uni.generate_fog_circle_bitmap();
Ok(uni)
}
fn generate_fog_circle_bitmap(&mut self) {
let fog_radius = self.fog_radius;
let height = 2*fog_radius - 1;
let word_width = (height - 1) / 64 + 1;
self.fog_circle = BitGrid::new(word_width, height);
for y in 0 .. height {
for x in 0 .. word_width {
self.fog_circle[y][x] = u64::max_value();
}
}
let center_x = (fog_radius - 1) as isize;
let center_y = (fog_radius - 1) as isize;
for y in 0 .. height {
for bit_x in 0 .. word_width*64 {
let shift = 63 - (bit_x & 63);
let mask = 1<<shift;
let x_delta = isize::abs(center_x - bit_x as isize) as usize;
let y_delta = isize::abs(center_y - y as isize) as usize;
if x_delta*x_delta + y_delta*y_delta < fog_radius*fog_radius {
self.fog_circle[y][bit_x/64] &= !mask;
}
}
}
}
pub fn latest_gen(&self) -> usize {
assert!(self.generation != 0);
self.generation
}
fn next_single_gen(nw: u64, n: u64, ne: u64, w: u64, center: u64, e: u64, sw: u64, s: u64, se: u64) -> u64 {
let a = (nw << 63) | (n >> 1);
let b = n;
let c = (n << 1) | (ne >> 63);
let d = (w << 63) | (center >> 1);
let y6 = center;
let e = (center << 1) | (e >> 63);
let f = (sw << 63) | (s >> 1);
let g = s;
let h = (s << 1) | (se >> 63);
let b_xor_c = b^c;
let y1 = (a & b_xor_c) | (b & c);
let y2 = a ^ b_xor_c;
let e_xor_f = e^f;
let c2 = (d & e_xor_f) | (e & f);
let s2 = d ^ e_xor_f;
let c3 = g & h;
let s3 = g ^ h;
let c4 = s2 & s3;
let y5 = s2 ^ s3;
let c2_xor_c3 = c2 ^ c3;
let y3 = (c4 & c2_xor_c3) | (c2 & c3);
let y4 = c4 ^ c2_xor_c3;
let int1 = !y3 & !y4;
!y1&y6&(y2&int1&y5 | y4&!y5) | y1&int1&(!y2&(y5 | y6) | y2&!y5) | !y1&y4&(y2^y5)
}
fn contagious_zero(nw: u64, n: u64, ne: u64, w: u64, center: u64, e: u64, sw: u64, s: u64, se: u64) -> u64 {
let a = (nw << 63) | (n >> 1);
let b = n;
let c = (n << 1) | (ne >> 63);
let d = (w << 63) | (center >> 1);
let e = (center << 1) | (e >> 63);
let f = (sw << 63) | (s >> 1);
let g = s;
let h = (s << 1) | (se >> 63);
a & b & c & d & center & e & f & g & h
}
fn contagious_one(nw: u64, n: u64, ne: u64, w: u64, center: u64, e: u64, sw: u64, s: u64, se: u64) -> u64 {
let a = (nw << 63) | (n >> 1);
let b = n;
let c = (n << 1) | (ne >> 63);
let d = (w << 63) | (center >> 1);
let e = (center << 1) | (e >> 63);
let f = (sw << 63) | (s >> 1);
let g = s;
let h = (s << 1) | (se >> 63);
a | b | c | d | center | e | f | g | h
}
pub fn next(&mut self) -> usize {
assert!(self.gen_states[self.state_index].gen_or_none.unwrap() == self.generation);
let history = self.gen_states.len();
let next_state_index = (self.state_index + 1) % history;
let (gen_state, gen_state_next) = if self.state_index < next_state_index {
let (p0, p1) = self.gen_states.split_at_mut(next_state_index);
(&p0[next_state_index - 1], &mut p1[0])
} else {
let (p0, p1) = self.gen_states.split_at_mut(next_state_index + 1);
(&p1[history - 2], &mut p0[0])
};
{
let cells = &gen_state.cells;
let wall = &gen_state.wall_cells;
let known = &gen_state.known;
let cells_next = &mut gen_state_next.cells;
let wall_next = &mut gen_state_next.wall_cells;
let known_next = &mut gen_state_next.known;
for row_idx in 0 .. self.height {
for player_id in 0 .. self.num_players {
gen_state_next.player_states[player_id].fog[row_idx].copy_from_slice(&gen_state.player_states[player_id].fog[row_idx]);
}
}
for row_idx in 0 .. self.height {
let n_row_idx = (row_idx + self.height - 1) % self.height;
let s_row_idx = (row_idx + 1) % self.height;
let cells_row_n = &cells[n_row_idx];
let cells_row_c = &cells[ row_idx ];
let cells_row_s = &cells[s_row_idx];
let wall_row_c = &wall[ row_idx ];
let known_row_n = &known[n_row_idx];
let known_row_c = &known[ row_idx ];
let known_row_s = &known[s_row_idx];
let mut cells_nw;
let mut cells_w;
let mut cells_sw;
let mut cells_n = cells_row_n[self.width_in_words - 1];
let mut cells_cen = cells_row_c[self.width_in_words - 1];
let mut cells_s = cells_row_s[self.width_in_words - 1];
let mut cells_ne = cells_row_n[0];
let mut cells_e = cells_row_c[0];
let mut cells_se = cells_row_s[0];
let mut known_nw;
let mut known_w;
let mut known_sw;
let mut known_n = known_row_n[self.width_in_words - 1];
let mut known_cen = known_row_c[self.width_in_words - 1];
let mut known_s = known_row_s[self.width_in_words - 1];
let mut known_ne = known_row_n[0];
let mut known_e = known_row_c[0];
let mut known_se = known_row_s[0];
for col_idx in 0 .. self.width_in_words {
cells_nw = cells_n;
cells_n = cells_ne;
cells_w = cells_cen;
cells_cen = cells_e;
cells_sw = cells_s;
cells_s = cells_se;
cells_ne = cells_row_n[(col_idx + 1) % self.width_in_words];
cells_e = cells_row_c[(col_idx + 1) % self.width_in_words];
cells_se = cells_row_s[(col_idx + 1) % self.width_in_words];
known_nw = known_n;
known_n = known_ne;
known_w = known_cen;
known_cen = known_e;
known_sw = known_s;
known_s = known_se;
known_ne = known_row_n[(col_idx + 1) % self.width_in_words];
known_e = known_row_c[(col_idx + 1) % self.width_in_words];
known_se = known_row_s[(col_idx + 1) % self.width_in_words];
let mut cells_cen_next = Universe::next_single_gen(cells_nw, cells_n, cells_ne, cells_w, cells_cen, cells_e, cells_sw, cells_s, cells_se);
known_next[row_idx][col_idx] = Universe::contagious_zero(known_nw, known_n, known_ne, known_w, known_cen, known_e, known_sw, known_s, known_se);
cells_cen_next &= known_next[row_idx][col_idx];
cells_cen_next &= !wall_row_c[col_idx];
cells_next[row_idx][col_idx] = cells_cen_next;
let mut in_multiple: u64 = 0;
let mut seen_before: u64 = 0;
for player_id in 0 .. self.num_players {
let player_cell_next =
Universe::contagious_one(
gen_state.player_states[player_id].cells[n_row_idx][(col_idx + self.width_in_words - 1) % self.width_in_words],
gen_state.player_states[player_id].cells[n_row_idx][col_idx],
gen_state.player_states[player_id].cells[n_row_idx][(col_idx + 1) % self.width_in_words],
gen_state.player_states[player_id].cells[ row_idx ][(col_idx + self.width_in_words - 1) % self.width_in_words],
gen_state.player_states[player_id].cells[ row_idx ][col_idx],
gen_state.player_states[player_id].cells[ row_idx ][(col_idx + 1) % self.width_in_words],
gen_state.player_states[player_id].cells[s_row_idx][(col_idx + self.width_in_words - 1) % self.width_in_words],
gen_state.player_states[player_id].cells[s_row_idx][col_idx],
gen_state.player_states[player_id].cells[s_row_idx][(col_idx + 1) % self.width_in_words]
) & cells_cen_next;
in_multiple |= player_cell_next & seen_before;
seen_before |= player_cell_next;
gen_state_next.player_states[player_id].cells[row_idx][col_idx] = player_cell_next;
}
for player_id in 0 .. self.num_players {
let cell_cur = gen_state.player_states[player_id].cells[row_idx][col_idx];
let mut cell_next = gen_state_next.player_states[player_id].cells[row_idx][col_idx];
cell_next &= !in_multiple; gen_state_next.player_states[player_id].cells[row_idx][col_idx] = cell_next;
Universe::clear_fog(&mut gen_state_next.player_states[player_id].fog, &self.fog_circle, self.fog_radius, self.width, self.height, row_idx, col_idx, cell_next & !cell_cur);
}
}
wall_next[row_idx].copy_from_slice(wall_row_c);
}
}
self.generation += 1;
self.state_index = next_state_index;
gen_state_next.gen_or_none = Some(self.generation);
self.generation
}
fn clear_fog(player_fog: &mut BitGrid,
fog_circle: &BitGrid,
fog_radius: usize,
uni_width: usize,
uni_height: usize,
center_row_idx: usize,
center_col_idx: usize,
bits_to_clear: u64) {
if bits_to_clear == 0 {
return; }
debug!("---");
let mut col_of_highest_to_clear = center_col_idx * 64;
for shift in (0..64).rev() {
if bits_to_clear & (1 << shift) > 0 {
break;
}
col_of_highest_to_clear += 1;
}
let mut col_of_lowest_to_clear = center_col_idx * 64 + 63;
for shift in 0..64 {
if bits_to_clear & (1 << shift) > 0 {
break;
}
col_of_lowest_to_clear -= 1;
}
debug!("bits_to_clear: row {} and cols range [{}, {}]", center_row_idx, col_of_highest_to_clear, col_of_lowest_to_clear);
let row_top = (uni_height + center_row_idx - (fog_radius - 1)) % uni_height;
let row_bottom = (center_row_idx + fog_radius - 1) % uni_height;
let col_left = (uni_width + col_of_highest_to_clear - (fog_radius - 1)) % uni_width;
let col_right = (col_of_lowest_to_clear + fog_radius - 1) % uni_width;
debug!("row_(top,bottom) range is [{}, {}]", row_top, row_bottom);
debug!("col_(left,right) range is [{}, {}]", col_left, col_right);
let col_idx_left = col_left/64;
let col_idx_right = col_right/64;
let mut row_idx = row_top;
let uni_word_width = uni_width/64;
loop {
let mut col_idx = col_idx_left;
loop {
debug!("row {}, col range [{}, {}]", row_idx, col_idx*64, col_idx*64+63);
let mut mask = u64::max_value();
for shift in (0..64).rev() {
if mask == 0 {
break;
}
if bits_to_clear & (1 << shift) > 0 {
let fog_row_idx = (uni_height + row_idx - center_row_idx + (fog_radius - 1)) % uni_height;
let current_highest_col = col_idx * 64;
let current_lowest_col = col_idx * 64 + 63;
for fog_col_idx in 0 .. fog_circle.width_in_words() {
let fog_highest_col = (uni_width + center_col_idx*64 + (63 - shift) - (fog_radius - 1)) % uni_width;
let fog_lowest_col = (uni_width + center_col_idx*64 + (63 - shift) - (fog_radius - 1) + 63) % uni_width;
debug!(" fog col range [{}, {}]", fog_highest_col, fog_lowest_col);
if current_highest_col == fog_highest_col && current_lowest_col == fog_lowest_col {
mask &= fog_circle[fog_row_idx][fog_col_idx];
debug!(" mask is now {:016x}, cleared by fog circle R{}, Ci{}, no shift", mask, fog_row_idx, fog_col_idx);
} else if current_highest_col <= fog_lowest_col && fog_lowest_col < current_lowest_col {
mask &= !(!fog_circle[fog_row_idx][fog_col_idx] << (current_lowest_col - fog_lowest_col));
debug!(" fog word is {:016x}", fog_circle[fog_row_idx][fog_col_idx]);
debug!(" mask is now {:016x}, cleared by fog circle R{}, Ci{}, fog circle << {}", mask, fog_row_idx, fog_col_idx, current_lowest_col - fog_lowest_col);
} else if current_highest_col < fog_highest_col && fog_highest_col <= current_lowest_col {
mask &= !(!fog_circle[fog_row_idx][fog_col_idx] >> (fog_highest_col - current_highest_col));
debug!(" fog word is {:016x}", fog_circle[fog_row_idx][fog_col_idx]);
debug!(" mask is now {:016x}, cleared by fog circle R{}, Ci{}, fog circle >> {}", mask, fog_row_idx, fog_col_idx, fog_highest_col - current_highest_col);
}
}
}
}
player_fog[row_idx][col_idx] &= mask;
if col_idx == col_idx_right {
break;
}
col_idx = (col_idx + 1) % uni_word_width;
}
if row_idx == row_bottom {
break;
}
row_idx = (row_idx + 1) % uni_height;
}
}
pub fn each_non_dead(&self, region: Region, visibility: Option<usize>, callback: &mut FnMut(usize, usize, CellState)) {
let cells = &self.gen_states[self.state_index].cells;
let wall = &self.gen_states[self.state_index].wall_cells;
let known = &self.gen_states[self.state_index].known;
let opt_player_state = if let Some(player_id) = visibility {
Some(&self.gen_states[self.state_index].player_states[player_id])
} else { None };
let mut col;
for row in 0 .. self.height {
let cells_row = &cells[row];
let wall_row = &wall [row];
let known_row = &known[row];
if (row as isize) >= region.top() && (row as isize) < (region.top() + region.height() as isize) {
col = 0;
for col_idx in 0 .. self.width_in_words {
let cells_word = cells_row[col_idx];
let wall_word = wall_row [col_idx];
let known_word = known_row[col_idx];
let opt_player_words;
if let Some(player_state) = opt_player_state {
let player_cells_word = player_state.cells[row][col_idx];
let player_fog_word = player_state.fog[row][col_idx];
opt_player_words = Some((player_cells_word, player_fog_word));
} else {
opt_player_words = None;
}
for shift in (0..64).rev() {
if (col as isize) >= region.left() &&
(col as isize) < (region.left() + region.width() as isize) {
let mut state = CellState::Wall;
let c = (cells_word>>shift)&1 == 1;
let w = (wall_word >>shift)&1 == 1;
let k = (known_word>>shift)&1 == 1;
if c && w {
panic!("Cannot be both cell and wall at ({}, {})", col, row);
}
if !k && ((c && !w) || (!c && w)) {
panic!("Unspecified invalid state at ({}, {})", col, row);
}
if c && !w && k {
let mut opt_player_id = None;
for player_id in 0 .. self.num_players {
let player_state = &self.gen_states[self.state_index].player_states[player_id];
let pc = (player_state.cells[row][col_idx] >> shift) & 1 == 1;
let pf = (player_state.fog[row][col_idx] >> shift) & 1 == 1;
if pc && pf {
panic!("Player cell and player fog at ({}, {}) for player {}", col, row, player_id);
}
if pc {
if let Some(other_player_id) = opt_player_id {
panic!("Cell ({}, {}) belongs to player {} and player {}!", col, row, other_player_id, player_id);
}
opt_player_id = Some(player_id);
}
}
state = CellState::Alive(opt_player_id);
} else {
if !c && !w {
state = if k { CellState::Dead } else { CellState::Fog };
} else if !c && w {
state = CellState::Wall;
}
}
if let Some((player_cells_word, player_fog_word)) = opt_player_words {
let pc = (player_cells_word>>shift)&1 == 1;
let pf = (player_fog_word>>shift)&1 == 1;
if !k && pc {
panic!("Player can't have cells where unknown, at ({}, {})", col, row);
}
if w && pc {
panic!("Player can't have cells where wall, at ({}, {})", col, row);
}
if pf {
state = CellState::Fog;
}
}
if state != CellState::Dead {
callback(col, row, state);
}
}
col += 1;
}
}
}
}
}
pub fn each_non_dead_full(&self, visibility: Option<usize>, callback: &mut FnMut(usize, usize, CellState)) {
self.each_non_dead(self.region(), visibility, callback);
}
pub fn region(&self) -> Region {
Region::new(0, 0, self.width, self.height)
}
pub fn copy_from_bit_grid(&mut self, src: &BitGrid, dst_region: Region, opt_player_id: Option<usize>) {
let region;
if let Some(player_id) = opt_player_id {
if let Some(_region) = dst_region.intersection(self.player_writable[player_id]) {
region = _region;
} else {
return;
}
} else {
region = dst_region;
}
let latest_gen = &mut self.gen_states[self.state_index];
latest_gen.copy_from_bit_grid(src, region, opt_player_id);
}
pub fn apply(&mut self, diff: &GenStateDiff, visibility: Option<usize>) -> ConwayResult<Option<usize>> {
use ConwayError::*;
assert!(diff.gen0 < diff.gen1, format!("expected gen0 < gen1, but {} >= {}",
diff.gen0, diff.gen1));
let gen_state_len = self.gen_states.len();
if diff.gen0 > 0 && diff.gen1 - diff.gen0 >= gen_state_len {
return Err(InvalidData {
reason: format!("diff is across too many generations to be applied: {} >= {}",
diff.gen1 - diff.gen0, gen_state_len)});
}
let mut opt_gen0_idx = None;
let mut opt_largest_gen_idx = None;
let mut opt_largest_gen_value = None;
for i in 0..self.gen_states.len() {
if let Some(gen) = self.gen_states[i].gen_or_none {
if diff.gen0 > 0 {
if gen == diff.gen0 {
opt_gen0_idx = Some(i);
}
}
if let Some(largest_gen) = opt_largest_gen_value {
if gen > largest_gen {
opt_largest_gen_value = Some(gen);
opt_largest_gen_idx = Some(i);
}
} else {
opt_largest_gen_value = Some(gen);
opt_largest_gen_idx = Some(i);
}
if gen >= diff.gen1 {
return Ok(None);
}
}
}
if diff.gen0 > 0 && opt_gen0_idx.is_none() {
return Ok(None);
}
for i in 0..self.gen_states.len() {
if let Some(gen) = self.gen_states[i].gen_or_none {
if gen + gen_state_len <= diff.gen1 {
self.gen_states[i].gen_or_none = None; }
}
}
let gen1_idx = if let Some(largest_gen_idx) = opt_largest_gen_idx {
let largest_gen = opt_largest_gen_value.unwrap();
(largest_gen_idx + diff.gen1 - largest_gen) % gen_state_len
} else {
0
};
if diff.gen0 > 0 {
let gen0_idx = opt_gen0_idx.unwrap();
let cut_idx = cmp::max(gen0_idx, gen1_idx);
{
let (lower, upper): (&mut [GenState], &mut [GenState]) = self.gen_states.split_at_mut(cut_idx);
if gen1_idx < cut_idx {
lower[gen1_idx].clear();
upper[gen0_idx - cut_idx].copy(&mut lower[gen1_idx]); } else {
upper[gen1_idx - cut_idx].clear();
lower[gen0_idx].copy(&mut upper[gen1_idx - cut_idx]); }
}
} else {
self.gen_states[gen1_idx].clear();
}
let new_gen = diff.gen1;
self.generation = new_gen;
self.state_index = gen1_idx;
self.gen_states[gen1_idx].gen_or_none = Some(new_gen);
diff.pattern.to_grid(&mut self.gen_states[gen1_idx], visibility)?;
Ok(Some(new_gen))
}
pub fn diff(&self, gen0: usize, gen1: usize, visibility: Option<usize>) -> Option<GenStateDiff> {
assert!(gen0 < gen1, format!("expected gen0 < gen1, but {} >= {}", gen0, gen1));
let mut opt_genstate0 = None;
let mut opt_genstate1 = None;
for gen_idx in 0..self.gen_states.len() {
let gs = &self.gen_states[gen_idx];
if let Some(gen) = gs.gen_or_none {
if gen == gen0 {
opt_genstate0 = Some(gs);
} else if gen == gen1 {
opt_genstate1 = Some(gs);
}
}
}
if gen0 == 0 && opt_genstate1.is_some() {
let pattern = opt_genstate1.unwrap().to_pattern(visibility);
Some(GenStateDiff{gen0, gen1, pattern})
} else {
if opt_genstate0.is_none() || opt_genstate1.is_none() {
None
} else {
Some(opt_genstate0.unwrap().diff(opt_genstate1.unwrap(), visibility))
}
}
}
}
impl CharGrid for Universe {
fn is_valid(ch: char) -> bool {
GenState::is_valid(ch)
}
fn write_at_position(&mut self, _col: usize, _row: usize, _ch: char, _visibility: Option<usize>) {
unimplemented!("This interface is not intended for modifying Universes");
}
fn width(&self) -> usize {
return self.width;
}
fn height(&self) -> usize {
return self.height;
}
fn get_run(&self, col: usize, row: usize, visibility: Option<usize>) -> (usize, char) {
let gen_state = &self.gen_states[self.state_index];
gen_state.get_run(col, row, visibility)
}
}
#[derive(Eq,PartialEq,Ord,PartialOrd,Copy,Clone,Debug)]
pub struct Region {
left: isize,
top: isize,
width: usize,
height: usize,
}
impl Region {
pub fn new(left: isize, top: isize, width: usize, height: usize) -> Self {
assert!(width != 0);
assert!(height != 0);
Region {
left: left,
top: top,
width: width,
height: height,
}
}
pub fn left(&self) -> isize {
self.left
}
pub fn right(&self) -> isize {
self.left + (self.width as isize) - 1
}
pub fn top(&self) -> isize {
self.top
}
pub fn bottom(&self) -> isize {
self.top + (self.height as isize) - 1
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn contains(&self, col: isize, row: isize) -> bool {
self.left <= col &&
col <= self.right() &&
self.top <= row &&
row <= self.bottom()
}
pub fn intersection(&self, other: Region) -> Option<Region> {
let left = cmp::max(self.left(), other.left());
let right = cmp::min(self.right(), other.right());
if left > right {
return None;
}
let width = right - left + 1;
let top = cmp::max(self.top(), other.top());
let bottom = cmp::min(self.bottom(), other.bottom());
if top > bottom {
return None;
}
let height = bottom - top + 1;
Some(Region::new(left, top, width as usize, height as usize))
}
}
#[cfg(test)]
pub mod test_helpers {
use super::*;
#[derive(PartialEq)]
pub enum UniType {
Server,
Client,
}
pub const GEN_BUFSIZE: usize = 16;
pub fn generate_test_universe_with_default_params(uni_type: UniType) -> Universe {
let player0 = PlayerBuilder::new(Region::new(100, 70, 34, 16)); let player1 = PlayerBuilder::new(Region::new(0, 0, 80, 80));
let players = vec![player0, player1];
let bigbang = BigBang::new()
.width(256)
.height(128)
.server_mode(uni_type == UniType::Server)
.history(GEN_BUFSIZE)
.fog_radius(9)
.add_players(players)
.birth();
bigbang.unwrap()
}
pub fn make_gen_state() -> GenState {
let player0 = PlayerBuilder::new(Region::new(100, 70, 34, 16));
let player1 = PlayerBuilder::new(Region::new(0, 0, 80, 80));
let players = vec![player0, player1];
let mut uni = BigBang::new()
.history(1)
.add_players(players)
.birth()
.unwrap();
uni.gen_states.pop().unwrap()
}
}
#[cfg(test)]
mod universe_tests {
use super::*;
use super::test_helpers::*;
use crate::error::ConwayError::*;
#[test]
fn next_single_gen_test_data1_with_wrapping() {
let nw = 0x0000000000000000;
let n = 0x0000000400000002;
let ne = 0x8000000000000000;
let w = 0x0000000000000001;
let cen= 0xC000000400000001;
let e = 0x8000000000000000;
let sw = 0x0000000000000000;
let s = 0x8000000400000001;
let se = 0x0000000000000000;
let next_center = Universe::next_single_gen(nw, n, ne, w, cen, e, sw, s, se);
assert_eq!(next_center, 0xC000000E00000002);
}
#[test]
fn set_checked_cannot_set_a_fog_cell() {
let mut uni = generate_test_universe_with_default_params(UniType::Server);
let player_id = 1; let alive_player_cell = CellState::Alive(Some(player_id));
let state_index = uni.state_index;
uni.gen_states[state_index].player_states[player_id].fog[0][0] |= 1<<63;
uni.set(0, 0, alive_player_cell, player_id);
let cell_state = uni.get_cell_state(0,0, Some(player_id));
assert_eq!(cell_state, CellState::Dead);
}
#[test]
fn toggle_unchecked_cell_toggled_is_owned_by_player() {
let mut uni = generate_test_universe_with_default_params(UniType::Server);
let state_index = uni.state_index;
let row = 0;
let col = 0;
let bit = 63;
let player_one_opt = Some(0);
let player_two_opt = Some(1);
assert_eq!(uni.toggle_unchecked(row, col, player_one_opt), CellState::Alive(player_one_opt));
assert_eq!(uni.gen_states[state_index].player_states[player_one_opt.unwrap()].cells[row][col] >> bit, 1);
assert_eq!(uni.gen_states[state_index].player_states[player_two_opt.unwrap()].cells[row][col] >> bit, 0);
}
#[test]
fn toggle_unchecked_cell_toggled_by_both_players_repetitively() {
let mut uni = generate_test_universe_with_default_params(UniType::Server);
let state_index = uni.state_index;
let row = 0;
let col = 0;
let bit = 63;
let player_one_opt = Some(0);
let player_two_opt = Some(1);
assert_eq!(uni.toggle_unchecked(row, col, player_one_opt), CellState::Alive(player_one_opt));
assert_eq!(uni.gen_states[state_index].player_states[player_one_opt.unwrap()].cells[row][col] >> bit, 1);
assert_eq!(uni.gen_states[state_index].player_states[player_two_opt.unwrap()].cells[row][col] >> bit, 0);
assert_eq!(uni.toggle_unchecked(row, col, player_two_opt), CellState::Dead);
assert_eq!(uni.gen_states[state_index].player_states[player_one_opt.unwrap()].cells[row][col] >> bit, 0);
assert_eq!(uni.gen_states[state_index].player_states[player_two_opt.unwrap()].cells[row][col] >> bit, 0);
}
#[test]
fn toggle_checked_players_cannot_toggle_a_wall_cell() {
let mut uni = generate_test_universe_with_default_params(UniType::Server);
let player_two = 1;
let row = 0;
let col = 0;
let state_index = uni.state_index;
uni.gen_states[state_index].wall_cells.modify_bits_in_word(row, col, 1<<63, BitOperation::Set);
assert_eq!(uni.toggle(row, col, player_two),
Err(AccessDenied{reason: "cannot write to wall cell: col=0, row=0".to_owned()}));
}
#[test]
fn toggle_checked_players_can_toggle_an_known_cell_if_writable() {
let mut uni = generate_test_universe_with_default_params(UniType::Server);
let player_one = 0;
let player_two = 1;
let row = 0;
let col = 0;
let state_index = uni.state_index;
uni.gen_states[state_index].known.modify_bits_in_word(row, col, 1<<63, BitOperation::Set);
assert_eq!(uni.toggle(row, col, player_one),
Err(AccessDenied{reason: "outside writable area for player 0: col=0, row=0".to_owned()}));
assert_eq!(uni.toggle(row, col, player_two), Ok(CellState::Alive(Some(player_two))));
}
#[test]
fn toggle_checked_players_cannot_toggle_an_unknown_cell() {
let mut uni = generate_test_universe_with_default_params(UniType::Server);
let player_two = 1;
let row = 0;
let col = 0;
let state_index = uni.state_index;
uni.gen_states[state_index].known.modify_bits_in_word(row, col, 1<<63, BitOperation::Clear);
assert_eq!(uni.toggle(row, col, player_two),
Err(AccessDenied{reason: "not a known cell for player 1: col=0, row=0".to_owned()}));
}
#[test]
fn contagious_one_with_all_neighbors_set() {
let north = u64::max_value();
let northwest = u64::max_value();
let northeast = u64::max_value();
let west = u64::max_value();
let mut center = u64::max_value();
let east = u64::max_value();
let southwest = u64::max_value();
let south = u64::max_value();
let southeast = u64::max_value();
let mut output = Universe::contagious_one(northwest, north, northeast, west, center, east, southwest, south, southeast);
assert_eq!(output, u64::max_value());
center &= !(0x0000000F00000000);
output = Universe::contagious_one(northwest, north, northeast, west, center, east, southwest, south, southeast);
assert_eq!(output, 0xFFFFFFFFFFFFFFFF);
}
#[test]
fn contagious_zero_with_all_neighbors_set() {
let north = u64::max_value();
let northwest = u64::max_value();
let northeast = u64::max_value();
let west = u64::max_value();
let mut center = u64::max_value();
let east = u64::max_value();
let southwest = u64::max_value();
let south = u64::max_value();
let southeast = u64::max_value();
let mut output = Universe::contagious_zero(northwest, north, northeast, west, center, east, southwest, south, southeast);
assert_eq!(output, u64::max_value());
center &= !(0x0000000F00000000);
output = Universe::contagious_zero(northwest, north, northeast, west, center, east, southwest, south, southeast);
assert_eq!(output, 0xFFFFFFE07FFFFFFF);
}
#[test]
fn verify_fog_circle_bitmap_generation() {
let mut uni = generate_test_universe_with_default_params(UniType::Server);
let fog_radius_of_nine = vec![
vec![0xf007ffffffffffff],
vec![0xe003ffffffffffff],
vec![0xc001ffffffffffff],
vec![0x8000ffffffffffff],
vec![0x00007fffffffffff],
vec![0x00007fffffffffff],
vec![0x00007fffffffffff],
vec![0x00007fffffffffff],
vec![0x00007fffffffffff],
vec![0x00007fffffffffff],
vec![0x00007fffffffffff],
vec![0x00007fffffffffff],
vec![0x00007fffffffffff],
vec![0x8000ffffffffffff],
vec![0xc001ffffffffffff],
vec![0xe003ffffffffffff],
vec![0xf007ffffffffffff]];
uni.fog_radius = 9;
uni.generate_fog_circle_bitmap();
assert_eq!(fog_radius_of_nine, uni.fog_circle.0);
let fog_radius_of_four = vec![
vec![0x83ffffffffffffff],
vec![0x01ffffffffffffff],
vec![0x01ffffffffffffff],
vec![0x01ffffffffffffff],
vec![0x01ffffffffffffff],
vec![0x01ffffffffffffff],
vec![0x83ffffffffffffff],
];
uni.fog_radius = 4;
uni.generate_fog_circle_bitmap();
assert_eq!(fog_radius_of_four, uni.fog_circle.0);
let fog_radius_of_thirtyfive = vec![
vec![0xffffffc0001fffff, 0xffffffffffffffff, ],
vec![0xfffffe000003ffff, 0xffffffffffffffff, ],
vec![0xfffff00000007fff, 0xffffffffffffffff, ],
vec![0xffffc00000001fff, 0xffffffffffffffff, ],
vec![0xffff0000000007ff, 0xffffffffffffffff, ],
vec![0xfffe0000000003ff, 0xffffffffffffffff, ],
vec![0xfffc0000000001ff, 0xffffffffffffffff, ],
vec![0xfff000000000007f, 0xffffffffffffffff, ],
vec![0xffe000000000003f, 0xffffffffffffffff, ],
vec![0xffc000000000001f, 0xffffffffffffffff, ],
vec![0xff8000000000000f, 0xffffffffffffffff, ],
vec![0xff00000000000007, 0xffffffffffffffff, ],
vec![0xfe00000000000003, 0xffffffffffffffff, ],
vec![0xfe00000000000003, 0xffffffffffffffff, ],
vec![0xfc00000000000001, 0xffffffffffffffff, ],
vec![0xf800000000000000, 0xffffffffffffffff, ],
vec![0xf000000000000000, 0x7fffffffffffffff, ],
vec![0xf000000000000000, 0x7fffffffffffffff, ],
vec![0xe000000000000000, 0x3fffffffffffffff, ],
vec![0xe000000000000000, 0x3fffffffffffffff, ],
vec![0xc000000000000000, 0x1fffffffffffffff, ],
vec![0xc000000000000000, 0x1fffffffffffffff, ],
vec![0xc000000000000000, 0x1fffffffffffffff, ],
vec![0x8000000000000000, 0x0fffffffffffffff, ],
vec![0x8000000000000000, 0x0fffffffffffffff, ],
vec![0x8000000000000000, 0x0fffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x0000000000000000, 0x07ffffffffffffff, ],
vec![0x8000000000000000, 0x0fffffffffffffff, ],
vec![0x8000000000000000, 0x0fffffffffffffff, ],
vec![0x8000000000000000, 0x0fffffffffffffff, ],
vec![0xc000000000000000, 0x1fffffffffffffff, ],
vec![0xc000000000000000, 0x1fffffffffffffff, ],
vec![0xc000000000000000, 0x1fffffffffffffff, ],
vec![0xe000000000000000, 0x3fffffffffffffff, ],
vec![0xe000000000000000, 0x3fffffffffffffff, ],
vec![0xf000000000000000, 0x7fffffffffffffff, ],
vec![0xf000000000000000, 0x7fffffffffffffff, ],
vec![0xf800000000000000, 0xffffffffffffffff, ],
vec![0xfc00000000000001, 0xffffffffffffffff, ],
vec![0xfe00000000000003, 0xffffffffffffffff, ],
vec![0xfe00000000000003, 0xffffffffffffffff, ],
vec![0xff00000000000007, 0xffffffffffffffff, ],
vec![0xff8000000000000f, 0xffffffffffffffff, ],
vec![0xffc000000000001f, 0xffffffffffffffff, ],
vec![0xffe000000000003f, 0xffffffffffffffff, ],
vec![0xfff000000000007f, 0xffffffffffffffff, ],
vec![0xfffc0000000001ff, 0xffffffffffffffff, ],
vec![0xfffe0000000003ff, 0xffffffffffffffff, ],
vec![0xffff0000000007ff, 0xffffffffffffffff, ],
vec![0xffffc00000001fff, 0xffffffffffffffff, ],
vec![0xfffff00000007fff, 0xffffffffffffffff, ],
vec![0xfffffe000003ffff, 0xffffffffffffffff, ],
vec![0xffffffc0001fffff, 0xffffffffffffffff, ],
];
uni.fog_radius = 35;
uni.generate_fog_circle_bitmap();
assert_eq!(fog_radius_of_thirtyfive, uni.fog_circle.0);
}
#[test]
fn clear_fog_with_standard_radius() {
let player0 = PlayerBuilder::new(Region::new(100, 70, 34, 16)); let player1 = PlayerBuilder::new(Region::new(0, 0, 80, 80));
let players = vec![player0, player1];
let mut uni = BigBang::new()
.width(256)
.height(128)
.server_mode(true)
.history(16)
.fog_radius(4)
.add_players(players)
.birth()
.unwrap();
let history = uni.gen_states.len();
let next_state_index = (uni.state_index + 1) % history;
let player_id = 0;
let gen_state_next = if uni.state_index < next_state_index {
let (_, p1) = uni.gen_states.split_at_mut(next_state_index);
&mut p1[player_id]
} else {
let (p0, _) = uni.gen_states.split_at_mut(next_state_index + 1);
&mut p0[player_id]
};
let row_index_outside_of_p0_region = 1;
let col_index_outside_of_p0_region = 1;
let one_bit_to_clear = 1;
Universe::clear_fog(&mut gen_state_next.player_states[player_id].fog,
&uni.fog_circle,
uni.fog_radius,
uni.width,
uni.height,
row_index_outside_of_p0_region,
col_index_outside_of_p0_region,
one_bit_to_clear);
for x in 0..4 {
for y in 1..2 {
assert_eq!(gen_state_next.player_states[0].fog[x][y], 0b1111111111111111111111111111111111111111111111111111111111110000);
}
}
}
#[test]
fn universe_copy_from_bit_grid_as_player() {
let mut uni = generate_test_universe_with_default_params(UniType::Server);
let grid = Pattern("64o$64o!".to_owned()).to_new_bit_grid(64, 2).unwrap();
let write_pattern_as = Some(1); let dst_region = Region::new(0, 0, 32, 3);
uni.copy_from_bit_grid(&grid, dst_region, write_pattern_as);
{
let genstate = &uni.gen_states[uni.state_index];
assert_eq!(genstate.cells.to_pattern(None).0, "32o$32o!".to_owned());
assert_eq!(genstate.wall_cells.to_pattern(None).0, "!".to_owned());
assert_eq!(genstate.player_states[0].cells.to_pattern(None).0, "!".to_owned());
assert_eq!(genstate.player_states[0].fog[0][0], u64::max_value()); assert_eq!(genstate.player_states[0].fog[1][0], u64::max_value()); assert_eq!(genstate.player_states[1].cells.to_pattern(None).0, "32o$32o!".to_owned());
}
}
#[test]
fn universe_copy_from_bit_grid_as_player_out_of_range() {
let mut uni = generate_test_universe_with_default_params(UniType::Server);
let grid = Pattern("64o$64o!".to_owned()).to_new_bit_grid(64, 2).unwrap();
let write_pattern_as = Some(0); let dst_region = Region::new(0, 0, 32, 3);
uni.copy_from_bit_grid(&grid, dst_region, write_pattern_as);
{
let genstate = &uni.gen_states[uni.state_index];
assert_eq!(genstate.cells.to_pattern(None).0, "!".to_owned());
assert_eq!(genstate.wall_cells.to_pattern(None).0, "!".to_owned());
assert_eq!(genstate.player_states[0].cells.to_pattern(None).0, "!".to_owned()); assert_eq!(genstate.player_states[0].fog[0][0], u64::max_value()); assert_eq!(genstate.player_states[0].fog[1][0], u64::max_value()); assert_eq!(genstate.player_states[1].cells.to_pattern(None).0, "!".to_owned()); }
}
#[test]
fn universe_apply_basic() {
let mut s_uni = generate_test_universe_with_default_params(UniType::Server);
let mut c_uni = generate_test_universe_with_default_params(UniType::Client); let player1 = 1;
s_uni.toggle(16, 15, player1).unwrap();
s_uni.toggle(17, 16, player1).unwrap();
s_uni.toggle(15, 17, player1).unwrap();
s_uni.toggle(16, 17, player1).unwrap();
s_uni.toggle(17, 17, player1).unwrap();
let gens = 4;
for _ in 0..gens {
s_uni.next();
}
let diff = s_uni.diff(0, 5, None).unwrap();
assert_eq!(c_uni.apply(&diff, None), Ok(Some(5)));
let s_idx = s_uni.state_index;
let c_idx = c_uni.state_index;
assert_eq!(s_uni.gen_states[s_idx].gen_or_none, c_uni.gen_states[c_idx].gen_or_none);
assert_eq!(s_uni.gen_states[s_idx].cells, c_uni.gen_states[c_idx].cells);
}
#[test]
fn universe_apply_basic_player_visibility() {
let mut s_uni = generate_test_universe_with_default_params(UniType::Server);
let mut c_uni = generate_test_universe_with_default_params(UniType::Client); let player1 = 1;
s_uni.toggle(16, 15, player1).unwrap();
s_uni.toggle(17, 16, player1).unwrap();
s_uni.toggle(15, 17, player1).unwrap();
s_uni.toggle(16, 17, player1).unwrap();
s_uni.toggle(17, 17, player1).unwrap();
s_uni.toggle(89+16, 68+15, 0).unwrap();
s_uni.toggle(89+17, 68+15, 0).unwrap();
s_uni.toggle(89+15, 68+16, 0).unwrap();
s_uni.toggle(89+16, 68+16, 0).unwrap();
s_uni.toggle(89+16, 68+17, 0).unwrap();
let gens = 4;
for _ in 0..gens {
s_uni.next();
}
let player_id = 0;
let diff = s_uni.diff(0, 5, Some(player_id)).unwrap();
assert_eq!(c_uni.apply(&diff, Some(player_id)), Ok(Some(5)));
let s_idx = s_uni.state_index;
let c_idx = c_uni.state_index;
assert_eq!(s_uni.gen_states[s_idx].gen_or_none, c_uni.gen_states[c_idx].gen_or_none);
assert!(s_uni.gen_states[s_idx].cells != c_uni.gen_states[c_idx].cells); assert_eq!(s_uni.gen_states[s_idx].player_states[0].cells, c_uni.gen_states[c_idx].player_states[0].cells);
}
}
#[cfg(test)]
mod genstate_tests {
use super::*;
use super::test_helpers::*;
#[test]
fn write_at_position_server_wall() {
let mut genstate = make_gen_state();
genstate.write_at_position(63, 0, 'W', None);
assert_eq!(genstate.cells[0][0], 0);
assert_eq!(genstate.wall_cells[0][0], 1);
assert_eq!(genstate.known[0][0], u64::max_value());
assert_eq!(genstate.player_states[0].cells[0][0], 0);
assert_eq!(genstate.player_states[1].cells[0][0], 0);
}
#[test]
fn write_at_position_server_wall_overwrite() {
let mut genstate = make_gen_state();
genstate.write_at_position(63, 0, 'b', None);
genstate.write_at_position(63, 0, 'W', None);
assert_eq!(genstate.cells[0][0], 0);
assert_eq!(genstate.wall_cells[0][0], 1);
assert_eq!(genstate.known[0][0], u64::max_value());
assert_eq!(genstate.player_states[0].cells[0][0], 0);
assert_eq!(genstate.player_states[1].cells[0][0], 0);
}
#[test]
fn write_at_position_server_player0() {
let mut genstate = make_gen_state();
genstate.write_at_position(63, 0, 'A', None);
assert_eq!(genstate.cells[0][0], 1);
assert_eq!(genstate.wall_cells[0][0], 0);
assert_eq!(genstate.known[0][0], u64::max_value());
assert_eq!(genstate.player_states[0].cells[0][0], 1);
assert_eq!(genstate.player_states[1].cells[0][0], 0);
}
#[test]
fn write_at_position_server_player1() {
let mut genstate = make_gen_state();
genstate.write_at_position(63, 0, 'B', None);
assert_eq!(genstate.cells[0][0], 1);
assert_eq!(genstate.wall_cells[0][0], 0);
assert_eq!(genstate.known[0][0], u64::max_value());
assert_eq!(genstate.player_states[0].cells[0][0], 0);
assert_eq!(genstate.player_states[1].cells[0][0], 1);
}
#[test]
fn write_at_position_server_player0_then_1() {
let mut genstate = make_gen_state();
genstate.write_at_position(63, 0, 'A', None);
genstate.write_at_position(63, 0, 'B', None);
assert_eq!(genstate.cells[0][0], 1);
assert_eq!(genstate.player_states[0].cells[0][0], 0);
assert_eq!(genstate.player_states[1].cells[0][0], 1);
}
#[test]
fn write_at_position_server_player1_then_unowned() {
let mut genstate = make_gen_state();
genstate.write_at_position(63, 0, 'B', None);
genstate.write_at_position(63, 0, 'o', None);
assert_eq!(genstate.cells[0][0], 1);
assert_eq!(genstate.wall_cells[0][0], 0);
assert_eq!(genstate.known[0][0], u64::max_value());
assert_eq!(genstate.player_states[0].cells[0][0], 0);
assert_eq!(genstate.player_states[1].cells[0][0], 0);
}
#[test]
fn write_at_position_server_player1_then_blank() {
let mut genstate = make_gen_state();
genstate.write_at_position(63, 0, 'B', None);
genstate.write_at_position(63, 0, 'b', None);
assert_eq!(genstate.cells[0][0], 0);
assert_eq!(genstate.wall_cells[0][0], 0);
assert_eq!(genstate.known[0][0], u64::max_value());
assert_eq!(genstate.player_states[0].cells[0][0], 0);
assert_eq!(genstate.player_states[1].cells[0][0], 0);
}
#[test]
fn write_at_position_as_player0_player1_then_blank() {
let mut genstate = make_gen_state();
genstate.write_at_position(63, 0, 'B', Some(0));
genstate.write_at_position(63, 0, 'b', Some(0));
assert_eq!(genstate.cells[0][0], 0);
assert_eq!(genstate.wall_cells[0][0], 0);
assert_eq!(genstate.known[0][0], u64::max_value());
assert_eq!(genstate.player_states[0].cells[0][0], 0);
assert_eq!(genstate.player_states[1].cells[0][0], 0);
}
#[test]
fn write_at_position_as_player0_clears_only_that_players_fog() {
let mut genstate = make_gen_state();
genstate.player_states[0].fog[0][0] = 0;
genstate.player_states[1].fog[0][0] = 0;
genstate.write_at_position(63, 0, '?', Some(0));
assert_eq!(genstate.cells[0][0], 0);
assert_eq!(genstate.wall_cells[0][0], 0);
assert_eq!(genstate.player_states[0].cells[0][0], 0);
assert_eq!(genstate.player_states[0].fog[0][0], 1);
assert_eq!(genstate.player_states[1].cells[0][0], 0);
assert_eq!(genstate.player_states[1].fog[0][0], 0);
}
#[test]
fn write_at_position_as_player0_sets_only_that_players_fog() {
let mut genstate = make_gen_state();
genstate.player_states[0].fog[0][0] = 1;
genstate.player_states[1].fog[0][0] = 1;
genstate.write_at_position(63, 0, 'o', Some(0));
assert_eq!(genstate.cells[0][0], 1);
assert_eq!(genstate.wall_cells[0][0], 0);
assert_eq!(genstate.player_states[0].cells[0][0], 0);
assert_eq!(genstate.player_states[0].fog[0][0], 0);
assert_eq!(genstate.player_states[1].cells[0][0], 0);
assert_eq!(genstate.player_states[1].fog[0][0], 1);
}
#[test]
fn gen_state_get_run_player0_fog() {
let mut genstate = make_gen_state();
assert_eq!(genstate.player_states[0].fog[0][0], u64::max_value()); assert_eq!(genstate.player_states[1].fog[0][0], 0);
let write_pattern_as = Some(1); Pattern("o!".to_owned()).to_grid(&mut genstate, write_pattern_as).unwrap();
let visibility = Some(0); assert_eq!(genstate.get_run(0, 0, visibility), (genstate.width(), '?'));
}
#[test]
fn gen_state_copy_from_bit_grid_simple() {
let mut genstate = make_gen_state();
let grid = Pattern("64o$64o!".to_owned()).to_new_bit_grid(64, 2).unwrap();
let write_pattern_as = None;
let dst_region = Region::new(0, 0, 32, 3);
genstate.copy_from_bit_grid(&grid, dst_region, write_pattern_as);
assert_eq!(genstate.cells.to_pattern(None).0, "32o$32o!".to_owned());
assert_eq!(genstate.wall_cells.to_pattern(None).0, "!".to_owned());
for player_id in 0..genstate.player_states.len() {
assert_eq!(genstate.player_states[player_id].cells.to_pattern(None).0, "!".to_owned());
}
}
#[test]
fn gen_state_copy_from_bit_grid_as_player() {
let mut genstate = make_gen_state();
let grid = Pattern("64o$64o!".to_owned()).to_new_bit_grid(64, 2).unwrap();
assert_eq!(genstate.player_states[0].fog[0][0], u64::max_value()); assert_eq!(genstate.player_states[1].fog[0][0], 0);
let write_pattern_as = Some(1); let raw_dst_region = Region::new(0, 0, 32, 3);
let dst_region = raw_dst_region.intersection(Region::new(0, 0, 80, 80)).unwrap();
genstate.copy_from_bit_grid(&grid, dst_region, write_pattern_as);
assert_eq!(genstate.cells.to_pattern(None).0, "32o$32o!".to_owned());
assert_eq!(genstate.wall_cells.to_pattern(None).0, "!".to_owned());
assert_eq!(genstate.player_states[0].cells.to_pattern(None).0, "!".to_owned());
assert_eq!(genstate.player_states[0].fog[0][0], u64::max_value()); assert_eq!(genstate.player_states[0].fog[1][0], u64::max_value()); assert_eq!(genstate.player_states[1].cells.to_pattern(None).0, "32o$32o!".to_owned());
}
#[test]
fn gen_state_pair_get_run_simple() {
let gs0 = make_gen_state();
let mut gs1 = make_gen_state();
Pattern("o!".to_owned()).to_grid(&mut gs1, None).unwrap();
let pair = GenStatePair {
gen_state0: &gs0,
gen_state1: &gs1,
};
assert_eq!(pair.get_run(0, 0, None), (1, 'o'));
assert_eq!(pair.get_run(1, 0, None), (gs0.width() - 1, '"'));
}
#[test]
fn gen_state_diff_and_restore_complex1() {
let gs0 = make_gen_state();
let mut gs1 = make_gen_state();
let visibility = None;
let pat_str = "b2o23b2o21b$b2o23bo22b$24bobo22b$15b2o7b2o23b$2o13bobo31b$2o13bob2o30b$16b2o31b$16bo32b$44b2o3b$16bo27b2o3b$16b2o31b$2o13bob2o13bo3bo12b$2o13bobo13bo5bo7b2o2b$15b2o14bo13b2o2b$31b2o3bo12b$b2o30b3o13b$b2o46b$33b3o13b$31b2o3bo12b$31bo13b2o2b$31bo5bo7b2o2b$32bo3bo12b2$44b2o3b$44b2o3b5$37b2o10b$37bobo7b2o$39bo7b2o$37b3o9b$22bobo24b$21b3o25b$21b3o25b$21bo15b3o9b$25bobo11bo9b$21b2o4bo9bobo9b$16b2o4bo3b2o9b2o10b$15bobo6bo24b$15bo33b$14b2o!".to_owned();
Pattern(pat_str).to_grid(&mut gs1, visibility).unwrap();
let gsdiff = gs0.diff(&gs1, visibility);
let mut new_gs = make_gen_state();
gsdiff.pattern.to_grid(&mut new_gs, visibility).unwrap();
assert_eq!(new_gs.gen_or_none, gs1.gen_or_none);
assert_eq!(new_gs.cells, gs1.cells);
assert_eq!(new_gs.known, gs1.known);
assert_eq!(new_gs.wall_cells, gs1.wall_cells);
for i in 0..new_gs.player_states.len() {
assert_eq!(new_gs.player_states[i].cells, gs1.player_states[i].cells);
}
}
#[test]
fn gen_state_diff_and_restore_complex1_with_visibility() {
let gs0 = make_gen_state();
let mut gs1 = make_gen_state();
let visibility = Some(1);
let pat_str = "b2o23b2o21b$b2o23bo22b$24bobo22b$15b2o7b2o23b$2o13bobo31b$2o13bob2o30b$16b2o31b$16bo32b$44b2o3b$16bo27b2o3b$16b2o31b$2o13bob2o13bo3bo12b$2o13bobo13bo5bo7b2o2b$15b2o14bo13b2o2b$31b2o3bo12b$b2o30b3o13b$b2o46b$33b3o13b$31b2o3bo12b$31bo13b2o2b$31bo5bo7b2o2b$32bo3bo12b2$44b2o3b$44b2o3b5$37b2o10b$37bobo7b2o$39bo7b2o$37b3o9b$22bobo24b$21b3o25b$21b3o25b$21bo15b3o9b$25bobo11bo9b$21b2o4bo9bobo9b$16b2o4bo3b2o9b2o10b$15bobo6bo24b$15bo33b$14b2o!".to_owned();
Pattern(pat_str).to_grid(&mut gs1, visibility).unwrap();
let gsdiff = gs0.diff(&gs1, visibility);
let mut new_gs = make_gen_state();
gsdiff.pattern.to_grid(&mut new_gs, visibility).unwrap();
assert_eq!(new_gs.gen_or_none, gs1.gen_or_none);
assert_eq!(new_gs.cells, gs1.cells);
assert_eq!(new_gs.known, gs1.known);
assert_eq!(new_gs.wall_cells, gs1.wall_cells);
for i in 0..new_gs.player_states.len() {
assert_eq!(new_gs.player_states[i].cells, gs1.player_states[i].cells);
assert_eq!(new_gs.player_states[i].fog, gs1.player_states[i].fog);
}
}
#[test]
fn gen_state_pair_get_run_simple2() {
let mut gs0 = make_gen_state();
Pattern("2o3b5o!".to_owned()).to_grid(&mut gs0, None).unwrap();
let mut gs1 = make_gen_state();
Pattern("2o3b5o!".to_owned()).to_grid(&mut gs1, None).unwrap();
let pair = GenStatePair {
gen_state0: &gs0,
gen_state1: &gs1,
};
assert_eq!(pair.get_run(0, 0, None), (gs0.width(), '"'));
}
#[test]
fn gen_state_pair_get_run_simple3() {
let mut gs0 = make_gen_state();
Pattern("4b5o!".to_owned()).to_grid(&mut gs0, None).unwrap();
let mut gs1 = make_gen_state();
Pattern("3b5o!".to_owned()).to_grid(&mut gs1, None).unwrap();
let pair = GenStatePair {
gen_state0: &gs0,
gen_state1: &gs1,
};
assert_eq!(pair.get_run(0, 0, None), (3, '"'));
assert_eq!(pair.get_run(3, 0, None), (1, 'o'));
assert_eq!(pair.get_run(3+1, 0, None), (4, '"'));
assert_eq!(pair.get_run(3+1+4, 0, None), (1, 'b'));
assert_eq!(pair.get_run(3+1+4+1, 0, None), (gs0.width() - (3+1+4+1), '"'));
}
}