Skip to main content

dice/statistics/
expression.rs

1// Copyright 2017 Kyle Mayes
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::{HashMap, HashSet};
16
17use super::ratio::{Ratio};
18use super::super::{Binary, Expression};
19
20//================================================
21// Macros
22//================================================
23
24// binary! _______________________________________
25
26macro_rules! binary {
27    ($left:expr, $right:expr, $operation:ident) =>  ({
28        match ($left, $right) {
29            (Some(left), Some(right)) => left.$operation(right),
30            _ => None,
31        }
32    });
33}
34
35//================================================
36// Structs
37//================================================
38
39// Expression ____________________________________
40
41impl Expression {
42    //- Accessors --------------------------------
43
44    /// Returns the smallest possible result of rolling this dice rolling expression.
45    pub fn minimum(&self) -> Option<i32> {
46        match *self {
47            Expression::Binary(binary, ref left, ref right) => match binary {
48                Binary::Add => binary!(left.minimum(), right.minimum(), checked_add),
49                Binary::Divide => binary!(left.minimum(), right.maximum(), checked_div),
50                Binary::Multiply => binary!(left.minimum(), right.minimum(), checked_mul),
51                Binary::Subtract => binary!(left.minimum(), right.maximum(), checked_sub),
52            },
53            Expression::Constant(constant) => Some(constant as i32),
54            Expression::Dice(dice, _, fold) => Some(dice.minimum(fold) as i32),
55            Expression::Die(die, _) => Some(die.minimum() as i32),
56        }
57    }
58
59    /// Returns the largest possible result of rolling this dice rolling expression.
60    pub fn maximum(&self) -> Option<i32> {
61        match *self {
62            Expression::Binary(binary, ref left, ref right) => match binary {
63                Binary::Add => binary!(left.maximum(), right.maximum(), checked_add),
64                Binary::Divide => binary!(left.maximum(), right.minimum(), checked_div),
65                Binary::Multiply => binary!(left.maximum(), right.maximum(), checked_mul),
66                Binary::Subtract => binary!(left.maximum(), right.minimum(), checked_sub),
67            },
68            Expression::Constant(constant) => Some(constant as i32),
69            Expression::Dice(dice, _, fold) => Some(dice.maximum(fold) as i32),
70            Expression::Die(die, _) => Some(die.maximum() as i32),
71        }
72    }
73
74    /// Returns the average result of rolling this dice rolling expression.
75    pub fn average(&self) -> Option<Ratio> {
76        match *self {
77            Expression::Binary(binary, ref left, ref right) => match binary {
78                Binary::Add => binary!(left.average(), right.average(), checked_add),
79                Binary::Divide => {
80                    let left = try_opt!(left.probabilities());
81                    let right = try_opt!(right.probabilities());
82                    let mut average = Ratio::zero();
83                    for (lr, lp) in left {
84                        for &(rr, rp) in &right {
85                            average += Ratio::new((lr / rr) as i128, 1) * (lp * rp);
86                        }
87                    }
88                    Some(average)
89                },
90                Binary::Multiply => binary!(left.average(), right.average(), checked_mul),
91                Binary::Subtract => binary!(left.average(), right.average(), checked_sub),
92            },
93            Expression::Constant(constant) => Some(Ratio::new(constant as i128, 1)),
94            Expression::Dice(dice, reroll, fold) => Some(dice.average(reroll, fold)),
95            Expression::Die(die, reroll) => Some(die.average(reroll)),
96        }
97    }
98
99    /// Returns all the possible results of rolling this dice rolling expression.
100    pub fn results(&self) -> Option<Vec<i32>> {
101        match *self {
102            Expression::Binary(binary, ref left, ref right) => {
103                let left = try_opt!(left.results());
104                let right = try_opt!(right.results());
105                let mut results = HashSet::new();
106                for lr in left {
107                    for rr in &right {
108                        results.insert(try_opt!(binary.combine(lr, *rr)));
109                    }
110                }
111                let mut results = results.into_iter().collect::<Vec<_>>();
112                results.sort();
113                Some(results)
114            },
115            Expression::Constant(constant) => Some(vec![constant as i32]),
116            Expression::Dice(dice, _, fold) => Some(dice.results(fold).collect()),
117            Expression::Die(die, _) => Some(die.results().collect()),
118        }
119    }
120
121    /// Returns the probabilities of all the possible results of rolling this dice rolling expression.
122    pub fn probabilities(&self) -> Option<Vec<(i32, Ratio)>> {
123        match *self {
124            Expression::Binary(binary, ref left, ref right) => {
125                let left = try_opt!(left.probabilities());
126                let right = try_opt!(right.probabilities());
127                let mut probabilities = try_opt!(self.results()).into_iter().map(|r| {
128                    (r, Ratio::zero())
129                }).collect::<HashMap<_, _>>();
130                for (lr, lp) in left {
131                    for &(rr, rp) in &right {
132                        let result = try_opt!(binary.combine(lr, rr));
133                        *probabilities.get_mut(&result).unwrap() += lp * rp;
134                    }
135                }
136                let mut probabilities = probabilities.into_iter().collect::<Vec<_>>();
137                probabilities.sort();
138                Some(probabilities)
139            },
140            Expression::Constant(constant) => Some(vec![(constant as i32, Ratio::new(1, 0))]),
141            Expression::Dice(dice, reroll, fold) => Some(dice.probabilities(reroll, fold)),
142            Expression::Die(die, reroll) => Some(die.probabilities(reroll)),
143        }
144    }
145}