initialization
This commit is contained in:
commit
36a93ab177
13 changed files with 601 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
58
Cargo.lock
generated
Normal file
58
Cargo.lock
generated
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "golrs"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"termion",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.132"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "numtoa"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_termios"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
|
||||||
|
dependencies = [
|
||||||
|
"redox_syscall",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termion"
|
||||||
|
version = "1.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"numtoa",
|
||||||
|
"redox_syscall",
|
||||||
|
"redox_termios",
|
||||||
|
]
|
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "golrs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
termion = "1.5.6"
|
9
README.md
Normal file
9
README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# golrs
|
||||||
|
|
||||||
|
Game Of Life RuSt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This is a TUI for vizualising a rust implementation of the game of life
|
11
patterns/flower.txt
Normal file
11
patterns/flower.txt
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
###
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
## ##
|
||||||
|
# # #
|
||||||
|
# # # #
|
||||||
|
# # #
|
||||||
|
## ##
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
###
|
3
patterns/glider.txt
Normal file
3
patterns/glider.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#
|
||||||
|
#
|
||||||
|
###
|
3
patterns/simple.txt
Normal file
3
patterns/simple.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
44
src/main.rs
Normal file
44
src/main.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use std::{env::args, fs, process::exit};
|
||||||
|
|
||||||
|
pub use utils::Pos;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
pub use world::{Cell, HashedWorld, World};
|
||||||
|
pub mod world;
|
||||||
|
|
||||||
|
pub use sim::{Sim, SimHandle};
|
||||||
|
mod sim;
|
||||||
|
|
||||||
|
pub use view::View;
|
||||||
|
mod view;
|
||||||
|
|
||||||
|
fn deserialize(str: &str) -> Vec<Pos> {
|
||||||
|
let mut result = vec![];
|
||||||
|
let mut pos = pos!(0, 0);
|
||||||
|
for c in str.chars() {
|
||||||
|
match c {
|
||||||
|
'#' => {
|
||||||
|
result.push(pos);
|
||||||
|
pos.x += 1
|
||||||
|
}
|
||||||
|
'\n' => pos = pos!(0, pos.y + 1),
|
||||||
|
_ => pos.x += 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let path = args().nth(1).unwrap_or_else(|| {
|
||||||
|
eprintln!("[error] must provide a path argument");
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
let content = fs::read_to_string(path).unwrap();
|
||||||
|
let actives = deserialize(&content);
|
||||||
|
let simulation = Sim::spawn(actives);
|
||||||
|
let view = View::spawn::<HashedWorld>(simulation.handle());
|
||||||
|
|
||||||
|
simulation.join();
|
||||||
|
view.join();
|
||||||
|
}
|
163
src/sim.rs
Normal file
163
src/sim.rs
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
use std::{
|
||||||
|
sync::mpsc,
|
||||||
|
thread::{self, JoinHandle},
|
||||||
|
time::{Duration, SystemTime},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{pos, Cell, Pos, World};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct State<W>
|
||||||
|
where
|
||||||
|
W: World,
|
||||||
|
{
|
||||||
|
world: W,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W> State<W>
|
||||||
|
where
|
||||||
|
W: World,
|
||||||
|
{
|
||||||
|
pub fn actives(&self) -> Vec<Pos> {
|
||||||
|
self.world.actives()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, pos: Pos) -> Cell {
|
||||||
|
self.world.get(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, pos: Pos, cell: Cell) {
|
||||||
|
self.world.set(pos, cell)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn possible_change_pos(&self) -> impl Iterator<Item = Pos> + '_ {
|
||||||
|
self.actives()
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| self.get_neighbors(p))
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_neighbors(&self, pos: Pos) -> impl Iterator<Item = Pos> + '_ {
|
||||||
|
(-1..=1)
|
||||||
|
.map(|x| (-1..=1).map(move |y| pos!(x, y)))
|
||||||
|
.flatten()
|
||||||
|
.map(move |p| pos + p)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_cell_alive(&self, pos: Pos) -> bool {
|
||||||
|
self.get(pos.clone()).is_active()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_neighbor_count(&self, pos: Pos) -> usize {
|
||||||
|
self.get_neighbors(pos)
|
||||||
|
.filter(|pos| self.is_cell_alive(pos.clone()))
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn snapshot(&self) -> W {
|
||||||
|
self.world.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SimCmd<W>
|
||||||
|
where
|
||||||
|
W: World,
|
||||||
|
{
|
||||||
|
Snapshot(mpsc::Sender<W>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SimHandle<W>
|
||||||
|
where
|
||||||
|
W: World,
|
||||||
|
{
|
||||||
|
sender: mpsc::Sender<SimCmd<W>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W> SimHandle<W>
|
||||||
|
where
|
||||||
|
W: World,
|
||||||
|
{
|
||||||
|
pub fn new(sender: mpsc::Sender<SimCmd<W>>) -> Self {
|
||||||
|
Self { sender }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn snapshot(&self) -> W {
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
self.sender.send(SimCmd::Snapshot(sender)).unwrap();
|
||||||
|
receiver.recv().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Sim<W>
|
||||||
|
where
|
||||||
|
W: World,
|
||||||
|
{
|
||||||
|
thread: JoinHandle<()>,
|
||||||
|
sender: mpsc::Sender<SimCmd<W>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W> Sim<W>
|
||||||
|
where
|
||||||
|
W: World,
|
||||||
|
{
|
||||||
|
pub fn spawn(actives: impl IntoIterator<Item = Pos>) -> Self {
|
||||||
|
let mut state: State<W> = State::default();
|
||||||
|
for active in actives.into_iter() {
|
||||||
|
state.set(active, Cell::active());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
let thread = thread::spawn(move || sim_loop(receiver, state));
|
||||||
|
|
||||||
|
Self { sender, thread }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(&self) -> SimHandle<W> {
|
||||||
|
let sender = self.sender.clone();
|
||||||
|
SimHandle { sender }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn join(self) {
|
||||||
|
self.thread.join().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const EVT_CHECK_TIMEOUT: Duration = Duration::from_millis(10);
|
||||||
|
const SIM_TICK_INTERVAL: Duration = Duration::from_millis(200);
|
||||||
|
|
||||||
|
fn sim_loop<W>(receiver: mpsc::Receiver<SimCmd<W>>, state: State<W>)
|
||||||
|
where
|
||||||
|
W: World,
|
||||||
|
{
|
||||||
|
let mut current_state = state;
|
||||||
|
let mut last_update = SystemTime::now();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Some(cmd) = receiver.try_recv().ok() {
|
||||||
|
match cmd {
|
||||||
|
SimCmd::Snapshot(sender) => sender.send(current_state.snapshot()).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if SystemTime::now().duration_since(last_update).unwrap() > SIM_TICK_INTERVAL {
|
||||||
|
let old_state = current_state;
|
||||||
|
let mut new_state: State<W> = State::default();
|
||||||
|
|
||||||
|
for pos in old_state.possible_change_pos() {
|
||||||
|
let is_active = old_state.is_cell_alive(pos);
|
||||||
|
let neighbor_count = old_state.get_neighbor_count(pos);
|
||||||
|
match (is_active, neighbor_count) {
|
||||||
|
(true, count) if count < 3 || count > 4 => (), // die
|
||||||
|
(true, _) => new_state.set(pos, Cell::active()), // stay
|
||||||
|
(false, 3) => new_state.set(pos, Cell::active()), // becomes alive
|
||||||
|
_ => (), // stays dead
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current_state = new_state;
|
||||||
|
last_update = SystemTime::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::sleep(EVT_CHECK_TIMEOUT);
|
||||||
|
}
|
||||||
|
}
|
28
src/utils.rs
Normal file
28
src/utils.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use std::ops::{Add, Sub};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||||
|
pub struct Pos {
|
||||||
|
pub x: i32,
|
||||||
|
pub y: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! pos {
|
||||||
|
($x:expr, $y:expr) => {
|
||||||
|
crate::Pos { x: $x, y: $y }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for Pos {
|
||||||
|
type Output = Self;
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
pos!(self.x + rhs.x, self.y + rhs.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub for Pos {
|
||||||
|
type Output = Self;
|
||||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
|
pos!(self.x - rhs.x, self.y - rhs.y)
|
||||||
|
}
|
||||||
|
}
|
120
src/view.rs
Normal file
120
src/view.rs
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
use std::{
|
||||||
|
io::{stdin, stdout, Write},
|
||||||
|
process::exit,
|
||||||
|
sync::mpsc,
|
||||||
|
thread::{self, JoinHandle},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use termion::{event::Key, input::TermRead, raw::IntoRawMode};
|
||||||
|
|
||||||
|
use crate::{pos, Pos, SimHandle, World};
|
||||||
|
|
||||||
|
pub struct View {
|
||||||
|
thread: JoinHandle<()>,
|
||||||
|
}
|
||||||
|
impl View {
|
||||||
|
pub fn spawn<W>(handle: SimHandle<W>) -> Self
|
||||||
|
where
|
||||||
|
W: World,
|
||||||
|
{
|
||||||
|
let thread = thread::spawn(|| view_loop(handle));
|
||||||
|
Self { thread }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn join(self) {
|
||||||
|
self.thread.join().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Dir {
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InputCmd {
|
||||||
|
Exit,
|
||||||
|
Move(Dir),
|
||||||
|
Accelerate,
|
||||||
|
Decelerate,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_loop(sender: mpsc::Sender<InputCmd>) {
|
||||||
|
let stdout = stdout().into_raw_mode().unwrap();
|
||||||
|
for c in stdin().keys() {
|
||||||
|
let command = match c.unwrap() {
|
||||||
|
Key::Char('q') => InputCmd::Exit,
|
||||||
|
Key::Up => InputCmd::Move(Dir::Up),
|
||||||
|
Key::Down => InputCmd::Move(Dir::Down),
|
||||||
|
Key::Left => InputCmd::Move(Dir::Left),
|
||||||
|
Key::Right => InputCmd::Move(Dir::Right),
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
sender.send(command).unwrap();
|
||||||
|
}
|
||||||
|
drop(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
const VIEW_REFRESH_INTERVAL: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
|
fn view_loop<W>(handle: SimHandle<W>)
|
||||||
|
where
|
||||||
|
W: World,
|
||||||
|
{
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
let _input_handle = thread::spawn(|| input_loop(sender));
|
||||||
|
|
||||||
|
let mut view_origin = pos!(0, 0);
|
||||||
|
loop {
|
||||||
|
handle_inputs(&receiver, &mut view_origin);
|
||||||
|
let world = handle.snapshot();
|
||||||
|
display_world(view_origin, world);
|
||||||
|
thread::sleep(VIEW_REFRESH_INTERVAL);
|
||||||
|
}
|
||||||
|
drop(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_inputs(receiver: &mpsc::Receiver<InputCmd>, view_origin: &mut Pos) {
|
||||||
|
if let Some(cmd) = receiver.try_recv().ok() {
|
||||||
|
match cmd {
|
||||||
|
InputCmd::Exit => exit(0),
|
||||||
|
InputCmd::Move(direction) => {
|
||||||
|
*view_origin = *view_origin
|
||||||
|
+ match direction {
|
||||||
|
Dir::Up => pos!(0, -4),
|
||||||
|
Dir::Down => pos!(0, 4),
|
||||||
|
Dir::Left => pos!(-4, 0),
|
||||||
|
Dir::Right => pos!(4, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InputCmd::Accelerate => todo!(),
|
||||||
|
InputCmd::Decelerate => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_world<W>(view_origin: Pos, world: W)
|
||||||
|
where
|
||||||
|
W: World,
|
||||||
|
{
|
||||||
|
let (width, height) = termion::terminal_size().unwrap();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
for ly in 0..(height) {
|
||||||
|
let next_line = termion::cursor::Goto(1, ly + 1);
|
||||||
|
result += &format!("{next_line}");
|
||||||
|
for lx in 0..width {
|
||||||
|
let pos = view_origin + pos!(lx as i32, ly as i32);
|
||||||
|
let char = &if world.get(pos).is_active() { "#" } else { " " };
|
||||||
|
result += char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let clear = termion::clear::All;
|
||||||
|
print!("{clear}{result}");
|
||||||
|
stdout().flush().unwrap();
|
||||||
|
}
|
35
src/world.rs
Normal file
35
src/world.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use crate::Pos;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Cell {
|
||||||
|
active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cell {
|
||||||
|
pub fn active() -> Self {
|
||||||
|
Self { active: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inactive() -> Self {
|
||||||
|
Self { active: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_active(&self) -> bool {
|
||||||
|
self.active
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Cell {
|
||||||
|
fn default() -> Self {
|
||||||
|
Cell { active: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait World: Default + Clone + Send + 'static {
|
||||||
|
fn get(&self, pos: Pos) -> Cell;
|
||||||
|
fn set(&mut self, pos: Pos, cell: Cell);
|
||||||
|
fn actives(&self) -> Vec<Pos>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use hashed_world::HashedWorld;
|
||||||
|
mod hashed_world;
|
117
src/world/hashed_world.rs
Normal file
117
src/world/hashed_world.rs
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::{pos, Cell, Pos, World};
|
||||||
|
|
||||||
|
const CHUNK_SIZE: usize = 16;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
struct Chunk {
|
||||||
|
cells: [[Cell; CHUNK_SIZE]; CHUNK_SIZE],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Chunk {
|
||||||
|
fn get(&self, pos: Pos) -> Cell {
|
||||||
|
let pos = HashedWorld::get_local_pos(pos);
|
||||||
|
self.cells[pos.x as usize][pos.y as usize].clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(&mut self, pos: Pos, cell: Cell) {
|
||||||
|
let pos = HashedWorld::get_local_pos(pos);
|
||||||
|
self.cells[pos.x as usize][pos.y as usize] = cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_actives(&self) -> impl Iterator<Item = Pos> + '_ {
|
||||||
|
self.cells
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(x, row)| {
|
||||||
|
row.iter().enumerate().filter_map(move |(y, cell)| {
|
||||||
|
cell.is_active().then_some(pos!(x as i32, y as i32))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
struct ChunkPos(Pos);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct HashedWorld {
|
||||||
|
chunks: HashMap<ChunkPos, Chunk>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HashedWorld {
|
||||||
|
/// gets the position of a chunk containing the passed position
|
||||||
|
fn get_chunk_pos(Pos { x, y }: Pos) -> ChunkPos {
|
||||||
|
let x = snap(x, CHUNK_SIZE as i32);
|
||||||
|
let y = snap(y, CHUNK_SIZE as i32);
|
||||||
|
ChunkPos(pos!(x, y))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// gets the position of a cell local to it's parent chunk.
|
||||||
|
fn get_local_pos(pos: Pos) -> Pos {
|
||||||
|
let ChunkPos(chunk_pos) = Self::get_chunk_pos(pos);
|
||||||
|
pos - chunk_pos
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_chunk(&self, pos: Pos) -> Option<&Chunk> {
|
||||||
|
let pos = Self::get_chunk_pos(pos);
|
||||||
|
self.chunks.get(&pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_chunk_mut(&mut self, pos: Pos) -> Option<&mut Chunk> {
|
||||||
|
let pos = Self::get_chunk_pos(pos);
|
||||||
|
self.chunks.get_mut(&pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_chunk(&mut self, pos: Pos, chunk: Chunk) {
|
||||||
|
let chunk_pos = Self::get_chunk_pos(pos);
|
||||||
|
self.chunks.insert(chunk_pos, chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn snap(n: i32, step: i32) -> i32 {
|
||||||
|
// frankly, I forgot how it works, but somehow it passes tests
|
||||||
|
let rem = ((n % step) + step) % step;
|
||||||
|
n - rem
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snap() {
|
||||||
|
assert_eq!(snap(0, 10), 0);
|
||||||
|
assert_eq!(snap(1, 10), 0);
|
||||||
|
assert_eq!(snap(-1, 10), -10);
|
||||||
|
assert_eq!(snap(10, 10), 10);
|
||||||
|
assert_eq!(snap(11, 10), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl World for HashedWorld {
|
||||||
|
fn get(&self, pos: Pos) -> Cell {
|
||||||
|
if let Some(chunk) = self.get_chunk(pos) {
|
||||||
|
chunk.get(pos)
|
||||||
|
} else {
|
||||||
|
Cell::inactive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(&mut self, pos: Pos, cell: Cell) {
|
||||||
|
if let Some(chunk) = self.get_chunk_mut(pos) {
|
||||||
|
chunk.set(pos, cell)
|
||||||
|
} else {
|
||||||
|
let mut chunk = Chunk::default();
|
||||||
|
chunk.set(pos, cell);
|
||||||
|
self.push_chunk(pos, chunk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn actives(&self) -> Vec<Pos> {
|
||||||
|
self.chunks
|
||||||
|
.iter()
|
||||||
|
.map(|(ChunkPos(chunk_pos), chunk)| {
|
||||||
|
chunk.get_actives().map(|pos| chunk_pos.clone() + pos)
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue