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
if !player.is_healthy() {
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();
messages.insert(0, format!("picked up potion and gained {} hp.", new - old).to_string());
self.health = 0;

View File

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

View File

@ -1,3 +1,5 @@
use crate::dungeon_slayer::do_challenge;
#[cfg(test)]
use crate::dungeon_slayer::PlayerStats;
use crate::level::{Level, StructureElement};
use crate::level_generator::LevelGenerator;
@ -157,11 +159,19 @@ impl Game {
match m {
None => {}
Some(m) => {
// TODO fight the monster
self.player.change_life(-1);
m.decrease_life(1);
self.messages.insert(0, format!("{} hits you.", m.get_name()).to_string());
self.messages.insert(0, format!("you hit {}.", m.get_name()).to_string());
// Player attacks monster
let attack = do_challenge(self.player.player_stats.get_hit());
if attack.0 {
let mut damage = attack.1;
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
if m.is_dead() {
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 mut g = Game::new(p);
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);
}

View File

@ -6,6 +6,7 @@ use rand::rngs::ThreadRng;
#[cfg(test)]
use crate::artifacts::{Chest, Potion};
use crate::artifacts::Artifact;
use crate::dungeon_slayer::do_challenge;
#[cfg(test)]
use crate::monster::{Orc, Rat};
use crate::monster::Monster;
@ -138,11 +139,19 @@ impl Level {
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);
if player.get_immutable_position().get_x() == new_x && player.get_immutable_position().get_y() == new_y {
self.monsters[index].decrease_life(1);
player.change_life(-1);
messages.insert(0, format!("{} hits you.", self.monsters[index].get_name()).to_string());
messages.insert(0, format!("you hit {}.", self.monsters[index].get_name()).to_string());
// if the attack did not kill the opponent, back down
// TODO monster attacks
let attack = do_challenge(self.monsters[index].get_hit());
if attack.0 {
let mut damage = attack.1;
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() {
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_experience_gain(&self) -> usize;
fn get_ticks_between_steps(&self) -> u128;
fn get_defense(&self) -> u8;
fn get_hit(&self) -> u8;
#[cfg(test)]
fn get_life(&self) -> usize;
}
@ -26,7 +28,8 @@ macro_rules! default_monster {
self.life = self.life.saturating_sub(by);
}
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)]
fn get_life(&self) -> usize { self.life }
}
@ -127,7 +130,7 @@ impl Orc {
color: Color::DarkGray,
experience_gain: 10,
ticks_between_steps: 10,
monster_stats
monster_stats,
}
}
}
@ -162,6 +165,6 @@ fn monsters_can_die() {
#[test]
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);
}

View File

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