Add a readme and some terrible experimental code
This commit is contained in:
parent
aff913a6f2
commit
d596ae082b
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
/target
|
/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
.idea
|
||||||
|
|
|
@ -7,3 +7,4 @@ version = "0.1.0"
|
||||||
authors = ["Jan Christian Grünhage <jan.christian@gruenhage.xyz>"]
|
authors = ["Jan Christian Grünhage <jan.christian@gruenhage.xyz>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
rand = "0.5.4"
|
||||||
|
|
49
README.md
Normal file
49
README.md
Normal 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
src/characteristic.rs
Normal file
17
src/characteristic.rs
Normal 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
src/dice.rs
Normal file
60
src/dice.rs
Normal 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
src/hero.rs
Normal file
43
src/hero.rs
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
12
src/lib.rs
12
src/lib.rs
|
@ -1,7 +1,5 @@
|
||||||
#[cfg(test)]
|
extern crate rand;
|
||||||
mod tests {
|
pub mod characteristic;
|
||||||
#[test]
|
pub mod dice;
|
||||||
fn it_works() {
|
pub mod hero;
|
||||||
assert_eq!(2 + 2, 4);
|
pub mod skill;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
296
src/skill.rs
Normal file
296
src/skill.rs
Normal 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…
Reference in a new issue