use super::evolution::EvolutionStage;
use super::types::Mood;
const W: usize = 18;
pub(super) fn width() -> usize {
W
}
pub(super) fn anim_ms_for(stage: &EvolutionStage) -> u32 {
match stage {
EvolutionStage::Egg => 1100,
EvolutionStage::Baby => 900,
EvolutionStage::Teen => 720,
EvolutionStage::Adult => 560,
EvolutionStage::Mythic => 420,
}
}
fn mood_eyes(mood: &Mood) -> (&'static str, &'static str) {
match mood {
Mood::Ecstatic => ("◕", "◕"),
Mood::Happy => ("●", "●"),
Mood::Content => ("o", "o"),
Mood::Worried => (">", "<"),
Mood::Sleeping => ("-", "-"),
}
}
pub(super) fn sprite_for(stage: &EvolutionStage, mood: &Mood) -> Vec<String> {
let (l, r) = mood_eyes(mood);
compose(raw_lines(stage), l, r)
}
pub(super) fn frames_for(stage: &EvolutionStage, mood: &Mood) -> Vec<Vec<String>> {
let mut frames = vec![sprite_for(stage, mood)];
frames.push(compose(raw_lines(stage), "-", "-"));
if matches!(stage, EvolutionStage::Adult | EvolutionStage::Mythic) {
let (l, r) = mood_eyes(mood);
frames.push(compose_sparkle(raw_lines(stage), l, r));
}
frames
}
fn compose(lines: &[&str], l: &str, r: &str) -> Vec<String> {
let subbed: Vec<String> = lines
.iter()
.map(|s| s.replace("{L}", l).replace("{R}", r))
.collect();
block_center(&subbed)
}
fn compose_sparkle(lines: &[&str], l: &str, r: &str) -> Vec<String> {
let subbed: Vec<String> = lines
.iter()
.map(|s| {
s.replace("{L}", l)
.replace("{R}", r)
.replace('✦', "✧")
.replace('◆', "◇")
})
.collect();
block_center(&subbed)
}
fn block_center(lines: &[String]) -> Vec<String> {
use super::super::theme::{pad_right, visual_len};
let max_w = lines.iter().map(|l| visual_len(l)).max().unwrap_or(0);
let pad = W.saturating_sub(max_w) / 2;
let margin = " ".repeat(pad);
lines
.iter()
.map(|l| pad_right(&format!("{margin}{l}"), W))
.collect()
}
fn raw_lines(stage: &EvolutionStage) -> &'static [&'static str] {
match stage {
EvolutionStage::Egg => &[
r" ▗▄▄▄▄▖ ",
r"▟██████▙",
r"███▘▝███",
r"████████",
r"▜██████▛",
r" ▀▀▀▀▀▀ ",
],
EvolutionStage::Baby => &[
r"▗▖ ▗▖",
r"▟█▀▀▀▀█▙",
r"█ {L} {R} █",
r"█ ▿▿ █",
r"▜█▄▄▄▄█▛",
r" ▝▘ ▝▘ ",
],
EvolutionStage::Teen => &[
r"▟▙ ▟▙",
r"▐█▀▀▀▀▀▀█▌",
r"▐█ {L} {R} █▌",
r"▐█ ▿▿ █▌",
r"▟██▄▄▄▄██▙",
r"▝█ █▘",
r" ▝▀▄▄▄▄▀▘ ",
r" ▝▘ ▝▘ ",
],
EvolutionStage::Adult => &[
r" ▟▙ ▟██▙ ▟▙ ",
r"▐█▀▀▀▀▀▀▀▀█▌",
r"▐█ {L} {R} █▌",
r"▐█ ▿▿ █▌",
r"▐██▄▄◆◆▄▄██▌",
r"▝██ ██▘",
r" ▝▀▄▄▄▄▄▄▀▘ ",
r" ▝█ █▘ ",
],
EvolutionStage::Mythic => &[
r"✦ ▟█◆█▙ ✦",
r" ▚▟▀▀▀▀▀▀▙▞ ",
r"▟█ {L} {R} █▙",
r"██ ▿▿ ██",
r"▜██▄◆◆◆◆▄██▛",
r" ▝██ ██▘ ",
r"✦ ▝▀▄▄▄▄▀▘ ✦",
r" ▝█ █▘ ",
],
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_stage_renders_at_fixed_width() {
let stages = [
EvolutionStage::Egg,
EvolutionStage::Baby,
EvolutionStage::Teen,
EvolutionStage::Adult,
EvolutionStage::Mythic,
];
for st in &stages {
for mood in &[Mood::Ecstatic, Mood::Worried, Mood::Sleeping] {
let sprite = sprite_for(st, mood);
assert!(!sprite.is_empty(), "{st:?} produced no lines");
for line in &sprite {
assert_eq!(
super::super::super::theme::visual_len(line),
W,
"{st:?}/{mood:?} line not width {W}: {line:?}"
);
}
}
}
}
#[test]
fn eyes_are_injected() {
let sprite = sprite_for(&EvolutionStage::Baby, &Mood::Happy);
assert!(sprite.join("\n").contains('●'), "happy eyes missing");
}
#[test]
fn frames_present_for_all_stages() {
for st in &[
EvolutionStage::Egg,
EvolutionStage::Baby,
EvolutionStage::Adult,
EvolutionStage::Mythic,
] {
assert!(frames_for(st, &Mood::Happy).len() >= 2);
}
}
}