sb-core: DomainError + signature conversions (WI-2, issue #2)
This commit is contained in:
parent
093012af20
commit
423997f2d7
5 changed files with 76 additions and 38 deletions
|
|
@ -1,4 +1,5 @@
|
|||
use crate::constants;
|
||||
use crate::error::DomainError;
|
||||
use crate::spells;
|
||||
use sb_shared::{Character, CharacterStats, Class, Race};
|
||||
|
||||
|
|
@ -351,17 +352,17 @@ pub fn level_up_cost(level: i32) -> i32 {
|
|||
constants::LEVEL_UP_GOLD_PER_LEVEL * level
|
||||
}
|
||||
|
||||
pub fn level_up(char: &mut Character) -> Result<i32, &'static str> {
|
||||
pub fn level_up(char: &mut Character) -> Result<i32, DomainError> {
|
||||
if char.level >= MAX_LEVEL {
|
||||
return Err("already at max level");
|
||||
return Err(DomainError::AlreadyAtMaxLevel);
|
||||
}
|
||||
let next_level = char.level + 1;
|
||||
if char.xp < xp_for_level(next_level) {
|
||||
return Err("not enough XP");
|
||||
return Err(DomainError::NotEnoughXp);
|
||||
}
|
||||
let cost = level_up_cost(next_level);
|
||||
if char.gold < cost {
|
||||
return Err("not enough gold");
|
||||
return Err(DomainError::NotEnoughGold);
|
||||
}
|
||||
char.gold -= cost;
|
||||
char.level = next_level;
|
||||
|
|
@ -483,7 +484,7 @@ mod tests {
|
|||
c.level = MAX_LEVEL;
|
||||
c.xp = i64::MAX;
|
||||
c.gold = i32::MAX;
|
||||
assert_eq!(level_up(&mut c), Err("already at max level"));
|
||||
assert_eq!(level_up(&mut c), Err(DomainError::AlreadyAtMaxLevel));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -512,7 +513,7 @@ mod tests {
|
|||
let mut c = base_char();
|
||||
c.xp = 100;
|
||||
c.gold = 10000;
|
||||
assert_eq!(level_up(&mut c), Err("not enough XP"));
|
||||
assert_eq!(level_up(&mut c), Err(DomainError::NotEnoughXp));
|
||||
assert_eq!(c.level, 1);
|
||||
assert_eq!(c.gold, 10000);
|
||||
}
|
||||
|
|
@ -522,7 +523,7 @@ mod tests {
|
|||
let mut c = base_char();
|
||||
c.xp = 1800;
|
||||
c.gold = 0;
|
||||
assert_eq!(level_up(&mut c), Err("not enough gold"));
|
||||
assert_eq!(level_up(&mut c), Err(DomainError::NotEnoughGold));
|
||||
assert_eq!(c.level, 1);
|
||||
}
|
||||
|
||||
|
|
|
|||
23
crates/sb-core/src/error.rs
Normal file
23
crates/sb-core/src/error.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum DomainError {
|
||||
#[error("character is incapacitated")]
|
||||
CharacterIncapacitated,
|
||||
#[error("not enough nerve")]
|
||||
NotEnoughNerve,
|
||||
#[error("not enough energy")]
|
||||
NotEnoughEnergy,
|
||||
#[error("not enough gold")]
|
||||
NotEnoughGold,
|
||||
#[error("not enough XP")]
|
||||
NotEnoughXp,
|
||||
#[error("already at max level")]
|
||||
AlreadyAtMaxLevel,
|
||||
#[error("invalid stat name")]
|
||||
InvalidStat,
|
||||
#[error("item not in inventory")]
|
||||
ItemNotInInventory,
|
||||
#[error("nothing equipped in slot")]
|
||||
NothingEquippedInSlot,
|
||||
#[error("encounter references unknown enemy")]
|
||||
UnknownEncounterEnemy,
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ pub mod character;
|
|||
pub mod combat;
|
||||
pub mod constants;
|
||||
pub mod data;
|
||||
pub mod error;
|
||||
pub mod items;
|
||||
pub mod services;
|
||||
pub mod songs;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::data::{EncounterEntry, EnemyDef};
|
||||
use crate::error::DomainError;
|
||||
use rand::Rng;
|
||||
use sb_shared::{AttackType, Character, Item};
|
||||
|
||||
|
|
@ -6,8 +7,11 @@ pub fn pick_enemy(
|
|||
rng: &mut impl Rng,
|
||||
encounters: &[EncounterEntry],
|
||||
enemies: &[EnemyDef],
|
||||
) -> EnemyDef {
|
||||
) -> Result<EnemyDef, DomainError> {
|
||||
let total_weight: i32 = encounters.iter().map(|e| e.weight).sum();
|
||||
if encounters.is_empty() || total_weight <= 0 {
|
||||
return Err(DomainError::UnknownEncounterEnemy);
|
||||
}
|
||||
let roll: i32 = rng.random_range(0..total_weight);
|
||||
let mut remaining = roll;
|
||||
for enc in encounters {
|
||||
|
|
@ -16,15 +20,18 @@ pub fn pick_enemy(
|
|||
return enemies
|
||||
.iter()
|
||||
.find(|e| e.id == enc.enemy_id)
|
||||
.expect("encounter references valid enemy")
|
||||
.clone();
|
||||
.cloned()
|
||||
.ok_or(DomainError::UnknownEncounterEnemy);
|
||||
}
|
||||
}
|
||||
let last = encounters
|
||||
.last()
|
||||
.ok_or(DomainError::UnknownEncounterEnemy)?;
|
||||
enemies
|
||||
.iter()
|
||||
.find(|e| e.id == encounters.last().unwrap().enemy_id)
|
||||
.expect("encounter references valid enemy")
|
||||
.clone()
|
||||
.find(|e| e.id == last.enemy_id)
|
||||
.cloned()
|
||||
.ok_or(DomainError::UnknownEncounterEnemy)
|
||||
}
|
||||
|
||||
pub fn equipped_attack_type(
|
||||
|
|
@ -72,12 +79,12 @@ pub fn total_defense(
|
|||
total
|
||||
}
|
||||
|
||||
pub fn equip_item(char: &mut Character, item: &Item, item_id: &str) -> Result<(), &'static str> {
|
||||
pub fn equip_item(char: &mut Character, item: &Item, item_id: &str) -> Result<(), DomainError> {
|
||||
let inv_pos = char
|
||||
.inventory
|
||||
.iter()
|
||||
.position(|id| id == item_id)
|
||||
.ok_or("item not in inventory")?;
|
||||
.ok_or(DomainError::ItemNotInInventory)?;
|
||||
|
||||
char.inventory.remove(inv_pos);
|
||||
let slot = item.slot.clone();
|
||||
|
|
@ -88,20 +95,20 @@ pub fn equip_item(char: &mut Character, item: &Item, item_id: &str) -> Result<()
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unequip_item(char: &mut Character, slot: &str) -> Result<String, &'static str> {
|
||||
pub fn unequip_item(char: &mut Character, slot: &str) -> Result<String, DomainError> {
|
||||
let prev = char
|
||||
.equipment
|
||||
.insert(slot.to_string(), None)
|
||||
.flatten()
|
||||
.ok_or("nothing equipped in slot")?;
|
||||
.ok_or(DomainError::NothingEquippedInSlot)?;
|
||||
|
||||
char.inventory.push(prev.clone());
|
||||
Ok(prev)
|
||||
}
|
||||
|
||||
pub fn train_stat(char: &mut Character, stat: &str) -> Result<i64, &'static str> {
|
||||
pub fn train_stat(char: &mut Character, stat: &str) -> Result<i64, DomainError> {
|
||||
if char.energy < crate::constants::TRAINING_ENERGY_COST {
|
||||
return Err("not enough energy");
|
||||
return Err(DomainError::NotEnoughEnergy);
|
||||
}
|
||||
let current = match stat {
|
||||
"strength" => char.stats.strength,
|
||||
|
|
@ -109,11 +116,11 @@ pub fn train_stat(char: &mut Character, stat: &str) -> Result<i64, &'static str>
|
|||
"dexterity" => char.stats.dexterity,
|
||||
"constitution" => char.stats.constitution,
|
||||
"luck" => char.stats.luck,
|
||||
_ => return Err("invalid stat"),
|
||||
_ => return Err(DomainError::InvalidStat),
|
||||
};
|
||||
let cost = super::character::training_cost(current);
|
||||
if char.gold < cost {
|
||||
return Err("not enough gold");
|
||||
return Err(DomainError::NotEnoughGold);
|
||||
}
|
||||
char.energy -= crate::constants::TRAINING_ENERGY_COST;
|
||||
char.gold -= cost;
|
||||
|
|
@ -135,7 +142,7 @@ pub fn train_stat(char: &mut Character, stat: &str) -> Result<i64, &'static str>
|
|||
"dexterity" => char.stats.dexterity,
|
||||
"constitution" => char.stats.constitution,
|
||||
"luck" => char.stats.luck,
|
||||
_ => return Err("invalid stat"),
|
||||
_ => return Err(DomainError::InvalidStat),
|
||||
};
|
||||
Ok(new_val)
|
||||
}
|
||||
|
|
@ -225,20 +232,29 @@ mod tests {
|
|||
fn train_stat_fails_without_energy() {
|
||||
let mut c = base_char();
|
||||
c.energy = 0;
|
||||
assert_eq!(train_stat(&mut c, "strength"), Err("not enough energy"));
|
||||
assert_eq!(
|
||||
train_stat(&mut c, "strength"),
|
||||
Err(DomainError::NotEnoughEnergy)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn train_stat_fails_without_gold() {
|
||||
let mut c = base_char();
|
||||
c.gold = 0;
|
||||
assert_eq!(train_stat(&mut c, "strength"), Err("not enough gold"));
|
||||
assert_eq!(
|
||||
train_stat(&mut c, "strength"),
|
||||
Err(DomainError::NotEnoughGold)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn train_stat_fails_invalid_stat() {
|
||||
let mut c = base_char();
|
||||
assert_eq!(train_stat(&mut c, "charisma"), Err("invalid stat"));
|
||||
assert_eq!(
|
||||
train_stat(&mut c, "charisma"),
|
||||
Err(DomainError::InvalidStat)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -283,7 +299,7 @@ mod tests {
|
|||
let mut c = base_char();
|
||||
assert_eq!(
|
||||
equip_item(&mut c, &sword(), "sword1"),
|
||||
Err("item not in inventory")
|
||||
Err(DomainError::ItemNotInInventory)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -303,7 +319,7 @@ mod tests {
|
|||
let mut c = base_char();
|
||||
assert_eq!(
|
||||
unequip_item(&mut c, "main_hand"),
|
||||
Err("nothing equipped in slot")
|
||||
Err(DomainError::NothingEquippedInSlot)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -407,7 +423,7 @@ mod tests {
|
|||
];
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
|
||||
for _ in 0..100 {
|
||||
let enemy = pick_enemy(&mut rng, &encounters, &enemies);
|
||||
let enemy = pick_enemy(&mut rng, &encounters, &enemies).unwrap();
|
||||
assert!(enemy.id == "kobold" || enemy.id == "rat");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue