Skip to main content

confetty_tui/
simulation.rs

1use ratatui::style::Color;
2
3#[derive(Debug, Clone)]
4pub struct Point {
5    pub x: f64,
6    pub y: f64,
7    pub z: f64,
8}
9
10#[derive(Debug, Clone)]
11pub struct Vector {
12    pub x: f64,
13    pub y: f64,
14    pub z: f64,
15}
16
17#[derive(Debug, Clone)]
18pub struct Physics {
19    pub position: Point,
20    pub velocity: Vector,
21    pub gravity: f64,
22    pub fps: f64,
23}
24
25impl Physics {
26    pub fn new(position: Point, velocity: Vector, fps: f64) -> Self {
27        Self {
28            position,
29            velocity,
30            gravity: 50.0, // Terminal gravity equivalent
31            fps,
32        }
33    }
34
35    pub fn update(&mut self) {
36        let dt = 1.0 / self.fps;
37        
38        // Update velocity with gravity
39        self.velocity.y += self.gravity * dt;
40        
41        // Update position with velocity
42        self.position.x += self.velocity.x * dt;
43        self.position.y += self.velocity.y * dt;
44        self.position.z += self.velocity.z * dt;
45    }
46
47    pub fn position(&self) -> Point {
48        self.position.clone()
49    }
50
51    pub fn velocity(&self) -> Vector {
52        self.velocity.clone()
53    }
54}
55
56pub struct Particle {
57    pub char: String,
58    pub color: Color,
59    pub tail_char: String,
60    pub physics: Physics,
61    pub hidden: bool,
62    pub shooting: bool,
63    pub explosion_call: Option<fn(color: Color, x: f64, y: f64, width: i32, height: i32) -> Vec<Particle>>,
64}
65
66impl Particle {
67    pub fn new(char: String, color: Color, position: Point, velocity: Vector, fps: f64) -> Self {
68        Self {
69            char,
70            color,
71            tail_char: String::new(),
72            physics: Physics::new(position, velocity, fps),
73            hidden: false,
74            shooting: false,
75            explosion_call: None,
76        }
77    }
78}
79
80pub struct Frame {
81    pub width: i32,
82    pub height: i32,
83}
84
85impl Default for Frame {
86    fn default() -> Self {
87        Self { width: 0, height: 0 }
88    }
89}
90
91pub struct System {
92    pub frame: Frame,
93    pub particles: Vec<Particle>,
94}
95
96impl System {
97    pub fn new() -> Self {
98        Self {
99            frame: Frame::default(),
100            particles: Vec::new(),
101        }
102    }
103
104    pub fn update(&mut self) {
105        let mut new_particles = Vec::new();
106        let mut i = 0;
107        
108        while i < self.particles.len() {
109            let pos = self.particles[i].physics.position();
110
111            // If the shooting particle is slow enough then hide it and call the explosion function
112            if !self.particles[i].hidden && self.particles[i].shooting && self.particles[i].physics.velocity().y > -3.0 {
113                self.particles[i].hidden = true;
114                if let Some(explosion_fn) = self.particles[i].explosion_call {
115                    let color = self.particles[i].color;
116                    let explosion_particles = explosion_fn(color, pos.x, pos.y, self.frame.width, self.frame.height);
117                    new_particles.extend(explosion_particles);
118                }
119            }
120
121            // Remove particles that are hidden or out of bounds
122            if self.particles[i].hidden 
123                || pos.x > self.frame.width as f64 
124                || pos.x < 0.0 
125                || pos.y > self.frame.height as f64 {
126                self.particles.remove(i);
127            } else {
128                self.particles[i].physics.update();
129                i += 1;
130            }
131        }
132        
133        // Add new explosion particles
134        self.particles.extend(new_particles);
135    }
136
137    pub fn visible(&self, particle: &Particle) -> bool {
138        let pos = particle.physics.position();
139        let x = pos.x as i32;
140        let y = pos.y as i32;
141        !particle.hidden 
142            && y >= 0 
143            && y < self.frame.height - 1 
144            && x >= 0 
145            && x < self.frame.width - 1
146    }
147
148    pub fn render(&self) -> Vec<Vec<String>> {
149        let mut plane = vec![vec![String::new(); self.frame.width as usize]; self.frame.height as usize];
150        
151        for particle in &self.particles {
152            if self.visible(particle) {
153                let pos = particle.physics.position();
154                let y = pos.y as usize;
155                let x = pos.x as usize;
156                
157                if y < plane.len() && x < plane[y].len() {
158                    plane[y][x] = particle.char.clone();
159                    
160                    if particle.shooting {
161                        let tail_length = (-particle.physics.velocity().y) as i32;
162                        for i in 1..tail_length {
163                            let tail_y = (pos.y as i32 + i) as usize;
164                            if tail_y > 0 && tail_y < (self.frame.height - 1) as usize && tail_y < plane.len() {
165                                plane[tail_y][x] = particle.tail_char.clone();
166                            }
167                        }
168                    }
169                }
170            }
171        }
172        
173        plane
174    }
175}