use drawing::colors::{FireworkType, PixelColor};
use drawing::{Drawable, Point};
use rand::{rngs::StdRng, Rng};
use std::{cmp, collections::HashSet, default::Default, vec::IntoIter};
use types::Drawables;
pub mod drawing;
pub mod types;
#[cfg(test)]
mod tests;
#[derive(PartialEq, Copy, Clone, Debug)]
pub enum FireworkState {
Flying, Exploding, Falling, Fading, Gone, }
impl FireworkState {
pub fn ordinal(&self) -> u8 {
match self {
FireworkState::Flying => 0,
FireworkState::Exploding => 1,
FireworkState::Falling => 2,
FireworkState::Fading => 3,
FireworkState::Gone => 25,
}
}
}
pub trait Memoried {
fn remember(&mut self);
}
#[derive(Debug)]
pub struct Firework {
total_state: IntoIter<FireworkState>,
center: Center,
tail_points: TailPoints,
exploding_points: ExplodingPoints,
falling_points: FallingPoints,
extinguished: bool,
firework_type: FireworkType,
}
impl Firework {
pub fn new(mut rng: StdRng, term_height: u16) -> Self {
let firework_type = drawing::colors::pick_firework_type(&mut rng);
let center = Center::new(&mut rng);
let center_point = center.position;
Firework {
total_state: Firework::build_total_state(&mut rng, term_height).into_iter(),
center,
tail_points: TailPoints::new(center_point),
exploding_points: Default::default(),
falling_points: Default::default(),
extinguished: false,
firework_type,
}
}
pub fn advance(&mut self) -> FireworkState {
let state = self.total_state.next();
if let Some(s) = state {
match s {
FireworkState::Flying => self.advance_flying(),
FireworkState::Exploding => self.advance_exploding(),
FireworkState::Falling => self.advance_falling(),
FireworkState::Fading => self.advance_fading(),
FireworkState::Gone => self.advance_gone(),
}
return s;
};
FireworkState::Gone
}
pub fn drawables(&self) -> Drawables {
vec![
Box::new(self.tail_points.clone()),
Box::new(self.exploding_points.clone()),
Box::new(self.falling_points.clone()),
]
}
pub fn extinguished(&self) -> bool {
self.extinguished
}
fn build_total_state(rng: &mut StdRng, term_height: u16) -> Vec<FireworkState> {
let mut total_state = vec![];
for state in vec![
FireworkState::Flying,
FireworkState::Exploding,
FireworkState::Falling,
FireworkState::Fading,
FireworkState::Gone,
]
.into_iter()
{
let mut advance = true;
let mut threshold = 0;
while advance {
if state == FireworkState::Gone {
total_state.push(state);
break;
}
if rng.gen_range(1, Firework::advance_threshold(state)) > threshold {
total_state.push(state);
threshold += match term_height {
0..=24 => 11,
25..=32 => 7,
_ => 5,
};
} else {
advance = false;
}
}
}
total_state
}
fn advance_flying(&mut self) {
self.tail_points.remember();
self.center.update_center(0, 1);
self.tail_points.grow_tail(
&self.center.position,
self.firework_type.derive_color(FireworkState::Flying),
);
}
fn advance_exploding(&mut self) {
self.tail_points.remember();
self.exploding_points.remember();
self.tail_points.shrink_tail();
self.exploding_points.explode(
&self.center.position,
self.firework_type.derive_color(FireworkState::Exploding),
);
}
fn advance_falling(&mut self) {
self.tail_points.remember();
self.exploding_points.remember();
self.falling_points.remember();
self.tail_points.erase();
self.exploding_points.collapse();
self.falling_points.fall(
&self.exploding_points.diagonal_tips,
&self.exploding_points.perpendicular_tips,
self.firework_type.derive_color(FireworkState::Falling),
);
}
fn advance_fading(&mut self) {
self.exploding_points.remember();
self.falling_points.remember();
self.exploding_points.erase();
self.falling_points.fade();
}
fn advance_gone(&mut self) {
self.tail_points.remember();
self.exploding_points.remember();
self.falling_points.remember();
self.tail_points.erase();
self.exploding_points.erase();
self.falling_points.erase();
self.extinguished = true;
}
fn advance_threshold(state: FireworkState) -> u64 {
match state {
FireworkState::Flying => 1000,
FireworkState::Exploding => 500,
FireworkState::Falling => 200,
FireworkState::Fading => 200,
FireworkState::Gone => 1,
}
}
}
#[derive(Debug)]
struct Center {
position: Point,
}
impl Center {
fn new(rng: &mut StdRng) -> Self {
let x = rng.gen_range(0, u16::MAX);
Center {
position: Point {
x,
y: 0,
pixel_color: Default::default(),
},
}
}
fn update_center(&mut self, x_move: i16, y_move: i16) {
self.position = self.position.move_point(x_move, y_move)
}
}
#[derive(Clone, Debug)]
struct TailPoints {
tail: Vec<Point>,
old_tail: Vec<Point>,
}
impl TailPoints {
fn new(center: Point) -> TailPoints {
TailPoints {
tail: vec![center],
old_tail: Default::default(),
}
}
fn grow_tail(&mut self, center: &Point, pixel_color: PixelColor) {
let tail_size = self.tail.len();
self.tail.clear();
for i in 0..tail_size + 1 {
self.tail.push(
center
.move_point(0, -(i as i16))
.with_pixel_color(pixel_color),
)
}
}
fn shrink_tail(&mut self) {
if self.tail.len() != 1 {
self.tail.pop();
}
}
fn erase(&mut self) {
self.tail.clear();
}
}
impl Drawable for TailPoints {
fn draw(&self) -> Vec<Point> {
difference(&self.tail, &self.old_tail)
}
fn clear(&self) -> Vec<Point> {
difference(&self.old_tail, &self.tail)
}
}
impl Memoried for TailPoints {
fn remember(&mut self) {
self.old_tail = self.tail.clone();
}
}
#[derive(Clone, Default, Debug)]
struct ExplodingPoints {
explosion_iter: u8,
perpendicular_tips: Vec<Point>,
diagonal_tips: Vec<Point>,
explosion: Vec<Point>,
old_explosion: Vec<Point>,
}
impl ExplodingPoints {
fn explode(&mut self, center: &Point, pixel_color: PixelColor) {
self.explosion_iter += 1;
self.perpendicular_tips.clear();
self.explode_perpendicular(center, pixel_color);
self.maybe_explode_diagonal(center, pixel_color);
}
fn collapse(&mut self) {
self
.explosion
.drain(..(calc_drain_amt(self.explosion_iter, self.explosion.len())));
self.explosion_iter = self.explosion_iter.saturating_sub(1);
}
fn erase(&mut self) {
self.explosion.clear();
}
fn explode_perpendicular(&mut self, center: &Point, pixel_color: PixelColor) {
let up = center
.move_point(0, self.explosion_iter.into())
.with_pixel_color(pixel_color);
let down = center
.move_point(0, -(self.explosion_iter as i16))
.with_pixel_color(pixel_color);
let left = center
.move_point(self.explosion_iter as i16, 0)
.with_pixel_color(pixel_color);
let right = center
.move_point(-(self.explosion_iter as i16), 0)
.with_pixel_color(pixel_color);
self.perpendicular_tips.push(up);
self.perpendicular_tips.push(down);
self.perpendicular_tips.push(left);
self.perpendicular_tips.push(right);
self.explosion.push(up);
self.explosion.push(down);
self.explosion.push(left);
self.explosion.push(right);
}
fn maybe_explode_diagonal(&mut self, center: &Point, pixel_color: PixelColor) {
if self.explosion_iter % 2 == 0 {
self.diagonal_tips.clear();
let move_val = self.explosion_iter / 2;
let up_left = center
.move_point(-(move_val as i16), move_val.into())
.with_pixel_color(pixel_color);
let up_right = center
.move_point(move_val.into(), move_val.into())
.with_pixel_color(pixel_color);
let down_left = center
.move_point(-(move_val as i16), -(move_val as i16))
.with_pixel_color(pixel_color);
let down_right = center
.move_point(move_val.into(), -(move_val as i16))
.with_pixel_color(pixel_color);
self.diagonal_tips.push(up_left);
self.diagonal_tips.push(up_right);
self.diagonal_tips.push(down_left);
self.diagonal_tips.push(down_right);
self.explosion.push(up_left);
self.explosion.push(up_right);
self.explosion.push(down_left);
self.explosion.push(down_right);
}
}
}
impl Drawable for ExplodingPoints {
fn draw(&self) -> Vec<Point> {
difference(&self.explosion, &self.old_explosion)
}
fn clear(&self) -> Vec<Point> {
difference(&self.old_explosion, &self.explosion)
}
}
impl Memoried for ExplodingPoints {
fn remember(&mut self) {
self.old_explosion = self.explosion.clone();
}
}
#[derive(Debug, Default, Clone)]
struct FallingPoints {
falling_iter: u8,
falling: Vec<Point>,
ready_for_fall: bool,
old_falling: Vec<Point>,
falling_tips: Vec<Point>,
}
impl FallingPoints {
fn fall(
&mut self,
perpendicular_tips: &[Point],
diagonal_tips: &[Point],
pixel_color: PixelColor,
) {
self.old_falling = self.falling.clone();
if self.falling.is_empty() {
self.falling_tips.extend(
perpendicular_tips
.iter()
.map(|tip| tip.with_pixel_color(pixel_color)),
);
self.falling_tips.extend(
diagonal_tips
.iter()
.map(|tip| tip.with_pixel_color(pixel_color)),
);
}
self.fall_internal(perpendicular_tips, pixel_color);
self.fall_internal(diagonal_tips, pixel_color);
}
fn fall_internal(&mut self, falling_tips: &[Point], pixel_color: PixelColor) {
self.falling_iter += 1;
falling_tips.iter().for_each(|tip| {
self.falling.push(
tip
.move_point(0, -(self.falling_iter as i16))
.with_pixel_color(pixel_color),
)
});
}
fn fade(&mut self) {
if !self.ready_for_fall {
self.falling_iter = self.falling_iter.saturating_add(1);
self.ready_for_fall = true;
}
self
.falling
.drain(..calc_drain_amt(self.falling_iter, self.falling.len()));
self.falling_iter = self.falling_iter.saturating_sub(1);
}
fn erase(&mut self) {
self.falling.clear();
}
}
impl Drawable for FallingPoints {
fn draw(&self) -> Vec<Point> {
difference(&self.falling, &self.old_falling)
}
fn clear(&self) -> Vec<Point> {
difference(&self.old_falling, &self.falling)
}
}
impl Memoried for FallingPoints {
fn remember(&mut self) {
self.old_falling = self.falling.clone();
}
}
fn calc_drain_amt(iteration: u8, drainable_len: usize) -> usize {
cmp::min(
match iteration {
0..=3 => 0,
_ => {
if iteration % 2 == 0 {
8
} else {
4
}
}
},
drainable_len,
)
}
fn difference(points_1: &[Point], points_2: &[Point]) -> Vec<Point> {
let set_1 = points_1.iter().cloned().collect::<HashSet<Point>>();
let set_2 = points_2.iter().cloned().collect::<HashSet<Point>>();
set_1.difference(&set_2).cloned().collect()
}