also: - update dependencies - clippy fixes - metadata
This commit is contained in:
parent
1c9ac90b82
commit
4144db2d21
75
Cargo.lock
generated
75
Cargo.lock
generated
|
@ -2,15 +2,6 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aho-corasick"
|
|
||||||
version = "1.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.14"
|
version = "0.6.14"
|
||||||
|
@ -202,29 +193,6 @@ dependencies = [
|
||||||
"litrs",
|
"litrs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "env_filter"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"regex",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "env_logger"
|
|
||||||
version = "0.11.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
|
|
||||||
dependencies = [
|
|
||||||
"anstream",
|
|
||||||
"anstyle",
|
|
||||||
"env_filter",
|
|
||||||
"humantime",
|
|
||||||
"log",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.11"
|
version = "0.3.11"
|
||||||
|
@ -258,12 +226,6 @@ version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "humantime"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.0"
|
version = "1.70.0"
|
||||||
|
@ -304,12 +266,6 @@ version = "0.4.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.7.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
|
@ -426,35 +382,6 @@ dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex"
|
|
||||||
version = "1.10.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"memchr",
|
|
||||||
"regex-automata",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-automata"
|
|
||||||
version = "0.4.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"memchr",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.8.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-lzma"
|
name = "rust-lzma"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
@ -503,8 +430,6 @@ version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"env_logger",
|
|
||||||
"log",
|
|
||||||
"rand",
|
"rand",
|
||||||
"servicepoint",
|
"servicepoint",
|
||||||
]
|
]
|
||||||
|
|
|
@ -12,9 +12,7 @@ rust-version = "1.70.0"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
env_logger = "0.11"
|
|
||||||
crossterm = "0.29"
|
crossterm = "0.29"
|
||||||
log = "0.4"
|
|
||||||
|
|
||||||
[dependencies.servicepoint]
|
[dependencies.servicepoint]
|
||||||
package = "servicepoint"
|
package = "servicepoint"
|
||||||
|
|
153
src/app.rs
Normal file
153
src/app.rs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
use crate::{
|
||||||
|
print::{println_debug, println_info, println_warning},
|
||||||
|
simulation::{Simulation, SimulationEvent},
|
||||||
|
Cli,
|
||||||
|
};
|
||||||
|
use crossterm::{
|
||||||
|
event,
|
||||||
|
event::{Event, KeyCode, KeyEvent, KeyEventKind},
|
||||||
|
execute,
|
||||||
|
terminal::{
|
||||||
|
disable_raw_mode, enable_raw_mode, EnableLineWrap, EnterAlternateScreen,
|
||||||
|
LeaveAlternateScreen,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use servicepoint::{
|
||||||
|
Bitmap, BitmapCommand, BrightnessGrid, BrightnessGridCommand, SendCommandExt, UdpSocketExt,
|
||||||
|
FRAME_PACING, TILE_HEIGHT, TILE_WIDTH,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
io::stdout,
|
||||||
|
net::UdpSocket,
|
||||||
|
thread,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) struct App {
|
||||||
|
connection: UdpSocket,
|
||||||
|
sim: Simulation,
|
||||||
|
target_duration: Duration,
|
||||||
|
pixels: Bitmap,
|
||||||
|
luma: BrightnessGrid,
|
||||||
|
terminated: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn new(cli: Cli) -> Self {
|
||||||
|
let connection = UdpSocket::bind_connect(cli.destination)
|
||||||
|
.expect("Could not connect. Did you forget `--destination`?");
|
||||||
|
|
||||||
|
execute!(stdout(), EnterAlternateScreen, EnableLineWrap)
|
||||||
|
.expect("could not enter alternate screen");
|
||||||
|
enable_raw_mode().expect("could not enable raw terminal mode");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
connection,
|
||||||
|
sim: Simulation::new(),
|
||||||
|
terminated: false,
|
||||||
|
target_duration: FRAME_PACING,
|
||||||
|
pixels: Bitmap::max_sized(),
|
||||||
|
luma: BrightnessGrid::new(TILE_WIDTH, TILE_HEIGHT),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn run_iteration(&mut self) {
|
||||||
|
let start = Instant::now();
|
||||||
|
self.sim.run_iteration();
|
||||||
|
|
||||||
|
self.sim.draw_state(&mut self.pixels, &mut self.luma);
|
||||||
|
let cmd: BitmapCommand = self.pixels.clone().into();
|
||||||
|
self.connection.send_command(cmd).unwrap();
|
||||||
|
let cmd: BrightnessGridCommand = self.luma.clone().into();
|
||||||
|
self.connection.send_command(cmd).unwrap();
|
||||||
|
|
||||||
|
self.poll_events();
|
||||||
|
|
||||||
|
let tick_time = start.elapsed();
|
||||||
|
if tick_time < self.target_duration {
|
||||||
|
thread::sleep(self.target_duration - tick_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn terminated(&self) -> bool {
|
||||||
|
self.terminated
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_events(&mut self) -> bool {
|
||||||
|
while event::poll(Duration::from_secs(0)).expect("could not poll") {
|
||||||
|
let event = event::read().expect("could not read event");
|
||||||
|
|
||||||
|
if let Event::Key(KeyEvent {
|
||||||
|
kind: KeyEventKind::Press,
|
||||||
|
code,
|
||||||
|
..
|
||||||
|
}) = event
|
||||||
|
{
|
||||||
|
if let Some(sim_event) = self.handle_key(code) {
|
||||||
|
self.sim.handle_event(sim_event);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
println_debug(format!("unhandled event {event:?}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_key(&mut self, code: KeyCode) -> Option<SimulationEvent> {
|
||||||
|
match code {
|
||||||
|
KeyCode::Char('d') => Some(SimulationEvent::RandomizeLeftPixels),
|
||||||
|
KeyCode::Char('e') => Some(SimulationEvent::RandomizeLeftLuma),
|
||||||
|
KeyCode::Char('f') => Some(SimulationEvent::RandomizeRightPixels),
|
||||||
|
KeyCode::Char('r') => Some(SimulationEvent::RandomizeRightLuma),
|
||||||
|
KeyCode::Right => Some(SimulationEvent::SeparatorAccelerate),
|
||||||
|
KeyCode::Left => Some(SimulationEvent::SeparatorDecelerate),
|
||||||
|
KeyCode::Char('h') => {
|
||||||
|
println_info("[h] help");
|
||||||
|
println_info("[q] quit");
|
||||||
|
println_info("[d] randomize left pixels");
|
||||||
|
println_info("[e] randomize left luma");
|
||||||
|
println_info("[r] randomize right pixels");
|
||||||
|
println_info("[f] randomize right luma");
|
||||||
|
println_info("[→] accelerate divider right");
|
||||||
|
println_info("[←] accelerate divider left");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
KeyCode::Char('q') => {
|
||||||
|
println_warning("terminating");
|
||||||
|
self.terminated = true;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
KeyCode::Up => {
|
||||||
|
self.target_duration = self
|
||||||
|
.target_duration
|
||||||
|
.saturating_sub(Duration::from_millis(1));
|
||||||
|
println_info(format!(
|
||||||
|
"increased simulation speed to {} ups",
|
||||||
|
1f64 / self.target_duration.as_secs_f64()
|
||||||
|
));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
KeyCode::Down => {
|
||||||
|
self.target_duration = self
|
||||||
|
.target_duration
|
||||||
|
.saturating_add(Duration::from_millis(1));
|
||||||
|
println_info(format!(
|
||||||
|
"decreased simulation speed to {} ups",
|
||||||
|
1f64 / self.target_duration.as_secs_f64()
|
||||||
|
));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
key_code => {
|
||||||
|
println_debug(format!("unhandled KeyCode {key_code:?}"));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for App {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
disable_raw_mode().expect("could not disable raw terminal mode");
|
||||||
|
execute!(stdout(), LeaveAlternateScreen).expect("could not leave alternate screen");
|
||||||
|
}
|
||||||
|
}
|
295
src/main.rs
295
src/main.rs
|
@ -1,39 +1,11 @@
|
||||||
use std::{
|
use crate::app::App;
|
||||||
io::stdout,
|
|
||||||
net::UdpSocket,
|
|
||||||
num::Wrapping,
|
|
||||||
thread,
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
game::Game,
|
|
||||||
print::{println_debug, println_info, println_warning},
|
|
||||||
rules::{generate_bb3, generate_u8b3},
|
|
||||||
};
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use crossterm::{
|
|
||||||
event,
|
|
||||||
event::{Event, KeyCode, KeyEventKind},
|
|
||||||
execute,
|
|
||||||
terminal::{
|
|
||||||
disable_raw_mode, enable_raw_mode, EnableLineWrap, EnterAlternateScreen,
|
|
||||||
LeaveAlternateScreen,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use log::LevelFilter;
|
|
||||||
use rand::{
|
|
||||||
distributions::{Distribution, Standard},
|
|
||||||
Rng,
|
|
||||||
};
|
|
||||||
use servicepoint::{
|
|
||||||
Bitmap, BitmapCommand, Brightness, BrightnessGrid, BrightnessGridCommand, ByteGrid, Grid,
|
|
||||||
SendCommandExt, UdpSocketExt, ValueGrid, FRAME_PACING, PIXEL_HEIGHT, PIXEL_WIDTH, TILE_HEIGHT,
|
|
||||||
TILE_WIDTH,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
mod app;
|
||||||
mod game;
|
mod game;
|
||||||
mod print;
|
mod print;
|
||||||
mod rules;
|
mod rules;
|
||||||
|
mod simulation;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
|
@ -42,263 +14,8 @@ struct Cli {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let connection = init();
|
let mut app = App::new(Cli::parse());
|
||||||
|
while !app.terminated() {
|
||||||
let mut left_pixels = Game {
|
app.run_iteration();
|
||||||
rules: generate_bb3(),
|
|
||||||
field: ValueGrid::new(PIXEL_WIDTH, PIXEL_HEIGHT),
|
|
||||||
};
|
|
||||||
let mut right_pixels = Game {
|
|
||||||
rules: generate_bb3(),
|
|
||||||
field: ValueGrid::new(PIXEL_WIDTH, PIXEL_HEIGHT),
|
|
||||||
};
|
|
||||||
let mut left_luma = Game {
|
|
||||||
rules: generate_u8b3(),
|
|
||||||
field: ByteGrid::new(TILE_WIDTH, TILE_HEIGHT),
|
|
||||||
};
|
|
||||||
let mut right_luma = Game {
|
|
||||||
rules: generate_u8b3(),
|
|
||||||
field: ByteGrid::new(TILE_WIDTH, TILE_HEIGHT),
|
|
||||||
};
|
|
||||||
|
|
||||||
randomize(&mut left_luma.field);
|
|
||||||
randomize(&mut left_pixels.field);
|
|
||||||
randomize(&mut right_luma.field);
|
|
||||||
randomize(&mut right_pixels.field);
|
|
||||||
|
|
||||||
let mut pixels = Bitmap::max_sized();
|
|
||||||
let mut luma = BrightnessGrid::new(TILE_WIDTH, TILE_HEIGHT);
|
|
||||||
|
|
||||||
let mut split_pixel = 0;
|
|
||||||
let mut split_speed: i32 = 1;
|
|
||||||
|
|
||||||
let mut iteration = Wrapping(0u8);
|
|
||||||
|
|
||||||
let mut target_duration = FRAME_PACING;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let start = Instant::now();
|
|
||||||
|
|
||||||
left_pixels.step();
|
|
||||||
right_pixels.step();
|
|
||||||
|
|
||||||
if iteration % Wrapping(10) == Wrapping(0) {
|
|
||||||
left_luma.step();
|
|
||||||
right_luma.step();
|
|
||||||
}
|
|
||||||
|
|
||||||
iteration += Wrapping(1u8);
|
|
||||||
|
|
||||||
if split_speed > 0 && split_pixel == pixels.width() {
|
|
||||||
split_pixel = 0;
|
|
||||||
|
|
||||||
(left_luma, right_luma) = (right_luma, left_luma);
|
|
||||||
(left_pixels, right_pixels) = (right_pixels, left_pixels);
|
|
||||||
|
|
||||||
randomize(&mut left_pixels.field);
|
|
||||||
randomize(&mut left_luma.field);
|
|
||||||
left_pixels.rules = generate_bb3();
|
|
||||||
left_luma.rules = generate_u8b3();
|
|
||||||
} else if split_speed < 0 && split_pixel == 0 {
|
|
||||||
split_pixel = pixels.width();
|
|
||||||
|
|
||||||
(left_luma, right_luma) = (right_luma, left_luma);
|
|
||||||
(left_pixels, right_pixels) = (right_pixels, left_pixels);
|
|
||||||
|
|
||||||
randomize(&mut right_pixels.field);
|
|
||||||
randomize(&mut right_luma.field);
|
|
||||||
right_pixels.rules = generate_bb3();
|
|
||||||
right_luma.rules = generate_u8b3();
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
send_to_screen(&connection, &pixels, &luma);
|
|
||||||
|
|
||||||
while event::poll(Duration::from_secs(0)).expect("could not poll") {
|
|
||||||
match event::read().expect("could not read event").try_into() {
|
|
||||||
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::SeparatorAccelerate) => {
|
|
||||||
split_speed += 1;
|
|
||||||
println_info(format!("increased separator speed to {split_speed}"));
|
|
||||||
}
|
|
||||||
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 tick_time = start.elapsed();
|
|
||||||
if tick_time < target_duration {
|
|
||||||
thread::sleep(target_duration - tick_time);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AppEvent {
|
|
||||||
Close,
|
|
||||||
RandomizeLeftPixels,
|
|
||||||
RandomizeRightPixels,
|
|
||||||
RandomizeLeftLuma,
|
|
||||||
RandomizeRightLuma,
|
|
||||||
SeparatorAccelerate,
|
|
||||||
SeparatorDecelerate,
|
|
||||||
SimulationSpeedUp,
|
|
||||||
SimulationSpeedDown,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<Event> for AppEvent {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn try_from(event: Event) -> Result<Self, Self::Error> {
|
|
||||||
match event {
|
|
||||||
Event::Key(key_event) if key_event.kind == KeyEventKind::Press => {
|
|
||||||
match key_event.code {
|
|
||||||
KeyCode::Char('h') => {
|
|
||||||
println_info("[h] help");
|
|
||||||
println_info("[q] quit");
|
|
||||||
println_info("[d] randomize left pixels");
|
|
||||||
println_info("[e] randomize left luma");
|
|
||||||
println_info("[r] randomize right pixels");
|
|
||||||
println_info("[f] randomize right luma");
|
|
||||||
println_info("[→] accelerate divider right");
|
|
||||||
println_info("[←] accelerate divider left");
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
event => {
|
|
||||||
println_debug(format!("unhandled event {event:?}"));
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_pixels(
|
|
||||||
pixels: &mut Bitmap,
|
|
||||||
left: &ValueGrid<bool>,
|
|
||||||
right: &ValueGrid<bool>,
|
|
||||||
split_index: usize,
|
|
||||||
) {
|
|
||||||
for x in 0..pixels.width() {
|
|
||||||
let left_or_right = if x < split_index { left } else { right };
|
|
||||||
for y in 0..pixels.height() {
|
|
||||||
let set = x == split_index || left_or_right.get(x, y);
|
|
||||||
pixels.set(x, y, set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_luma(luma: &mut BrightnessGrid, left: &ByteGrid, right: &ByteGrid, split_tile: usize) {
|
|
||||||
for x in 0..luma.width() {
|
|
||||||
let left_or_right = if x < split_tile { left } else { right };
|
|
||||||
for y in 0..luma.height() {
|
|
||||||
let set: u8 = left_or_right.get(x, y) / u8::MAX * u8::from(Brightness::MAX);
|
|
||||||
let set = Brightness::try_from(set).unwrap();
|
|
||||||
luma.set(x, y, set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_to_screen(connection: &UdpSocket, pixels: &Bitmap, luma: &BrightnessGrid) {
|
|
||||||
let cmd: BitmapCommand = pixels.clone().into();
|
|
||||||
connection.send_command(cmd).unwrap();
|
|
||||||
let cmd: BrightnessGridCommand = luma.clone().into();
|
|
||||||
connection.send_command(cmd).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn randomize<TGrid, TValue>(field: &mut TGrid)
|
|
||||||
where
|
|
||||||
TGrid: Grid<TValue>,
|
|
||||||
Standard: Distribution<TValue>,
|
|
||||||
{
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
for y in 0..field.height() {
|
|
||||||
for x in 0..field.width() {
|
|
||||||
field.set(x, y, rng.gen());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init() -> UdpSocket {
|
|
||||||
env_logger::builder()
|
|
||||||
.filter_level(LevelFilter::Info)
|
|
||||||
.parse_default_env()
|
|
||||||
.init();
|
|
||||||
|
|
||||||
execute!(stdout(), EnterAlternateScreen, EnableLineWrap)
|
|
||||||
.expect("could not enter alternate screen");
|
|
||||||
enable_raw_mode().expect("could not enable raw terminal mode");
|
|
||||||
|
|
||||||
UdpSocket::bind_connect(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");
|
|
||||||
}
|
|
||||||
|
|
180
src/simulation.rs
Normal file
180
src/simulation.rs
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
use crate::{
|
||||||
|
game::Game,
|
||||||
|
print::{println_debug, println_info},
|
||||||
|
rules::{generate_bb3, generate_u8b3},
|
||||||
|
};
|
||||||
|
use rand::{distributions::Standard, prelude::Distribution, Rng};
|
||||||
|
use servicepoint::{
|
||||||
|
Bitmap, Brightness, BrightnessGrid, Grid, Value, ValueGrid, PIXEL_HEIGHT, PIXEL_WIDTH,
|
||||||
|
TILE_HEIGHT, TILE_SIZE, TILE_WIDTH,
|
||||||
|
};
|
||||||
|
use std::num::Wrapping;
|
||||||
|
|
||||||
|
pub(crate) struct Simulation {
|
||||||
|
pub(crate) left_pixels: Game<bool, bool, 3>,
|
||||||
|
pub(crate) right_pixels: Game<bool, bool, 3>,
|
||||||
|
pub(crate) left_luma: Game<u8, bool, 3>,
|
||||||
|
pub(crate) right_luma: Game<u8, bool, 3>,
|
||||||
|
split_pixel: usize,
|
||||||
|
pub(crate) split_speed: i32,
|
||||||
|
iteration: Wrapping<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SimulationEvent {
|
||||||
|
RandomizeLeftPixels,
|
||||||
|
RandomizeRightPixels,
|
||||||
|
RandomizeLeftLuma,
|
||||||
|
RandomizeRightLuma,
|
||||||
|
SeparatorAccelerate,
|
||||||
|
SeparatorDecelerate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Simulation {
|
||||||
|
pub fn new() -> Simulation {
|
||||||
|
let left_pixels = Game {
|
||||||
|
rules: generate_bb3(),
|
||||||
|
field: make_randomized(PIXEL_WIDTH, PIXEL_HEIGHT),
|
||||||
|
};
|
||||||
|
let right_pixels = Game {
|
||||||
|
rules: generate_bb3(),
|
||||||
|
field: make_randomized(PIXEL_WIDTH, PIXEL_HEIGHT),
|
||||||
|
};
|
||||||
|
let left_luma = Game {
|
||||||
|
rules: generate_u8b3(),
|
||||||
|
field: make_randomized(TILE_WIDTH, TILE_HEIGHT),
|
||||||
|
};
|
||||||
|
let right_luma = Game {
|
||||||
|
rules: generate_u8b3(),
|
||||||
|
field: make_randomized(TILE_WIDTH, TILE_HEIGHT),
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
left_pixels,
|
||||||
|
right_pixels,
|
||||||
|
left_luma,
|
||||||
|
right_luma,
|
||||||
|
split_pixel: 0,
|
||||||
|
split_speed: 1,
|
||||||
|
iteration: Wrapping(0u8),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn swap_left_right(&mut self) {
|
||||||
|
std::mem::swap(&mut self.left_luma, &mut self.right_luma);
|
||||||
|
std::mem::swap(&mut self.left_pixels, &mut self.right_pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn regenerate(pixels: &mut Game<bool, bool, 3>, luma: &mut Game<u8, bool, 3>) {
|
||||||
|
randomize(&mut pixels.field);
|
||||||
|
randomize(&mut luma.field);
|
||||||
|
pixels.rules = generate_bb3();
|
||||||
|
luma.rules = generate_u8b3();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn run_iteration(&mut self) {
|
||||||
|
self.left_pixels.step();
|
||||||
|
self.right_pixels.step();
|
||||||
|
|
||||||
|
if self.iteration % Wrapping(10) == Wrapping(0) {
|
||||||
|
self.left_luma.step();
|
||||||
|
self.right_luma.step();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.iteration += Wrapping(1u8);
|
||||||
|
|
||||||
|
if self.split_speed > 0 && self.split_pixel == self.left_pixels.field.width() {
|
||||||
|
self.split_pixel = 0;
|
||||||
|
self.swap_left_right();
|
||||||
|
Self::regenerate(&mut self.left_pixels, &mut self.left_luma);
|
||||||
|
} else if self.split_speed < 0 && self.split_pixel == 0 {
|
||||||
|
self.split_pixel = self.left_pixels.field.width();
|
||||||
|
self.swap_left_right();
|
||||||
|
Self::regenerate(&mut self.right_pixels, &mut self.right_luma);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.split_pixel = i32::clamp(
|
||||||
|
self.split_pixel as i32 + self.split_speed,
|
||||||
|
0,
|
||||||
|
self.left_pixels.field.width() as i32,
|
||||||
|
) as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn draw_state(&self, pixels: &mut Bitmap, luma: &mut BrightnessGrid) {
|
||||||
|
for x in 0..pixels.width() {
|
||||||
|
let left_or_right = if x < self.split_pixel {
|
||||||
|
&self.left_pixels.field
|
||||||
|
} else {
|
||||||
|
&self.right_pixels.field
|
||||||
|
};
|
||||||
|
for y in 0..pixels.height() {
|
||||||
|
let set = x == self.split_pixel || left_or_right.get(x, y);
|
||||||
|
pixels.set(x, y, set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let split_tile = self.split_pixel / TILE_SIZE;
|
||||||
|
for x in 0..luma.width() {
|
||||||
|
let left_or_right = if x < split_tile {
|
||||||
|
&self.left_luma.field
|
||||||
|
} else {
|
||||||
|
&self.right_luma.field
|
||||||
|
};
|
||||||
|
for y in 0..luma.height() {
|
||||||
|
let set: u8 = left_or_right.get(x, y) / u8::MAX * u8::from(Brightness::MAX);
|
||||||
|
let set = Brightness::try_from(set).unwrap();
|
||||||
|
luma.set(x, y, set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_event(&mut self, event: SimulationEvent) {
|
||||||
|
match event {
|
||||||
|
SimulationEvent::RandomizeLeftPixels => {
|
||||||
|
randomize(&mut self.left_pixels.field);
|
||||||
|
println_debug("randomized left pixels");
|
||||||
|
}
|
||||||
|
SimulationEvent::RandomizeRightPixels => {
|
||||||
|
randomize(&mut self.right_pixels.field);
|
||||||
|
println_info("randomized right pixels");
|
||||||
|
}
|
||||||
|
SimulationEvent::RandomizeLeftLuma => {
|
||||||
|
randomize(&mut self.left_luma.field);
|
||||||
|
println_info("randomized left luma");
|
||||||
|
}
|
||||||
|
SimulationEvent::RandomizeRightLuma => {
|
||||||
|
randomize(&mut self.right_luma.field);
|
||||||
|
println_info("randomized right luma");
|
||||||
|
}
|
||||||
|
SimulationEvent::SeparatorAccelerate => {
|
||||||
|
self.split_speed += 1;
|
||||||
|
println_info(format!("increased separator speed to {}", self.split_speed));
|
||||||
|
}
|
||||||
|
SimulationEvent::SeparatorDecelerate => {
|
||||||
|
self.split_speed -= 1;
|
||||||
|
println_info(format!("decreased separator speed to {}", self.split_speed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_randomized<T: Value>(width: usize, height: usize) -> ValueGrid<T>
|
||||||
|
where
|
||||||
|
Standard: Distribution<T>,
|
||||||
|
{
|
||||||
|
let mut pixels = ValueGrid::new(width, height);
|
||||||
|
randomize(&mut pixels);
|
||||||
|
pixels
|
||||||
|
}
|
||||||
|
|
||||||
|
fn randomize<T: Value>(field: &mut ValueGrid<T>)
|
||||||
|
where
|
||||||
|
Standard: Distribution<T>,
|
||||||
|
{
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
for y in 0..field.height() {
|
||||||
|
for x in 0..field.width() {
|
||||||
|
field.set(x, y, rng.gen());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue