1use std::{
4 num::TryFromIntError,
5 ops::{Add, Neg, Sub},
6};
7
8use crate::{
9 ast::{DiceExpr, Keep, RollSpec},
10 error::DiceError,
11};
12use rand::{Rng, rngs::ThreadRng};
13
14pub struct Roller<R: Rng> {
18 rng: R,
19}
20
21impl Default for Roller<ThreadRng> {
22 fn default() -> Self {
23 Roller { rng: rand::rng() }
24 }
25}
26
27#[derive(Debug, Clone)]
33pub struct RollResult {
34 pub total: u32,
35 pub detail: RollDetail,
36}
37impl RollResult {
38 pub fn new(total: u32, detail: RollDetail) -> Self {
39 RollResult { total, detail }
40 }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
50pub enum RollDetail {
51 Dice(Vec<u32>),
52 Constant(i32),
53}
54
55#[derive(Debug, Clone)]
98pub struct ExprResult {
99 pub total: i32,
101 pub rolls: Vec<i32>,
103 pub modifier: i32,
105}
106
107impl TryFrom<RollResult> for ExprResult {
108 type Error = DiceError;
109 fn try_from(val: RollResult) -> Result<Self, DiceError> {
110 let expr = ExprResult {
111 total: val.total.try_into()?,
113 rolls: match &val.detail {
114 RollDetail::Dice(d) => d
115 .iter()
116 .map(|&x| x.try_into())
117 .collect::<Result<Vec<i32>, TryFromIntError>>()?,
118 RollDetail::Constant(_) => Vec::new(),
119 },
120 modifier: if let RollDetail::Constant(n) = &val.detail {
121 *n
122 } else {
123 0
124 },
125 };
126 Ok(expr)
127 }
128}
129
130impl Add<ExprResult> for ExprResult {
131 type Output = Self;
132 fn add(mut self, other: ExprResult) -> Self {
133 self.rolls.extend(other.rolls.iter());
134 self.modifier += other.modifier;
135 self.total += other.total;
136 self
137 }
138}
139
140impl Neg for ExprResult {
141 type Output = Self;
142 fn neg(mut self) -> Self {
143 self.rolls.iter_mut().for_each(|x| *x = -*x);
145 self.modifier = -self.modifier;
146 self.total = -self.total;
147 self
148 }
149}
150
151impl Sub<ExprResult> for ExprResult {
152 type Output = Self;
153 fn sub(self, other: ExprResult) -> Self {
154 self + (-other)
155 }
156}
157
158impl ExprResult {
159 pub fn new(total: i32, rolls: Vec<i32>, modifier: i32) -> Self {
160 ExprResult {
161 total,
162 rolls,
163 modifier,
164 }
165 }
166}
167
168impl<R: Rng> Roller<R> {
169 pub fn from_rng(rng: R) -> Self {
173 Roller { rng }
174 }
175
176 fn roll_die(&mut self, sides: u32) -> u32 {
183 debug_assert!(sides > 0);
184 self.rng.random_range(1..=sides)
185 }
186
187 fn roll_d100(&mut self) -> u32 {
189 let ones = self.rng.random_range(1..=10);
190 let tens = self.rng.random_range(0..=9);
191 if tens == 0 { ones } else { ones + tens * 10 }
192 }
193
194 fn roll_dice(&mut self, sides: u32, count: u32) -> Vec<u32> {
202 debug_assert!(sides > 0 && count > 0);
203 (0..count)
204 .map(|_| match sides {
205 100 => self.roll_d100(),
206 _ => self.roll_die(sides),
207 })
208 .collect()
209 }
210
211 pub fn roll_spec(&mut self, spec: &RollSpec) -> Result<RollResult, DiceError> {
223 if spec.count == 0 {
224 return Ok(RollResult::new(0, RollDetail::Constant(0)));
225 }
226
227 if spec.sides == 0 {
228 return Ok(RollResult::new(
229 spec.count,
230 RollDetail::Constant(spec.count as i32),
231 ));
232 }
233
234 let mut rolls = self.roll_dice(spec.sides, spec.count);
235 let rolled = RollDetail::Dice(rolls.clone());
236
237 if let Some(keep) = &spec.keep {
238 rolls.sort_unstable();
239 let total: u32 = match keep {
240 Keep::Highest(n) => {
241 if *n as usize > rolls.len() {
242 return Err(DiceError::InvalidSpec(
243 spec.clone(),
244 String::from("tried to keep more than total amount of rolled dice"),
245 ));
246 }
247 rolls[(rolls.len() - *n as usize)..].iter().sum()
248 }
249 Keep::Lowest(n) => {
250 if *n as usize > rolls.len() {
251 return Err(DiceError::InvalidSpec(
252 spec.clone(),
253 String::from("tried to keep more than total amount of rolled dice"),
254 ));
255 }
256
257 rolls[..*n as usize].iter().sum()
258 }
259 };
260
261 Ok(RollResult::new(total, rolled))
262 } else {
263 let total = rolls.iter().sum();
264 Ok(RollResult::new(total, rolled))
265 }
266 }
267
268 pub fn roll_expr(&mut self, expr: &DiceExpr) -> Result<ExprResult, DiceError> {
276 match expr {
277 DiceExpr::Sum(lhs, rhs) => Ok(self.roll_expr(lhs)? + self.roll_expr(rhs)?),
278 DiceExpr::Difference(lhs, rhs) => Ok(self.roll_expr(lhs)? - self.roll_expr(rhs)?),
279 DiceExpr::Roll(spec) => self.roll_spec(spec)?.try_into(),
280 DiceExpr::Literal(lit) => Ok(ExprResult {
281 total: *lit,
282 rolls: Vec::new(),
283 modifier: *lit,
284 }),
285 }
286 }
287}
288
289#[cfg(test)]
290mod test {
291 use super::*;
292 use rand::{self, SeedableRng, rngs::StdRng};
293
294 #[test]
297 fn test_roll() {
298 let mut roller = Roller::from_rng(StdRng::seed_from_u64(12));
299 let spec = RollSpec::new(2, 20, None);
300
301 let res = roller.roll_spec(&spec).unwrap();
302
303 assert_eq!(res.total, 10 + 9);
304 if let RollDetail::Dice(dice) = res.detail {
305 assert_eq!(dice, vec![9, 10])
306 }
307 }
308
309 #[test]
310 fn test_0_sides() {
311 let mut roller = Roller::from_rng(StdRng::seed_from_u64(42));
312 let spec = RollSpec::new(3, 0, None);
313 let res = roller.roll_spec(&spec).unwrap();
314 assert_eq!(res.total, 3);
315 assert_eq!(res.detail, RollDetail::Constant(3));
316 }
317
318 #[test]
319 fn test_0_count() {
320 let mut roller = Roller::from_rng(StdRng::seed_from_u64(36));
321 let spec = RollSpec::new(0, 12, None);
322
323 let res = roller.roll_spec(&spec).unwrap();
324
325 assert_eq!(res.total, 0);
326 assert_eq!(res.detail, RollDetail::Constant(0))
327 }
328
329 #[test]
330 fn test_keep_highest() {
331 let mut roller = Roller::from_rng(StdRng::seed_from_u64(12));
333
334 let spec = RollSpec::new(5, 20, Some(Keep::Highest(1)));
335 let res = roller.roll_spec(&spec).unwrap();
336
337 assert_eq!(res.total, 20);
338 if let RollDetail::Dice(d) = res.detail {
339 assert_eq!(d, vec![9, 10, 14, 12, 20])
340 } else {
341 panic!()
342 }
343
344 let mut roller = Roller::from_rng(StdRng::seed_from_u64(12));
346 let spec = RollSpec::new(5, 20, Some(Keep::Highest(2)));
347 let res = roller.roll_spec(&spec).unwrap();
348
349 assert_eq!(res.total, 34);
350 if let RollDetail::Dice(d) = res.detail {
351 assert_eq!(d, vec![9, 10, 14, 12, 20])
352 } else {
353 panic!()
354 }
355 }
356
357 #[test]
358 fn test_keep_lowest() {
359 let mut roller = Roller::from_rng(StdRng::seed_from_u64(12));
361
362 let spec = RollSpec::new(5, 20, Some(Keep::Lowest(1)));
363 let res = roller.roll_spec(&spec).unwrap();
364
365 assert_eq!(res.total, 9);
366 if let RollDetail::Dice(d) = res.detail {
367 assert_eq!(d, vec![9, 10, 14, 12, 20])
368 } else {
369 panic!()
370 }
371
372 let mut roller = Roller::from_rng(StdRng::seed_from_u64(12));
374 let spec = RollSpec::new(5, 20, Some(Keep::Lowest(2)));
375 let res = roller.roll_spec(&spec).unwrap();
376
377 assert_eq!(res.total, 19);
378 if let RollDetail::Dice(d) = res.detail {
379 assert_eq!(d, vec![9, 10, 14, 12, 20])
380 } else {
381 panic!()
382 }
383 }
384
385 #[test]
386 fn test_keep_all_dice() {
387 let mut roller = Roller::from_rng(StdRng::seed_from_u64(42));
389 let spec = RollSpec::new(4, 6, Some(Keep::Highest(4)));
390 let res = roller.roll_spec(&spec).unwrap();
391 if let RollDetail::Dice(d) = res.detail {
392 assert_eq!(d.len(), 4);
393 } else {
394 panic!("Expected Dice variant")
395 }
396 }
398
399 #[test]
400 fn test_keep_single_from_many() {
401 let mut roller = Roller::from_rng(StdRng::seed_from_u64(42));
403 let spec = RollSpec::new(10, 20, Some(Keep::Highest(1)));
404 let res = roller.roll_spec(&spec).unwrap();
405 if let RollDetail::Dice(rolls) = &res.detail {
407 assert_eq!(rolls.len(), 10);
408 let max = rolls.iter().max().unwrap();
409 assert_eq!(res.total, *max);
410 }
411 }
412
413 #[test]
414 fn test_keep_too_many_high() {
415 let mut roller = Roller::from_rng(StdRng::seed_from_u64(42));
416 let spec = RollSpec::new(4, 20, Some(Keep::Highest(5)));
417 let res = roller.roll_spec(&spec);
418
419 match res {
420 Ok(_) => panic!("Expected Err variant"),
421 Err(e) => {
422 if !matches!(e, DiceError::InvalidSpec(_, _)) {
423 panic!("expected `DiceError::InvdalidSpec` variant")
424 }
425 }
426 }
427 }
428
429 #[test]
430 fn test_keep_too_many_low() {
431 let mut roller = Roller::from_rng(StdRng::seed_from_u64(42));
432 let spec = RollSpec::new(4, 20, Some(Keep::Lowest(5)));
433 let res = roller.roll_spec(&spec);
434
435 match res {
436 Ok(_) => panic!("Expected Err variant"),
437 Err(e) => {
438 if !matches!(e, DiceError::InvalidSpec(_, _)) {
439 panic!("expected `DiceError::InvdalidSpec` variant")
440 }
441 }
442 }
443 }
444
445 #[test]
446 fn test_keep_too_few_high() {
447 let mut roller = Roller::from_rng(StdRng::seed_from_u64(42));
448 let spec = RollSpec::new(6, 8, Some(Keep::Highest(0)));
449 let res = roller.roll_spec(&spec).unwrap();
450
451 assert_eq!(res.total, 0);
452 if let RollDetail::Dice(d) = &res.detail {
453 assert_eq!(d.len(), 6);
454 } else {
455 panic!("expected Dice variant")
456 }
457 }
458
459 #[test]
460 fn test_keep_too_few_low() {
461 let mut roller = Roller::from_rng(StdRng::seed_from_u64(42));
462 let spec = RollSpec::new(4, 12, Some(Keep::Lowest(0)));
463 let res = roller.roll_spec(&spec).unwrap();
464
465 assert_eq!(res.total, 0);
466 if let RollDetail::Dice(d) = &res.detail {
467 assert_eq!(d.len(), 4);
468 } else {
469 panic!("expected Dice variant")
470 }
471 }
472 #[test]
473 fn test_d100_range_validation() {
474 let mut roller = Roller::from_rng(StdRng::seed_from_u64(500));
475 let spec = RollSpec::new(1000000, 100, None);
476 let res = roller.roll_spec(&spec).unwrap();
477
478 if let RollDetail::Dice(rolls) = res.detail {
479 for roll in rolls {
480 assert!(
481 (1_u32..=100_u32).contains(&roll),
482 "D100 roll out of range: {}",
483 roll
484 );
485 }
486 }
487 }
488 #[test]
491 fn test_expr_literal() {
492 let mut roller = Roller::from_rng(StdRng::seed_from_u64(1));
493 let expr = DiceExpr::Literal(7);
494 let result = roller.roll_expr(&expr).unwrap();
495 assert_eq!(result.total, 7);
496 assert_eq!(result.rolls, vec![]);
497 assert_eq!(result.modifier, 7);
498 }
499
500 #[test]
501 fn test_expr_literal_negative() {
502 let mut roller = Roller::from_rng(StdRng::seed_from_u64(1));
503 let expr = DiceExpr::Literal(-15);
504 let result = roller.roll_expr(&expr).unwrap();
505 assert_eq!(result.total, -15);
506 assert_eq!(result.rolls, vec![]);
507 assert_eq!(result.modifier, -15);
508 }
509
510 #[test]
511 fn test_expr_literal_zero() {
512 let mut roller = Roller::from_rng(StdRng::seed_from_u64(1));
513 let expr = DiceExpr::Literal(0);
514 let result = roller.roll_expr(&expr).unwrap();
515 assert_eq!(result.total, 0);
516 assert_eq!(result.rolls, vec![]);
517 assert_eq!(result.modifier, 0);
518 }
519
520 #[test]
521 fn test_expr_roll_basic() {
522 let mut roller = Roller::from_rng(StdRng::seed_from_u64(42));
523 let expr = DiceExpr::Roll(RollSpec::new(2, 6, None));
524 let result = roller.roll_expr(&expr).unwrap();
525 assert_eq!(result.rolls.len(), 2);
526 assert_eq!(result.total, result.rolls.iter().sum());
527 assert_eq!(result.modifier, 0);
528 for roll in &result.rolls {
530 assert!(*roll >= 1 && *roll <= 6);
531 }
532 }
533
534 #[test]
535 fn test_expr_roll_d20() {
536 let mut roller = Roller::from_rng(StdRng::seed_from_u64(999));
537 let expr = DiceExpr::Roll(RollSpec::new(1, 20, None));
538 let result = roller.roll_expr(&expr).unwrap();
539 assert_eq!(result.rolls.len(), 1);
540 assert!(result.rolls[0] >= 1 && result.rolls[0] <= 20);
541 assert_eq!(result.total, result.rolls[0]);
542 assert_eq!(result.modifier, 0);
543 }
544
545 #[test]
546 fn test_expr_roll_multiple_d100() {
547 let mut roller = Roller::from_rng(StdRng::seed_from_u64(777));
548 let expr = DiceExpr::Roll(RollSpec::new(3, 100, None));
549 let result = roller.roll_expr(&expr).unwrap();
550 assert_eq!(result.rolls.len(), 3);
551 for roll in &result.rolls {
552 assert!(*roll >= 1 && *roll <= 100);
553 }
554 assert_eq!(result.total, result.rolls.iter().sum());
555 }
556
557 #[test]
558 fn test_expr_sum_literal_and_roll() {
559 let mut roller = Roller::from_rng(StdRng::seed_from_u64(100));
560 let left = DiceExpr::Roll(RollSpec::new(1, 20, None));
561 let right = DiceExpr::Literal(5);
562 let sum_expr = DiceExpr::Sum(Box::new(left), Box::new(right));
563 let result = roller.roll_expr(&sum_expr).unwrap();
564
565 assert_eq!(result.rolls.len(), 1);
567 assert_eq!(result.modifier, 5);
568 assert_eq!(result.total, result.rolls[0] + 5);
569 }
570
571 #[test]
572 fn test_expr_sum_multiple_literals() {
573 let mut roller = Roller::from_rng(StdRng::seed_from_u64(1));
574 let left = DiceExpr::Literal(10);
575 let right = DiceExpr::Literal(20);
576 let sum_expr = DiceExpr::Sum(Box::new(left), Box::new(right));
577 let result = roller.roll_expr(&sum_expr).unwrap();
578
579 assert_eq!(result.total, 30);
580 assert_eq!(result.rolls, vec![]);
581 assert_eq!(result.modifier, 30);
582 }
583
584 #[test]
585 fn test_expr_sum_multiple_rolls() {
586 let mut roller = Roller::from_rng(StdRng::seed_from_u64(333));
587 let left = DiceExpr::Roll(RollSpec::new(2, 6, None));
588 let right = DiceExpr::Roll(RollSpec::new(1, 8, None));
589 let sum_expr = DiceExpr::Sum(Box::new(left), Box::new(right));
590 let result = roller.roll_expr(&sum_expr).unwrap();
591
592 assert_eq!(result.rolls.len(), 3);
594 assert_eq!(result.modifier, 0);
595 assert_eq!(result.total, result.rolls.iter().sum());
596 }
597
598 #[test]
599 fn test_expr_difference_roll_minus_literal() {
600 let mut roller = Roller::from_rng(StdRng::seed_from_u64(200));
601 let left = DiceExpr::Roll(RollSpec::new(1, 20, None));
602 let right = DiceExpr::Literal(5);
603 let diff_expr = DiceExpr::Difference(Box::new(left), Box::new(right));
604 let result = roller.roll_expr(&diff_expr).unwrap();
605
606 assert_eq!(result.rolls.len(), 1);
608 assert_eq!(result.modifier, -5);
609 assert_eq!(result.total, result.rolls[0] - 5);
610 }
611
612 #[test]
613 fn test_expr_difference_literal_minus_roll() {
614 let mut roller = Roller::from_rng(StdRng::seed_from_u64(201));
615 let left = DiceExpr::Literal(10);
616 let right = DiceExpr::Roll(RollSpec::new(1, 6, None));
617 let diff_expr = DiceExpr::Difference(Box::new(left), Box::new(right));
618 let result = roller.roll_expr(&diff_expr).unwrap();
619
620 assert_eq!(result.rolls.len(), 1);
622 assert!(result.rolls[0].abs() >= 1 && result.rolls[0].abs() <= 6);
623 assert_eq!(result.total, result.modifier + result.rolls[0]);
624 assert_eq!(result.modifier, 10);
625 }
626
627 #[test]
628 fn test_expr_difference_multiple_literals() {
629 let mut roller = Roller::from_rng(StdRng::seed_from_u64(1));
630 let left = DiceExpr::Literal(50);
631 let right = DiceExpr::Literal(20);
632 let diff_expr = DiceExpr::Difference(Box::new(left), Box::new(right));
633 let result = roller.roll_expr(&diff_expr).unwrap();
634
635 assert_eq!(result.total, 30);
636 assert_eq!(result.rolls, vec![]);
637 assert_eq!(result.modifier, 30);
638 }
639
640 #[test]
641 fn test_expr_nested_sum_difference() {
642 let mut roller = Roller::from_rng(StdRng::seed_from_u64(555));
644 let left = DiceExpr::Roll(RollSpec::new(1, 8, None));
645 let inner_sum = DiceExpr::Sum(Box::new(DiceExpr::Literal(3)), Box::new(left.clone()));
646 let expr = DiceExpr::Difference(Box::new(inner_sum), Box::new(left));
647 let result = roller.roll_expr(&expr).unwrap();
648
649 let mut ref_rng = StdRng::seed_from_u64(555);
651 let mut rolls: Vec<i32> = (1..=2).map(|_| ref_rng.random_range(1..=8)).collect();
652
653 rolls[1] = -rolls[1];
654
655 assert_eq!(result.total, 3 + rolls.iter().sum::<i32>());
657 assert_eq!(result.rolls, rolls);
658 assert_eq!(result.modifier, 3);
659 assert_eq!(result.rolls.len(), 2);
660 }
661
662 #[test]
663 fn test_expr_complex_nested() {
664 let mut roller = Roller::from_rng(StdRng::seed_from_u64(666));
665 let d2d6 = DiceExpr::Roll(RollSpec::new(2, 6, None));
667 let sum1 = DiceExpr::Sum(Box::new(d2d6), Box::new(DiceExpr::Literal(5)));
668 let diff = DiceExpr::Difference(Box::new(sum1), Box::new(DiceExpr::Literal(3)));
669 let d1d4 = DiceExpr::Roll(RollSpec::new(1, 4, None));
670 let final_expr = DiceExpr::Sum(Box::new(diff), Box::new(d1d4));
671
672 let result = roller.roll_expr(&final_expr).unwrap();
673
674 assert_eq!(result.rolls.len(), 3);
676 assert_eq!(result.modifier, 2);
678 assert_eq!(
680 result.total,
681 result.rolls.iter().sum::<i32>() + result.modifier
682 );
683 }
684
685 #[test]
686 fn test_expr_keep_highest() {
687 let mut roller = Roller::from_rng(StdRng::seed_from_u64(123));
688 let expr = DiceExpr::Roll(RollSpec::new(4, 10, Some(Keep::Highest(2))));
689 let result = roller.roll_expr(&expr).unwrap();
690
691 assert_eq!(result.rolls.len(), 4);
693 assert_eq!(result.modifier, 0);
694
695 let mut rolls_sorted = result.rolls.clone();
697 rolls_sorted.sort_unstable();
698 let expected_total = rolls_sorted[2] + rolls_sorted[3];
699 assert_eq!(result.total, expected_total);
700 }
701
702 #[test]
703 fn test_expr_keep_lowest() {
704 let mut roller = Roller::from_rng(StdRng::seed_from_u64(124));
705 let expr = DiceExpr::Roll(RollSpec::new(4, 10, Some(Keep::Lowest(2))));
706 let result = roller.roll_expr(&expr).unwrap();
707
708 assert_eq!(result.rolls.len(), 4);
710 assert_eq!(result.modifier, 0);
711
712 let mut rolls_sorted = result.rolls.clone();
714 rolls_sorted.sort_unstable();
715 let expected_total = rolls_sorted[0] + rolls_sorted[1];
716 assert_eq!(result.total, expected_total);
717 }
718
719 #[test]
720 fn test_expr_keep_highest_with_sum() {
721 let mut roller = Roller::from_rng(StdRng::seed_from_u64(125));
722 let keep_roll = DiceExpr::Roll(RollSpec::new(3, 6, Some(Keep::Highest(1))));
723 let literal = DiceExpr::Literal(10);
724 let expr = DiceExpr::Sum(Box::new(keep_roll), Box::new(literal));
725 let result = roller.roll_expr(&expr).unwrap();
726
727 assert_eq!(result.rolls.len(), 3);
729 assert_eq!(result.modifier, 10);
730
731 let mut rolls_sorted = result.rolls.clone();
733 rolls_sorted.sort_unstable();
734 let expected_total = rolls_sorted[2] + 10;
735 assert_eq!(result.total, expected_total);
736 }
737
738 #[test]
739 fn test_exprresult_add_preserves_order() {
740 let left = ExprResult::new(10, vec![3, 7], 0);
741 let right = ExprResult::new(15, vec![5, 10], 0);
742 let sum = left + right;
743
744 assert_eq!(sum.rolls.len(), 4);
745 assert_eq!(sum.total, 25);
746 assert_eq!(sum.modifier, 0);
747 }
748
749 #[test]
750 fn test_exprresult_negation_flips_signs() {
751 let expr = ExprResult::new(10, vec![3, 7], 5);
752 let neg = -expr;
753
754 assert_eq!(neg.total, -10);
755 assert_eq!(neg.rolls, vec![-3, -7]);
756 assert_eq!(neg.modifier, -5);
757 }
758
759 #[test]
760 fn test_exprresult_sub_uses_negation() {
761 let left = ExprResult::new(20, vec![10, 10], 0);
762 let right = ExprResult::new(5, vec![5], 0);
763 let diff = left - right;
764
765 assert_eq!(diff.total, 15);
766 assert_eq!(diff.rolls.len(), 3);
767 assert_eq!(diff.rolls, vec![10, 10, -5])
768 }
769
770 #[test]
771 fn test_deeply_nested_expressions() {
772 let mut expr = DiceExpr::Roll(RollSpec::new(1, 6, None));
774 for _ in 0..10 {
775 expr = DiceExpr::Sum(Box::new(expr), Box::new(DiceExpr::Literal(1)));
776 }
777
778 let mut roller = Roller::from_rng(StdRng::seed_from_u64(42));
779 let result = roller.roll_expr(&expr).unwrap();
780
781 assert_eq!(result.modifier, 10);
782 assert_eq!(result.rolls.len(), 1);
783 }
784
785 #[test]
788 fn test_rollresult_to_exprresult_constant_positive() {
789 let rr = RollResult::new(42, RollDetail::Constant(42));
790 let expr_result = ExprResult::try_from(rr).unwrap();
791
792 assert_eq!(expr_result.total, 42);
793 assert_eq!(expr_result.rolls, vec![]);
794 assert_eq!(expr_result.modifier, 42);
795 }
796
797 #[test]
798 fn test_rollresult_to_exprresult_constant_negative() {
799 let rr = RollResult::new(5, RollDetail::Constant(-20));
800 let expr_result = ExprResult::try_from(rr).unwrap();
801
802 assert_eq!(expr_result.total, 5);
803 assert_eq!(expr_result.rolls, vec![]);
804 assert_eq!(expr_result.modifier, -20);
805 }
806
807 #[test]
808 fn test_rollresult_to_exprresult_constant_zero() {
809 let rr = RollResult::new(0, RollDetail::Constant(0));
810 let expr_result = ExprResult::try_from(rr).unwrap();
811
812 assert_eq!(expr_result.total, 0);
813 assert_eq!(expr_result.rolls, vec![]);
814 assert_eq!(expr_result.modifier, 0);
815 }
816
817 #[test]
818 fn test_rollresult_to_exprresult_dice_simple() {
819 let rr = RollResult::new(9, RollDetail::Dice(vec![4, 5]));
820 let expr_result = ExprResult::try_from(rr).unwrap();
821
822 assert_eq!(expr_result.total, 9);
823 assert_eq!(expr_result.rolls, vec![4, 5]);
824 assert_eq!(expr_result.modifier, 0);
825 }
826
827 #[test]
828 fn test_rollresult_to_exprresult_dice_multiple() {
829 let rr = RollResult::new(21, RollDetail::Dice(vec![3, 7, 5, 6]));
830 let expr_result = ExprResult::try_from(rr).unwrap();
831
832 assert_eq!(expr_result.total, 21);
833 assert_eq!(expr_result.rolls, vec![3, 7, 5, 6]);
834 assert_eq!(expr_result.modifier, 0);
835 }
836
837 #[test]
838 fn test_rollresult_to_exprresult_dice_single() {
839 let rr = RollResult::new(12, RollDetail::Dice(vec![12]));
840 let expr_result = ExprResult::try_from(rr).unwrap();
841
842 assert_eq!(expr_result.total, 12);
843 assert_eq!(expr_result.rolls, vec![12]);
844 assert_eq!(expr_result.modifier, 0);
845 }
846
847 #[test]
848 #[should_panic = "Overflow as expected"]
849 fn test_rollresult_to_exprresult_overflow() {
850 let rr = RollResult::new(u32::MAX, RollDetail::Dice(vec![u32::MAX]));
851 let result = ExprResult::try_from(rr);
852
853 assert!(result.is_err());
854 match result.unwrap_err() {
855 DiceError::Overflow(_) => panic!("Overflow as expected"),
856 _ => panic!("Expected Overflow Variant"),
857 }
858 }
859
860 #[test]
861 #[should_panic = "Overflow as expected"]
862 fn test_rollresult_to_exprresult_dice_with_overflow() {
863 let rr = RollResult::new(100, RollDetail::Dice(vec![u32::MAX - 1, 2]));
864 let result = ExprResult::try_from(rr);
865
866 assert!(result.is_err());
867 match result.unwrap_err() {
868 DiceError::Overflow(_) => panic!("Overflow as expected"),
869 _ => {
870 panic!("Expected Overflow variant.")
871 }
872 }
873 }
874
875 #[test]
876 fn test_rollresult_to_exprresult_empty_dice() {
877 let rr = RollResult::new(0, RollDetail::Dice(vec![]));
878 let expr_result = ExprResult::try_from(rr).unwrap();
879
880 assert_eq!(expr_result.total, 0);
881 assert_eq!(expr_result.rolls, vec![]);
882 assert_eq!(expr_result.modifier, 0);
883 }
884
885 #[test]
886 fn test_rollresult_to_exprresult_large_valid_values() {
887 let rr = RollResult::new(1000, RollDetail::Dice(vec![200, 300, 500]));
888 let expr_result = ExprResult::try_from(rr).unwrap();
889
890 assert_eq!(expr_result.total, 1000);
891 assert_eq!(expr_result.rolls, vec![200, 300, 500]);
892 assert_eq!(expr_result.modifier, 0);
893 }
894
895 #[test]
896 fn test_rollresult_from_keep_highest_preserves_detail() {
897 let mut roller = Roller::from_rng(StdRng::seed_from_u64(777));
898 let spec = RollSpec::new(5, 12, Some(Keep::Highest(2)));
899 let rr = roller.roll_spec(&spec).unwrap();
900
901 if let RollDetail::Dice(ref rolls) = rr.detail {
903 assert_eq!(rolls.len(), 5);
904 } else {
905 panic!("Expected Dice variant");
906 }
907
908 let expr_result = ExprResult::try_from(rr).unwrap();
909 assert_eq!(expr_result.rolls.len(), 5);
911 }
912}