added script to generate random patterns
This commit is contained in:
parent
dc2a80bf4f
commit
1c1ad2c4fd
5 changed files with 208 additions and 164 deletions
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "golrs"
|
name = "golrs"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "a TUI for vizualising a rust implementation of the game of life."
|
description = "a TUI for vizualising a rust implementation of the game of life."
|
||||||
authors = ["Matthieu JOLIMAITRE <matthieu@imagevo.fr>"]
|
authors = ["Matthieu JOLIMAITRE <matthieu@imagevo.fr>"]
|
||||||
|
@ -9,4 +9,4 @@ repository = "https://github.com/MajorBarnulf/golrs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
termion = "1.5"
|
termion = "1.5"
|
||||||
metrohash = "1.0"
|
metrohash = "1.0"
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
const { args } = Deno;
|
const { args } = Deno;
|
||||||
|
|
||||||
|
if (args[0] == "--help") {
|
||||||
|
console.log("usage: [bin] <size> <frequency>");
|
||||||
|
}
|
||||||
|
|
||||||
const size = parseInt(args[0] ?? "5");
|
const size = parseInt(args[0] ?? "5");
|
||||||
const frequency = parseFloat(args[1] ?? "0.5");
|
const frequency = parseFloat(args[1] ?? "0.5");
|
||||||
|
|
||||||
|
@ -18,4 +22,4 @@ for (const _y of range(0, size)) {
|
||||||
result += '\n';
|
result += '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(result);
|
console.log(result);
|
||||||
|
|
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
hard_tabs = true
|
50
src/main.rs
50
src/main.rs
|
@ -13,34 +13,34 @@ pub use view::View;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
fn deserialize(str: &str) -> Vec<Pos> {
|
fn deserialize(str: &str) -> Vec<Pos> {
|
||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
let mut pos = pos!(0, 0);
|
let mut pos = pos!(0, 0);
|
||||||
for c in str.chars() {
|
for c in str.chars() {
|
||||||
match c {
|
match c {
|
||||||
' ' => {
|
' ' => {
|
||||||
pos.x += 1;
|
pos.x += 1;
|
||||||
}
|
}
|
||||||
'\n' => pos = pos!(0, pos.y + 1),
|
'\n' => pos = pos!(0, pos.y + 1),
|
||||||
_ => {
|
_ => {
|
||||||
result.push(pos);
|
result.push(pos);
|
||||||
pos.x += 1
|
pos.x += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
let path = args().nth(1).unwrap_or_else(|| {
|
let path = args().nth(1).unwrap_or_else(|| {
|
||||||
eprintln!("[error] must provide a path argument");
|
eprintln!("[error] must provide a path argument");
|
||||||
exit(1);
|
exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
let content = fs::read_to_string(path).unwrap();
|
let content = fs::read_to_string(path).unwrap();
|
||||||
let actives = deserialize(&content);
|
let actives = deserialize(&content);
|
||||||
let simulation = Sim::spawn(actives);
|
let simulation = Sim::<HashedWorld>::spawn(actives);
|
||||||
let view = View::spawn::<HashedWorld>(simulation.handle());
|
let view = View::spawn(simulation.handle());
|
||||||
|
|
||||||
simulation.join();
|
simulation.join();
|
||||||
view.join();
|
view.join();
|
||||||
}
|
}
|
||||||
|
|
311
src/view.rs
311
src/view.rs
|
@ -1,9 +1,9 @@
|
||||||
use std::{
|
use std::{
|
||||||
io::{stdin, stdout},
|
io::{stdin, stdout},
|
||||||
process::exit,
|
process::exit,
|
||||||
sync::mpsc,
|
sync::mpsc,
|
||||||
thread::{self, JoinHandle},
|
thread::{self, JoinHandle},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use canvas::Canvas;
|
pub use canvas::Canvas;
|
||||||
|
@ -13,189 +13,228 @@ use termion::{event::Key, input::TermRead, raw::IntoRawMode};
|
||||||
|
|
||||||
use crate::{pos, Pos, SimHandle, World};
|
use crate::{pos, Pos, SimHandle, World};
|
||||||
|
|
||||||
pub struct View {
|
pub struct View<W>
|
||||||
thread: JoinHandle<()>,
|
where
|
||||||
|
W: World,
|
||||||
|
{
|
||||||
|
thread: JoinHandle<()>,
|
||||||
|
sender: mpsc::Sender<ViewCmd<W>>,
|
||||||
}
|
}
|
||||||
impl View {
|
impl<W> View<W>
|
||||||
pub fn spawn<W>(handle: SimHandle<W>) -> Self
|
where
|
||||||
where
|
W: World,
|
||||||
W: World,
|
{
|
||||||
{
|
pub fn spawn(handle: SimHandle<W>) -> Self
|
||||||
let thread = thread::spawn(|| view_loop(handle));
|
where
|
||||||
Self { thread }
|
W: World,
|
||||||
}
|
{
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
let thread = thread::spawn(|| view_loop(receiver, handle));
|
||||||
|
Self { thread, sender }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn join(self) {
|
pub fn join(self) {
|
||||||
self.thread.join().unwrap();
|
self.thread.join().unwrap();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ViewRemote<W>
|
||||||
|
where
|
||||||
|
W: World,
|
||||||
|
{
|
||||||
|
sender: mpsc::Sender<ViewCmd<W>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ViewCmd<W>
|
||||||
|
where
|
||||||
|
W: World,
|
||||||
|
{
|
||||||
|
Refresh,
|
||||||
|
UpdateWorld(W),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W> ViewRemote<W>
|
||||||
|
where
|
||||||
|
W: World,
|
||||||
|
{
|
||||||
|
fn new(sender: mpsc::Sender<ViewCmd<W>>) -> Self {
|
||||||
|
Self { sender }
|
||||||
|
}
|
||||||
|
pub fn refresh(&self) {
|
||||||
|
self.sender.send(ViewCmd::Refresh).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_world(&self, world: W) {
|
||||||
|
self.sender.send(ViewCmd::UpdateWorld(world)).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Dir {
|
pub enum Dir {
|
||||||
Up,
|
Up,
|
||||||
Down,
|
Down,
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum InputCmd {
|
pub enum InputCmd {
|
||||||
Exit,
|
Exit,
|
||||||
ToggleDebug,
|
ToggleDebug,
|
||||||
Move(Dir),
|
Move(Dir),
|
||||||
Accelerate,
|
Accelerate,
|
||||||
Decelerate,
|
Decelerate,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input_loop(sender: mpsc::Sender<InputCmd>) {
|
fn input_loop(sender: mpsc::Sender<InputCmd>) {
|
||||||
let stdout = stdout().into_raw_mode().unwrap();
|
let stdout = stdout().into_raw_mode().unwrap();
|
||||||
for c in stdin().keys() {
|
for c in stdin().keys() {
|
||||||
let command = match c.unwrap() {
|
let command = match c.unwrap() {
|
||||||
Key::Char('q') => InputCmd::Exit,
|
Key::Char('q') => InputCmd::Exit,
|
||||||
Key::Char('d') => InputCmd::ToggleDebug,
|
Key::Char('d') => InputCmd::ToggleDebug,
|
||||||
Key::Up => InputCmd::Move(Dir::Up),
|
Key::Up => InputCmd::Move(Dir::Up),
|
||||||
Key::Down => InputCmd::Move(Dir::Down),
|
Key::Down => InputCmd::Move(Dir::Down),
|
||||||
Key::Left => InputCmd::Move(Dir::Left),
|
Key::Left => InputCmd::Move(Dir::Left),
|
||||||
Key::Right => InputCmd::Move(Dir::Right),
|
Key::Right => InputCmd::Move(Dir::Right),
|
||||||
Key::Char('+') => InputCmd::Accelerate,
|
Key::Char('+') => InputCmd::Accelerate,
|
||||||
Key::Char('-') => InputCmd::Decelerate,
|
Key::Char('-') => InputCmd::Decelerate,
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
sender.send(command).unwrap();
|
sender.send(command).unwrap();
|
||||||
}
|
}
|
||||||
drop(stdout);
|
drop(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
const VIEW_REFRESH_INTERVAL: Duration = Duration::from_millis(100);
|
const VIEW_REFRESH_INTERVAL: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
#[allow(unreachable_code)]
|
#[allow(unreachable_code)]
|
||||||
fn view_loop<W>(handle: SimHandle<W>)
|
fn view_loop<W>(receiver: mpsc::Receiver<ViewCmd<W>>, handle: SimHandle<W>)
|
||||||
where
|
where
|
||||||
W: World,
|
W: World,
|
||||||
{
|
{
|
||||||
let (sender, receiver) = mpsc::channel();
|
let (sender, receiver) = mpsc::channel();
|
||||||
let _input_handle = thread::spawn(|| input_loop(sender));
|
let _input_handle = thread::spawn(|| input_loop(sender));
|
||||||
|
|
||||||
let (x, y) = termion::terminal_size().unwrap();
|
let (x, y) = termion::terminal_size().unwrap();
|
||||||
let mut tick_delay = 200u64;
|
let mut tick_delay = 200;
|
||||||
let mut view_origin = pos!(-(x as i32) / 2, -(y as i32));
|
let mut view_origin = pos!(-(x as i32) / 2, -(y as i32));
|
||||||
let mut debug = false;
|
let mut debug = false;
|
||||||
loop {
|
loop {
|
||||||
handle_inputs(&receiver, &mut view_origin, &mut debug, &mut tick_delay);
|
handle_inputs(&receiver, &mut view_origin, &mut debug, &mut tick_delay);
|
||||||
handle.set_delay(tick_delay);
|
handle.set_delay(tick_delay);
|
||||||
let world = handle.snapshot();
|
let world = handle.snapshot();
|
||||||
|
|
||||||
let mut canvas = Canvas::from_screen();
|
let mut canvas = Canvas::from_screen();
|
||||||
if debug {
|
if debug {
|
||||||
dbg_layer(&mut canvas, &world, view_origin);
|
dbg_layer(&mut canvas, &world, view_origin);
|
||||||
}
|
}
|
||||||
grid_layer(&mut canvas, view_origin);
|
grid_layer(&mut canvas, view_origin);
|
||||||
world_layer(&mut canvas, &world, view_origin);
|
world_layer(&mut canvas, &world, view_origin);
|
||||||
title_layer(&mut canvas, handle.delay());
|
title_layer(&mut canvas, handle.delay());
|
||||||
canvas.display();
|
canvas.display();
|
||||||
}
|
}
|
||||||
drop(handle);
|
drop(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
const MOVEMENT_STEP: i32 = 3;
|
const MOVEMENT_STEP: i32 = 3;
|
||||||
|
|
||||||
fn handle_inputs(
|
fn handle_inputs(
|
||||||
receiver: &mpsc::Receiver<InputCmd>,
|
receiver: &mpsc::Receiver<InputCmd>,
|
||||||
view_origin: &mut Pos,
|
view_origin: &mut Pos,
|
||||||
debug: &mut bool,
|
debug: &mut bool,
|
||||||
delay: &mut u64,
|
delay: &mut u64,
|
||||||
) {
|
) {
|
||||||
if let Ok(cmd) = receiver.recv_timeout(VIEW_REFRESH_INTERVAL) {
|
if let Ok(cmd) = receiver.recv_timeout(VIEW_REFRESH_INTERVAL) {
|
||||||
match cmd {
|
match cmd {
|
||||||
InputCmd::Exit => {
|
InputCmd::Exit => {
|
||||||
println!("{}{}", termion::clear::All, termion::cursor::Goto(1, 1));
|
println!("{}{}", termion::clear::All, termion::cursor::Goto(1, 1));
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
InputCmd::ToggleDebug => *debug = !*debug,
|
InputCmd::ToggleDebug => *debug = !*debug,
|
||||||
InputCmd::Move(direction) => {
|
InputCmd::Move(direction) => {
|
||||||
*view_origin = *view_origin
|
*view_origin = *view_origin
|
||||||
+ match direction {
|
+ match direction {
|
||||||
Dir::Up => pos!(0, -MOVEMENT_STEP),
|
Dir::Up => pos!(0, -MOVEMENT_STEP),
|
||||||
Dir::Down => pos!(0, MOVEMENT_STEP),
|
Dir::Down => pos!(0, MOVEMENT_STEP),
|
||||||
Dir::Left => pos!(-2 * MOVEMENT_STEP, 0),
|
Dir::Left => pos!(-2 * MOVEMENT_STEP, 0),
|
||||||
Dir::Right => pos!(2 * MOVEMENT_STEP, 0),
|
Dir::Right => pos!(2 * MOVEMENT_STEP, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputCmd::Accelerate => {
|
InputCmd::Accelerate => {
|
||||||
if *delay > 0 {
|
if *delay > 0 {
|
||||||
*delay -= 100
|
*delay -= 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputCmd::Decelerate => *delay += 100,
|
InputCmd::Decelerate => *delay += 100,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn grid_layer(canvas: &mut Canvas, view_origin: Pos) {
|
fn grid_layer(canvas: &mut Canvas, view_origin: Pos) {
|
||||||
canvas.layer(|local_pos| {
|
canvas.layer(|local_pos| {
|
||||||
let Pos { x, y } = screen_pos_to_world(local_pos, view_origin);
|
let Pos { x, y } = screen_pos_to_world(local_pos, view_origin);
|
||||||
match (x % 16 == 0, dmod(y, 16) <= 1) {
|
match (x % 16 == 0, dmod(y, 16) <= 1) {
|
||||||
(true, true) => Some('┼'),
|
(true, true) => Some('┼'),
|
||||||
(true, _) => Some('│'),
|
(true, _) => Some('│'),
|
||||||
(_, true) => Some('─'),
|
(_, true) => Some('─'),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn world_layer<W>(canvas: &mut Canvas, world: &W, view_origin: Pos)
|
fn world_layer<W>(canvas: &mut Canvas, world: &W, view_origin: Pos)
|
||||||
where
|
where
|
||||||
W: World,
|
W: World,
|
||||||
{
|
{
|
||||||
canvas.layer(|mut local_pos| {
|
canvas.layer(|mut local_pos| {
|
||||||
local_pos.y *= 2;
|
local_pos.y *= 2;
|
||||||
let pos_top = local_pos + view_origin;
|
let pos_top = local_pos + view_origin;
|
||||||
let top = world.get(pos_top).is_active();
|
let top = world.get(pos_top).is_active();
|
||||||
let pos_bottom = pos_top + pos!(0, 1);
|
let pos_bottom = pos_top + pos!(0, 1);
|
||||||
let bottom = world.get(pos_bottom).is_active();
|
let bottom = world.get(pos_bottom).is_active();
|
||||||
|
|
||||||
match (top, bottom) {
|
match (top, bottom) {
|
||||||
(true, true) => Some('█'),
|
(true, true) => Some('█'),
|
||||||
(true, _) => Some('▀'),
|
(true, _) => Some('▀'),
|
||||||
(_, true) => Some('▄'),
|
(_, true) => Some('▄'),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::useless_format)]
|
#[allow(clippy::useless_format)]
|
||||||
fn title_layer(canvas: &mut Canvas, de: usize) {
|
fn title_layer(canvas: &mut Canvas, de: usize) {
|
||||||
let table = [
|
let table = [
|
||||||
format!("│ <golrs> | [+]: speed up │"),
|
format!("│ <golrs> | [+]: speed up │"),
|
||||||
format!("│ | [-]: slow down │"),
|
format!("│ | [-]: slow down │"),
|
||||||
format!("│ tick delay: | [d]: debug │"),
|
format!("│ tick delay: | [d]: debug │"),
|
||||||
format!("│ {de:>7} ms | [q]: quit │"),
|
format!("│ {de:>7} ms | [q]: quit │"),
|
||||||
format!("└──────────────────────────────┘"),
|
format!("└──────────────────────────────┘"),
|
||||||
];
|
];
|
||||||
canvas.layer(|Pos { x, y }| {
|
canvas.layer(|Pos { x, y }| {
|
||||||
table
|
table
|
||||||
.get(y as usize)
|
.get(y as usize)
|
||||||
.and_then(|line| line.chars().nth(x as usize))
|
.and_then(|line| line.chars().nth(x as usize))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dbg_layer<W>(canvas: &mut Canvas, world: &W, view_origin: Pos)
|
fn dbg_layer<W>(canvas: &mut Canvas, world: &W, view_origin: Pos)
|
||||||
where
|
where
|
||||||
W: World,
|
W: World,
|
||||||
{
|
{
|
||||||
canvas.layer(|local_pos| {
|
canvas.layer(|local_pos| {
|
||||||
let pos = screen_pos_to_world(local_pos, view_origin);
|
let pos = screen_pos_to_world(local_pos, view_origin);
|
||||||
world.dbg_is_loaded(pos).then_some('.')
|
world.dbg_is_loaded(pos).then_some('.')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn screen_pos_to_world(mut pos: Pos, view_origin: Pos) -> Pos {
|
fn screen_pos_to_world(mut pos: Pos, view_origin: Pos) -> Pos {
|
||||||
pos.y *= 2;
|
pos.y *= 2;
|
||||||
pos + view_origin
|
pos + view_origin
|
||||||
}
|
}
|
||||||
|
|
||||||
/// double module to avoid irregularities between negatives and positives
|
/// double module to avoid irregularities between negatives and positives
|
||||||
fn dmod(a: i32, module: i32) -> i32 {
|
fn dmod(a: i32, module: i32) -> i32 {
|
||||||
((a % module) + module) % module
|
((a % module) + module) % module
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue