Browse Source

Add a readme and some terrible experimental code

master
Jan Christian Grünhage 2 years ago
parent
commit
d596ae082b
8 changed files with 472 additions and 7 deletions
  1. +1
    -0
      .gitignore
  2. +1
    -0
      Cargo.toml
  3. +49
    -0
      README.md
  4. +17
    -0
      src/characteristic.rs
  5. +60
    -0
      src/dice.rs
  6. +43
    -0
      src/hero.rs
  7. +5
    -7
      src/lib.rs
  8. +296
    -0
      src/skill.rs

+ 1
- 0
.gitignore View File

@@ -1,3 +1,4 @@
/target
**/*.rs.bk
Cargo.lock
.idea

+ 1
- 0
Cargo.toml View File

@@ -7,3 +7,4 @@ version = "0.1.0"
authors = ["Jan Christian Grünhage <jan.christian@gruenhage.xyz>"]

[dependencies]
rand = "0.5.4"

+ 49
- 0
README.md View File

@@ -0,0 +1,49 @@
### dsa-rs

This is a ***work-in-progress*** library for "Das Schwarze Auge", a german pen and paper RPG.
It targets the fifth edition and intentionally does not come with any content,
to avoid infringement of the copyright of Ulisses Medien & Spiel Distribution GmbH.
Instead, it does come with a schema of how the content needs to be formatted.
For usage, please create the content files yourself, using their rule books as sources.

## Current features:
- Characteristics
- Skills
- Trials for each of the above.
The architecture of the code above isn't very good though,
so for now I'd just wait (or participate in planning a better architecture).

## Planned features:
- [ ] Heroes
- [ ] Create and change heroes, based on the rules
- [ ] Skills
- [ ] trials
- [ ] and probabilities
- [ ] Characteristics
- [ ] trials
- [ ] Derived Values
- [ ] management of current values for things like LE/AE/KE
- [ ] Conditions
- [ ] Wounds
- [ ] Special abilities
- [ ] Advantages/Disadvantages
- [ ] Species
- [ ] Culture
- [ ] Profession
- [ ] Inventory, including money
- [ ] Equipment
- [ ] Notes about adventures
- [ ] Relationships
- [ ] AP/SE
- [ ] Combat
- [ ] Rolling back changes (each change should be saved as a transaction)
- [ ] Insert your proposal here!
## Out of scope (for this library):

- Any kind of UI/CLI for the things above
- Content: This library should only implement concepts, like skills,
but not contain actual instances of those concepts. The content is something the user needs
to obtain themselves.

+ 17
- 0
src/characteristic.rs View File

@@ -0,0 +1,17 @@
use super::dice::DiceThrow;

/// A characteristic is a basic trait of a hero.
/// There are 8 of those, and they are usually abbreviated with two capital letters.
pub struct Characteristic {
/// This is the identifier, it's a static str containing the two capital letter abbreviation.
pub identifier: &'static str,
/// This is the value indicating how good the hero is in regard of this characteristic.
pub value: i8,
}

impl Characteristic {
/// Test the hero's ability to do something regarding this characteristic.
pub fn trial(&self, dice_throw: DiceThrow, modificator: i8) -> i8 {
self.value + modificator - (dice_throw.throw as i8)
}
}

+ 60
- 0
src/dice.rs View File

@@ -0,0 +1,60 @@
use rand::distributions::{Distribution, Uniform};
use rand::{rngs::ThreadRng, thread_rng};

// TODO: We'd want move to constant generics here and for DiceThrows,
// but that requires new lang features https://github.com/rust-lang/rust/issues/44580
#[derive(Copy, Clone)]
pub struct Dice {
pub sides: u8,
}

impl Dice {
pub fn new(sides: u8) -> Dice {
Dice { sides }
}
pub fn throw_multiple(&self, amount: u8) -> Vec<DiceThrow> {
let mut results = Vec::new();
for _ in 0..amount {
results.push(self.throw());
}
results
}
pub fn throw(&self) -> DiceThrow {
self.throw_with_result(Uniform::from(1..self.sides).sample::<ThreadRng>(&mut thread_rng()))
}

pub fn throw_with_result(&self, result: u8) -> DiceThrow {
DiceThrow {
sides: self.sides,
throw: result,
}
}
}

#[derive(Copy, Clone)]
pub struct DiceThrow {
pub sides: u8,
pub throw: u8,
}

#[cfg(test)]
mod tests {
use super::Dice;
#[test]
fn create_dice() {
assert_eq!(Dice::new(20).sides, 20)
}

#[test]
fn throw_dice() {
let throw = Dice::new(20).throw();
assert!(throw.throw < 21);
assert!(throw.throw > 0);
}

#[test]
fn throw_dice_with_result() {
let throw = Dice::new(20).throw_with_result(15);
assert_eq!(throw.throw, 15);
}
}

+ 43
- 0
src/hero.rs View File

@@ -0,0 +1,43 @@
use super::{characteristic::Characteristic, dice::DiceThrow, skill::Skill};
use std::collections::HashMap;

pub struct Hero<'a> {
pub characteristics: HashMap<&'static str, Characteristic>,
pub skills: HashMap<&'static str, Skill<'a>>,
}

impl Hero {
pub fn new() -> Hero {
Hero {
characteristics: HashMap::new(),
skills: HashMap::new(),
}
}

pub fn add_characteristic(&mut self, identifier: &'static str, value: i8) {
match self.characteristics
.insert(identifier, Characteristic { identifier, value })
{
_ => {} //TODO: handle whatever this returns
};
}

pub fn add_skill(
&mut self,
identifier: &'static str,
value: i8,
characteristics: [&'static str; 3],
) {
match self.skills.insert(
identifier,
Skill {
identifier,
value,
characteristics,
hero: self,
},
) {
_ => {} //TODO: handle whatever this returns
};
}
}

+ 5
- 7
src/lib.rs View File

@@ -1,7 +1,5 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
extern crate rand;
pub mod characteristic;
pub mod dice;
pub mod hero;
pub mod skill;

+ 296
- 0
src/skill.rs View File

@@ -0,0 +1,296 @@
use super::{characteristic::Characteristic, dice::DiceThrow, hero::Hero};

/// A skill identifier with a value how good the hero is in it.
pub struct Skill {
/// The identifier takes the form of `<book it is introduced in first>/<name of the skill in lowercase ascii>`
pub identifier: &'static str,
pub value: i8,
pub characteristics: [&'static str; 3],
pub hero: Hero,
}

impl Skill {
pub fn new(
identifier: &'static str,
value: i8,
characteristics: [&'static str; 3],
hero: Hero,
) -> Skill {
Skill {
identifier,
value,
characteristics,
hero,
}
}

pub fn trial(
&self,
dice_throws: [DiceThrow; 3],
modifier: i8,
) -> ([DiceThrow; 3], SkillTrialResult) {
let mut ones = 0;
let mut twenties = 0;
let mut per_trait_mod = 0;
let mut remainder = self.value + modifier;
if remainder < 0 {
per_trait_mod = remainder;
remainder = 0;
}
for i in 0..3 {
let trait_trial_result = self.hero
.characteristics
.get(self.characteristics[i])
.unwrap()
.trial(dice_throws[i], per_trait_mod);
if trait_trial_result < 0 {
remainder += trait_trial_result;
}
match dice_throws[i].throw {
20 => twenties += 1,
1 => ones += 1,
_ => {}
}
}
{
use self::SkillTrialResult::*;
(
dice_throws,
match (ones, twenties, remainder) {
(_, 3, _) => TripleTwenty,
(_, 2, _) => DoubleTwenty,
(3, _, _) => TripleOne,
(2, _, _) => DoubleOne,
(_, _, n) if n < 0 => Failure,
(_, _, n) if n >= 0 && n <= 3 => Success(1),
(_, _, n) if n >= 4 && n <= 6 => Success(2),
(_, _, n) if n >= 7 && n <= 9 => Success(3),
(_, _, n) if n >= 10 && n <= 12 => Success(4),
(_, _, n) if n >= 13 && n <= 15 => Success(5),
(_, _, n) if n >= 16 => Success(6),
(_, _, _) => Failure,
// TODO: exhaustive integer matching, this is unreachable, removal blocked by
// https://github.com/rust-lang/rust/pull/50912 being in stable
},
)
}
}
}

pub enum SkillTrialResult {
TripleTwenty,
DoubleTwenty,
Failure,
Success(u8),
DoubleOne,
TripleOne,
}

#[cfg(test)]
mod tests {
use super::super::{
characteristic::Characteristic, dice::Dice, hero::Hero, skill::{Skill, SkillTrialResult},
};

#[test]
fn create_skill() {
let skill = skill();
}

fn skill() -> Skill {
let mut hero = Hero::new();
hero.add_characteristic("AA", 12);
hero.add_characteristic("BB", 15);
hero.add_characteristic("CC", 09);
Skill::new("skill", 6, ["AA", "BB", "CC"], hero)
}

#[test]
fn triple_twenty() {
let skill = skill();
let d20 = Dice::new(20);
let throws = [
d20.throw_with_result(20),
d20.throw_with_result(20),
d20.throw_with_result(20),
];
match skill.trial(throws, 0) {
(_, SkillTrialResult::TripleTwenty) => {}
(_, _) => panic!("Putting in three twenties doesn't return triple twenty"),
}
}

#[test]
fn double_twenty() {
let skill = skill();
let d20 = Dice::new(20);
let throws = [
d20.throw_with_result(20),
d20.throw_with_result(19),
d20.throw_with_result(20),
];
match skill.trial(throws, 0) {
(_, SkillTrialResult::DoubleTwenty) => {}
(_, _) => panic!("Putting in two twenties doesn't return double twenty"),
}
}

#[test]
fn triple_one() {
let skill = skill();
let d20 = Dice::new(20);
let throws = [
d20.throw_with_result(1),
d20.throw_with_result(1),
d20.throw_with_result(1),
];
match skill.trial(throws, 0) {
(_, SkillTrialResult::TripleOne) => {}
(_, _) => panic!("Putting in three ones doesn't return triple one"),
}
}

#[test]
fn double_one() {
let skill = skill();
let d20 = Dice::new(20);
let throws = [
d20.throw_with_result(1),
d20.throw_with_result(1),
d20.throw_with_result(20),
];
match skill.trial(throws, 0) {
(_, SkillTrialResult::DoubleOne) => {}
(_, _) => panic!("Putting in two ones doesn't return double one"),
}
}

#[test]
fn failure() {
let skill = skill();
let d20 = Dice::new(20);
let throws = [
d20.throw_with_result(19),
d20.throw_with_result(19),
d20.throw_with_result(19),
];
match skill.trial(throws, 0) {
(_, SkillTrialResult::Failure) => {}
(_, _) => panic!("Failing doesn't fail"),
}
}

#[test]
fn success_qs1() {
let skill = skill();
let d20 = Dice::new(20);
let throws = [
d20.throw_with_result(2),
d20.throw_with_result(2),
d20.throw_with_result(2),
];
match skill.trial(throws, -4) {
(_, SkillTrialResult::Success(1)) => {}
(_, _) => panic!("QS 1 is failing"),
}
}

#[test]
fn success_qs2() {
let skill = skill();
let d20 = Dice::new(20);
let throws = [
d20.throw_with_result(2),
d20.throw_with_result(2),
d20.throw_with_result(2),
];
match skill.trial(throws, -1) {
(_, SkillTrialResult::Success(2)) => {}
(_, _) => panic!("QS 1 is failing"),
}
}

#[test]
fn success_qs3() {
let skill = skill();
let d20 = Dice::new(20);
let throws = [
d20.throw_with_result(2),
d20.throw_with_result(2),
d20.throw_with_result(2),
];
match skill.trial(throws, 2) {
(_, SkillTrialResult::Success(3)) => {}
(_, _) => panic!("QS 1 is failing"),
}
}

#[test]
fn success_qs4() {
let skill = skill();
let d20 = Dice::new(20);
let throws = [
d20.throw_with_result(2),
d20.throw_with_result(2),
d20.throw_with_result(2),
];
match skill.trial(throws, 5) {
(_, SkillTrialResult::Success(4)) => {}
(_, _) => panic!("QS 1 is failing"),
}
}

#[test]
fn success_qs5() {
let skill = skill();
let d20 = Dice::new(20);
let throws = [
d20.throw_with_result(2),
d20.throw_with_result(2),
d20.throw_with_result(2),
];
match skill.trial(throws, 8) {
(_, SkillTrialResult::Success(5)) => {}
(_, _) => panic!("QS 1 is failing"),
}
}

#[test]
fn success_qs6() {
let skill = skill();
let d20 = Dice::new(20);
let throws = [
d20.throw_with_result(2),
d20.throw_with_result(2),
d20.throw_with_result(2),
];
match skill.trial(throws, 11) {
(_, SkillTrialResult::Success(6)) => {}
(_, _) => panic!("QS 1 is failing"),
}
}

#[test]
fn success_with_19_qs6() {
let skill = skill();
let d20 = Dice::new(20);
let throws = [
d20.throw_with_result(19),
d20.throw_with_result(2),
d20.throw_with_result(2),
];
match skill.trial(throws, 20) {
(_, SkillTrialResult::Success(6)) => {}
(_, _) => panic!("QS 1 is failing"),
}
}
}


struct A {
bs: Vec<B>,
}

struct B {
a: A
}

Loading…
Cancel
Save