From 816cc2c8c3e41012e622b7d25838717b7f27c332 Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Sun, 23 Jun 2024 09:41:37 +0200 Subject: [PATCH] add ability to speed up, slow down --- src/game.rs | 10 +++- src/main.rs | 127 ++++++++++++++++++++++++++++----------------------- src/print.rs | 4 +- src/rules.rs | 55 +++++++++++++--------- 4 files changed, 114 insertions(+), 82 deletions(-) diff --git a/src/game.rs b/src/game.rs index 0cd6f17..308440d 100644 --- a/src/game.rs +++ b/src/game.rs @@ -3,14 +3,20 @@ use servicepoint2::Grid; use crate::rules::Rules; pub(crate) struct Game - where TGrid: Grid, TState: Copy + PartialEq, TKernel: Copy +where + TGrid: Grid, + TState: Copy + PartialEq, + TKernel: Copy, { pub field: TGrid, pub rules: Rules, } impl Game - where TGrid: Grid, TState: Copy + PartialEq, TKernel: Copy +where + TGrid: Grid, + TState: Copy + PartialEq, + TKernel: Copy, { pub fn step(&mut self) { self.field = self.field_iteration(); diff --git a/src/main.rs b/src/main.rs index 1b651a3..02e8bda 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,10 @@ use crossterm::terminal::{ use log::LevelFilter; use rand::distributions::{Distribution, Standard}; 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 crate::game::Game; @@ -20,12 +23,12 @@ use crate::print::{println_debug, println_info, println_warning}; use crate::rules::{generate_bb3, generate_u8b3}; mod game; -mod rules; mod print; +mod rules; #[derive(Parser, Debug)] struct Cli { - #[arg(short, long, default_value = "localhost:2342")] + #[arg(short, long, default_value = "172.23.42.29:2342")] destination: String, } @@ -62,16 +65,18 @@ fn main() { let mut iteration = Wrapping(0u8); + let mut target_duration = FRAME_PACING; + loop { let start = Instant::now(); - if iteration % Wrapping(5) == Wrapping(0) { - left_pixels.step(); - right_pixels.step(); - } + left_pixels.step(); + right_pixels.step(); - left_luma.step(); - right_luma.step(); + if iteration % Wrapping(10) == Wrapping(0) { + left_luma.step(); + right_luma.step(); + } iteration += Wrapping(1u8); @@ -97,10 +102,21 @@ fn main() { 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_luma(&mut luma, &left_luma.field, &right_luma.field, split_pixel / 8); + draw_pixels( + &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); while event::poll(Duration::from_secs(0)).expect("could not poll") { @@ -108,34 +124,47 @@ fn main() { Err(_) => {} Ok(AppEvent::RandomizeLeftPixels) => { randomize(&mut left_pixels.field); + println_debug("randomized left pixels"); } Ok(AppEvent::RandomizeRightPixels) => { randomize(&mut right_pixels.field); + println_info("randomized right pixels"); } Ok(AppEvent::RandomizeLeftLuma) => { randomize(&mut left_luma.field); + println_info("randomized left luma"); } Ok(AppEvent::RandomizeRightLuma) => { randomize(&mut right_luma.field); + println_info("randomized right luma"); } - Ok(AppEvent::Accelerate) => { + Ok(AppEvent::SeparatorAccelerate) => { split_speed += 1; + println_info(format!("increased separator speed to {split_speed}")); } - Ok(AppEvent::Decelerate) => { + Ok(AppEvent::SeparatorDecelerate) => { split_speed -= 1; + println_info(format!("decreased separator speed to {split_speed}")); } Ok(AppEvent::Close) => { + println_warning("terminating"); de_init(); 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(); - if tick_time < wanted_time { - thread::sleep(wanted_time - tick_time); + if tick_time < target_duration { + thread::sleep(target_duration - tick_time); } } } @@ -146,8 +175,10 @@ enum AppEvent { RandomizeRightPixels, RandomizeLeftLuma, RandomizeRightLuma, - Accelerate, - Decelerate, + SeparatorAccelerate, + SeparatorDecelerate, + SimulationSpeedUp, + SimulationSpeedDown, } impl TryFrom for AppEvent { @@ -168,32 +199,15 @@ impl TryFrom for AppEvent { println_info("[←] accelerate divider left"); Err(()) } - KeyCode::Char('q') => { - println_warning("terminating"); - Ok(AppEvent::Close) - } - KeyCode::Char('d') => { - println_debug("randomizing left pixels"); - Ok(AppEvent::RandomizeLeftPixels) - } - KeyCode::Char('e') => { - 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) - } + KeyCode::Char('q') => Ok(AppEvent::Close), + KeyCode::Char('d') => Ok(AppEvent::RandomizeLeftPixels), + KeyCode::Char('e') => Ok(AppEvent::RandomizeLeftLuma), + KeyCode::Char('f') => Ok(AppEvent::RandomizeRightPixels), + KeyCode::Char('r') => Ok(AppEvent::RandomizeRightLuma), + KeyCode::Right => Ok(AppEvent::SeparatorAccelerate), + KeyCode::Left => Ok(AppEvent::SeparatorDecelerate), + KeyCode::Up => Ok(AppEvent::SimulationSpeedUp), + KeyCode::Down => Ok(AppEvent::SimulationSpeedDown), key_code => { println_debug(format!("unhandled KeyCode {key_code:?}")); 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 }; for y in 0..luma.height() { 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) { - let pixel_cmd = - BitmapLinearWin(Origin(0, 0), pixels.clone(), CompressionCode::Uncompressed); + let pixel_cmd = BitmapLinearWin(Origin(0, 0), pixels.clone(), CompressionCode::Uncompressed); connection .send(pixel_cmd.into()) .expect("could not send pixels"); @@ -241,7 +257,9 @@ fn send_to_screen(connection: &Connection, pixels: &PixelGrid, luma: &ByteGrid) } fn randomize(field: &mut TGrid) - where TGrid: Grid, Standard: Distribution + where + TGrid: Grid, + Standard: Distribution, { let mut rng = rand::thread_rng(); @@ -260,16 +278,13 @@ fn init() -> Connection { execute!(stdout(), EnterAlternateScreen, EnableLineWrap) .expect("could not enter alternate screen"); - enable_raw_mode() - .expect("could not enable raw terminal mode"); + enable_raw_mode().expect("could not enable raw terminal mode"); Connection::open(Cli::parse().destination) .expect("Could not connect. Did you forget `--destination`?") } fn de_init() { - disable_raw_mode() - .expect("could not disable raw terminal mode"); - execute!(stdout(), LeaveAlternateScreen) - .expect("could not leave alternate screen"); + disable_raw_mode().expect("could not disable raw terminal mode"); + execute!(stdout(), LeaveAlternateScreen).expect("could not leave alternate screen"); } diff --git a/src/print.rs b/src/print.rs index a09a0ce..87707b0 100644 --- a/src/print.rs +++ b/src/print.rs @@ -1,6 +1,6 @@ -use std::io::stdout; use crossterm::queue; use crossterm::style::{Print, PrintStyledContent, Stylize}; +use std::io::stdout; pub fn println_info(text: impl Into) { println_command(PrintStyledContent(text.into().white())) @@ -16,4 +16,4 @@ pub fn println_warning(text: impl Into) { pub fn println_command(command: impl crossterm::Command) { queue!(stdout(), command, Print("\r\n")).expect("could not print"); -} \ No newline at end of file +} diff --git a/src/rules.rs b/src/rules.rs index a54b280..6156a5d 100644 --- a/src/rules.rs +++ b/src/rules.rs @@ -1,37 +1,41 @@ -use rand::{Rng, thread_rng}; use rand::rngs::ThreadRng; +use rand::{thread_rng, Rng}; use crate::print::println_info; +const MAX_BRIGHTNESS: u8 = 12; + pub struct Rules - where TState: Copy + PartialEq, TKernel: Copy +where + TState: Copy + PartialEq, + TKernel: Copy, { pub kernel: [[TKernel; KERNEL_SIZE]; KERNEL_SIZE], pub count_neighbor: Box i32>, pub next_state: Box TState>, } -pub const MOORE_NEIGHBORHOOD: [[bool; 3]; 3] = [ - [true, true, true], - [true, false, true], - [true, true, true] -]; +pub const MOORE_NEIGHBORHOOD: [[bool; 3]; 3] = + [[true, true, true], [true, false, true], [true, true, true]]; pub const NEUMANN_NEIGHBORHOOD: [[bool; 3]; 3] = [ [false, true, false], [true, false, true], - [false, true, false] + [false, true, false], ]; pub const DIAGONALS_NEIGHBORHOOD: [[bool; 3]; 3] = [ [true, false, true], [false, false, false], - [true, false, true] + [true, false, true], ]; -pub fn count_true_neighbor(neighbor_state: bool, kernel_value: bool) -> i32 -{ - if neighbor_state && kernel_value { 1 } else { 0 } +pub fn count_true_neighbor(neighbor_state: bool, kernel_value: bool) -> i32 { + if neighbor_state && kernel_value { + 1 + } else { + 0 + } } #[must_use] @@ -39,26 +43,31 @@ pub fn generate_bb3() -> Rules { let mut rng = thread_rng(); 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 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, &[]); - println_info(format!("generated bb3: Birth {birth:?} Survival {survive:?}, kernel: {kernel:?}")); + println_info(format!( + "generated bb3: Birth {birth:?} Survival {survive:?}, kernel: {kernel:?}" + )); Rules { kernel, count_neighbor: Box::new(count_true_neighbor), next_state: Box::new(move |old_state, neighbors| { - old_state && survive.contains(&neighbors) - || !old_state && birth.contains(&neighbors) + old_state && survive.contains(&neighbors) || !old_state && birth.contains(&neighbors) }), } } fn generate_neighbor_counts(count: u8, rng: &mut ThreadRng, exclude: &[i32]) -> Vec { - let mut result = vec!(); + let mut result = vec![]; for _ in 0..count { let value = rng.gen_range(0..=count) as i32; if !exclude.contains(&value) { @@ -76,13 +85,17 @@ pub fn generate_u8b3() -> Rules { 0 => MOORE_NEIGHBORHOOD, 1 => NEUMANN_NEIGHBORHOOD, 2 => DIAGONALS_NEIGHBORHOOD, - _ => panic!() + _ => panic!(), }; let alive_threshold = rng.gen(); 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 sub = rng.gen_range(5..40); @@ -91,9 +104,7 @@ pub fn generate_u8b3() -> Rules { Rules { kernel, - count_neighbor: Box::new(|state, kernel| { - if kernel { state as i32 } else { 0 } - }), + count_neighbor: Box::new(|state, kernel| if kernel { state as i32 } else { 0 }), next_state: Box::new(move |old_state, neighbors| { let neighbors = neighbors / alive_threshold as i32; let old_is_alive = old_state >= alive_threshold;