line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Games::Dice::Probability; |
2
|
|
|
|
|
|
|
|
3
|
29
|
|
|
29
|
|
669610
|
use 5.006; |
|
29
|
|
|
|
|
196
|
|
|
29
|
|
|
|
|
1316
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
# Be a Good Module |
6
|
29
|
|
|
29
|
|
179
|
use strict; |
|
29
|
|
|
|
|
61
|
|
|
29
|
|
|
|
|
990
|
|
7
|
29
|
|
|
29
|
|
805
|
use warnings; |
|
29
|
|
|
|
|
64
|
|
|
29
|
|
|
|
|
1614
|
|
8
|
|
|
|
|
|
|
#use diagnostics; |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
# Our version number. |
11
|
|
|
|
|
|
|
our $VERSION = '0.02'; |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
# Required Modules |
14
|
|
|
|
|
|
|
# Math::Sumbolic::AuxFunctions is for the calcs of binomial coefficients. |
15
|
|
|
|
|
|
|
# |
16
|
|
|
|
|
|
|
# Parse::RecDescent parses the dice expressions. |
17
|
29
|
|
|
29
|
|
32161
|
use Math::Symbolic::AuxFunctions; |
|
29
|
|
|
|
|
2863530
|
|
|
29
|
|
|
|
|
1133
|
|
18
|
29
|
|
|
29
|
|
58556
|
use Parse::RecDescent; |
|
29
|
|
|
|
|
30093252
|
|
|
29
|
|
|
|
|
296
|
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# Optional Modules |
21
|
|
|
|
|
|
|
# Debug::ShowStuff is used by $self->debug() to descend and display hashes |
22
|
|
|
|
|
|
|
# in the object. |
23
|
|
|
|
|
|
|
# |
24
|
|
|
|
|
|
|
# Memoize is used by $self->new() to speed up calculations on individual |
25
|
|
|
|
|
|
|
# nodes in the expression tree. If there are duplicate nodes, the second |
26
|
|
|
|
|
|
|
# and all subsequent calc_distribution calls with the same parameters will |
27
|
|
|
|
|
|
|
# just return the cached values. |
28
|
|
|
|
|
|
|
# |
29
|
|
|
|
|
|
|
# Thanks to Mark Mills for the optional-module code snippet, and general |
30
|
|
|
|
|
|
|
# ideas, tips, and tricks for the whole module. He's my local Perl |
31
|
|
|
|
|
|
|
# Monk...you can email him thanks and offers of cookies at: |
32
|
|
|
|
|
|
|
# extremely{plus}pm{at}hostile{dot}org |
33
|
|
|
|
|
|
|
BEGIN { |
34
|
|
|
|
|
|
|
# Is Debug::ShowStuff available? |
35
|
29
|
50
|
|
29
|
|
5194
|
if ( eval q/ require Debug::ShowStuff / ) { |
36
|
|
|
|
|
|
|
# If so, import the routines. |
37
|
0
|
|
|
|
|
0
|
Debug::ShowStuff->import("showref"); |
38
|
|
|
|
|
|
|
} else { |
39
|
|
|
|
|
|
|
# Not available. Place stub in its place. |
40
|
29
|
|
|
0
|
0
|
1557
|
eval q/ sub showref { print @_; print "\n"; } /; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
41
|
|
|
|
|
|
|
} |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
# Is Memoize available? |
44
|
29
|
50
|
|
|
|
1722
|
if ( eval q/ require Memoize / ) { |
45
|
|
|
|
|
|
|
# If so, import the routines. |
46
|
29
|
|
|
|
|
93772
|
Memoize->import(); |
47
|
|
|
|
|
|
|
} else { |
48
|
|
|
|
|
|
|
# Not available. Place stub in its place. |
49
|
0
|
|
|
|
|
0
|
eval q/ sub memoize { return; } /; |
50
|
|
|
|
|
|
|
} |
51
|
|
|
|
|
|
|
} |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
# Binomial Coefficient Shortcut |
54
|
|
|
|
|
|
|
# Non-polluted namespaces are cool and all, but sometimes it's too much to |
55
|
|
|
|
|
|
|
# type a 44 character long subroutine name multiple times in a simple |
56
|
|
|
|
|
|
|
# equation. Sheesh. |
57
|
|
|
|
|
|
|
my $binco = \&Math::Symbolic::AuxFunctions::binomial_coeff; |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
# Recursive Parsing Grammar |
60
|
|
|
|
|
|
|
# Parsing grammar and tree code outright stolen from Sam Holden's |
61
|
|
|
|
|
|
|
# DiceDistribution.pm at http://sam.holden.id.au/junk/DICEDISTRIBUTION/. |
62
|
|
|
|
|
|
|
# Added a divide expression for division calculations. Changed the way |
63
|
|
|
|
|
|
|
# Fudge Dice are expressed to be #d[fF] which is more inline with how |
64
|
|
|
|
|
|
|
# they are represented elsewhere. Added mid# dice expression as part |
65
|
|
|
|
|
|
|
# of dicenode. |
66
|
|
|
|
|
|
|
my $DiceGrammar = <<'END_GRAMMAR'; |
67
|
|
|
|
|
|
|
expression: add_sub end { $item[1] } |
68
|
|
|
|
|
|
|
add_sub: mult_div '+' add_sub { { left => $item[1], op => '+', right => $item[3] } } |
69
|
|
|
|
|
|
|
add_sub: mult_div '-' add_sub { { left => $item[1], op => '-', right => $item[3] } } |
70
|
|
|
|
|
|
|
add_sub: mult_div |
71
|
|
|
|
|
|
|
mult_div: bracket '/' mult_div { { left => $item[1], op => '/', right => $item[3] } } |
72
|
|
|
|
|
|
|
mult_div: bracket '*' mult_div { { left => $item[1], op => '*', right => $item[3] } } |
73
|
|
|
|
|
|
|
mult_div: bracket |
74
|
|
|
|
|
|
|
bracket: '(' add_sub ')' { $item[2] } |
75
|
|
|
|
|
|
|
bracket: dicenode |
76
|
|
|
|
|
|
|
dicenode: /(\d+|mi)d(\d+|f)/i |
77
|
|
|
|
|
|
|
dicenode: /\d+/ |
78
|
|
|
|
|
|
|
end: /\s*$/ |
79
|
|
|
|
|
|
|
END_GRAMMAR |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
# Dice Parsing Object |
82
|
|
|
|
|
|
|
my $DiceParser = Parse::RecDescent->new($DiceGrammar) || die("bad grammar"); |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# import() |
85
|
|
|
|
|
|
|
# Faux import function that either memoizes the calc portions of the |
86
|
|
|
|
|
|
|
# module (default) or doesn't. |
87
|
|
|
|
|
|
|
sub import { |
88
|
|
|
|
|
|
|
# Default is to memoize. Saving cycles is a Good Thing. However, if |
89
|
|
|
|
|
|
|
# someone passes an unmemoize argument, then we will respect their wishes. |
90
|
29
|
50
|
|
29
|
|
983
|
if ( ! grep(/(un|no)memo(ize)*/i,@_) ) { |
91
|
|
|
|
|
|
|
# Attempt to memoize the calculation subroutines. This will either |
92
|
|
|
|
|
|
|
# truly memoize, or the stub memoize function will simply return having |
93
|
|
|
|
|
|
|
# done nothing. |
94
|
29
|
|
|
|
|
555
|
memoize('calc_distribution'); |
95
|
|
|
|
|
|
|
# Not certain if calc_combination will benefit from Memoize. Need to |
96
|
|
|
|
|
|
|
# test further. |
97
|
29
|
|
|
|
|
8690
|
memoize('calc_combination'); |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
# All is well. Return. |
101
|
29
|
|
|
|
|
265266
|
return; |
102
|
|
|
|
|
|
|
} |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
# debug() |
105
|
|
|
|
|
|
|
# Print debugging information about object. |
106
|
|
|
|
|
|
|
sub debug { |
107
|
|
|
|
|
|
|
# The object of our attention. |
108
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
# For every piece of the object... |
111
|
0
|
|
|
|
|
0
|
foreach my $key ( sort(keys(%$self)) ) { |
112
|
|
|
|
|
|
|
# Output the name... |
113
|
0
|
|
|
|
|
0
|
print "self->{$key}="; |
114
|
|
|
|
|
|
|
# And if it is a reference... |
115
|
0
|
0
|
|
|
|
0
|
if ( ref($self->{$key}) ) { |
116
|
|
|
|
|
|
|
# Print the contents of the reference... |
117
|
0
|
|
|
|
|
0
|
print "\n"; |
118
|
0
|
|
|
|
|
0
|
showref($self->{$key}); |
119
|
|
|
|
|
|
|
} else { |
120
|
|
|
|
|
|
|
# Or, print the value. |
121
|
0
|
|
|
|
|
0
|
print $self->{$key} . "\n"; |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
# All is well. |
126
|
0
|
|
|
|
|
0
|
return(0); |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# new(expression) |
130
|
|
|
|
|
|
|
# Creates a new object based on the provided dice expression. |
131
|
|
|
|
|
|
|
sub new { |
132
|
29
|
|
|
29
|
0
|
461
|
my $invocant = shift; |
133
|
29
|
|
33
|
|
|
318
|
my $class = ref($invocant) || $invocant; |
134
|
29
|
|
|
|
|
81
|
my $self = {}; |
135
|
|
|
|
|
|
|
|
136
|
29
|
50
|
|
|
|
140
|
die("must provide dice expression") unless (@_); |
137
|
|
|
|
|
|
|
|
138
|
29
|
50
|
|
|
|
158
|
if (@_ != 1) { |
139
|
0
|
|
|
|
|
0
|
die("new() called with too many arguments"); |
140
|
|
|
|
|
|
|
} else { |
141
|
29
|
|
50
|
|
|
509
|
$self->{EXPRESSION} = $DiceParser->expression(@_) || die "could not parse expression"; |
142
|
29
|
|
50
|
|
|
388373
|
$self->{DISTRIBUTION} = travel($self->{EXPRESSION}) || die "could not travel parsed expression"; |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
|
145
|
29
|
|
|
|
|
1424
|
return(bless($self, $class)); |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
# travel(expression) |
149
|
|
|
|
|
|
|
# Travel the parsed expression, returning hash of value => permutations. |
150
|
|
|
|
|
|
|
sub travel { |
151
|
61
|
|
50
|
61
|
0
|
382
|
my $node = shift || die "invalid or missing expression node"; |
152
|
|
|
|
|
|
|
|
153
|
61
|
100
|
|
|
|
212
|
if ( ref($node) ) { |
154
|
16
|
|
|
|
|
48
|
for ($node->{op}) { |
155
|
16
|
100
|
|
|
|
110
|
/(\+)/ && return( calc_combination($1, travel($node->{left}), travel($node->{right})) ); |
156
|
9
|
100
|
|
|
|
62
|
/(\-)/ && return( calc_combination($1, travel($node->{left}), travel($node->{right})) ); |
157
|
6
|
100
|
|
|
|
47
|
/(\*)/ && return( calc_combination($1, travel($node->{left}), travel($node->{right})) ); |
158
|
2
|
50
|
|
|
|
43
|
/(\/)/ && return( calc_combination($1, travel($node->{left}), travel($node->{right})) ); |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
|
162
|
45
|
|
|
|
|
133
|
for ($node) { |
163
|
45
|
100
|
|
|
|
1256
|
/^(\d+)(d)(\d+|f)/i && return( calc_distribution($2,$1,$3) ); |
164
|
8
|
100
|
|
|
|
208
|
/^(mi)(d)(\d+|f)/i && return( calc_distribution("m",3,$3) ); |
165
|
2
|
50
|
|
|
|
49
|
/^\d+$/ && return( {$node => 1} ); |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
0
|
|
|
|
|
0
|
die("invalid token in expression."); |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
# calc_distribution(method,numdice,numsides) |
172
|
|
|
|
|
|
|
# Calculate distribution of values/permutations given: method, number of |
173
|
|
|
|
|
|
|
# dice, and number of sides per die. |
174
|
|
|
|
|
|
|
sub calc_distribution { |
175
|
|
|
|
|
|
|
# The dice method requested. |
176
|
|
|
|
|
|
|
my $method = shift; |
177
|
|
|
|
|
|
|
# The number of dice. |
178
|
|
|
|
|
|
|
my $n = shift; $n += 0; |
179
|
|
|
|
|
|
|
# The number of sides/faces on each die. |
180
|
|
|
|
|
|
|
# f/F = Fudge dice = d3. |
181
|
|
|
|
|
|
|
my $s = shift; $s += 0 unless $s =~ /f/i; |
182
|
|
|
|
|
|
|
# Loop value based on total/face. |
183
|
|
|
|
|
|
|
my $t1; |
184
|
|
|
|
|
|
|
# Hash to return containing the distribution. |
185
|
|
|
|
|
|
|
my %dist; |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
# If dice node method is simple-sum nDs or nDf... |
188
|
|
|
|
|
|
|
if ( $method =~ /d/i ) { |
189
|
|
|
|
|
|
|
if ( $s =~ /f/i ) { |
190
|
|
|
|
|
|
|
# If dice node method is fudge nDf... |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
# First, get the distribution for nDs... |
193
|
|
|
|
|
|
|
my $tempdist = calc_distribution("d",$n,3); |
194
|
|
|
|
|
|
|
# Then loop to build the new distribution from (-n..0)... |
195
|
|
|
|
|
|
|
my $origkey = $n; |
196
|
|
|
|
|
|
|
for ($t1=-1 * $n; $t1 <= 0; $t1++) { |
197
|
|
|
|
|
|
|
# Changing the original values in (n..ns) to (-n..n), |
198
|
|
|
|
|
|
|
# copy the original distribution to the new one with |
199
|
|
|
|
|
|
|
# the correct values... |
200
|
|
|
|
|
|
|
$dist{$t1} = $$tempdist{$origkey}; |
201
|
|
|
|
|
|
|
$dist{$t1*-1} = $$tempdist{$origkey}; |
202
|
|
|
|
|
|
|
$origkey++; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
} elsif ( $n eq 1 ) { |
205
|
|
|
|
|
|
|
# Save compute cycles if only one die... |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
# Each face has a single chance... |
208
|
|
|
|
|
|
|
for ($t1=1; $t1 <= $s; $t1++) { |
209
|
|
|
|
|
|
|
$dist{$t1} = 1; |
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
} else { |
212
|
|
|
|
|
|
|
# Else compute number of combinations for a total on the face of n |
213
|
|
|
|
|
|
|
# dice. |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
# Minimum and maximum sums. |
216
|
|
|
|
|
|
|
my $tmin = $n; |
217
|
|
|
|
|
|
|
my $tmax = $n*$s; |
218
|
|
|
|
|
|
|
# Peak sum is the sum around which the bell-curve mirrors, saving |
219
|
|
|
|
|
|
|
# compute time. |
220
|
|
|
|
|
|
|
my $tpeak = ($tmin+$tmax)/2; |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
# For each total (t1) in $tmin..$tmax, calculate the number of |
223
|
|
|
|
|
|
|
# combinations giving that total. |
224
|
|
|
|
|
|
|
for ($t1=$tmin; $t1 <= $tpeak; $t1++) { |
225
|
|
|
|
|
|
|
# Total (t2) that is the mirror point in the bell curve. |
226
|
|
|
|
|
|
|
my $t2 = $tmin + $tmax - $t1; |
227
|
|
|
|
|
|
|
# Ceiling for the sum function. |
228
|
|
|
|
|
|
|
my $ceil = int( ($t1-$n) / $s ); |
229
|
|
|
|
|
|
|
# Result. |
230
|
|
|
|
|
|
|
my $res = 0; |
231
|
|
|
|
|
|
|
# Loop control for the sum funtion. |
232
|
|
|
|
|
|
|
my $k; |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
# Sum Function: For each k in 0 to $ceil... |
235
|
|
|
|
|
|
|
for ($k=0; $k <= $ceil; $k++) { |
236
|
|
|
|
|
|
|
# Calculate and add to previous results. |
237
|
|
|
|
|
|
|
$res += ((-1)**$k) * &$binco($n,$k) * &$binco(($t1-($s*$k)-1),($n-1)); |
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
# Set the mirrored points of the distribution. |
241
|
|
|
|
|
|
|
# Note: $t1 and $t2 can be equal once at |
242
|
|
|
|
|
|
|
# $t1=$tpeak when s is even. |
243
|
|
|
|
|
|
|
$dist{$t1} = $res; |
244
|
|
|
|
|
|
|
$dist{$t2} = $res; |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
} elsif ( $method =~ /m/i ) { |
248
|
|
|
|
|
|
|
# If dice method is take-the-middle-value nMs... |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
# Minimum and maximum values. |
251
|
|
|
|
|
|
|
my $tmin = 1; |
252
|
|
|
|
|
|
|
my $tmax = $s; |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
# For each value (t1) in $tmin..$tmax, calculate the number of |
255
|
|
|
|
|
|
|
# combinations giving t1 as the middle face value. |
256
|
|
|
|
|
|
|
for ($t1=$tmin; $t1 <= $tmax; $t1++) { |
257
|
|
|
|
|
|
|
$dist{$t1} = 1 + ( 3 * ($s - 1) ) + ( 6 * ($t1 - 1) * ($s - $t1) ); |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
# Return the value=>combinations distribution hash for this node. |
262
|
|
|
|
|
|
|
return({%dist}); |
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
# calc_combination(operand,distribution1,distribution2) |
266
|
|
|
|
|
|
|
# Combine two distributions with the method provided. |
267
|
|
|
|
|
|
|
sub calc_combination { |
268
|
|
|
|
|
|
|
# Calculation to perform on values. |
269
|
|
|
|
|
|
|
my $op = shift; |
270
|
|
|
|
|
|
|
# Distributions to combine. |
271
|
|
|
|
|
|
|
my $dist1 = shift; |
272
|
|
|
|
|
|
|
my $dist2 = shift; |
273
|
|
|
|
|
|
|
# The combined distribution. |
274
|
|
|
|
|
|
|
my %cdist; |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
# For each value in the first distribution... |
277
|
|
|
|
|
|
|
foreach my $val1 ( sort {$a+0 <=> $b+0} keys(%$dist1) ) { |
278
|
|
|
|
|
|
|
# Combine it with every value in the second distribution... |
279
|
|
|
|
|
|
|
foreach my $val2 ( sort {$a+0 <=> $b+0} keys(%$dist2) ) { |
280
|
|
|
|
|
|
|
# The new value of which is calculated based on combine method... |
281
|
|
|
|
|
|
|
my $newval; |
282
|
|
|
|
|
|
|
for ($op) { |
283
|
|
|
|
|
|
|
/\+/ && do { $newval = $val1 + $val2 }; |
284
|
|
|
|
|
|
|
/\-/ && do { $newval = $val1 - $val2 }; |
285
|
|
|
|
|
|
|
/\*/ && do { $newval = $val1 * $val2 }; |
286
|
|
|
|
|
|
|
/\// && do { $newval = int($val1 / $val2) }; |
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
# Calculate the new combined combinations and set it to the new |
289
|
|
|
|
|
|
|
# value in the new distribution. |
290
|
|
|
|
|
|
|
$cdist{$newval} += $$dist1{$val1} * $$dist2{$val2}; |
291
|
|
|
|
|
|
|
} |
292
|
|
|
|
|
|
|
} |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
# Return the combined distribution. |
295
|
|
|
|
|
|
|
return({%cdist}); |
296
|
|
|
|
|
|
|
} |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
# combinations(targetvalue) |
299
|
|
|
|
|
|
|
# Calculate number of combinations for a target value. |
300
|
|
|
|
|
|
|
sub combinations { |
301
|
303
|
|
|
303
|
0
|
547
|
my $self = shift; |
302
|
303
|
|
|
|
|
378
|
my $targetvalue = shift; |
303
|
|
|
|
|
|
|
|
304
|
303
|
100
|
|
|
|
738
|
$targetvalue = "ALL" unless defined($targetvalue); |
305
|
|
|
|
|
|
|
|
306
|
303
|
50
|
|
|
|
651
|
if ( ref($self) ) { |
307
|
303
|
50
|
|
|
|
608
|
if (@_ != 0) { |
308
|
0
|
|
|
|
|
0
|
die("combinations() called incorrectly"); |
309
|
|
|
|
|
|
|
} else { |
310
|
303
|
100
|
|
|
|
679
|
if ( $targetvalue eq "ALL" ) { |
311
|
28
|
50
|
|
|
|
133
|
if ( $self->{COMBINATIONS} ) { |
312
|
0
|
|
|
|
|
0
|
return ( $self->{COMBINATIONS} ); |
313
|
|
|
|
|
|
|
} else { |
314
|
28
|
|
|
|
|
56
|
foreach my $value ( values(%{$self->{DISTRIBUTION}}) ) { |
|
28
|
|
|
|
|
880
|
|
315
|
12201
|
|
|
|
|
12281
|
$self->{COMBINATIONS} += $value; |
316
|
|
|
|
|
|
|
} |
317
|
28
|
|
|
|
|
125
|
return($self->{COMBINATIONS}); |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
} else { |
320
|
275
|
|
|
|
|
1355
|
return( $self->{DISTRIBUTION}->{$targetvalue} ); |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
} |
323
|
|
|
|
|
|
|
} else { |
324
|
0
|
|
|
|
|
0
|
die("combinations() called on non-object"); |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
} |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
# distribution() |
329
|
|
|
|
|
|
|
# Returns a hash containing the distribution in value=>combinations format. |
330
|
|
|
|
|
|
|
sub distribution { |
331
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
332
|
|
|
|
|
|
|
|
333
|
0
|
0
|
|
|
|
0
|
if ( ref($self) ) { |
334
|
0
|
0
|
|
|
|
0
|
if (@_ != 0) { |
335
|
0
|
|
|
|
|
0
|
die("distribution() called with argument on object"); |
336
|
|
|
|
|
|
|
} else { |
337
|
0
|
|
|
|
|
0
|
return( $self->{DISTRIBUTION} ); |
338
|
|
|
|
|
|
|
} |
339
|
|
|
|
|
|
|
} else { |
340
|
0
|
0
|
|
|
|
0
|
if (@_ != 0) { |
341
|
0
|
|
0
|
|
|
0
|
my $expression = $DiceParser->expression(@_) || die "could not parse expression"; |
342
|
0
|
|
|
|
|
0
|
return( travel($expression) ); |
343
|
|
|
|
|
|
|
} else { |
344
|
0
|
|
|
|
|
0
|
die("no expression provided for non-object distribution() call"); |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
} |
347
|
|
|
|
|
|
|
} |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
# probability(targetvalue) |
350
|
|
|
|
|
|
|
# Returns the probability for targetvalue, or a hash of probabilities in |
351
|
|
|
|
|
|
|
# value=>probability format. |
352
|
|
|
|
|
|
|
sub probability { |
353
|
12201
|
|
|
12201
|
0
|
17924
|
my $self = shift; |
354
|
12201
|
|
|
|
|
13823
|
my $targetvalue = shift; |
355
|
|
|
|
|
|
|
|
356
|
12201
|
50
|
|
|
|
23596
|
$targetvalue = "ALL" unless defined($targetvalue); |
357
|
|
|
|
|
|
|
|
358
|
12201
|
50
|
|
|
|
22107
|
if ( ref($self) ) { |
359
|
12201
|
50
|
|
|
|
21747
|
if (@_ != 0) { |
360
|
0
|
|
|
|
|
0
|
die("probability() called incorrectly"); |
361
|
|
|
|
|
|
|
} else { |
362
|
12201
|
100
|
|
|
|
27573
|
if ( ! exists($self->{PROBABILITIES}) ) { |
363
|
28
|
|
|
|
|
157
|
my $combs = $self->combinations(); |
364
|
28
|
|
|
|
|
67
|
my %probs; |
365
|
28
|
|
|
|
|
71
|
foreach my $value ( keys(%{$self->{DISTRIBUTION}}) ) { |
|
28
|
|
|
|
|
36321
|
|
366
|
12201
|
|
|
|
|
26113
|
$probs{$value} = $self->{DISTRIBUTION}->{$value} / $combs; |
367
|
|
|
|
|
|
|
} |
368
|
28
|
|
|
|
|
11049
|
$self->{PROBABILITIES} = {%probs}; |
369
|
|
|
|
|
|
|
} |
370
|
12201
|
50
|
|
|
|
22987
|
if ( $targetvalue eq "ALL" ) { |
371
|
0
|
|
|
|
|
0
|
return( $self->{PROBABILITIES} ); |
372
|
|
|
|
|
|
|
} else { |
373
|
12201
|
|
50
|
|
|
78851
|
return( $self->{PROBABILITIES}->{$targetvalue} || undef ); |
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
} |
376
|
|
|
|
|
|
|
} else { |
377
|
0
|
|
|
|
|
0
|
die("probability() called on non-object"); |
378
|
|
|
|
|
|
|
} |
379
|
|
|
|
|
|
|
} |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
# bounds() |
382
|
|
|
|
|
|
|
# Returns the min and max values of the valueset. |
383
|
|
|
|
|
|
|
sub bounds { |
384
|
56
|
|
|
56
|
0
|
125
|
my $self = shift; |
385
|
|
|
|
|
|
|
|
386
|
56
|
50
|
|
|
|
190
|
if ( ref($self) ) { |
387
|
56
|
50
|
|
|
|
180
|
if (@_ != 0) { |
388
|
0
|
|
|
|
|
0
|
die("bounds() called with argument on object"); |
389
|
|
|
|
|
|
|
} else { |
390
|
56
|
|
|
|
|
161
|
return( [ $self->min(), $self->max() ] ); |
391
|
|
|
|
|
|
|
} |
392
|
|
|
|
|
|
|
} else { |
393
|
0
|
|
|
|
|
0
|
die("bounds() called on non-object"); |
394
|
|
|
|
|
|
|
} |
395
|
|
|
|
|
|
|
} |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
# max() |
398
|
|
|
|
|
|
|
# Returns the max value of the valueset. |
399
|
|
|
|
|
|
|
sub max { |
400
|
84
|
|
|
84
|
0
|
160
|
my $self = shift; |
401
|
|
|
|
|
|
|
|
402
|
84
|
50
|
|
|
|
220
|
if ( ref($self) ) { |
403
|
84
|
50
|
|
|
|
236
|
if (@_ != 0) { |
404
|
0
|
|
|
|
|
0
|
die("max() called with argument on object"); |
405
|
|
|
|
|
|
|
} else { |
406
|
84
|
100
|
|
|
|
210
|
if ( $self->{MAX} ) { |
407
|
56
|
|
|
|
|
401
|
return( $self->{MAX} ); |
408
|
|
|
|
|
|
|
} else { |
409
|
28
|
|
|
|
|
58
|
my @values = sort {$b+0 <=> $a+0} keys(%{$self->{DISTRIBUTION}}); |
|
145546
|
|
|
|
|
170861
|
|
|
28
|
|
|
|
|
2033
|
|
410
|
28
|
|
|
|
|
1013
|
$self->{MAX} = shift(@values); |
411
|
28
|
|
|
|
|
726
|
return( $self->{MAX} ); |
412
|
|
|
|
|
|
|
} |
413
|
|
|
|
|
|
|
} |
414
|
|
|
|
|
|
|
} else { |
415
|
0
|
|
|
|
|
0
|
die("max() called on non-object"); |
416
|
|
|
|
|
|
|
} |
417
|
|
|
|
|
|
|
} |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
# min() |
420
|
|
|
|
|
|
|
# Returns the min value of the valueset. |
421
|
|
|
|
|
|
|
sub min { |
422
|
84
|
|
|
84
|
0
|
12866
|
my $self = shift; |
423
|
|
|
|
|
|
|
|
424
|
84
|
50
|
|
|
|
292
|
if ( ref($self) ) { |
425
|
84
|
50
|
|
|
|
234
|
if (@_ != 0) { |
426
|
0
|
|
|
|
|
0
|
die("min() called with argument on object"); |
427
|
|
|
|
|
|
|
} else { |
428
|
84
|
100
|
|
|
|
508
|
if ( $self->{MIN} ) { |
429
|
52
|
|
|
|
|
225
|
return( $self->{MIN} ); |
430
|
|
|
|
|
|
|
} else { |
431
|
32
|
|
|
|
|
98
|
my @values = sort {$a+0 <=> $b+0} keys(%{$self->{DISTRIBUTION}}); |
|
145751
|
|
|
|
|
168339
|
|
|
32
|
|
|
|
|
1538
|
|
432
|
32
|
|
|
|
|
1546
|
$self->{MIN} = shift(@values); |
433
|
32
|
|
|
|
|
1235
|
return( $self->{MIN} ); |
434
|
|
|
|
|
|
|
} |
435
|
|
|
|
|
|
|
} |
436
|
|
|
|
|
|
|
} else { |
437
|
0
|
|
|
|
|
|
die("min() called on non-object"); |
438
|
|
|
|
|
|
|
} |
439
|
|
|
|
|
|
|
} |
440
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
1; |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
__END__ |