use std::ops::RangeInclusive; use rand::Rng; use ratatui::prelude::Color; use crate::position::{HasPosition, Position}; pub trait Monster: HasPosition { fn get_name(&self) -> &str; fn is_dead(&self) -> bool; fn get_representation(&self) -> (&str, Color); fn decrease_life(&mut self, by: usize); // fn get_immutable_position(&self) -> &Position; fn get_experience_gain(&self) -> usize; fn get_ticks_between_steps(&self) -> u128; #[cfg(test)] fn get_life(&self) -> usize; fn damage(&self) -> usize; } macro_rules! create_monster { ($($t:ident),+ $(,)?) => ($( pub struct $t { name: String, life: usize, position: Position, symbol: String, color: Color, experience_gain: usize, ticks_between_steps: u128, damage_range: RangeInclusive, } impl Monster for $t { fn get_name(&self) -> &str { &self.name } fn is_dead(&self) -> bool { self.life <= 0 } fn get_experience_gain(&self) -> usize { self.experience_gain } fn get_representation(&self) -> (&str, Color) { (&self.symbol, self.color) } fn decrease_life(&mut self, by: usize) { self.life = self.life.saturating_sub(by); } fn get_ticks_between_steps(&self) -> u128 { self.ticks_between_steps } fn damage(&self) -> usize { rand::thread_rng().gen_range(self.damage_range.clone()) } #[cfg(test)] fn get_life(&self) -> usize { self.life } } impl HasPosition for $t { fn get_position(&mut self) -> &mut Position { &mut self.position } fn get_immutable_position(&self) -> &Position { &self.position } } )+) } impl Rat { pub fn new_with_position(position: Position) -> Self { Self { name: "rat".to_string(), life: 2, position, symbol: String::from("R"), color: Color::Black, experience_gain: 5, ticks_between_steps: 5, damage_range: 1..=2, } } } create_monster!(Rat); impl Orc { pub fn new_with_position(position: Position) -> Self { Self { name: "orc".to_string(), life: 4, position, symbol: String::from("O"), color: Color::DarkGray, experience_gain: 10, ticks_between_steps: 10, damage_range: 2..=3, } } } create_monster!(Orc); impl Snake { pub fn new_with_position(position: Position) -> Self { Self { name: "snake".to_string(), life: 3, position, symbol: String::from("S"), color: Color::DarkGray, experience_gain: 10, ticks_between_steps: 20, damage_range: 1..=4, } } } create_monster!(Snake); pub fn create_monster_by_type(mtype: &str, position: Position) -> Box { match mtype { "Rat" => Box::new(Rat::new_with_position(position)), "Orc" => Box::new(Orc::new_with_position(position)), "Snake" => Box::new(Snake::new_with_position(position)), &_ => { panic!("Unknown species: {}", mtype) } } } #[test] fn monsters_can_move() { let mut m = Rat::new_with_position(Position::new(0, 0, 0)); assert_eq!(m.get_position(), &Position::new(0, 0, 0)); m.get_position().change(1, 2); assert_eq!(m.get_position(), &Position::new(0, 1, 2)); m.get_position().change(2, 1); assert_eq!(m.get_position(), &Position::new(0, 3, 3)); m.get_position().set(1, 2, 3); m.get_position().change(2, 1); assert_eq!(m.get_position(), &Position::new(1, 4, 4)); } #[test] fn monsters_can_die() { let mut m = Rat::new_with_position(Position::new(0, 0, 0)); assert_eq!(m.get_life(), 2); assert_eq!(m.is_dead(), false); m.decrease_life(1); assert_eq!(m.get_life(), 1); m.decrease_life(2); assert_eq!(m.get_life(), 0); assert_eq!(m.is_dead(), true); }