confetty_tui/
simulation.rs1use 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, fps,
32 }
33 }
34
35 pub fn update(&mut self) {
36 let dt = 1.0 / self.fps;
37
38 self.velocity.y += self.gravity * dt;
40
41 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 !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 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 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}