summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorArne Dußin2021-04-26 10:06:58 +0200
committerArne Dußin2021-04-26 10:06:58 +0200
commit7ae33fba15b2adf9f903869b3c896a7490427b04 (patch)
treecc6605131081ee444165e92284c8ffe9fa6b9f63 /src
downloadcappuccino-7ae33fba15b2adf9f903869b3c896a7490427b04.tar.gz
cappuccino-7ae33fba15b2adf9f903869b3c896a7490427b04.zip
Initial commit
Diffstat (limited to 'src')
-rw-r--r--src/character/mod.rs207
-rw-r--r--src/character/stats.rs117
-rw-r--r--src/dice.rs227
-rw-r--r--src/inventory.rs41
-rw-r--r--src/main.rs5
5 files changed, 597 insertions, 0 deletions
diff --git a/src/character/mod.rs b/src/character/mod.rs
new file mode 100644
index 0000000..fc67e42
--- /dev/null
+++ b/src/character/mod.rs
@@ -0,0 +1,207 @@
+pub mod stats;
+use std::io;
+
+use io::BufRead;
+pub use stats::*;
+
+use crate::inventory::Inventory;
+
+pub const MAX_LEVEL: u8 = 10;
+
+fn swap_stats_according_to_string(stats: &mut Stats, string: String)
+{
+ let string = string.trim();
+ let parts: Vec<&str> = string.split_whitespace().collect();
+
+ if parts.len() == 2 && stats.swap(parts[0], parts[1]) {
+ println!("{} and {} values have been switched.", parts[0], parts[1]);
+ }
+ else {
+ println!("Not switching any stats.");
+ }
+}
+
+pub fn cli_create_character() -> Character
+{
+ println!("Starting character creation.");
+ println!("Rolling initial stats...");
+
+ let mut stats = Stats::roll_initial();
+ println!("Initial stats: {}", stats);
+
+ println!(
+ "Would you like to swap two stats' values? If so, write the names of them seperated by a \
+ whitespace and press enter, if not just press enter."
+ );
+
+ let mut buf = String::new();
+
+ {
+ let stdin = io::stdin();
+ let mut lock = stdin.lock();
+ lock.read_line(&mut buf)
+ .expect("Unable to read from console");
+ }
+
+ swap_stats_according_to_string(&mut stats, buf);
+
+ unimplemented!()
+}
+
+#[derive(Clone, Debug)]
+pub struct Character
+{
+ health: u8,
+ max_health: u8,
+ stats: Stats,
+ /// Free text describing the special trait of the character
+ special_traits: Vec<String>,
+ /// Total value of experience. The level can be calculated through it
+ experience: u32,
+ wealth: u32,
+ inventory: Inventory,
+ goals: Vec<String>,
+}
+
+impl Character
+{
+ pub fn new(
+ health: u8,
+ max_health: u8,
+ stats: Stats,
+ special_traits: Vec<String>,
+ experience: u32,
+ wealth: u32,
+ inventory: Inventory,
+ goals: Vec<String>,
+ ) -> Self
+ {
+ Self {
+ health,
+ max_health,
+ stats,
+ special_traits,
+ experience,
+ wealth,
+ inventory,
+ goals,
+ }
+ }
+
+ pub fn stats(&self) -> &Stats { &self.stats }
+ pub fn stats_mut(&mut self) -> &mut Stats { &mut self.stats }
+
+ pub fn special_traits(&self) -> &Vec<String> { &self.special_traits }
+
+ pub fn experience(&self) -> u32 { self.experience }
+
+ pub fn wealth(&self) -> u32 { self.wealth }
+
+ pub fn level(&self) -> u8
+ {
+ /* Start iteration at first level */
+ let mut level_counter = 1;
+ let mut xp_remaining = self.experience;
+ let mut xp_required_for_levelup = 200;
+
+ while level_counter < MAX_LEVEL && xp_remaining >= xp_required_for_levelup {
+ level_counter += 1;
+ xp_remaining -= xp_required_for_levelup;
+ xp_required_for_levelup += 100;
+ }
+
+ level_counter
+ }
+}
+
+#[cfg(test)]
+mod tests
+{
+ use super::*;
+
+ const DEFAULT_STATS: Stats = Stats::from_values([10; NUM_STATS]);
+
+ #[test]
+ fn create_character()
+ {
+ let character = Character::new(
+ 10,
+ 10,
+ DEFAULT_STATS.clone(),
+ Vec::new(),
+ 1,
+ 2,
+ Inventory::new(),
+ Vec::new(),
+ );
+
+ assert_eq!(character.stats(), &DEFAULT_STATS);
+ assert_eq!(character.special_traits(), &Vec::<String>::new());
+ assert_eq!(character.experience(), 1);
+ assert_eq!(character.wealth, 2);
+ }
+
+ #[test]
+ fn min_level_calculation()
+ {
+ let character = Character::new(
+ 10,
+ 10,
+ DEFAULT_STATS.clone(),
+ Vec::new(),
+ 199,
+ 0,
+ Inventory::new(),
+ Vec::new(),
+ );
+
+ assert_eq!(character.level(), 1);
+ }
+
+ #[test]
+ fn mid_level_calculation()
+ {
+ let character = Character::new(
+ 10,
+ 10,
+ DEFAULT_STATS.clone(),
+ Vec::new(),
+ 900,
+ 0,
+ Inventory::new(),
+ Vec::new(),
+ );
+
+ assert_eq!(character.level(), 4);
+ }
+
+ #[test]
+ fn max_level_calculation()
+ {
+ let character = Character::new(
+ 10,
+ 10,
+ DEFAULT_STATS.clone(),
+ Vec::new(),
+ 5400,
+ 0,
+ Inventory::new(),
+ Vec::new(),
+ );
+
+ assert_eq!(character.level(), 10);
+
+ let character = Character::new(
+ 10,
+ 10,
+ DEFAULT_STATS.clone(),
+ Vec::new(),
+ 6500,
+ 0,
+ Inventory::new(),
+ Vec::new(),
+ );
+
+ assert_eq!(character.level(), 10);
+ }
+}
diff --git a/src/character/stats.rs b/src/character/stats.rs
new file mode 100644
index 0000000..d405eb7
--- /dev/null
+++ b/src/character/stats.rs
@@ -0,0 +1,117 @@
+use std::fmt::{self, Display, Formatter};
+
+use rand::prelude::ThreadRng;
+use rand::Rng;
+
+pub const NUM_STATS: usize = 7;
+pub const STATS: [&str; NUM_STATS] = [
+ "charisma",
+ "constitution",
+ "dexterity",
+ "health",
+ "intelligence",
+ "strength",
+ "wisdom",
+];
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Stats
+{
+ values: [u8; NUM_STATS],
+}
+
+impl Stats
+{
+ pub const fn from_values(values: [u8; NUM_STATS]) -> Self { Self { values } }
+
+ fn roll_initial_value(rng: &mut ThreadRng) -> u8
+ {
+ let mut v = 0;
+ for _ in 0..3 {
+ v += rng.gen_range(1..=6);
+ }
+
+ v
+ }
+
+ pub fn roll_initial() -> Self
+ {
+ let mut rng = rand::thread_rng();
+ let mut values = [0; NUM_STATS];
+ for v in &mut values {
+ *v = Self::roll_initial_value(&mut rng);
+ }
+
+ Self { values }
+ }
+
+ pub fn swap(&mut self, val1: &str, val2: &str) -> bool
+ {
+ unimplemented!();
+ }
+
+ pub fn get(&self, attribute: &str) -> Option<u8>
+ {
+ match STATS.binary_search(&attribute) {
+ Ok(pos) => Some(self.values[pos]),
+ Err(_) => None,
+ }
+ }
+
+ pub fn get_mut(&mut self, attribute: &str) -> Option<&mut u8>
+ {
+ match STATS.binary_search(&attribute) {
+ Ok(pos) => Some(self.values.get_mut(pos).unwrap()),
+ Err(_) => None,
+ }
+ }
+}
+
+impl Display for Stats
+{
+ fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error>
+ {
+ writeln!(fmt, "[")?;
+ for i in 0..NUM_STATS {
+ writeln!(fmt, " {} = {}", STATS[i], self.values[i])?;
+ }
+ writeln!(fmt, "]")?;
+
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests
+{
+ use super::*;
+
+ fn test_stats() -> Stats { Stats::from_values([1, 2, 3, 4, 5, 6, 7]) }
+
+ #[test]
+ fn swap_same_argument()
+ {
+ let mut stats = test_stats();
+ assert_eq!(stats.get("intelligence"), Some(5));
+ assert!(stats.swap("intelligence", "intelligence"));
+ assert_eq!(stats.get("intelligence"), Some(5));
+
+ assert_eq!(stats.get("wisdom"), Some(7));
+ assert!(stats.swap("wisdom", "wisdom"));
+ assert_eq!(stats.get("wisdom"), Some(7));
+ }
+
+ #[test]
+ fn swap_invalid_argument()
+ {
+ let mut stats = test_stats();
+ assert!(!stats.swap("", "intelligence"));
+ assert_eq!(stats, test_stats());
+ assert!(!stats.swap("intelligence", ""));
+ assert_eq!(stats, test_stats());
+ assert!(!stats.swap("aoeu", "aoeu"));
+ assert_eq!(stats, test_stats());
+ assert!(!stats.swap("", ""));
+ assert_eq!(stats, test_stats());
+ }
+}
diff --git a/src/dice.rs b/src/dice.rs
new file mode 100644
index 0000000..c89c4c9
--- /dev/null
+++ b/src/dice.rs
@@ -0,0 +1,227 @@
+use std::ops::Add;
+
+use rand::rngs::ThreadRng;
+use rand::Rng;
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct RiskDie(u8);
+pub const NUM_DIE_LEVELS: usize = 6;
+pub const RISK_DIE_VALUES: [u8; NUM_DIE_LEVELS] = [4, 6, 8, 10, 12, 20];
+pub const DELTA_4: RiskDie = RiskDie(0);
+pub const DELTA_6: RiskDie = RiskDie(1);
+pub const DELTA_8: RiskDie = RiskDie(2);
+pub const DELTA_10: RiskDie = RiskDie(3);
+pub const DELTA_12: RiskDie = RiskDie(4);
+pub const DELTA_20: RiskDie = RiskDie(5);
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum Error
+{
+ InvalidLevel,
+ InvalidValue,
+}
+
+impl RiskDie
+{
+ pub fn from_level(level: u8) -> Result<Self, Error>
+ {
+ if (level as usize) < RISK_DIE_VALUES.len() {
+ Ok(Self(level))
+ }
+ else {
+ Err(Error::InvalidLevel)
+ }
+ }
+
+ pub fn from_value(value: u8) -> Result<Self, Error>
+ {
+ for (i, val) in RISK_DIE_VALUES.iter().enumerate() {
+ if *val == value {
+ return Ok(Self(i as u8));
+ }
+ }
+
+ Err(Error::InvalidValue)
+ }
+
+ pub fn level(&self) -> u8 { self.0 }
+
+ pub fn value(&self) -> u8 { RISK_DIE_VALUES[self.0 as usize] }
+
+ /// Roll the die. If the die shows 3 or less, it returns the next lower die.
+ /// If it is more, it returns itself. If it was already the lowest die value
+ /// and it would be stepped down it returns `None`.
+ pub fn roll(self) -> Option<Self>
+ {
+ let mut rng = rand::thread_rng();
+ self.roll_with_rng(&mut rng)
+ }
+
+ pub fn roll_with_rng(self, rng: &mut ThreadRng) -> Option<Self>
+ {
+ let rolled = rng.gen_range(1..=self.value());
+ if rolled > 3 {
+ Some(self)
+ }
+ else {
+ self.level_down()
+ }
+ }
+
+ pub fn can_be_lowered(&self) -> bool { self.0 > 0 }
+
+ pub fn level_down(mut self) -> Option<Self>
+ {
+ if self.can_be_lowered() {
+ self.0 -= 1;
+ Some(self)
+ }
+ else {
+ /* Resource depleted */
+ None
+ }
+ }
+
+ pub fn can_be_upped(&self) -> bool { self.0 < NUM_DIE_LEVELS as u8 - 1 }
+
+ pub fn level_up(mut self) -> Self
+ {
+ if self.can_be_upped() {
+ self.0 += 1;
+ }
+
+ self
+ }
+}
+
+#[rustfmt::skip]
+const DELTA_ADDITION_TABLE: [[u8; NUM_DIE_LEVELS]; NUM_DIE_LEVELS] = [
+ /* ------------------- with d4 d6 d8 d10 d12 d20 */
+ /* delta 4 combinations */ [1, 2, 3, 4, 4, 5],
+ /* delta 6 combinations */ [2, 2, 3, 4, 5, 5],
+ /* delta 8 combinations */ [3, 3, 4, 4, 5, 5],
+ /* delta 10 combinations */ [4, 4, 4, 5, 5, 5],
+ /* delta 12 combinations */ [4, 5, 5, 5, 5, 5],
+ /* delta 20 combinations */ [5, 5, 5, 5, 5, 5],
+];
+impl Add for RiskDie
+{
+ type Output = Self;
+
+ fn add(self, other: Self) -> Self::Output
+ {
+ RiskDie(DELTA_ADDITION_TABLE[self.0 as usize][other.0 as usize])
+ }
+}
+
+#[cfg(test)]
+mod tests
+{
+ use super::*;
+
+ #[test]
+ fn create_die_from_level()
+ {
+ let die = RiskDie::from_level(0).expect("Unable to create die for correct level");
+ assert_eq!(die.value(), 4);
+ let die = RiskDie::from_level(1).expect("Unable to create die for correct level");
+ assert_eq!(die.value(), 6);
+ let die = RiskDie::from_level(2).expect("Unable to create die for correct level");
+ assert_eq!(die.value(), 8);
+ let die = RiskDie::from_level(3).expect("Unable to create die for correct level");
+ assert_eq!(die.value(), 10);
+ let die = RiskDie::from_level(4).expect("Unable to create die for correct level");
+ assert_eq!(die.value(), 12);
+ let die = RiskDie::from_level(5).expect("Unable to create die for correct level");
+ assert_eq!(die.value(), 20);
+ }
+
+ #[test]
+ fn create_die_from_invalid_level()
+ {
+ assert_eq!(
+ RiskDie::from_level(6).expect_err("Should not be able to create die from level"),
+ Error::InvalidLevel
+ );
+ }
+
+ #[test]
+ fn create_from_value()
+ {
+ let die = RiskDie::from_value(4).expect("Unable to create die from correct value");
+ assert_eq!(die.level(), 0);
+ let die = RiskDie::from_value(6).expect("Unable to create die from correct value");
+ assert_eq!(die.level(), 1);
+ let die = RiskDie::from_value(8).expect("Unable to create die from correct value");
+ assert_eq!(die.level(), 2);
+ let die = RiskDie::from_value(10).expect("Unable to create die from correct value");
+ assert_eq!(die.level(), 3);
+ let die = RiskDie::from_value(12).expect("Unable to create die from correct value");
+ assert_eq!(die.level(), 4);
+ let die = RiskDie::from_value(20).expect("Unable to create die from correct value");
+ assert_eq!(die.level(), 5);
+ }
+
+ #[test]
+ fn level_down()
+ {
+ assert_eq!(DELTA_4.level_down(), None);
+ assert_eq!(DELTA_6.level_down(), Some(RiskDie(0)));
+ assert_eq!(DELTA_8.level_down(), Some(RiskDie(1)));
+ assert_eq!(DELTA_10.level_down(), Some(RiskDie(2)));
+ assert_eq!(DELTA_12.level_down(), Some(RiskDie(3)));
+ assert_eq!(DELTA_20.level_down(), Some(RiskDie(4)));
+ }
+
+ #[test]
+ fn level_up()
+ {
+ assert_eq!(DELTA_4.level_up(), RiskDie(1));
+ assert_eq!(DELTA_6.level_up(), RiskDie(2));
+ assert_eq!(DELTA_8.level_up(), RiskDie(3));
+ assert_eq!(DELTA_10.level_up(), RiskDie(4));
+ assert_eq!(DELTA_12.level_up(), RiskDie(5));
+ assert_eq!(DELTA_20.level_up(), RiskDie(5));
+ }
+
+ #[test]
+ fn add_risk_dice()
+ {
+ assert_eq!(DELTA_4 + DELTA_4, DELTA_6);
+ assert_eq!(DELTA_4 + DELTA_6, DELTA_8);
+ assert_eq!(DELTA_4 + DELTA_8, DELTA_10);
+ assert_eq!(DELTA_4 + DELTA_10, DELTA_12);
+ assert_eq!(DELTA_4 + DELTA_12, DELTA_12);
+ assert_eq!(DELTA_4 + DELTA_20, DELTA_20);
+ assert_eq!(DELTA_6 + DELTA_4, DELTA_8);
+ assert_eq!(DELTA_6 + DELTA_6, DELTA_8);
+ assert_eq!(DELTA_6 + DELTA_8, DELTA_10);
+ assert_eq!(DELTA_6 + DELTA_10, DELTA_12);
+ assert_eq!(DELTA_6 + DELTA_12, DELTA_20);
+ assert_eq!(DELTA_6 + DELTA_20, DELTA_20);
+ assert_eq!(DELTA_8 + DELTA_4, DELTA_10);
+ assert_eq!(DELTA_8 + DELTA_6, DELTA_10);
+ assert_eq!(DELTA_8 + DELTA_8, DELTA_12);
+ assert_eq!(DELTA_8 + DELTA_10, DELTA_12);
+ assert_eq!(DELTA_8 + DELTA_12, DELTA_20);
+ assert_eq!(DELTA_8 + DELTA_20, DELTA_20);
+ assert_eq!(DELTA_10 + DELTA_4, DELTA_12);
+ assert_eq!(DELTA_10 + DELTA_6, DELTA_12);
+ assert_eq!(DELTA_10 + DELTA_8, DELTA_12);
+ assert_eq!(DELTA_10 + DELTA_10, DELTA_20);
+ assert_eq!(DELTA_10 + DELTA_12, DELTA_20);
+ assert_eq!(DELTA_10 + DELTA_20, DELTA_20);
+ assert_eq!(DELTA_12 + DELTA_4, DELTA_12);
+ assert_eq!(DELTA_12 + DELTA_6, DELTA_20);
+ assert_eq!(DELTA_12 + DELTA_8, DELTA_20);
+ assert_eq!(DELTA_12 + DELTA_10, DELTA_20);
+ assert_eq!(DELTA_12 + DELTA_12, DELTA_20);
+ assert_eq!(DELTA_12 + DELTA_20, DELTA_20);
+ assert_eq!(DELTA_20 + DELTA_4, DELTA_20);
+ assert_eq!(DELTA_20 + DELTA_6, DELTA_20);
+ assert_eq!(DELTA_20 + DELTA_8, DELTA_20);
+ assert_eq!(DELTA_20 + DELTA_10, DELTA_20);
+ assert_eq!(DELTA_20 + DELTA_12, DELTA_20);
+ assert_eq!(DELTA_20 + DELTA_20, DELTA_20);
+ }
+}
diff --git a/src/inventory.rs b/src/inventory.rs
new file mode 100644
index 0000000..0e44313
--- /dev/null
+++ b/src/inventory.rs
@@ -0,0 +1,41 @@
+use crate::dice::RiskDie;
+
+#[derive(Clone, Debug)]
+pub enum Item
+{
+ Countable(String, u8),
+ Approximate(String, RiskDie),
+}
+
+#[derive(Clone, Debug)]
+pub struct Inventory
+{
+ items: Vec<Item>,
+}
+
+impl Inventory
+{
+ pub fn new() -> Self { Self { items: Vec::new() } }
+
+ pub fn size(&self) -> usize { self.items.len() }
+
+ pub fn add_item(&mut self, item: Item) { self.items.push(item); }
+}
+
+impl Default for Inventory
+{
+ fn default() -> Self { Self::new() }
+}
+
+#[cfg(test)]
+mod tests
+{
+ use super::*;
+
+ #[test]
+ pub fn new_empty_inventory()
+ {
+ let inventory = Inventory::new();
+ assert_eq!(inventory.size(), 0);
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..8534936
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,5 @@
+pub mod character;
+pub mod dice;
+pub mod inventory;
+
+fn main() {}