add ability to speed up, slow down

This commit is contained in:
Vinzenz Schroeter 2024-06-23 09:41:37 +02:00
parent 59bd2f3cf2
commit 816cc2c8c3
4 changed files with 114 additions and 82 deletions

View file

@ -3,14 +3,20 @@ use servicepoint2::Grid;
use crate::rules::Rules; use crate::rules::Rules;
pub(crate) struct Game<TState, TGrid, TKernel, const KERNEL_SIZE: usize> pub(crate) struct Game<TState, TGrid, TKernel, const KERNEL_SIZE: usize>
where TGrid: Grid<TState>, TState: Copy + PartialEq, TKernel: Copy where
TGrid: Grid<TState>,
TState: Copy + PartialEq,
TKernel: Copy,
{ {
pub field: TGrid, pub field: TGrid,
pub rules: Rules<TState, TKernel, KERNEL_SIZE>, pub rules: Rules<TState, TKernel, KERNEL_SIZE>,
} }
impl<TState, TGrid, TKernel, const KERNEL_SIZE: usize> Game<TState, TGrid, TKernel, KERNEL_SIZE> impl<TState, TGrid, TKernel, const KERNEL_SIZE: usize> Game<TState, TGrid, TKernel, KERNEL_SIZE>
where TGrid: Grid<TState>, TState: Copy + PartialEq, TKernel: Copy where
TGrid: Grid<TState>,
TState: Copy + PartialEq,
TKernel: Copy,
{ {
pub fn step(&mut self) { pub fn step(&mut self) {
self.field = self.field_iteration(); self.field = self.field_iteration();

View file

@ -12,7 +12,10 @@ use crossterm::terminal::{
use log::LevelFilter; use log::LevelFilter;
use rand::distributions::{Distribution, Standard}; use rand::distributions::{Distribution, Standard};
use rand::Rng; use rand::Rng;
use servicepoint2::{ByteGrid, CompressionCode, Connection, Grid, Origin, PixelGrid, TILE_HEIGHT, TILE_WIDTH}; use servicepoint2::{
ByteGrid, CompressionCode, Connection, FRAME_PACING, Grid, Origin, PixelGrid, TILE_HEIGHT,
TILE_WIDTH,
};
use servicepoint2::Command::{BitmapLinearWin, CharBrightness}; use servicepoint2::Command::{BitmapLinearWin, CharBrightness};
use crate::game::Game; use crate::game::Game;
@ -20,12 +23,12 @@ use crate::print::{println_debug, println_info, println_warning};
use crate::rules::{generate_bb3, generate_u8b3}; use crate::rules::{generate_bb3, generate_u8b3};
mod game; mod game;
mod rules;
mod print; mod print;
mod rules;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct Cli { struct Cli {
#[arg(short, long, default_value = "localhost:2342")] #[arg(short, long, default_value = "172.23.42.29:2342")]
destination: String, destination: String,
} }
@ -62,16 +65,18 @@ fn main() {
let mut iteration = Wrapping(0u8); let mut iteration = Wrapping(0u8);
let mut target_duration = FRAME_PACING;
loop { loop {
let start = Instant::now(); let start = Instant::now();
if iteration % Wrapping(5) == Wrapping(0) {
left_pixels.step(); left_pixels.step();
right_pixels.step(); right_pixels.step();
}
if iteration % Wrapping(10) == Wrapping(0) {
left_luma.step(); left_luma.step();
right_luma.step(); right_luma.step();
}
iteration += Wrapping(1u8); iteration += Wrapping(1u8);
@ -97,10 +102,21 @@ fn main() {
right_luma.rules = generate_u8b3(); right_luma.rules = generate_u8b3();
} }
split_pixel = i32::clamp(split_pixel as i32 + split_speed, 0, pixels.width() as i32) as usize; split_pixel =
i32::clamp(split_pixel as i32 + split_speed, 0, pixels.width() as i32) as usize;
draw_pixels(&mut pixels, &left_pixels.field, &right_pixels.field, split_pixel); draw_pixels(
draw_luma(&mut luma, &left_luma.field, &right_luma.field, split_pixel / 8); &mut pixels,
&left_pixels.field,
&right_pixels.field,
split_pixel,
);
draw_luma(
&mut luma,
&left_luma.field,
&right_luma.field,
split_pixel / 8,
);
send_to_screen(&connection, &pixels, &luma); send_to_screen(&connection, &pixels, &luma);
while event::poll(Duration::from_secs(0)).expect("could not poll") { while event::poll(Duration::from_secs(0)).expect("could not poll") {
@ -108,34 +124,47 @@ fn main() {
Err(_) => {} Err(_) => {}
Ok(AppEvent::RandomizeLeftPixels) => { Ok(AppEvent::RandomizeLeftPixels) => {
randomize(&mut left_pixels.field); randomize(&mut left_pixels.field);
println_debug("randomized left pixels");
} }
Ok(AppEvent::RandomizeRightPixels) => { Ok(AppEvent::RandomizeRightPixels) => {
randomize(&mut right_pixels.field); randomize(&mut right_pixels.field);
println_info("randomized right pixels");
} }
Ok(AppEvent::RandomizeLeftLuma) => { Ok(AppEvent::RandomizeLeftLuma) => {
randomize(&mut left_luma.field); randomize(&mut left_luma.field);
println_info("randomized left luma");
} }
Ok(AppEvent::RandomizeRightLuma) => { Ok(AppEvent::RandomizeRightLuma) => {
randomize(&mut right_luma.field); randomize(&mut right_luma.field);
println_info("randomized right luma");
} }
Ok(AppEvent::Accelerate) => { Ok(AppEvent::SeparatorAccelerate) => {
split_speed += 1; split_speed += 1;
println_info(format!("increased separator speed to {split_speed}"));
} }
Ok(AppEvent::Decelerate) => { Ok(AppEvent::SeparatorDecelerate) => {
split_speed -= 1; split_speed -= 1;
println_info(format!("decreased separator speed to {split_speed}"));
} }
Ok(AppEvent::Close) => { Ok(AppEvent::Close) => {
println_warning("terminating");
de_init(); de_init();
return; return;
} }
Ok(AppEvent::SimulationSpeedUp) => {
target_duration = target_duration.saturating_sub(Duration::from_millis(1));
println_info(format!("increased simulation speed to {} ups", 1f64 / target_duration.as_secs_f64()));
}
Ok(AppEvent::SimulationSpeedDown) => {
target_duration = target_duration.saturating_add(Duration::from_millis(1));
println_info(format!("decreased simulation speed to {} ups", 1f64 / target_duration.as_secs_f64()));
}
} }
} }
let wanted_time = Duration::from_millis(100);
let tick_time = start.elapsed(); let tick_time = start.elapsed();
if tick_time < wanted_time { if tick_time < target_duration {
thread::sleep(wanted_time - tick_time); thread::sleep(target_duration - tick_time);
} }
} }
} }
@ -146,8 +175,10 @@ enum AppEvent {
RandomizeRightPixels, RandomizeRightPixels,
RandomizeLeftLuma, RandomizeLeftLuma,
RandomizeRightLuma, RandomizeRightLuma,
Accelerate, SeparatorAccelerate,
Decelerate, SeparatorDecelerate,
SimulationSpeedUp,
SimulationSpeedDown,
} }
impl TryFrom<Event> for AppEvent { impl TryFrom<Event> for AppEvent {
@ -168,32 +199,15 @@ impl TryFrom<Event> for AppEvent {
println_info("[←] accelerate divider left"); println_info("[←] accelerate divider left");
Err(()) Err(())
} }
KeyCode::Char('q') => { KeyCode::Char('q') => Ok(AppEvent::Close),
println_warning("terminating"); KeyCode::Char('d') => Ok(AppEvent::RandomizeLeftPixels),
Ok(AppEvent::Close) KeyCode::Char('e') => Ok(AppEvent::RandomizeLeftLuma),
} KeyCode::Char('f') => Ok(AppEvent::RandomizeRightPixels),
KeyCode::Char('d') => { KeyCode::Char('r') => Ok(AppEvent::RandomizeRightLuma),
println_debug("randomizing left pixels"); KeyCode::Right => Ok(AppEvent::SeparatorAccelerate),
Ok(AppEvent::RandomizeLeftPixels) KeyCode::Left => Ok(AppEvent::SeparatorDecelerate),
} KeyCode::Up => Ok(AppEvent::SimulationSpeedUp),
KeyCode::Char('e') => { KeyCode::Down => Ok(AppEvent::SimulationSpeedDown),
println_info("randomizing left luma");
Ok(AppEvent::RandomizeLeftLuma)
}
KeyCode::Char('f') => {
println_info("randomizing right pixels");
Ok(AppEvent::RandomizeRightPixels)
}
KeyCode::Char('r') => {
println_info("randomizing right luma");
Ok(AppEvent::RandomizeRightLuma)
}
KeyCode::Right => {
Ok(AppEvent::Accelerate)
}
KeyCode::Left => {
Ok(AppEvent::Decelerate)
}
key_code => { key_code => {
println_debug(format!("unhandled KeyCode {key_code:?}")); println_debug(format!("unhandled KeyCode {key_code:?}"));
Err(()) Err(())
@ -223,14 +237,16 @@ fn draw_luma(luma: &mut ByteGrid, left: &ByteGrid, right: &ByteGrid, split_tile:
let left_or_right = if x < split_tile { left } else { right }; let left_or_right = if x < split_tile { left } else { right };
for y in 0..luma.height() { for y in 0..luma.height() {
let set = u8::max(48, left_or_right.get(x, y)); let set = u8::max(48, left_or_right.get(x, y));
luma.set(x, y, set);
let set = set as f32 / u8::MAX as f32 * 12f32;
luma.set(x, y, set as u8);
} }
} }
} }
fn send_to_screen(connection: &Connection, pixels: &PixelGrid, luma: &ByteGrid) { fn send_to_screen(connection: &Connection, pixels: &PixelGrid, luma: &ByteGrid) {
let pixel_cmd = let pixel_cmd = BitmapLinearWin(Origin(0, 0), pixels.clone(), CompressionCode::Uncompressed);
BitmapLinearWin(Origin(0, 0), pixels.clone(), CompressionCode::Uncompressed);
connection connection
.send(pixel_cmd.into()) .send(pixel_cmd.into())
.expect("could not send pixels"); .expect("could not send pixels");
@ -241,7 +257,9 @@ fn send_to_screen(connection: &Connection, pixels: &PixelGrid, luma: &ByteGrid)
} }
fn randomize<TGrid, TValue>(field: &mut TGrid) fn randomize<TGrid, TValue>(field: &mut TGrid)
where TGrid: Grid<TValue>, Standard: Distribution<TValue> where
TGrid: Grid<TValue>,
Standard: Distribution<TValue>,
{ {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
@ -260,16 +278,13 @@ fn init() -> Connection {
execute!(stdout(), EnterAlternateScreen, EnableLineWrap) execute!(stdout(), EnterAlternateScreen, EnableLineWrap)
.expect("could not enter alternate screen"); .expect("could not enter alternate screen");
enable_raw_mode() enable_raw_mode().expect("could not enable raw terminal mode");
.expect("could not enable raw terminal mode");
Connection::open(Cli::parse().destination) Connection::open(Cli::parse().destination)
.expect("Could not connect. Did you forget `--destination`?") .expect("Could not connect. Did you forget `--destination`?")
} }
fn de_init() { fn de_init() {
disable_raw_mode() disable_raw_mode().expect("could not disable raw terminal mode");
.expect("could not disable raw terminal mode"); execute!(stdout(), LeaveAlternateScreen).expect("could not leave alternate screen");
execute!(stdout(), LeaveAlternateScreen)
.expect("could not leave alternate screen");
} }

View file

@ -1,6 +1,6 @@
use std::io::stdout;
use crossterm::queue; use crossterm::queue;
use crossterm::style::{Print, PrintStyledContent, Stylize}; use crossterm::style::{Print, PrintStyledContent, Stylize};
use std::io::stdout;
pub fn println_info(text: impl Into<String>) { pub fn println_info(text: impl Into<String>) {
println_command(PrintStyledContent(text.into().white())) println_command(PrintStyledContent(text.into().white()))

View file

@ -1,37 +1,41 @@
use rand::{Rng, thread_rng};
use rand::rngs::ThreadRng; use rand::rngs::ThreadRng;
use rand::{thread_rng, Rng};
use crate::print::println_info; use crate::print::println_info;
const MAX_BRIGHTNESS: u8 = 12;
pub struct Rules<TState, TKernel, const KERNEL_SIZE: usize> pub struct Rules<TState, TKernel, const KERNEL_SIZE: usize>
where TState: Copy + PartialEq, TKernel: Copy where
TState: Copy + PartialEq,
TKernel: Copy,
{ {
pub kernel: [[TKernel; KERNEL_SIZE]; KERNEL_SIZE], pub kernel: [[TKernel; KERNEL_SIZE]; KERNEL_SIZE],
pub count_neighbor: Box<dyn Fn(TState, TKernel) -> i32>, pub count_neighbor: Box<dyn Fn(TState, TKernel) -> i32>,
pub next_state: Box<dyn Fn(TState, i32) -> TState>, pub next_state: Box<dyn Fn(TState, i32) -> TState>,
} }
pub const MOORE_NEIGHBORHOOD: [[bool; 3]; 3] = [ pub const MOORE_NEIGHBORHOOD: [[bool; 3]; 3] =
[true, true, true], [[true, true, true], [true, false, true], [true, true, true]];
[true, false, true],
[true, true, true]
];
pub const NEUMANN_NEIGHBORHOOD: [[bool; 3]; 3] = [ pub const NEUMANN_NEIGHBORHOOD: [[bool; 3]; 3] = [
[false, true, false], [false, true, false],
[true, false, true], [true, false, true],
[false, true, false] [false, true, false],
]; ];
pub const DIAGONALS_NEIGHBORHOOD: [[bool; 3]; 3] = [ pub const DIAGONALS_NEIGHBORHOOD: [[bool; 3]; 3] = [
[true, false, true], [true, false, true],
[false, false, false], [false, false, false],
[true, false, true] [true, false, true],
]; ];
pub fn count_true_neighbor(neighbor_state: bool, kernel_value: bool) -> i32 pub fn count_true_neighbor(neighbor_state: bool, kernel_value: bool) -> i32 {
{ if neighbor_state && kernel_value {
if neighbor_state && kernel_value { 1 } else { 0 } 1
} else {
0
}
} }
#[must_use] #[must_use]
@ -39,26 +43,31 @@ pub fn generate_bb3() -> Rules<bool, bool, 3> {
let mut rng = thread_rng(); let mut rng = thread_rng();
let is_moore = rng.gen_bool(1.0 / 2.0); let is_moore = rng.gen_bool(1.0 / 2.0);
let kernel = if is_moore { MOORE_NEIGHBORHOOD } else { NEUMANN_NEIGHBORHOOD }; let kernel = if is_moore {
MOORE_NEIGHBORHOOD
} else {
NEUMANN_NEIGHBORHOOD
};
let max_neighbors = if is_moore { 8 } else { 4 }; let max_neighbors = if is_moore { 8 } else { 4 };
let birth = generate_neighbor_counts(rng.gen_range(1..=max_neighbors), &mut rng, &[0]); let birth = generate_neighbor_counts(rng.gen_range(1..=max_neighbors), &mut rng, &[0]);
let survive = generate_neighbor_counts(rng.gen_range(1..=max_neighbors), &mut rng, &[]); let survive = generate_neighbor_counts(rng.gen_range(1..=max_neighbors), &mut rng, &[]);
println_info(format!("generated bb3: Birth {birth:?} Survival {survive:?}, kernel: {kernel:?}")); println_info(format!(
"generated bb3: Birth {birth:?} Survival {survive:?}, kernel: {kernel:?}"
));
Rules { Rules {
kernel, kernel,
count_neighbor: Box::new(count_true_neighbor), count_neighbor: Box::new(count_true_neighbor),
next_state: Box::new(move |old_state, neighbors| { next_state: Box::new(move |old_state, neighbors| {
old_state && survive.contains(&neighbors) old_state && survive.contains(&neighbors) || !old_state && birth.contains(&neighbors)
|| !old_state && birth.contains(&neighbors)
}), }),
} }
} }
fn generate_neighbor_counts(count: u8, rng: &mut ThreadRng, exclude: &[i32]) -> Vec<i32> { fn generate_neighbor_counts(count: u8, rng: &mut ThreadRng, exclude: &[i32]) -> Vec<i32> {
let mut result = vec!(); let mut result = vec![];
for _ in 0..count { for _ in 0..count {
let value = rng.gen_range(0..=count) as i32; let value = rng.gen_range(0..=count) as i32;
if !exclude.contains(&value) { if !exclude.contains(&value) {
@ -76,13 +85,17 @@ pub fn generate_u8b3() -> Rules<u8, bool, 3> {
0 => MOORE_NEIGHBORHOOD, 0 => MOORE_NEIGHBORHOOD,
1 => NEUMANN_NEIGHBORHOOD, 1 => NEUMANN_NEIGHBORHOOD,
2 => DIAGONALS_NEIGHBORHOOD, 2 => DIAGONALS_NEIGHBORHOOD,
_ => panic!() _ => panic!(),
}; };
let alive_threshold = rng.gen(); let alive_threshold = rng.gen();
let birth = generate_neighbor_counts(rng.gen_range(1..=9), &mut rng, &[0]); let birth = generate_neighbor_counts(rng.gen_range(1..=9), &mut rng, &[0]);
let survive = generate_neighbor_counts(rng.gen_range(1..=9 - birth.len()) as u8, &mut rng, &[]); let survive = generate_neighbor_counts(
rng.gen_range(1..=u8::max(1, 9 - birth.len() as u8)),
&mut rng,
&[],
);
let add = rng.gen_range(5..40); let add = rng.gen_range(5..40);
let sub = rng.gen_range(5..40); let sub = rng.gen_range(5..40);
@ -91,9 +104,7 @@ pub fn generate_u8b3() -> Rules<u8, bool, 3> {
Rules { Rules {
kernel, kernel,
count_neighbor: Box::new(|state, kernel| { count_neighbor: Box::new(|state, kernel| if kernel { state as i32 } else { 0 }),
if kernel { state as i32 } else { 0 }
}),
next_state: Box::new(move |old_state, neighbors| { next_state: Box::new(move |old_state, neighbors| {
let neighbors = neighbors / alive_threshold as i32; let neighbors = neighbors / alive_threshold as i32;
let old_is_alive = old_state >= alive_threshold; let old_is_alive = old_state >= alive_threshold;