summaryrefslogtreecommitdiff
path: root/src/screen.rs
blob: 2c701eaf1d5ffdbca90c22339d80411961c9d605 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
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
}