text_based_rpg 0.1.0

A text-based RPG game
Documentation
use rand::Rng;

use crate::entity::{Enemy, Hero};
use crate::entity::{Entity, User};
use crate::get_user_input;

pub enum BattleOutcome {
    Win,
    Loss,
}

///create a battle between two parties
pub fn battle(
    hero_party: &mut Vec<Box<&mut Hero>>,
    enemy_party: &mut Vec<Box<&mut Enemy>>,
) -> BattleOutcome {
    println!("The battle begins!");

    loop {
        //Print the stats of every party
        for hero in hero_party.iter_mut() {
            println!("{}: {}", hero.get_name(), hero.get_current_health());
        }
        for enemy in enemy_party.iter_mut() {
            println!("{}: {}", enemy.get_name(), enemy.get_current_health());
        }

        //give the hero_party initiative. let them act on the enemy party
        for hero in hero_party.iter_mut() {
            let mut user_input = String::new();
            //print enemy party with index on each
            println!("Select an enemy for {} to focus on: ", hero.get_name());
            for (index, enemy) in enemy_party.iter_mut().enumerate() {
                println!("{}: {}", index, enemy.get_name());
            }


            let user_input = get_user_input("");
            let mut user_input = user_input.trim().parse::<usize>().unwrap_or_default();

            if user_input >= enemy_party.len() {
                println!("Invalid input, targetting first enemy");
                user_input = 0;
            }


            hero.get_action(&mut enemy_party[user_input as usize]);
        }
        //check for deaths among enemy

        check_for_deaths(enemy_party);

        if enemy_party.is_empty() {
            println!("All enemies have been defeated!");
            return BattleOutcome::Win;
        }

        //give the enemy_party initiative. let them act on the hero party
        for enemy in enemy_party.iter_mut() {
            //randomly select a hero to focus on
            let len = hero_party.len();
            let rand_hero_index = rand::thread_rng().gen_range(0..len);

            enemy.make_decision(&mut hero_party[rand_hero_index as usize]);
        }

        //check for deaths among heroes
        check_for_deaths(hero_party);

        if hero_party.is_empty() {
            println!("All heros have been defeated!");
            return BattleOutcome::Loss;
        }

        println!("The battle rages on!");
    }
}

///check for deaths among a party
fn check_for_deaths<T: Entity>(party: &mut Vec<Box<&mut T>>) {
    let mut indices_to_remove = Vec::new();
    for (index, member) in party.iter_mut().enumerate() {
        if member.is_dead() {
            println!("{} has died!", member.get_name());
            indices_to_remove.push(index);
        }
    }

    for index in indices_to_remove.into_iter().rev() {
        party.remove(index);
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use std::io::{self, BufRead};

    // Function to be tested
    fn read_user_input<R: BufRead>(mut reader: R) -> String {
        let mut input = String::new();
        reader.read_line(&mut input).expect("Failed to read line");
        input.trim().to_string()
    }

    mod user_input_functions {
        use super::*;
        use std::io::Cursor;

        #[test]
        fn test_read_user_input() {
            let input_data = "test input\n";
            let cursor = Cursor::new(input_data);
            let result = read_user_input(cursor);
            assert_eq!(result, "test input");
        }
    }

    ///ensure the battle sequence runs
    #[test]
    #[ignore]
    fn test_battle_ownership() {
        let mut hero = Hero::default();
        let mut enemy = Enemy::default();

        let mut hero_party: Vec<Box<&mut Hero>> = Vec::new();
        let mut enemy_party: Vec<Box<&mut Enemy>> = Vec::new();

        hero_party.push(Box::new(&mut hero));
        enemy_party.push(Box::new(&mut enemy));

        battle(&mut hero_party, &mut enemy_party);
        assert!(true);
    }
}