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 }