damdara 0.7.0

Dragon Quest Core Module, created in Rust.
Documentation
use crate::constants::monster::{MONSTER_MASTER, MonsterAction};
use crate::monster::Monster;
use rand::Rng;
use std::collections::HashMap;

pub fn create_all_monsters() -> Vec<Monster> {
    (0..MONSTER_MASTER.len()).map(Monster::new).collect()
}

pub fn build_monster_name_index_map() -> HashMap<&'static str, usize> {
    let mut map = HashMap::new();
    for (index, monster) in MONSTER_MASTER.iter().enumerate() {
        map.insert(monster.name, index);
    }
    map
}

pub fn get_monster_by_name(name: &str) -> Option<Monster> {
    let name_map = build_monster_name_index_map();
    let index = name_map.get(name)?;
    Some(Monster::new(*index))
}

pub fn list_monster_names() -> Vec<&'static str> {
    MONSTER_MASTER.iter().map(|m| m.name).collect()
}

pub fn choose_action<'a>(candidates: &'a [MonsterAction]) -> Option<&'a MonsterAction> {
    let mut rng = rand::rng();
    candidates
        .iter()
        .find(|action| rng.random_bool(action.rate as f64 / 100.0))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::constants::monster::ActionType;
    use crate::constants::spell::Spell;

    #[test]
    fn test_monster_new_valid_index() {
        let monster = Monster::new(0);
        assert_eq!(monster.stats.name, "スライム");
    }

    #[test]
    fn test_monster_new_invalid_index_defaults_to_slime() {
        let monster = Monster::new(999);
        assert_eq!(monster.stats.name, "スライム");
    }

    #[test]
    fn test_create_all_monsters_length() {
        let monsters = create_all_monsters();
        assert_eq!(monsters.len(), MONSTER_MASTER.len());
    }

    #[test]
    fn test_build_monster_name_index_map_contains_slime() {
        let map = build_monster_name_index_map();
        assert!(map.contains_key("スライム"));
    }

    #[test]
    fn test_get_monster_by_name_existing() {
        let monster = get_monster_by_name("スライム");
        assert!(monster.is_some());
        assert_eq!(monster.unwrap().stats.name, "スライム");
    }

    #[test]
    fn test_get_monster_by_name_non_existing() {
        let monster = get_monster_by_name("ファントム");
        assert!(monster.is_none());
    }

    #[test]
    fn test_list_monster_names_count() {
        let names = list_monster_names();
        assert_eq!(names.len(), MONSTER_MASTER.len());
    }

    #[test]
    fn test_list_monster_names_contains_slime() {
        let names = list_monster_names();
        assert!(names.contains(&"スライム"));
    }

    #[test]
    fn test_choose_action_always_selects_high_rate() {
        let candidates = vec![
            MonsterAction {
                ab_type: "A",
                action: ActionType::Spell(Spell::Hoimi),
                rate: 100,
            },
            MonsterAction {
                ab_type: "B",
                action: ActionType::Spell(Spell::Mahoton),
                rate: 0,
            },
        ];
        for _ in 0..10 {
            let result = choose_action(&candidates);
            assert!(result.is_some());
            assert_eq!(result.unwrap().ab_type, "A");
        }
    }

    #[test]
    fn test_choose_action_none_when_all_zero() {
        let candidates = vec![
            MonsterAction {
                ab_type: "A",
                action: ActionType::Spell(Spell::Hoimi),
                rate: 0,
            },
            MonsterAction {
                ab_type: "B",
                action: ActionType::Spell(Spell::Mahoton),
                rate: 0,
            },
        ];
        for _ in 0..10 {
            let result = choose_action(&candidates);
            assert!(result.is_none());
        }
    }
}