diff --git a/Cargo.lock b/Cargo.lock index 72e69a2..59e6cd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,162 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rust-roller" version = "0.1.0" +dependencies = [ + "nom", + "rand", +] + +[[package]] +name = "syn" +version = "2.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 3ba0aaf..2d6ea56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] +nom = "7.1.3" +rand = "0.8.5" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..556b0f8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,165 @@ +use nom::{branch::alt, character::complete, IResult}; +use rand::Rng; + +pub struct Roller { + expr: Expr, +} + +impl Roller { + /// parse converts the str, of form: + /// 2d8 + 1d8 + 4 + /// into a parsed expression, returning an Expr enum + /// and the remaining unparsed string. + pub fn parse(expr: &str) -> Result>> { + let (mut expr, left) = term(expr)?; + let mut res = Expr::Term(left); + while expr.len() > 0 { + let (e, oper) = oper(expr)?; + let (e, term) = term(e)?; + res = Expr::Op(Box::new(res), oper, Box::new(Expr::Term(term))); + expr = e; + } + Ok(Roller{expr: res}) + } + pub fn roll(&self, rng: &mut R) -> u64 { + self.expr.val(rng) + } +} + +enum Expr { + Term(Term), + Op(Box, Oper, Box), +} + +impl Expr { + fn val(&self, rng: &mut R) -> u64 { + match self { + Expr::Term(Term::Const(x)) => *x, + Expr::Term(Term::Roll(r)) => r.val(rng), + Expr::Op(a, Oper::Add, b) => a.val(rng) + b.val(rng), + Expr::Op(a, Oper::Sub, b) => a.val(rng) - b.val(rng), + } + } +} + +enum Term { + Roll(Roll), + Const(u64), +} + +enum Oper { + Add, + Sub, +} + +pub struct Roll{ + reps: u64, + dice: u64, +} + +impl Roll { + fn val(&self, rng: &mut R) -> u64 { + let mut total = 0; + for _ in 0..self.reps { + total += rng.gen::() % self.dice + 1; + } + total + } +} + + +fn term(e: &str) -> IResult<&str, Term> { + alt((roll, cnst))(e) +} + +fn roll(e: &str) -> IResult<&str, Term> { + let (e, reps) = complete::u64(e)?; + let (e, _) = complete::char('d')(e)?; + let (e, dice) = complete::u64(e)?; + Ok((e, Term::Roll(Roll{reps, dice}))) +} + +fn cnst(e: &str) -> IResult<&str, Term> { + let (e, val) = complete::u64(e)?; + Ok((e, Term::Const(val))) +} + +fn oper(e: &str) -> IResult<&str, Oper> { + alt((add, sub))(e) +} + +fn add(e: &str) -> IResult<&str, Oper> { + let (e, _) = complete::char('+')(e)?; + Ok((e, Oper::Add)) +} + +fn sub(e: &str) -> IResult<&str, Oper> { + let (e, _) = complete::char('-')(e)?; + Ok((e, Oper::Sub)) +} + +#[cfg(test)] +mod tests { + use rand::{rngs::StdRng, SeedableRng}; + + use super::*; + #[test] + fn roll_many_d6s() { + let mut rng = StdRng::seed_from_u64(1337); + let roller = Roller::parse("1d6").unwrap(); + let mut results = [0u64; 6]; + for _ in 0..1000 { + results[(roller.roll(&mut rng)-1) as usize] += 1; + } + // We should have some in each bucket + for x in results { + assert!(x > 50); + } + } + + #[test] + fn roll_many_d6s_plus_2() { + let mut rng = StdRng::seed_from_u64(1337); + let roller = Roller::parse("1d6+2").unwrap(); + let mut results = [0u64; 6+2]; + for _ in 0..1000 { + results[(roller.roll(&mut rng)-1) as usize] += 1; + } + // Can't get a 1 or 2, but should have many others + assert!(results[0] == 0); + assert!(results[1] == 0); + for x in &results[2..] { + assert!(*x > 50); + } + } + + #[test] + fn roll_many_d6s_plus_1d6() { + let mut rng = StdRng::seed_from_u64(1337); + let roller = Roller::parse("1d6+1d6").unwrap(); + let mut results = [0u64; 6+6]; + for _ in 0..1000 { + results[(roller.roll(&mut rng)-1) as usize] += 1; + } + // Its impossible to roll 1 + assert!(results[0] == 0); + for x in &results[1..] { + assert!(*x > 1); + } + } + + #[test] + fn roll_many_d6s_plus_1d6_minus_one() { + let mut rng = StdRng::seed_from_u64(1337); + let roller = Roller::parse("1d6+1d6-1").unwrap(); + let mut results = [0u64; 6+6]; + for _ in 0..1000 { + results[(roller.roll(&mut rng)-1) as usize] += 1; + } + // Its impossible to roll 12 + assert!(results[11] == 0); + for x in &results[..11] { + assert!(*x > 1); + } + } +} \ No newline at end of file