From 862d73eabe498e1acdb6e6a7bd114de1bb3edc1b Mon Sep 17 00:00:00 2001 From: Chris Guidry Date: Thu, 30 Jul 2026 21:16:09 -0400 Subject: [PATCH] Allow d3 as a valid die size Chris reversed the earlier ruling after checking the SRD: d3 appears 92 times, always as "regains 1d3 expended charges daily at dawn" on magic item recharge tables, so it's a real 5e die size, not shorthand for a halved d6. Valid sizes are now 2, 3, 4, 6, 8, 10, 12, 20, and 100. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CUXjWo1zGFhdJUig1hcQGf --- src/dice.rs | 11 ++++++----- src/dice_tests.rs | 21 ++++++++++++++------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/dice.rs b/src/dice.rs index 0b75be4..5c99a7a 100644 --- a/src/dice.rs +++ b/src/dice.rs @@ -9,7 +9,7 @@ //! `r>N` reroll a die until the condition stops matching, and `miN` sets a //! floor on each die's value. Rerolls and minimums settle before keep and //! drop choose which dice count. A die size must be one this grammar -//! knows: 2, 4, 6, 8, 10, 12, 20, or 100. +//! knows: 2, 3, 4, 6, 8, 10, 12, 20, or 100. use rand::RngExt; @@ -19,9 +19,10 @@ use rand::RngExt; /// produces a roll instead of hanging. const MAX_REROLLS: u32 = 100; -/// The die sizes a 5e table rolls. Anything else, including `d3` (a halved -/// d6 in the SRD's shorthand), is not a die this grammar accepts. -const VALID_SIDES: [u32; 8] = [2, 4, 6, 8, 10, 12, 20, 100]; +/// The die sizes a 5e table rolls, including `d3` for the SRD's charge +/// tables ("regains 1d3 expended charges daily at dawn"). Anything else +/// is not a die this grammar accepts. +const VALID_SIDES: [u32; 9] = [2, 3, 4, 6, 8, 10, 12, 20, 100]; /// Notation that failed to parse, with a reason worded so that whatever /// wrote the notation can correct it and try again. @@ -296,7 +297,7 @@ fn parse_group( notation: original.to_string(), reason: format!( "a die size of `{sides}` in `{text}` is not a valid 5e die size; \ -use 2, 4, 6, 8, 10, 12, 20, or 100 (`%`)" +use 2, 3, 4, 6, 8, 10, 12, 20, or 100 (`%`)" ), }); } diff --git a/src/dice_tests.rs b/src/dice_tests.rs index 64f16ed..9ca00ea 100644 --- a/src/dice_tests.rs +++ b/src/dice_tests.rs @@ -62,6 +62,11 @@ fn a_standard_die_size_of_two() { parses_to("d2", notation("d2", vec![Term::Group(group(1, 2))])); } +#[test] +fn a_standard_die_size_of_three() { + parses_to("1d3", notation("1d3", vec![Term::Group(group(1, 3))])); +} + #[test] fn percentile_die_is_100_sided() { parses_to("d%", notation("d%", vec![Term::Group(group(1, 100))])); @@ -511,6 +516,15 @@ fn rolling_a_single_die() { assert_eq!(result.total, 17); } +#[test] +fn rolling_a_d3() { + let result = rolled("1d3", 0); + + assert_eq!(result.dice[0].sides, 3); + assert_eq!(result.dice[0].value, 3); + assert_eq!(result.total, 3); +} + #[test] fn rolling_multiple_dice() { let result = rolled("3d6", 0); @@ -784,13 +798,6 @@ fn a_zero_die_size_is_an_error() { assert!(error.reason.contains("die size")); } -#[test] -fn d3_is_not_a_standard_die_size() { - let error = parse("d3").unwrap_err(); - assert!(error.reason.contains("die size")); - assert!(error.reason.contains('3')); -} - #[test] fn an_unsupported_die_size_is_an_error() { let error = parse("1d7").unwrap_err(); -- 2.51.2