Player and monster attacks follow the dungeon slayer rules

This commit is contained in:
Joachim Lusiardi 2024-01-06 13:02:31 +01:00
parent 0431d872e4
commit cad08bd300
6 changed files with 59 additions and 25 deletions

View File

@ -72,7 +72,7 @@ impl Artifact for Potion {
// only consume potion of the player can gain at least one health point // only consume potion of the player can gain at least one health point
if !player.is_healthy() { if !player.is_healthy() {
let old = player.get_life(); let old = player.get_life();
player.change_life(self.health.try_into().unwrap()); player.increase_life(self.health.try_into().unwrap());
let new = player.get_life(); let new = player.get_life();
messages.insert(0, format!("picked up potion and gained {} hp.", new - old).to_string()); messages.insert(0, format!("picked up potion and gained {} hp.", new - old).to_string());
self.health = 0; self.health = 0;

View File

@ -1,16 +1,22 @@
use rand::Rng; use rand::Rng;
pub fn do_challenge(stat: u8) -> bool { pub fn do_challenge(stat: u8) -> (bool, u8) {
if stat <= 20 { if stat <= 20 {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let dice_roll = rng.gen_range(1..21); let dice_roll = rng.gen_range(1..21);
return match dice_roll { return match dice_roll {
20 => { false } 20 => { (false, 0) }
1 => { true } 1 => { (true, dice_roll) }
_ => dice_roll <= stat _ => {
if dice_roll <= stat {
(true, stat - dice_roll)
} else {
(false, 0)
}
}
}; };
} }
true (false, 0)
} }
pub struct PlayerStats { pub struct PlayerStats {
@ -82,6 +88,9 @@ impl PlayerStats {
pub fn get_defense(&self) -> u8 { pub fn get_defense(&self) -> u8 {
self.body + self.toughness // TODO + self.armor self.body + self.toughness // TODO + self.armor
} }
pub fn get_hit(&self) -> u8 {
self.body + self.strength // TODO + self.weapon
}
} }
pub struct MonsterStats { pub struct MonsterStats {

View File

@ -1,3 +1,5 @@
use crate::dungeon_slayer::do_challenge;
#[cfg(test)]
use crate::dungeon_slayer::PlayerStats; use crate::dungeon_slayer::PlayerStats;
use crate::level::{Level, StructureElement}; use crate::level::{Level, StructureElement};
use crate::level_generator::LevelGenerator; use crate::level_generator::LevelGenerator;
@ -157,11 +159,19 @@ impl Game {
match m { match m {
None => {} None => {}
Some(m) => { Some(m) => {
// TODO fight the monster // Player attacks monster
self.player.change_life(-1); let attack = do_challenge(self.player.player_stats.get_hit());
m.decrease_life(1); if attack.0 {
self.messages.insert(0, format!("{} hits you.", m.get_name()).to_string()); let mut damage = attack.1;
self.messages.insert(0, format!("you hit {}.", m.get_name()).to_string()); let defense = do_challenge(m.get_defense());
if defense.0 {
damage = damage.saturating_sub(defense.1);
}
self.messages.insert(0, format!("attack on {} successful with {} damage.", m.get_name(), damage).to_string());
m.decrease_life(damage as usize);
} else {
self.messages.insert(0, format!("attack on {} failed.", m.get_name()).to_string());
}
// monster died, player gains experience // monster died, player gains experience
if m.is_dead() { if m.is_dead() {
self.player.gain_experience(m.get_experience_gain()); self.player.gain_experience(m.get_experience_gain());
@ -245,7 +255,7 @@ fn game_has_mutable_player() {
let p = Player::new("foo", player_stats); let p = Player::new("foo", player_stats);
let mut g = Game::new(p); let mut g = Game::new(p);
assert_eq!(g.get_player().get_name(), "foo"); assert_eq!(g.get_player().get_name(), "foo");
g.get_mutable_player().change_life(-1); g.get_mutable_player().decrease_life(1);
assert_eq!(g.get_player().get_life(), 20); assert_eq!(g.get_player().get_life(), 20);
} }

View File

@ -6,6 +6,7 @@ use rand::rngs::ThreadRng;
#[cfg(test)] #[cfg(test)]
use crate::artifacts::{Chest, Potion}; use crate::artifacts::{Chest, Potion};
use crate::artifacts::Artifact; use crate::artifacts::Artifact;
use crate::dungeon_slayer::do_challenge;
#[cfg(test)] #[cfg(test)]
use crate::monster::{Orc, Rat}; use crate::monster::{Orc, Rat};
use crate::monster::Monster; use crate::monster::Monster;
@ -138,11 +139,19 @@ impl Level {
if self.can_monster_move(self.monsters[index].as_ref(), dx, dy) { if self.can_monster_move(self.monsters[index].as_ref(), dx, dy) {
let (new_x, new_y) = self.monsters[index].get_position().change(dx, dy); let (new_x, new_y) = self.monsters[index].get_position().change(dx, dy);
if player.get_immutable_position().get_x() == new_x && player.get_immutable_position().get_y() == new_y { if player.get_immutable_position().get_x() == new_x && player.get_immutable_position().get_y() == new_y {
self.monsters[index].decrease_life(1); // TODO monster attacks
player.change_life(-1); let attack = do_challenge(self.monsters[index].get_hit());
messages.insert(0, format!("{} hits you.", self.monsters[index].get_name()).to_string()); if attack.0 {
messages.insert(0, format!("you hit {}.", self.monsters[index].get_name()).to_string()); let mut damage = attack.1;
// if the attack did not kill the opponent, back down let defense = do_challenge(player.player_stats.get_defense());
if defense.0 {
damage = damage.saturating_sub(defense.1);
}
messages.insert(0, format!("attack from {} successful with {} damage.", self.monsters[index].get_name(), damage).to_string());
player.decrease_life(damage);
} else {
messages.insert(0, format!("attack from {} failed.", self.monsters[index].get_name()).to_string());
}
if !player.is_dead() { if !player.is_dead() {
self.monsters[index].get_position().change(-dx, -dy); self.monsters[index].get_position().change(-dx, -dy);
} }

View File

@ -11,6 +11,8 @@ pub trait Monster: HasPosition {
// fn get_immutable_position(&self) -> &Position; // fn get_immutable_position(&self) -> &Position;
fn get_experience_gain(&self) -> usize; fn get_experience_gain(&self) -> usize;
fn get_ticks_between_steps(&self) -> u128; fn get_ticks_between_steps(&self) -> u128;
fn get_defense(&self) -> u8;
fn get_hit(&self) -> u8;
#[cfg(test)] #[cfg(test)]
fn get_life(&self) -> usize; fn get_life(&self) -> usize;
} }
@ -26,7 +28,8 @@ macro_rules! default_monster {
self.life = self.life.saturating_sub(by); self.life = self.life.saturating_sub(by);
} }
fn get_ticks_between_steps(&self) -> u128 { self.ticks_between_steps } fn get_ticks_between_steps(&self) -> u128 { self.ticks_between_steps }
fn get_defense(&self) -> u8 { self.monster_stats.defense }
fn get_hit(&self) -> u8 { self.monster_stats.hit }
#[cfg(test)] #[cfg(test)]
fn get_life(&self) -> usize { self.life } fn get_life(&self) -> usize { self.life }
} }
@ -127,7 +130,7 @@ impl Orc {
color: Color::DarkGray, color: Color::DarkGray,
experience_gain: 10, experience_gain: 10,
ticks_between_steps: 10, ticks_between_steps: 10,
monster_stats monster_stats,
} }
} }
} }
@ -162,6 +165,6 @@ fn monsters_can_die() {
#[test] #[test]
fn test_rat_values() { fn test_rat_values() {
let mut m = Rat::new_with_position(Position::new(0, 0, 0)); let m = Rat::new_with_position(Position::new(0, 0, 0));
assert_eq!(m.monster_stats.max_life, 3); assert_eq!(m.monster_stats.max_life, 3);
} }

View File

@ -26,8 +26,11 @@ impl Player {
pub fn get_name(&self) -> String { pub fn get_name(&self) -> String {
return self.name.clone(); return self.name.clone();
} }
pub fn change_life(&mut self, by: i16) { pub fn decrease_life(&mut self, by: u8) {
self.life = max(0, min(self.get_max_life(), self.life + by)); self.life = max(0, self.life.saturating_sub(by as i16));
}
pub fn increase_life(&mut self, by: u8) {
self.life = min(self.life.saturating_add(by as i16), self.get_max_life());
} }
pub fn get_life(&self) -> i16 { pub fn get_life(&self) -> i16 {
self.life self.life
@ -125,11 +128,11 @@ fn test_change_life() {
}, },
}; };
assert_eq!(p.get_life(), 5); assert_eq!(p.get_life(), 5);
p.change_life(-2); p.decrease_life(2);
assert_eq!(p.get_life(), 3); assert_eq!(p.get_life(), 3);
p.change_life(10); p.increase_life(10);
assert_eq!(p.get_life(), 10); assert_eq!(p.get_life(), 10);
p.change_life(-12); p.decrease_life(12);
assert_eq!(p.get_life(), 0); assert_eq!(p.get_life(), 0);
} }