use std::time::{Duration, SystemTime};
use rusty_bubbletea::model::Model as ModelTrait;
use rusty_bubbletea::screen::WindowSizeMsg;
use rusty_bubbletea::{quit, tick, Cmd, KeyPressMsg, Msg, Program, View};
use rusty_lipgloss::{new_style, Color};
const FIRE_PALETTE: [i64; 26] = [
0, 233, 234, 52, 53, 88, 89, 94, 95, 96, 130, 131, 132, 133, 172, 214, 215, 220, 220, 221, 3,
226, 227, 230, 231, 7,
];
struct Rng(u64);
impl Rng {
fn seeded() -> Rng {
let seed = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0x9E37_79B9_7F4A_7C15);
Rng(seed)
}
fn next(&mut self) -> u64 {
let mut x = self.0;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
self.0 = x;
x.wrapping_mul(0x2545_F491_4F6C_DD1D)
}
fn intn(&mut self, n: u64) -> u64 {
self.next() % n
}
}
#[derive(Debug)]
struct TickMsg;
fn tick_cmd() -> Cmd {
tick(Duration::from_millis(50), |_| Some(Box::new(TickMsg)))
}
struct Model {
screen_buf: Vec<i32>,
width: usize,
height: usize,
fire_palette: Vec<i64>,
start_time: SystemTime,
rng: Rng,
}
impl ModelTrait for Model {
fn init(&self) -> Cmd {
tick_cmd()
}
fn update(&mut self, msg: &dyn Msg) -> Cmd {
if let Some(k) = msg.as_any().downcast_ref::<KeyPressMsg>() {
let s = k.0.to_string();
if s == "q" || s == "ctrl+c" {
return quit();
}
}
if msg.as_any().is::<TickMsg>() {
self.spread_fire();
return tick_cmd();
}
if let Some(ws) = msg.as_any().downcast_ref::<WindowSizeMsg>() {
self.width = ws.width;
self.height = ws.height * 2; self.screen_buf = vec![0; self.width * self.height];
for i in 0..self.width {
self.screen_buf[(self.height - 1) * self.width + i] =
(self.fire_palette.len() - 1) as i32;
}
}
None
}
fn view(&self) -> View {
if self.width == 0 {
return View::new("Initializing...");
}
let mut s = String::new();
let mut y = 0;
while y < self.height - 2 {
for x in 0..self.width {
let pixel_hi = self.screen_buf[y * self.width + x];
let pixel_lo = self.screen_buf[(y + 1) * self.width + x];
let hi_color = self.fire_palette[pixel_hi as usize] as u8;
let lo_color = self.fire_palette[pixel_lo as usize] as u8;
s += &new_style()
.foreground_color(Color::Ansi256(hi_color))
.background_color(Color::Ansi256(lo_color))
.render("▀");
}
if y < self.height - 2 {
s.push('\n');
}
y += 2;
}
let elapsed = SystemTime::now()
.duration_since(self.start_time)
.unwrap_or_default();
s += &new_style().foreground("7").render(&format!(
"Press q or ctrl+c to quit. Elapsed: {:?}",
Duration::from_secs(elapsed.as_secs())
));
let mut v = View::new(&s);
v.alt_screen = true;
v
}
}
impl Model {
fn spread_fire(&mut self) {
for x in 0..self.width {
for y in 0..self.height {
self.spread_pixel((y * self.width + x) as i64);
}
}
}
fn spread_pixel(&mut self, idx: i64) {
if idx < self.width as i64 {
return;
}
let pixel = self.screen_buf[idx as usize];
if pixel == 0 {
self.screen_buf[(idx - self.width as i64) as usize] = 0;
return;
}
let rnd = self.rng.intn(3) as i64;
let dst = idx - rnd + 1;
if dst - (self.width as i64) >= 0
&& dst - (self.width as i64) < self.screen_buf.len() as i64
{
let decay = rnd & 1;
let new_value = (pixel - decay as i32).max(0);
self.screen_buf[(dst - self.width as i64) as usize] = new_value;
}
}
}
fn initial_model() -> Model {
Model {
screen_buf: Vec::new(),
width: 0,
height: 0,
fire_palette: FIRE_PALETTE.to_vec(),
start_time: SystemTime::now(),
rng: Rng::seeded(),
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let p = Program::new(initial_model());
p.run()?;
Ok(())
}