use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct PokemonEntry {
pub name: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StatKind {
Hp,
Attack,
Defense,
SpecialAttack,
SpecialDefense,
Speed,
}
impl StatKind {
pub fn from_api(slug: &str) -> Option<Self> {
match slug {
"hp" => Some(Self::Hp),
"attack" => Some(Self::Attack),
"defense" => Some(Self::Defense),
"special-attack" => Some(Self::SpecialAttack),
"special-defense" => Some(Self::SpecialDefense),
"speed" => Some(Self::Speed),
_ => None,
}
}
pub fn order(&self) -> u8 {
match self {
Self::Hp => 0,
Self::Attack => 1,
Self::Defense => 2,
Self::SpecialAttack => 3,
Self::SpecialDefense => 4,
Self::Speed => 5,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Stat {
pub kind: StatKind,
pub base: u16,
}
#[derive(Debug, Clone)]
pub struct PokemonDetail {
pub name: String,
pub species: String,
pub dex_number: u32,
pub is_legendary: bool,
pub is_mythical: bool,
pub is_baby: bool,
pub types: Vec<String>,
pub stats: Vec<Stat>,
pub height: u32,
pub weight: u32,
pub sprite_url: Option<String>,
pub genera: HashMap<String, String>,
pub flavors: HashMap<String, String>,
}
impl PokemonDetail {
pub fn stat_total(&self) -> u32 {
self.stats.iter().map(|s| s.base as u32).sum()
}
pub fn genus_for(&self, code: &str) -> Option<&str> {
self.genera
.get(code)
.or_else(|| self.genera.get("en"))
.map(String::as_str)
}
}
#[derive(Debug, Clone)]
pub struct EvolutionTree {
pub name: String,
pub children: Vec<EvolutionTree>,
}
impl EvolutionTree {
pub fn collect_names(&self, out: &mut Vec<String>) {
out.push(self.name.clone());
for child in &self.children {
child.collect_names(out);
}
}
pub fn leaf_count(&self) -> usize {
if self.children.is_empty() {
1
} else {
self.children.iter().map(EvolutionTree::leaf_count).sum()
}
}
pub fn depth(&self) -> usize {
1 + self.children.iter().map(EvolutionTree::depth).max().unwrap_or(0)
}
}
#[derive(Debug, Clone)]
pub struct Sprite {
pub width: u32,
pub height: u32,
pub pixels: Vec<[u8; 4]>,
}
impl Sprite {
pub fn box_average(&self, x0: u32, y0: u32, x1: u32, y1: u32) -> [u8; 4] {
let x1 = x1.min(self.width.saturating_sub(1)).max(x0);
let y1 = y1.min(self.height.saturating_sub(1)).max(y0);
let (mut r, mut g, mut b, mut a, mut n) = (0u32, 0u32, 0u32, 0u32, 0u32);
for y in y0..=y1 {
for x in x0..=x1 {
let p = self.pixels[(y * self.width + x) as usize];
let pa = p[3] as u32;
r += p[0] as u32 * pa;
g += p[1] as u32 * pa;
b += p[2] as u32 * pa;
a += pa;
n += 1;
}
}
if a == 0 || n == 0 {
return [0, 0, 0, 0];
}
[(r / a) as u8, (g / a) as u8, (b / a) as u8, (a / n) as u8]
}
pub fn content_bounds(&self) -> (u32, u32, u32, u32) {
let (mut x0, mut y0, mut x1, mut y1) = (self.width, self.height, 0u32, 0u32);
let mut found = false;
for y in 0..self.height {
for x in 0..self.width {
if self.pixels[(y * self.width + x) as usize][3] >= 128 {
found = true;
x0 = x0.min(x);
y0 = y0.min(y);
x1 = x1.max(x);
y1 = y1.max(y);
}
}
}
if found {
(x0, y0, x1, y1)
} else {
(0, 0, self.width.saturating_sub(1), self.height.saturating_sub(1))
}
}
}
pub fn title_case(raw: &str) -> String {
raw.split(['-', ' '])
.filter(|part| !part.is_empty())
.map(|part| {
let mut chars = part.chars();
match chars.next() {
Some(first) => first.to_uppercase().chain(chars).collect::<String>(),
None => String::new(),
}
})
.collect::<Vec<_>>()
.join(" ")
}