diff --git a/src/dungeon_slayer.rs b/src/dungeon_slayer.rs new file mode 100644 index 0000000..5168515 --- /dev/null +++ b/src/dungeon_slayer.rs @@ -0,0 +1,138 @@ +use rand::Rng; + +pub fn do_challenge(stat: u8) -> bool { + if stat <= 20 { + let mut rng = rand::thread_rng(); + let dice_roll = rng.gen_range(1..21); + return match dice_roll { + 20 => { false } + 1 => { true } + _ => dice_roll <= stat + }; + } + true +} + +pub struct PlayerStats { + // dungeon slayer attributes + pub(crate) body: u8, + pub(crate) agility: u8, + pub(crate) mind: u8, + + // dungeon slayer properties + // for body + pub(crate) strength: u8, + pub(crate) toughness: u8, + // for agility + pub(crate) movement: u8, + pub(crate) dexterity: u8, + // for mind + pub(crate) wisdom: u8, + // TODO maybe the wrong translation of Verstand + pub(crate) aura: u8, +} + +impl PlayerStats { + /// create random values for the 3 attributes of the dungeon slayer character + /// Each attribute is between 4 and 8, not over 20 in sum. + fn create_random_attributes() -> (u8, u8, u8) { + let mut values: [u8; 3] = [8, 8, 8]; + let mut rng = rand::thread_rng(); + while values[0] + values[1] + values[2] > 20 { + let i = rng.gen_range(0..3); + values[i] = values[i].saturating_sub(1); + } + (values[0], values[1], values[2]) + } + + /// create random values for the 6 properties of the dungeon slayer character + /// Each property is between 0 and 4, not over 8 in sum. + fn create_random_properties() -> (u8, u8, u8, u8, u8, u8) { + let mut values: [u8; 6] = [4, 4, 4, 4, 4, 4]; + let mut rng = rand::thread_rng(); + while values[0] + values[1] + values[2] + values[3] + values[4] + values[5] > 8 { + let i = rng.gen_range(0..6); + values[i] = values[i].saturating_sub(1); + } + (values[0], values[1], values[2], values[3], values[4], values[5]) + } + + /// create a set of player stats containing attributes and properties according to the + /// dungeon slayer rules. + pub fn create_random() -> PlayerStats { + let attributes = PlayerStats::create_random_attributes(); + let properties = PlayerStats::create_random_properties(); + PlayerStats { + body: attributes.0, + agility: attributes.1, + mind: attributes.2, + strength: properties.0, + toughness: properties.1, + movement: properties.2, + dexterity: properties.3, + wisdom: properties.4, + aura: properties.5, + } + } + + /// calculate the max life based on this player stats. + pub fn get_max_life(&self) -> u8 { + self.body + self.toughness + 10 + } + pub fn get_defense(&self) -> u8 { + self.body + self.toughness // TODO + self.armor + } +} + +pub struct MonsterStats { + // dungeon slayer attributes + pub(crate) body: u8, + pub(crate) agility: u8, + pub(crate) mind: u8, + + // dungeon slayer properties + // for body + pub(crate) strength: u8, + pub(crate) toughness: u8, + // for agility + pub(crate) movement: u8, + pub(crate) dexterity: u8, + // for mind + pub(crate) wisdom: u8, + // TODO maybe the wrong translation of Verstand + pub(crate) aura: u8, + + pub(crate) max_life: u8, + pub(crate) defense: u8, + pub(crate) initiative: u8, + pub(crate) walk: u8, + pub(crate) hit: u8, + pub(crate) shoot: u8, + pub(crate) cast: u8, + pub(crate) targeted_cast: u8, +} + +#[test] +fn test_create_random_attributes() { + for _ in 0..1000 { + let attributes = PlayerStats::create_random_attributes(); + assert!(attributes.0 <= 8); + assert!(attributes.1 <= 8); + assert!(attributes.2 <= 8); + assert_eq!(attributes.0 + attributes.1 + attributes.2, 20); + } +} + +#[test] +fn test_create_random_properties() { + for _ in 0..1000 { + let properties = PlayerStats::create_random_properties(); + assert!(properties.0 <= 4); + assert!(properties.1 <= 4); + assert!(properties.2 <= 4); + assert!(properties.3 <= 4); + assert!(properties.4 <= 4); + assert!(properties.5 <= 4); + assert_eq!(properties.0 + properties.1 + properties.2 + properties.3 + properties.4 + properties.5, 8); + } +} \ No newline at end of file diff --git a/src/game.rs b/src/game.rs index 87a8ec2..c8e12bf 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,3 +1,4 @@ +use crate::dungeon_slayer::PlayerStats; use crate::level::{Level, StructureElement}; use crate::level_generator::LevelGenerator; use crate::player::Player; @@ -193,14 +194,36 @@ impl Game { #[test] fn game_has_correct_number_of_levels() { - let p = Player::new("foo", 42); + let player_stats = PlayerStats { + body: 8, + agility: 8, + mind: 4, + strength: 3, + toughness: 3, + movement: 1, + dexterity: 1, + wisdom: 0, + aura: 0, + }; + let p = Player::new("foo", player_stats); let g = Game::new(p); assert_eq!(g.levels.len(), LEVELS); } #[test] fn game_has_player() { - let p = Player::new("foo", 42); + let player_stats = PlayerStats { + body: 8, + agility: 8, + mind: 4, + strength: 3, + toughness: 3, + movement: 1, + dexterity: 1, + wisdom: 0, + aura: 0, + }; + let p = Player::new("foo", player_stats); let g = Game::new(p); assert_eq!(g.get_player().get_name(), "foo"); assert_eq!(g.get_player().get_immutable_position().get_level(), 0); @@ -208,16 +231,38 @@ fn game_has_player() { #[test] fn game_has_mutable_player() { - let p = Player::new("foo", 42); + let player_stats = PlayerStats { + body: 8, + agility: 8, + mind: 4, + strength: 3, + toughness: 3, + movement: 1, + dexterity: 1, + wisdom: 0, + aura: 0, + }; + let p = Player::new("foo", player_stats); let mut g = Game::new(p); assert_eq!(g.get_player().get_name(), "foo"); g.get_mutable_player().change_life(-1); - assert_eq!(g.get_player().get_life(), 41); + assert_eq!(g.get_player().get_life(), 20); } #[test] fn game_get_level() { - let p = Player::new("foo", 42); + let player_stats = PlayerStats { + body: 8, + agility: 8, + mind: 4, + strength: 3, + toughness: 3, + movement: 1, + dexterity: 1, + wisdom: 0, + aura: 0, + }; + let p = Player::new("foo", player_stats); let mut g = Game::new(p); g.get_level(0); assert_ne!(g.get_level(0).start, (0, 0)); diff --git a/src/level.rs b/src/level.rs index 67b2497..4c80f77 100644 --- a/src/level.rs +++ b/src/level.rs @@ -304,9 +304,9 @@ fn test_discover_get_monster() { assert_eq!(elem.0.unwrap(), StructureElement::Floor); assert!(elem.1.is_some()); let m = elem.1.unwrap(); - assert_eq!(m.get_life(), 2); + assert_eq!(m.get_life(), 3); - m.decrease_life(2); + m.decrease_life(3); assert_eq!(l.get_element(10, 10).1.unwrap().get_life(), 0); } @@ -342,5 +342,5 @@ fn test_discover_get_monster_can_move() { assert!(m.is_none()); let m = l.get_element(11, 11).1; assert!(m.is_some()); - assert_eq!(m.unwrap().get_life(), 2); + assert_eq!(m.unwrap().get_life(), 3); } diff --git a/src/main.rs b/src/main.rs index 0602000..71cbf6d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ use ratatui::widgets::{Block, Borders, BorderType, Wrap}; use ratatui::widgets::block::{Position, Title}; use whoami::realname; +use crate::dungeon_slayer::PlayerStats; use crate::game::{Game, GameState}; use crate::level_widget::LevelWidget; use crate::player::Player; @@ -29,13 +30,15 @@ mod level_widget; mod level_generator; mod artifacts; mod monster; +mod dungeon_slayer; /// length of a game frame in ms pub const FRAME_LENGTH: u64 = 100; // fn main() -> Result<()> { - let mut game = Game::new(Player::new(realname().as_str(), 10)); + let player_stats = PlayerStats::create_random(); + let mut game = Game::new(Player::new(realname().as_str(), player_stats)); stdout().execute(EnterAlternateScreen)?; enable_raw_mode()?; diff --git a/src/monster.rs b/src/monster.rs index 633a6f4..8d2483b 100644 --- a/src/monster.rs +++ b/src/monster.rs @@ -1,5 +1,6 @@ use ratatui::prelude::Color; +use crate::dungeon_slayer::MonsterStats; use crate::position::{HasPosition, Position}; pub trait Monster: HasPosition { @@ -48,18 +49,39 @@ pub struct Rat { color: Color, experience_gain: usize, ticks_between_steps: u128, + monster_stats: MonsterStats, } impl Rat { pub fn new_with_position(position: Position) -> Self { + let monster_stats = MonsterStats { + body: 2, + agility: 4, + mind: 1, + strength: 1, + toughness: 0, + movement: 2, + dexterity: 0, + wisdom: 0, + aura: 0, + max_life: 3, + defense: 2, + initiative: 6, + walk: 3, + hit: 4, + shoot: 0, + cast: 0, + targeted_cast: 0, + }; Self { name: "rat".to_string(), - life: 2, + life: 3, position, symbol: String::from("R"), color: Color::Black, experience_gain: 5, ticks_between_steps: 5, + monster_stats, } } } @@ -73,18 +95,39 @@ pub struct Orc { color: Color, experience_gain: usize, ticks_between_steps: u128, + monster_stats: MonsterStats, } impl Orc { pub fn new_with_position(position: Position) -> Self { + let monster_stats = MonsterStats { + body: 10, + agility: 6, + mind: 2, + strength: 2, + toughness: 3, + movement: 0, + dexterity: 3, + wisdom: 1, + aura: 0, + max_life: 23, + defense: 14, + initiative: 1, + walk: 4, + hit: 13, + shoot: 10, + cast: 0, + targeted_cast: 0, + }; Self { name: "orc".to_string(), - life: 4, + life: 23, position, symbol: String::from("O"), color: Color::DarkGray, experience_gain: 10, ticks_between_steps: 10, + monster_stats } } } @@ -108,11 +151,17 @@ fn monsters_can_move() { #[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.get_life(), 3); 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(), 2); + m.decrease_life(3); assert_eq!(m.get_life(), 0); assert_eq!(m.is_dead(), true); +} + +#[test] +fn test_rat_values() { + let mut m = Rat::new_with_position(Position::new(0, 0, 0)); + assert_eq!(m.monster_stats.max_life, 3); } \ No newline at end of file diff --git a/src/player.rs b/src/player.rs index 0801005..0d700e4 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,32 +1,33 @@ use std::cmp::{max, min}; +use crate::dungeon_slayer::PlayerStats; use crate::position::{HasPosition, Position}; pub struct Player { name: String, position: Position, life: i16, - max_life: i16, gold: usize, experience: usize, + pub(crate) player_stats: PlayerStats, } impl Player { - pub fn new(name: &str, max_life: i16) -> Player { + pub fn new(name: &str, player_stats: PlayerStats) -> Player { Player { name: name.to_string(), position: Position::new(0, 0, 0), - life: max_life, - max_life, + life: player_stats.get_max_life() as i16, gold: 0, experience: 0, + player_stats, } } pub fn get_name(&self) -> String { return self.name.clone(); } pub fn change_life(&mut self, by: i16) { - self.life = max(0, min(self.max_life, self.life + by)); + self.life = max(0, min(self.get_max_life(), self.life + by)); } pub fn get_life(&self) -> i16 { self.life @@ -34,9 +35,9 @@ impl Player { /// returns true if the player is dead (life <= 0) pub fn is_dead(&self) -> bool { self.life <= 0 } /// returns true if the player's life is at maximum - pub fn is_healthy(&self) -> bool { self.life == self.max_life } + pub fn is_healthy(&self) -> bool { self.life == self.get_max_life() } pub fn get_max_life(&self) -> i16 { - self.max_life + self.player_stats.get_max_life() as i16 } /// add the given amount to the players gold stash @@ -65,16 +66,37 @@ fn test_get_name() { name: "Teddy Tester".to_string(), position: Position::new(0, 1, 1), life: 5, - max_life: 10, gold: 0, experience: 0, + player_stats: PlayerStats { + body: 0, + agility: 0, + mind: 0, + strength: 0, + toughness: 0, + movement: 0, + dexterity: 0, + wisdom: 0, + aura: 0, + }, }; assert_eq!(p.get_name(), "Teddy Tester"); } #[test] fn test_can_receive_gold() { - let mut p = Player::new("Teddy Tester", 10); + let player_stats = PlayerStats { + body: 8, + agility: 8, + mind: 4, + strength: 3, + toughness: 3, + movement: 1, + dexterity: 1, + wisdom: 0, + aura: 0, + }; + let mut p = Player::new("Teddy Tester", player_stats); assert_eq!(p.get_gold(), 0); p.retrieve_gold(13); @@ -88,9 +110,19 @@ fn test_change_life() { name: "Teddy Tester".to_string(), position: Position::new(0, 1, 1), life: 5, - max_life: 10, gold: 0, experience: 0, + player_stats: PlayerStats { + body: 0, + agility: 0, + mind: 0, + strength: 0, + toughness: 0, + movement: 0, + dexterity: 0, + wisdom: 0, + aura: 0, + }, }; assert_eq!(p.get_life(), 5); p.change_life(-2); @@ -103,7 +135,18 @@ fn test_change_life() { #[test] fn player_can_move() { - let mut p = Player::new("Teddy Tester", 10); + let player_stats = PlayerStats { + body: 8, + agility: 8, + mind: 4, + strength: 3, + toughness: 3, + movement: 1, + dexterity: 1, + wisdom: 0, + aura: 0, + }; + let mut p = Player::new("Teddy Tester", player_stats); assert_eq!(p.get_position(), &Position::new(0, 0, 0)); p.get_position().change(1, 2); assert_eq!(p.get_position(), &Position::new(0, 1, 2)); @@ -121,9 +164,19 @@ fn test_max_life() { name: "Teddy Tester".to_string(), position: Position::new(0, 1, 1), life: 5, - max_life: 10, gold: 0, experience: 0, + player_stats: PlayerStats { + body: 0, + agility: 0, + mind: 0, + strength: 0, + toughness: 0, + movement: 0, + dexterity: 0, + wisdom: 0, + aura: 0, + }, }; assert_eq!(p.get_max_life(), 10); }