summaryrefslogtreecommitdiff
path: root/src/screen.rs
diff options
context:
space:
mode:
authorRuben Beltran del Rio <git@r.bdr.sh>2025-08-25 12:36:37 +0200
committerRuben Beltran del Rio <git@r.bdr.sh>2025-08-25 12:36:37 +0200
commite5de70f2c1dcac767bd34a6b90ac1797a7acb433 (patch)
tree25a8c8f339fb5b496b460391ee06a9effeb64855 /src/screen.rs
parent6b909d95ec07848136a6f337db28318c1cd46c60 (diff)
parentd7bc21d19e168f3a99f54e5ba4866ed49f1187f1 (diff)
Merge branch 'rust'
Diffstat (limited to 'src/screen.rs')
-rw-r--r--src/screen.rs242
1 files changed, 242 insertions, 0 deletions
diff --git a/src/screen.rs b/src/screen.rs
new file mode 100644
index 0000000..2c701ea
--- /dev/null
+++ b/src/screen.rs
@@ -0,0 +1,242 @@
+use crate::renderer::Renderer;
+use rand::{Rng, rng};
+use std::fmt::Write;
+
+#[derive(Clone)]
+pub enum Screen {
+ Circle,
+ Checkerboard,
+ Gradients,
+ Mirrors,
+ Random,
+ Sprinkles,
+}
+
+impl Screen {
+ pub fn random() -> Self {
+ let mut rng = rng();
+ let screens = [
+ Self::Circle,
+ Self::Checkerboard,
+ Self::Gradients,
+ Self::Mirrors,
+ Self::Random,
+ Self::Sprinkles,
+ ];
+ let index = rng.random_range(0..screens.len());
+ screens.get(index).unwrap_or(&Self::Random).clone()
+ }
+
+ pub fn render(&self, modulation: u8, width: u16, height: u16, renderer: &Renderer) -> String {
+ match self {
+ Screen::Circle => render_circle(modulation, width, height, renderer),
+ Screen::Checkerboard => render_checkerboard(modulation, width, height, renderer),
+ Screen::Gradients => render_gradients(modulation, width, height, renderer),
+ Screen::Mirrors => render_mirrors(modulation, width, height, renderer),
+ Screen::Random => render_random(modulation, width, height, renderer),
+ Screen::Sprinkles => render_sprinkles(modulation, width, height, renderer),
+ }
+ }
+}
+
+fn render_circle(_modulation: u8, width: u16, height: u16, renderer: &Renderer) -> String {
+ let mut response = String::new();
+ let mut rng = rng();
+
+ let circles = if width > height { height } else { width };
+
+ for i in 0..circles {
+ let center_x = (width / 2) + 1;
+ let center_y = (height / 2) + 1;
+
+ let red = rng.random_range(0..=255);
+ let blue = rng.random_range(0..=255);
+ let green = rng.random_range(0..=255);
+
+ for j in 0..180 {
+ let angle = 2.0 * f64::from(j) * (std::f64::consts::PI / 180.0);
+
+ #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
+ let x = (f64::from(center_x) + angle.sin() * f64::from(i)).round() as u16;
+ #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
+ let y = (f64::from(center_y) + angle.cos() * f64::from(i)).round() as u16;
+
+ if x <= width && x > 0 && y <= height && y > 0 {
+ let position = format!("\x1B[{y};{x}H"); // Move cursor to y,x (CSI: y;x H)
+ let _ = write!(
+ response,
+ "{}{} ",
+ position,
+ renderer.render(red, green, blue)
+ );
+ }
+ }
+ }
+
+ response
+}
+
+fn render_checkerboard(modulation: u8, width: u16, height: u16, renderer: &Renderer) -> String {
+ let mut response = String::new();
+
+ // Calculate offset based on modulation
+ // Full cycle every 25 steps (5x5 grid)
+ let cycle_pos = modulation % 25;
+ let offset_x = u16::from(cycle_pos / 5);
+ let offset_y = u16::from(cycle_pos / 5);
+
+ // Use modulation / 25 as seed to get consistent colors during each cycle
+ let color_seed = u64::from(modulation / 25);
+
+ // Generate two colors based on the current cycle
+ let red1 = u8::try_from((color_seed * 17 + 42) % 256).unwrap_or(0);
+ let green1 = u8::try_from((color_seed * 31 + 73) % 256).unwrap_or(0);
+ let blue1 = u8::try_from((color_seed * 47 + 91) % 256).unwrap_or(0);
+
+ let red2 = u8::try_from((color_seed * 23 + 137) % 256).unwrap_or(0);
+ let green2 = u8::try_from((color_seed * 41 + 163) % 256).unwrap_or(0);
+ let blue2 = u8::try_from((color_seed * 37 + 197) % 256).unwrap_or(0);
+
+ for i in 0..height {
+ for j in 0..width {
+ // Calculate which checker square we're in, accounting for offset
+ let checker_x = (j + offset_x) / 5;
+ let checker_y = (i + offset_y) / 5;
+
+ // Checkerboard pattern: alternate colors
+ let is_first_color = (checker_x + checker_y) % 2 == 0;
+
+ if is_first_color {
+ response.push_str(&renderer.render(red1, green1, blue1));
+ } else {
+ response.push_str(&renderer.render(red2, green2, blue2));
+ }
+ response.push(' ');
+ }
+
+ if i < height - 1 {
+ response.push('\n');
+ }
+ }
+
+ response
+}
+
+fn render_gradients(modulation: u8, width: u16, height: u16, renderer: &Renderer) -> String {
+ let mut response = String::new();
+
+ for i in 0..height {
+ for j in 0..width {
+ let red =
+ ((u32::from(modulation) + u32::from(i)) * 255 / u32::from(height).max(1)) % 255;
+ let blue =
+ ((u32::from(modulation) + u32::from(j)) * 255 / u32::from(width).max(1)) % 255;
+ let green = ((u32::from(modulation) + u32::from(i) * u32::from(j)) * 255
+ / (u32::from(width) * u32::from(height)).max(1))
+ % 255;
+
+ response.push_str(&renderer.render(red as u8, green as u8, blue as u8));
+ response.push(' ');
+ }
+
+ if i < height - 1 {
+ response.push('\n');
+ }
+ }
+
+ response
+}
+
+fn render_mirrors(modulation: u8, width: u16, height: u16, renderer: &Renderer) -> String {
+ let mut rng = rng();
+ let scale = 2 + rng.random_range(0..5);
+ let scaled_height = height.saturating_div(scale).max(1);
+ let scaled_width = width.saturating_div(scale).max(1);
+
+ let mut response = vec![String::new(); height as usize];
+
+ for i in 0..scaled_height {
+ let mut row = Vec::new();
+ for j in 0..scaled_width {
+ let red =
+ ((u32::from(modulation) + u32::from(i)) * 255 / u32::from(height).max(1)) % 255;
+ let blue =
+ ((u32::from(modulation) + u32::from(j)) * 255 / u32::from(width).max(1)) % 255;
+ let green = ((u32::from(modulation) + u32::from(i) * u32::from(j)) * 255
+ / (u32::from(width) * u32::from(height)).max(1))
+ % 255;
+
+ let cell = format!("{} ", renderer.render(red as u8, green as u8, blue as u8));
+ row.push(cell);
+ }
+
+ let mut row_text = String::new();
+ for _ in 0..scale {
+ row.reverse();
+ row_text.push_str(&row.join(""));
+ }
+
+ for j in 1..scale {
+ let top_idx = (j.saturating_mul(i)) as usize;
+ let bottom_idx = height.saturating_sub(1).saturating_sub(j.saturating_mul(i)) as usize;
+ if let Some(line) = response.get_mut(top_idx) {
+ line.clone_from(&row_text);
+ }
+ if let Some(line) = response.get_mut(bottom_idx) {
+ line.clone_from(&row_text);
+ }
+ }
+ }
+
+ response.join("\n")
+}
+
+fn render_random(_modulation: u8, width: u16, height: u16, renderer: &Renderer) -> String {
+ let mut response = String::new();
+ let mut rng = rng();
+
+ for i in 0..height {
+ for _j in 0..width {
+ let red = rng.random_range(0..=255);
+ let blue = rng.random_range(0..=255);
+ let green = rng.random_range(0..=255);
+
+ response.push_str(&renderer.render(red, green, blue));
+ response.push(' ');
+ }
+
+ if i < height - 1 {
+ response.push('\n');
+ }
+ }
+
+ response
+}
+
+fn render_sprinkles(_modulation: u8, width: u16, height: u16, renderer: &Renderer) -> String {
+ let mut response = String::new();
+ let mut rng = rng();
+
+ let max_sprinkle_count = (width * height) / 2;
+ let min_sprinkle_count = (width * height) / 8;
+ let sprinkle_count = rng.random_range(min_sprinkle_count..=max_sprinkle_count);
+
+ let red = rng.random_range(0..=255);
+ let blue = rng.random_range(0..=255);
+ let green = rng.random_range(0..=255);
+
+ for _ in 0..sprinkle_count {
+ let x = rng.random_range(1..=width);
+ let y = rng.random_range(1..=height);
+
+ let position = format!("\x1B[{y};{x}H"); // Move cursor to y,x
+ let _ = write!(
+ response,
+ "{}{} ",
+ position,
+ renderer.render(red, green, blue)
+ );
+ }
+
+ response
+}