feat: assemble weapon and potions into item file

This commit is contained in:
2026-01-13 22:33:31 +01:00
parent 4d8d2207b0
commit 42bff2595f
5 changed files with 119 additions and 93 deletions

View File

@@ -1,4 +1,6 @@
use crate::weapon::Weapon;
use std::f64::consts::E;
use crate::items::Item;
/// # Character
///
@@ -24,67 +26,72 @@ use crate::weapon::Weapon;
/// for method chaining.
/// - `get_name` is only an accessor of `name`
pub struct Character {
life: u8,
pub life: u8,
name: &'static str,
weapon: Option<Box<Weapon>>,
item: Option<Box<dyn Item>>,
}
impl Character {
/// Creates a new `Character` with the given attributes.
/// Acts as a "static constructor" returning the
/// initialized struct.
pub fn new(life: u8, name: &'static str, weapon: Option<Box<Weapon>>) -> Self {
Character { life, name, weapon }
pub fn new(name: &'static str, life: u8, item: Option<Box<dyn Item>>) -> Self {
Character { life, name, item }
}
pub fn new_no_weapon(life: u8, name: &'static str) -> Self {
pub fn new_no_weapon(name: &'static str, life: u8) -> Self {
Character {
life,
name,
weapon: None,
item: None,
}
}
/// Attacks another character, reducing their `life` by
/// self.damage. Returns `self` to allow method chaining.
pub fn attack(&mut self, other: &mut Self) -> &mut Self {
if let Some(w) = &mut self.weapon {
other.life = w
.if_not_broken(|damage: u8| other.life - damage)
.unwrap_or(0);
w.consume();
pub fn use_item(&mut self, other: Option<&mut Character>) -> () {
let mut item: Option<Box<dyn Item>> = self.item.take(); // pop item and replace to None
if let Some(i) = &mut item {
i.apply(other.unwrap_or(self));
}
self
self.item = item;
}
/// Returns the character's name.
pub fn get_name(&self) -> &str {
self.name
}
pub fn set_item(&mut self, item: Option<Box<dyn Item>>) -> &mut Self {
self.item = item;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::items::{Potion, Weapon};
#[test]
/// Tests that a character is correctly initialized.
fn character_build() {
let c = Character::new_no_weapon(100, "test");
let c = Character::new_no_weapon("test", 100);
assert_eq!(c.life, 100);
assert_eq!(c.name, c.get_name());
}
#[test]
/// Tests that attacking reduces the target's life correctly.
fn character_attack() {
let w = Box::new(Weapon::new("test", 10, 100));
let mut c1 = Character::new(100, "test1", Option::Some(w));
let mut c2 = Character::new_no_weapon(200, "test2");
fn character_item() {
let w = Box::new(Weapon::new("testw", 10, 100));
let p = Box::new(Potion::new("testp", 50));
c1.attack(&mut c2);
assert_eq!(c2.life, 190);
Character::attack(&mut c2, &mut c1);
assert_eq!(c1.life, 100);
let mut c: Character = Character::new_no_weapon("testc", 100);
c.set_item(Some(p)).use_item(None);
assert_eq!(c.life, 150);
c.set_item(Some(w)).use_item(None);
assert_eq!(c.life, 140);
}
}

80
src/items.rs Normal file
View File

@@ -0,0 +1,80 @@
use crate::character::Character;
pub trait Item {
fn apply(&mut self, char: &mut Character) -> ();
fn get_name(&self) -> &'static str;
}
pub struct Weapon {
name: &'static str,
damage: u8,
durability: u8,
}
// #[warn(dead_code)]
impl Weapon {
pub fn new(name: &'static str, damage: u8, durability: u8) -> Self {
Weapon {
name,
damage,
durability,
}
}
fn reduce_durability(&mut self) -> () {
self.durability = self.durability.saturating_sub(self.damage / 2);
}
pub fn is_broken(&self) -> bool {
self.durability == 0
}
}
impl Item for Weapon {
fn apply(&mut self, char: &mut Character) -> () {
if !self.is_broken() {
char.life = char.life.saturating_sub(self.damage);
self.reduce_durability();
}
}
fn get_name(&self) -> &'static str {
self.name
}
}
pub struct Potion {
name: &'static str,
heal: u8,
}
impl Potion {
pub fn new(name: &'static str, heal: u8) -> Self {
Potion { name, heal }
}
}
impl Item for Potion {
fn apply(&mut self, char: &mut Character) -> () {
char.life += self.heal;
}
fn get_name(&self) -> &'static str {
self.name
}
}
#[cfg(test)]
mod tests {
mod weapon {
use super::super::*;
#[test]
fn build() {
let w = Weapon::new("test", 10, 100);
assert_eq!(w.damage, 10);
assert_eq!(w.durability, 100);
assert_eq!(w.get_name(), w.name);
}
}
}

View File

@@ -1,2 +1,2 @@
pub mod character;
pub mod weapon;
pub mod items;

View File

@@ -1 +1,6 @@
fn main() {}
use bakersadventure::items::{Item, Weapon};
fn main() {
let mut list: [Box<dyn Item>; 1] = [Box::new(Weapon::new("name", 1, 1))];
}

View File

@@ -1,66 +0,0 @@
pub struct Weapon {
name: &'static str,
damage: u8,
durability: u8,
}
impl Weapon {
pub fn new(name: &'static str, damage: u8, durability: u8) -> Self {
Weapon {
name,
damage,
durability,
}
}
pub fn consume(&mut self) -> &mut Self {
self.durability -= (self.damage / 2) as u8;
self
}
fn is_broken(&self) -> bool {
self.durability <= 0
}
pub fn if_not_broken<F, T>(&mut self, fun: F) -> Option<T>
where
F: FnOnce(u8) -> T,
{
if !self.is_broken() {
Some(fun(self.damage))
} else {
None
}
}
pub fn get_name(&self) -> &'static str {
self.name
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn weapon_build() {
let w = Weapon::new("test", 10, 100);
assert_eq!(w.damage, 10);
assert_eq!(w.durability, 100);
assert_eq!(w.get_name(), w.name);
}
#[test]
fn weapon_consumption() {
let mut w = Weapon::new("test", 10, 100);
w.consume();
assert_eq!(w.durability, 95);
}
#[test]
fn weapon_if_not_broken() {
let mut w = Weapon::new("test", 10, 100);
let val: u8 = 50;
assert_eq!(w.if_not_broken(|d: u8| { val - d }).unwrap(), 40);
}
}