use ratatui::style::Color;
#[derive(Debug, Clone)]
pub struct Point {
pub x: f64,
pub y: f64,
pub z: f64,
}
#[derive(Debug, Clone)]
pub struct Vector {
pub x: f64,
pub y: f64,
pub z: f64,
}
#[derive(Debug, Clone)]
pub struct Physics {
pub position: Point,
pub velocity: Vector,
pub gravity: f64,
pub fps: f64,
}
impl Physics {
pub fn new(position: Point, velocity: Vector, fps: f64) -> Self {
Self {
position,
velocity,
gravity: 50.0, fps,
}
}
pub fn update(&mut self) {
let dt = 1.0 / self.fps;
self.velocity.y += self.gravity * dt;
self.position.x += self.velocity.x * dt;
self.position.y += self.velocity.y * dt;
self.position.z += self.velocity.z * dt;
}
pub fn position(&self) -> Point {
self.position.clone()
}
pub fn velocity(&self) -> Vector {
self.velocity.clone()
}
}
pub struct Particle {
pub char: String,
pub color: Color,
pub tail_char: String,
pub physics: Physics,
pub hidden: bool,
pub shooting: bool,
pub explosion_call: Option<fn(color: Color, x: f64, y: f64, width: i32, height: i32) -> Vec<Particle>>,
}
impl Particle {
pub fn new(char: String, color: Color, position: Point, velocity: Vector, fps: f64) -> Self {
Self {
char,
color,
tail_char: String::new(),
physics: Physics::new(position, velocity, fps),
hidden: false,
shooting: false,
explosion_call: None,
}
}
}
pub struct Frame {
pub width: i32,
pub height: i32,
}
impl Default for Frame {
fn default() -> Self {
Self { width: 0, height: 0 }
}
}
pub struct System {
pub frame: Frame,
pub particles: Vec<Particle>,
}
impl System {
pub fn new() -> Self {
Self {
frame: Frame::default(),
particles: Vec::new(),
}
}
pub fn update(&mut self) {
let mut new_particles = Vec::new();
let mut i = 0;
while i < self.particles.len() {
let pos = self.particles[i].physics.position();
if !self.particles[i].hidden && self.particles[i].shooting && self.particles[i].physics.velocity().y > -3.0 {
self.particles[i].hidden = true;
if let Some(explosion_fn) = self.particles[i].explosion_call {
let color = self.particles[i].color;
let explosion_particles = explosion_fn(color, pos.x, pos.y, self.frame.width, self.frame.height);
new_particles.extend(explosion_particles);
}
}
if self.particles[i].hidden
|| pos.x > self.frame.width as f64
|| pos.x < 0.0
|| pos.y > self.frame.height as f64 {
self.particles.remove(i);
} else {
self.particles[i].physics.update();
i += 1;
}
}
self.particles.extend(new_particles);
}
pub fn visible(&self, particle: &Particle) -> bool {
let pos = particle.physics.position();
let x = pos.x as i32;
let y = pos.y as i32;
!particle.hidden
&& y >= 0
&& y < self.frame.height - 1
&& x >= 0
&& x < self.frame.width - 1
}
pub fn render(&self) -> Vec<Vec<String>> {
let mut plane = vec![vec![String::new(); self.frame.width as usize]; self.frame.height as usize];
for particle in &self.particles {
if self.visible(particle) {
let pos = particle.physics.position();
let y = pos.y as usize;
let x = pos.x as usize;
if y < plane.len() && x < plane[y].len() {
plane[y][x] = particle.char.clone();
if particle.shooting {
let tail_length = (-particle.physics.velocity().y) as i32;
for i in 1..tail_length {
let tail_y = (pos.y as i32 + i) as usize;
if tail_y > 0 && tail_y < (self.frame.height - 1) as usize && tail_y < plane.len() {
plane[tail_y][x] = particle.tail_char.clone();
}
}
}
}
}
}
plane
}
}