text_based_rpg 0.1.0

A text-based RPG game
Documentation
use crate::get_user_input;
use crate::items::Item;
use crate::weapons::{self, Weapon};
use std::io::BufRead;
pub mod monsters;

pub trait Entity {
    fn take_damage(&mut self, amount: i32);
    fn decrease_damage(&mut self, amount: i32);
    fn get_current_health(&self) -> i32;
    fn get_max_health(&self) -> i32;
    fn is_dead(&self) -> bool {
        self.get_current_health() <= 0
    }
    fn get_name(&self) -> &str;

    fn heal_damage(&mut self, amount: i32) {
        //prevent overheal
        if self.get_current_health() + amount >= self.get_max_health() {
            self.take_damage(-(self.get_current_health() + amount - self.get_max_health()));
        } else {
            self.take_damage(-amount);
        }
    }
}

pub trait User {
    ///get the users action against an enemy
    fn get_action(&mut self, target: &mut Enemy) -> ();
    ///requests the user to select an item from a vector
    fn user_select_ith_index_from_vec(&mut self) -> usize;
}

pub struct Hero {
    pub name: String,
    pub max_health: i32,
    pub current_health: i32,
    pub items: Vec<Item>,
    pub weapon: Weapon,
}

impl Entity for Hero {
    fn take_damage(&mut self, amount: i32) {
        self.current_health -= amount;
    }
    fn decrease_damage(&mut self, amount: i32) {
        self.weapon.damage -= amount;
        if self.weapon.damage < 0 {
            self.weapon.damage = 0;
        }
    }

    fn get_max_health(&self) -> i32 {
        self.max_health
    }

    fn get_current_health(&self) -> i32 {
        self.current_health
    }
    fn get_name(&self) -> &str {
        &self.name
    }
}

impl User for Hero {
    fn user_select_ith_index_from_vec(&mut self) -> usize {
        let mut user_input = String::new();

        println!("Choose from the selection:");

        for (index, item) in self.items.iter().enumerate() {
            println!("{}-{}", index, item.name);
        }

        std::io::stdin().read_line(&mut user_input).unwrap();
        let user_input: usize = user_input.trim().parse().unwrap();

        return user_input;
    }

    fn get_action(&mut self, target: &mut Enemy) -> () {
        let mut user_input = String::new();

        println!("Choose an action for {}:", self.name);
        println!("1-Attack");
        println!("2-Use Item");

        std::io::stdin().read_line(&mut user_input).unwrap();
        let decision = user_input.trim();

        //match input to function
        match decision {
            "1" => {
                self.weapon.apply(target);
            }
            "2" => {
                if self.items.is_empty() {
                    println!("No items to use");
                    self.get_action(target);
                }

                println!("Select an item to use:");

                for (index, item) in self.items.iter().enumerate() {
                    println!("{}-{}", index, item.name);
                }

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

                if user_input >= self.items.len() {
                    println!("Invalid input, selecting 1st item");
                    user_input = 0;
                }

                let item = self.items.remove(user_input as usize);
                item.apply(target);
            }
            _ => {
                println!("Invalid input");
                self.get_action(target);
            }
        }
    }
}

impl Hero {
    pub fn new(name: String, max_health: i32, items: Vec<Item>, weapon: Weapon) -> Hero {
        Hero {
            name,
            max_health,
            current_health: max_health,
            items,
            weapon,
        }
    }

    pub fn update_weapon(&mut self, weapon: Weapon) {
        self.weapon = weapon;
    }
}

impl Default for Hero {
    fn default() -> Self {
        Hero {
            name: "Default Hero".to_string(),
            max_health: 100,
            current_health: 100,
            items: vec![],
            weapon: Weapon::default(),
        }
    }
}

#[derive(Clone)]
pub struct Enemy {
    pub name: String,
    pub max_health: i32,
    pub current_health: i32,
    pub items: Vec<Item>,
    pub weapon: Weapon,
}

impl Entity for Enemy {
    fn take_damage(&mut self, amount: i32) {
        self.current_health -= amount;
    }
    fn get_max_health(&self) -> i32 {
        self.max_health
    }

    fn get_current_health(&self) -> i32 {
        self.current_health
    }
    fn get_name(&self) -> &str {
        &self.name
    }
    fn decrease_damage(&mut self, amount: i32) {
        self.weapon.damage -= amount;
        if self.weapon.damage < 0 {
            self.weapon.damage = 0;
        }
    }
}

impl Enemy {
    pub fn make_decision(&mut self, target: &mut Hero) {
        let decision = rand::Rng::gen_range(&mut rand::thread_rng(), 0..2);

        match decision {
            0 => {
                self.weapon.apply(target);
            }
            1 => {
                let item = self.items.pop();

                match item {
                    Some(item) => {
                        item.apply(target);
                    }
                    None => {
                        self.weapon.apply(target);
                    }
                }
            }
            _ => {
                println!("Invalid decision");
            }
        }
    }

    pub fn get_name(&self) -> &str {
        &self.name
    }
}

impl Default for Enemy {
    fn default() -> Self {
        Enemy {
            name: "Training Dummy".to_string(),
            max_health: 999999,
            current_health: 999999,
            items: vec![],
            weapon: Weapon::default(),
        }
    }
}

mod tests {
    //for testing user interface
    mod user {
        use crate::{
            entity::{Hero, User},
            items::Item,
        };

        #[test]
        #[ignore]
        //user input required. User must input 1
        fn test_user_select_ith_index_from_vec() {
            let mut hero = Hero::default();
            hero.items.push(Item::default());
            hero.items.push(Item::default());
            hero.items.push(Item::default());

            // Simulate user input

            let selected_index = hero.user_select_ith_index_from_vec();

            assert_eq!(selected_index, 1);
        }
    }
    mod enemy {
        use crate::entity::Entity;
        use crate::weapons::Weapon;
        #[test]
        fn successful_interaction() {
            //create a hero & update weapon
            let mut hero = super::super::Hero::default();
            let attack_weapon = Weapon::new("Attack Weapon", 10, |target| target.take_damage(10));

            hero.weapon = attack_weapon;
            //create an enemy
            let mut enemy = super::super::Enemy::default();

            let initial_health = enemy.get_current_health();

            //hero attacks enemy
            hero.weapon.apply(&mut enemy);

            let final_health = enemy.get_current_health();

            //enemy should have taken damage
            assert_eq!(initial_health - 20, final_health);
        }
    }

    mod entity {
        use crate::entity::Entity;
        use crate::weapons::Weapon;
        #[test]
        fn successful_interaction() {
            //create a hero & update weapon
            let mut hero = super::super::Hero::default();
            let attack_weapon = Weapon::new("Attack Weapon", 10, |target| target.take_damage(10));

            hero.weapon = attack_weapon;
            //create an enemy
            let mut enemy = super::super::Enemy::default();

            let initial_health = enemy.get_current_health();

            //hero attacks enemy
            hero.weapon.apply(&mut enemy);

            let final_health = enemy.get_current_health();

            //enemy should have taken damage
            assert_eq!(initial_health - 20, final_health);
        }
    }
}