weathr 1.2.1

A terminal-based ASCII weather application with animated scenes driven by real-time weather data
Documentation
use crate::render::TerminalRenderer;
use crate::weather::types::FogIntensity;
use crossterm::style::Color;
use std::io;

struct FogParticle {
    x: f32,
    y: f32,
    speed_y: f32,
    speed_x: f32,
    drift_offset: f32,
    character: char,
    color: Color,
}

pub struct FogSystem {
    particles: Vec<FogParticle>,
    terminal_width: u16,
    terminal_height: u16,
    intensity: FogIntensity,
    drift_x: f32,
}

impl FogSystem {
    pub fn new(terminal_width: u16, terminal_height: u16, intensity: FogIntensity) -> Self {
        let mut system = Self {
            particles: Vec::new(),
            terminal_width,
            terminal_height,
            intensity,
            drift_x: 0.0,
        };
        let drift_dir = if rand::random::<bool>() { 0.05 } else { -0.05 };
        system.set_intensity_with_dir(intensity, drift_dir);
        system
    }

    pub fn set_intensity(&mut self, intensity: FogIntensity) {
        let current_dir = if self.drift_x >= 0.0 { 1.0 } else { -1.0 };
        self.set_intensity_with_dir(intensity, current_dir);
    }

    pub fn set_intensity_with_dir(&mut self, intensity: FogIntensity, direction_multiplier: f32) {
        self.intensity = intensity;
        let base_drift = match intensity {
            FogIntensity::Light => 0.02,
            FogIntensity::Medium => 0.05,
            FogIntensity::Heavy => 0.08,
        };
        self.drift_x = base_drift * direction_multiplier;
    }

    fn spawn_particle(&mut self) {
        let x = (rand::random::<u32>() % (self.terminal_width as u32 * 3)) as f32
            - (self.terminal_width as f32);

        let y = if rand::random::<bool>() {
            0.0
        } else {
            (rand::random::<u32>() % self.terminal_height as u32) as f32
        };

        let depth = rand::random::<u8>() % 3;

        let (base_speed_y, chars, color) = match self.intensity {
            FogIntensity::Light => (
                if depth == 0 { 0.02 } else { 0.01 },
                vec!['.', '·'],
                if depth == 0 {
                    Color::Grey
                } else {
                    Color::DarkGrey
                },
            ),
            FogIntensity::Medium => (
                if depth == 0 {
                    0.03
                } else if depth == 1 {
                    0.02
                } else {
                    0.01
                },
                vec!['.', '·', ':'],
                match depth {
                    0 => Color::White,
                    1 => Color::Grey,
                    _ => Color::DarkGrey,
                },
            ),
            FogIntensity::Heavy => (
                if depth == 0 {
                    0.04
                } else if depth == 1 {
                    0.03
                } else {
                    0.02
                },
                vec!['.', '·', ':', ''],
                match depth {
                    0 => Color::White,
                    1 => Color::Grey,
                    _ => Color::DarkGrey,
                },
            ),
        };

        let char_idx = (rand::random::<u32>() as usize) % chars.len();

        self.particles.push(FogParticle {
            x,
            y,
            speed_y: base_speed_y + (rand::random::<f32>() * 0.01),
            speed_x: self.drift_x + (rand::random::<f32>() * 0.03 - 0.015),
            drift_offset: rand::random::<f32>() * 100.0,
            character: chars[char_idx],
            color,
        });
    }

    pub fn update(&mut self, terminal_width: u16, terminal_height: u16) {
        self.terminal_width = terminal_width;
        self.terminal_height = terminal_height;

        let target_count = match self.intensity {
            FogIntensity::Light => (terminal_width as usize * terminal_height as usize) / 8,
            FogIntensity::Medium => (terminal_width as usize * terminal_height as usize) / 4,
            FogIntensity::Heavy => (terminal_width as usize * terminal_height as usize) / 2,
        };

        if self.particles.len() < target_count {
            let spawn_rate = match self.intensity {
                FogIntensity::Light => 2,
                FogIntensity::Medium => 4,
                FogIntensity::Heavy => 8,
            };
            for _ in 0..spawn_rate {
                self.spawn_particle();
            }
        }

        self.particles.retain_mut(|particle| {
            particle.y += particle.speed_y;

            let drift = (particle.y * 0.1 + particle.drift_offset).sin() * 0.03;
            particle.x += particle.speed_x + drift;

            if particle.y >= (terminal_height - 1) as f32 {
                particle.y = 0.0;
                particle.x = (rand::random::<u32>() % terminal_width as u32) as f32;
            }

            if particle.x < -20.0 || particle.x > (terminal_width as f32 + 20.0) {
                return false;
            }

            true
        });
    }

    pub fn render(&self, renderer: &mut TerminalRenderer) -> io::Result<()> {
        for particle in &self.particles {
            let x = particle.x as i16;
            let y = particle.y as i16;

            if x >= 0 && x < self.terminal_width as i16 && y >= 0 && y < self.terminal_height as i16
            {
                renderer.render_char(x as u16, y as u16, particle.character, particle.color)?;
            }
        }
        Ok(())
    }
}