Player and monster attacks follow the dungeon slayer rules
This commit is contained in:
parent
0431d872e4
commit
cad08bd300
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
22
src/game.rs
22
src/game.rs
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
19
src/level.rs
19
src/level.rs
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue