use super::Animation;
use crate::render::{Canvas, RenderMode};
use rand::RngExt;
type PRow = &'static [(i32, char, bool)];
const V0: &[PRow] = &[
&[(0, '~', false)],
&[(0, '|', false)],
&[(0, 'Y', false)],
&[(0, 'Y', false)],
&[(-1, '(', true), (0, '@', true), (1, ')', true)],
];
const V1: &[PRow] = &[
&[(-1, ',', false), (0, ',', false)],
&[(0, 'J', false)],
&[(0, '|', false)],
&[(0, '*', true)],
&[(-1, 'o', true), (0, '*', true), (1, 'o', true)],
];
const V2: &[PRow] = &[
&[(0, ',', false)],
&[(0, 'b', false)],
&[(0, '|', false)],
&[(-1, '(', true), (1, ')', true)],
&[(-1, '{', true), (0, 'a', true), (1, '}', true)],
];
const V3: &[PRow] = &[
&[(-1, '~', false), (0, '~', false)],
&[(0, '|', false)],
&[(-1, '\\', false), (0, 'Y', false), (1, '/', false)],
&[
(-2, 'W', true),
(-1, 'W', true),
(0, 'W', true),
(1, 'W', true),
(2, 'W', true),
],
&[
(-3, 'W', true),
(-2, 'W', true),
(-1, 'W', true),
(0, 'W', true),
(1, 'W', true),
(2, 'W', true),
(3, 'W', true),
],
];
const V4: &[PRow] = &[
&[(0, 'Y', false)],
&[(0, '|', false)],
&[(0, '|', false)],
&[(-1, '(', true), (0, '|', false), (1, ')', true)],
&[(-1, '*', true), (0, '@', true), (1, '*', true)],
];
const V5: &[PRow] = &[
&[(-1, ',', false), (0, ',', false)],
&[(0, '|', false)],
&[(-1, '|', false), (0, '|', false), (1, '|', false)],
&[(0, '|', false)],
&[(-1, '(', true), (0, 'o', true), (1, ')', true)],
];
const VARIETIES: &[&[PRow]] = &[V0, V1, V2, V3, V4, V5];
const FLOWER_COLORS: [(u8, u8, u8); 6] = [
(255, 100, 30), (100, 180, 255), (220, 80, 200), (80, 220, 80), (255, 220, 0), (180, 100, 255), ];
const STEM_COLOR: (u8, u8, u8) = (60, 180, 60);
const GROUND_COLOR: (u8, u8, u8) = (120, 80, 40);
const SUN_COLOR: (u8, u8, u8) = (255, 220, 50);
const CLOUD_COLOR: (u8, u8, u8) = (200, 200, 220);
const RAIN_COLOR: (u8, u8, u8) = (150, 200, 255);
const SKY_DIM: (u8, u8, u8) = (15, 25, 50);
static ROSE_STEM: &[(i32, char, bool)] = &[(0, '|', false)];
static ROSE_STEM_LEAF: &[(i32, char, bool)] = &[(0, '|', false), (1, '~', false)];
static ROSE_BRANCH: &[(i32, char, bool)] = &[(0, 'Y', false)];
static ROSE_BLOOM: &[(i32, char, bool)] = &[(-1, '(', true), (0, '@', true), (1, ')', true)];
struct Plant {
col: usize,
variety: usize,
stage: usize, shape: Vec<PRow>, }
struct Raindrop {
x: f64,
y: f64,
speed: f64,
}
struct Cloud {
x: f64,
width: usize,
raining: bool,
rain_timer: f64, rain_cooldown: f64, spawn_timer: f64, }
struct Splash {
x: usize,
y: usize,
ttl: f64,
}
pub struct Garden {
plants: Vec<Plant>,
clouds: Vec<Cloud>,
drops: Vec<Raindrop>,
splashes: Vec<Splash>,
width: usize,
height: usize,
scale: f64,
bloom_timer: Option<f64>, rng: rand::rngs::ThreadRng,
}
fn gen_plants(rng: &mut rand::rngs::ThreadRng, width: usize, scale: f64) -> Vec<Plant> {
let num_plants = ((width as f64 / 8.0) * scale).clamp(3.0, 20.0) as usize;
let slots = (num_plants + 1) as f64;
(0..num_plants)
.filter_map(|i| {
if rng.random_bool(0.25) {
return None;
}
let variety = rng.random_range(0..VARIETIES.len());
let shape: Vec<PRow> = if variety == 0 {
let stem_rows = rng.random_range(2..=6_usize);
let mut s: Vec<PRow> = Vec::new();
for j in 0..stem_rows {
let leafed = (j % 2 == 1) ^ rng.random_bool(0.3);
s.push(if leafed { ROSE_STEM_LEAF } else { ROSE_STEM });
}
s.push(ROSE_BRANCH);
s.push(ROSE_BLOOM);
s
} else {
VARIETIES[variety].to_vec()
};
let col = ((width as f64 * (i as f64 + 1.0)) / slots) as usize;
Some(Plant {
col,
variety,
stage: 0,
shape,
})
})
.collect()
}
impl Garden {
pub fn new(width: usize, height: usize, scale: f64) -> Self {
let mut rng = rand::rng();
let plants = gen_plants(&mut rng, width, scale);
let num_clouds = ((width as f64 / 40.0) * scale).clamp(1.0, 4.0) as usize;
let clouds: Vec<Cloud> = (0..num_clouds)
.map(|i| Cloud {
x: (width as f64 / num_clouds as f64) * i as f64,
width: rng.random_range(6..14),
raining: false,
rain_timer: 0.0,
rain_cooldown: rng.random_range(3.0..12.0),
spawn_timer: 0.0,
})
.collect();
Garden {
plants,
clouds,
drops: Vec::new(),
splashes: Vec::new(),
width,
height,
scale,
bloom_timer: None,
rng: rand::rng(),
}
}
}
impl Animation for Garden {
fn name(&self) -> &str {
"garden"
}
fn preferred_render(&self) -> RenderMode {
RenderMode::Ascii
}
fn on_resize(&mut self, width: usize, height: usize) {
self.width = width;
self.height = height;
}
fn update(&mut self, canvas: &mut Canvas, dt: f64, _time: f64) {
if self.height < 5 {
return;
}
let ground_y = self.height - 1;
let cloud_y: usize = 3;
let all_bloomed =
!self.plants.is_empty() && self.plants.iter().all(|p| p.stage >= p.shape.len());
if all_bloomed && self.bloom_timer.is_none() {
self.bloom_timer = Some(0.0);
}
if let Some(ref mut t) = self.bloom_timer {
*t += dt;
if *t >= 60.0 {
self.plants = gen_plants(&mut self.rng, self.width, self.scale);
self.drops.clear();
self.splashes.clear();
self.bloom_timer = None;
}
}
canvas.clear();
for y in 0..ground_y {
for x in 0..self.width {
canvas.set_colored(x, y, 0.1, SKY_DIM.0, SKY_DIM.1, SKY_DIM.2);
}
}
for x in 0..self.width {
canvas.set_char(
x,
ground_y,
'=',
GROUND_COLOR.0,
GROUND_COLOR.1,
GROUND_COLOR.2,
);
}
let sun_cx = self.width as i32 - 1;
let sun_cy = 1_i32;
for &(dx, dy, ch) in &[
(-5, 0, '*'),
(-4, 0, '-'),
(-3, 0, '-'),
(-2, 0, '-'),
(-1, 0, '('),
(0, 0, '@'),
(0, -1, '|'),
(-1, -1, '\\'),
(0, 1, '|'),
(0, 2, '|'),
(0, 3, '*'),
(-1, 1, '/'),
(-2, 2, '/'),
(-3, 3, '/'),
] {
let px = sun_cx + dx;
let py = sun_cy + dy;
if px >= 0 && py >= 0 {
canvas.set_char(
px as usize,
py as usize,
ch,
SUN_COLOR.0,
SUN_COLOR.1,
SUN_COLOR.2,
);
}
}
let mut new_drops: Vec<Raindrop> = Vec::new();
for cloud in &mut self.clouds {
cloud.x += 5.0 * dt;
if cloud.x > (self.width + cloud.width + 2) as f64 {
cloud.x = -(cloud.width as f64 + 2.0);
cloud.width = self.rng.random_range(6..14);
cloud.raining = false;
cloud.rain_cooldown = self.rng.random_range(3.0..12.0);
}
if cloud.raining {
cloud.rain_timer -= dt;
if cloud.rain_timer <= 0.0 {
cloud.raining = false;
cloud.rain_cooldown = self.rng.random_range(8.0..20.0);
} else {
cloud.spawn_timer -= dt;
if cloud.spawn_timer <= 0.0 {
cloud.spawn_timer = 0.2;
let drop_x = cloud.x + self.rng.random_range(0.0..cloud.width as f64);
if drop_x >= 0.0 && (drop_x as usize) < self.width {
new_drops.push(Raindrop {
x: drop_x,
y: (cloud_y + 1) as f64,
speed: self.rng.random_range(20.0..35.0),
});
}
}
}
} else {
cloud.rain_cooldown -= dt;
if cloud.rain_cooldown <= 0.0 {
cloud.raining = true;
cloud.rain_timer = self.rng.random_range(3.0..8.0);
cloud.spawn_timer = 0.0;
}
}
let cx = cloud.x as i32;
for i in 0..cloud.width as i32 {
let px = cx + i;
if px < 0 || (px as usize) >= self.width {
continue;
}
let ch = match i {
0 => '(',
n if n == cloud.width as i32 - 1 => ')',
_ => {
if cloud.raining {
'~'
} else {
'-'
}
}
};
canvas.set_char(
px as usize,
cloud_y,
ch,
CLOUD_COLOR.0,
CLOUD_COLOR.1,
CLOUD_COLOR.2,
);
}
}
self.drops.extend(new_drops);
let mut hits: Vec<usize> = Vec::new();
self.drops.retain_mut(|drop| {
drop.y += drop.speed * dt;
if drop.y >= ground_y as f64 {
hits.push(drop.x as usize);
false
} else {
let x = drop.x as usize;
let y = drop.y as usize;
if y > cloud_y {
canvas.set_char(x, y, '|', RAIN_COLOR.0, RAIN_COLOR.1, RAIN_COLOR.2);
}
true
}
});
for hit_x in hits {
for plant in &mut self.plants {
let lo = plant.col.saturating_sub(2);
let hi = plant.col + 2;
if hit_x >= lo && hit_x <= hi && plant.stage < plant.shape.len() {
plant.stage += 1;
}
}
if ground_y >= 1 {
self.splashes.push(Splash {
x: hit_x,
y: ground_y - 1,
ttl: 0.4,
});
}
}
self.splashes.retain_mut(|s| {
s.ttl -= dt;
if s.ttl > 0.0 {
for (i, &ch) in ['.', '\'', '.'].iter().enumerate() {
let px = s.x as i32 + i as i32 - 1;
if px >= 0 {
canvas.set_char(
px as usize,
s.y,
ch,
RAIN_COLOR.0,
RAIN_COLOR.1,
RAIN_COLOR.2,
);
}
}
true
} else {
false
}
});
for plant in &self.plants {
let (fr, fg, fb) = FLOWER_COLORS[plant.variety];
let (sr, sg, sb) = STEM_COLOR;
if plant.stage == 0 {
if ground_y >= 1 {
canvas.set_char(plant.col, ground_y - 1, '.', sr, sg, sb);
}
continue;
}
let rows = &plant.shape;
let rows_to_draw = plant.stage.min(rows.len());
for (row_idx, row) in rows[..rows_to_draw].iter().enumerate() {
let y = ground_y as i32 - 1 - row_idx as i32;
if y < 0 {
continue;
}
let is_top = row_idx + 1 == rows_to_draw;
for &(dx, ch, is_flower) in *row {
let px = plant.col as i32 + dx;
if px < 0 {
continue;
}
let (r, g, b) = if is_flower && is_top {
(fr, fg, fb)
} else {
(sr, sg, sb)
};
canvas.set_char(px as usize, y as usize, ch, r, g, b);
}
}
}
}
}