Skip to content

Instantly share code, notes, and snippets.

@reckenrode
Last active July 9, 2019 23:24
Show Gist options
  • Save reckenrode/dd103588725717813f3696de20cf9322 to your computer and use it in GitHub Desktop.
Save reckenrode/dd103588725717813f3696de20cf9322 to your computer and use it in GitHub Desktop.
[package]
name = "ability-scores"
version = "0.1.0"
authors = ["Randy Eckenrode <randy@largeandhighquality.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.7"
streaming-stats = "0.2"
use rand::prelude::*;
// use statrs::statistics::*;
use stats::{MinMax, OnlineStats};
use std::thread;
#[derive(Debug)]
struct AbilityScores([u8; 6]);
impl AbilityScores {
fn point_buy_value(&self) -> i16 {
const POINT_BUY_MAPPING: [i16; 19] = [
-30, -25, -20, -16, -12, -9, -6, -4, -2, -1, 0, 1, 2, 3, 5, 7, 10, 13, 17,
];
let AbilityScores(arr) = self;
arr.iter().fold(0, |point_buy, score| {
point_buy + POINT_BUY_MAPPING[usize::from(*score)]
})
}
}
impl<I: IntoIterator<Item = u8>> From<I> for AbilityScores {
fn from(v: I) -> Self {
let mut storage = [0u8; 6];
let index = v.into_iter().take(6).fold(0, |index, x| {
storage[index] = x;
index + 1
});
if index != storage.len() {
panic!("tried to convert an iterator containing fewer than 6 elements")
} else {
AbilityScores(storage)
}
}
}
fn deal_scores(rng: &mut impl Rng) -> AbilityScores {
let cards = {
let mut deck1: Vec<u8> = (4..=9).collect();
let mut deck2: Vec<u8> = (4..=9).collect();
deck1.shuffle(rng);
deck2.shuffle(rng);
deck1.into_iter().zip(deck2.into_iter()).map(|(x, y)| x + y)
};
AbilityScores::from(cards)
}
fn roll_4d6_drop_lowest(rng: &mut impl Rng) -> u8 {
let rolls: [u8; 4] = [
rng.gen_range(1, 7),
rng.gen_range(1, 7),
rng.gen_range(1, 7),
rng.gen_range(1, 7),
];
let (total, lowest) = rolls.iter().fold((0, std::u8::MAX), |(x, min), y| {
let min = std::cmp::min(min, *y);
(x + y, min)
});
total - lowest
}
fn roll_scores(rng: &mut impl Rng) -> AbilityScores {
let scores: [u8; 6] = [
roll_4d6_drop_lowest(rng),
roll_4d6_drop_lowest(rng),
roll_4d6_drop_lowest(rng),
roll_4d6_drop_lowest(rng),
roll_4d6_drop_lowest(rng),
roll_4d6_drop_lowest(rng),
];
AbilityScores::from(scores.iter().cloned())
}
fn print_stats(heading: &str, scores: (f64, f64, f64, f64)) {
let (mean, std_dev, min, max) = scores;
println!("{}", heading);
println!("Mean: {: >6.2}, StdDev: {: >6.2}", mean, std_dev);
println!("Min: {: >6.2}, Max: {: >6.2}", min, max);
}
fn main() {
const MAX_ITER: usize = 10_000_000;
let card_result = thread::spawn(|| {
let mut rng = rand::thread_rng();
let mut minmax = MinMax::new();
let mut stats = OnlineStats::new();
for _ in 0..MAX_ITER {
let value = deal_scores(&mut rng).point_buy_value();
minmax.add(value);
stats.add(value);
}
(
stats.mean(),
stats.stddev(),
f64::from(*minmax.min().unwrap()),
f64::from(*minmax.max().unwrap()),
)
});
let roll_result = thread::spawn(|| {
let mut rng = rand::thread_rng();
let mut minmax = MinMax::new();
let mut stats = OnlineStats::new();
for _ in 0..MAX_ITER {
let value = roll_scores(&mut rng).point_buy_value();
minmax.add(value);
stats.add(value);
}
(
stats.mean(),
stats.stddev(),
f64::from(*minmax.min().unwrap()),
f64::from(*minmax.max().unwrap()),
)
});
let scores = card_result.join().unwrap();
print_stats("Card Method", scores);
let scores = roll_result.join().unwrap();
print_stats("Rolling Method", scores);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment