File Coverage

blib/lib/Games/Dice/Roller.pm
Criterion Covered Total %
statement 185 194 95.3
branch 127 160 79.3
condition 102 113 90.2
subroutine 12 12 100.0
pod 2 3 66.6
total 428 482 88.8


line stmt bran cond sub pod time code
1             package Games::Dice::Roller;
2            
3 10     10   80432 use 5.010;
  10         39  
4 10     10   51 use strict;
  10         24  
  10         225  
5 10     10   51 use warnings;
  10         19  
  10         265  
6            
7 10     10   52 use Carp;
  10         18  
  10         24911  
8            
9             our $VERSION = '0.01';
10             our $debug = $ENV{DICE_ROLLER_DEBUG} // 0;
11            
12             sub new{
13 12     12 1 9155 my $class = shift;
14 12         33 my %opts = @_;
15 12 100       49 if ( defined $opts{sub_rand} ){
16             croak "sub_rand must be a code reference meant to replace core rand function"
17 7 100       41 unless ref $opts{sub_rand} eq 'CODE';
18             }
19             return bless {
20 10013     10013   25767 sub_rand => $opts{sub_rand} // sub{ rand($_[0]) },
21 11   100     95 }, $class;
22             }
23            
24            
25             sub roll{
26 37     37 1 13430 my $self = shift;
27 37         64 my $arg = shift;
28 37 100       98 croak "roll method expects one argument" unless $arg;
29 36 100       90 croak "roll method expects a single string argument" if @_;
30            
31             # trim spaces
32 35         111 $arg =~ s/^\s+//;
33 35         90 $arg =~ s/\s+$//;
34            
35             # check if we received a dice pool
36 35         118 my @args = split /\s+/, $arg;
37            
38             # a dice pool
39 35 100       91 if ( scalar @args > 1 ){
40             # transform each one in resulting hashref returned by _identify_type
41 6         12 @args = map { _identify_type($_) } @args;
  23         43  
42 6         13 @args = _validate_pool( @args );
43             # transform each dice expression in its resulting format
44 4         7 foreach my $ele( @args ){
45 17 100       40 next unless $ele->{type} eq 'dice_expression';
46 9         46 my ($res, $descr) = $self->roll( $ele->{original} );
47 9         54 $ele = { result => $res, result_description => $descr, original => $ele->{original}};
48             }
49             # is the last element
50 4         7 my $global_modifier = pop @args;
51 4         15 my @sorted = sort{ $a->{result} <=> $b->{result} }@args;
  12         28  
52 4 100       12 @sorted = reverse @sorted if $global_modifier->{value} eq 'kh';
53 4         7 my $global_result = $sorted[0]->{result};
54             my @global_descr = (
55             ($sorted[0]->{original} ? $sorted[0]->{original} : $sorted[0]->{result}).
56 4 50       16 ($sorted[0]->{result_description} ? " = $sorted[0]->{result_description}": '')
    50          
57             );
58 4         7 shift @sorted;
59             push @global_descr, "( ".
60             ($_->{original} ? $_->{original} : '').
61             ($_->{result_description}?" = $_->{result_description} = ":'').
62             ($_->{result}?"$_->{result} ":'').
63 4 100       34 ")" for @sorted;
    100          
    50          
64            
65 4         32 return ($global_result, join ', ',@global_descr);
66            
67             }
68             # a single dice expression
69             else{
70             # transform it in a hashref as returned by _identify_type
71             # this will be returned as third element
72 29         63 my $ref = _identify_type( shift @args );
73            
74             # used to accumulate partial results and descriptive string elements
75 28         59 my ( @partial, @descr );
76            
77 28         95 my ($times, $sides) = split 'd', $ref->{dice_exp};
78 28         88 while( $times > 0 ){
79            
80 256         319 my $single_res;
81            
82             # BARE DICE EXPRESSION
83 256 100       488 unless ( $ref->{die_mod} ){
84 122         206 $single_res = $self->single_die( $sides );
85 122         902 push @partial, $single_res;
86 122         169 push @descr, $single_res;
87 122         154 $times--;
88 122         232 next;
89             }
90            
91             # DIE MODIFIERS #
92             # avg does not require further processing
93 134 100 66     360 if ( $ref->{die_mod} and $ref->{die_mod} eq 'avg' ){
94 5         10 $single_res = (1 + $sides) / 2;
95 5         8 push @partial, $single_res;
96 5         14 push @descr, $single_res;
97 5         10 $times--;
98 5         11 next;
99             }
100             # if r x cs roll the die
101             else{
102 129         219 $single_res = $self->single_die( $sides );
103             }
104             # process r x cs die modifiers
105             # if r
106 129 100 66     991 if ( $ref->{die_mod} and $ref->{die_mod} eq 'r' ){
107 42         80 my $comp_num = $ref->{die_mod_val};
108 42         56 my $comp_op = $ref->{comp_mod};
109             # check if it must be rerolled
110 42 100 100     352 if(
      100        
      100        
      100        
      100        
      100        
      100        
111             (not defined $ref->{comp_mod} and $single_res == $comp_num) or
112             (defined $ref->{comp_mod} and $ref->{comp_mod} eq 'lt' and $single_res < $comp_num ) or
113             (defined $ref->{comp_mod} and $ref->{comp_mod} eq 'gt' and $single_res > $comp_num )
114             ){
115             # REROLL
116 10         22 push @descr,"($single_res"."r)";
117 10         22 next;
118             }
119             else{
120 32         71 push @descr, $single_res;
121 32         44 push @partial, $single_res;
122 32         37 $times--;
123 32         70 next;
124             }
125             } # end of r check
126             # if x
127 87 100 66     267 if ( $ref->{die_mod} and $ref->{die_mod} eq 'x' ){
128 51         76 my $comp_num = $ref->{die_mod_val};
129 51         70 my $comp_op = $ref->{comp_mod};
130             # check if it must be exploded
131 51 100 100     507 if(
      100        
      100        
      100        
      100        
      100        
      100        
132             (not defined $ref->{comp_mod} and $single_res == $comp_num) or
133             (defined $ref->{comp_mod} and $ref->{comp_mod} eq 'lt' and $single_res < $comp_num ) or
134             (defined $ref->{comp_mod} and $ref->{comp_mod} eq 'gt' and $single_res > $comp_num )
135             ){
136             # EXPLODE
137 10         23 push @descr,$single_res."x";
138 10         17 push @partial, $single_res;
139 10         20 next;
140             }
141             else{
142 41         89 push @descr, $single_res;
143 41         84 push @partial, $single_res;
144 41         59 $times--;
145 41         78 next;
146             }
147            
148             } # end of x check
149            
150             # if cs
151 36 50 33     105 if ( $ref->{die_mod} and $ref->{die_mod} eq 'cs' ){
152 36         56 my $comp_num = $ref->{die_mod_val};
153 36         44 my $comp_op = $ref->{comp_mod};
154             # initialize partial with zero succes
155 36         48 push @partial, 0;
156             # check if it is success
157 36 100 100     252 if(
      100        
      100        
      100        
      100        
      100        
      100        
158             (not defined $ref->{comp_mod} and $single_res == $comp_num) or
159             (defined $ref->{comp_mod} and $ref->{comp_mod} eq 'lt' and $single_res < $comp_num ) or
160             (defined $ref->{comp_mod} and $ref->{comp_mod} eq 'gt' and $single_res > $comp_num )
161             ){
162             # SUCCESS
163 10         15 push @descr,$single_res;
164 10         16 push @partial, 1;
165 10         13 $times--;
166 10         20 next;
167             }
168             else{
169 26         48 push @descr, "($single_res)";
170 26         39 $times--;
171 26         47 next;
172             }
173             } # end of cs check
174             } # end of while loop
175            
176             # RESULT MODIFIERS kh kl dh dl #
177 28 100 66     93 if ( $ref->{res_mod} and $ref->{res_mod} =~/^(?:kh|kl|dh|dl)$/ ){
178 6         12 my @wanted;
179             my @dropped;
180             # sort from lowest to highest partial, temporary results
181 6         27 my @sorted = sort{ $a <=> $b }@partial;
  120         171  
182            
183             # kh and kl
184 6 100 100     43 if ( $ref->{res_mod} eq 'kh' or $ref->{res_mod} eq 'kl'){
185             # reverse if highest are needed
186 3 100       8 @sorted = reverse @sorted if $ref->{res_mod} eq 'kh';
187             # reset partial result array
188 3         16 undef @partial;
189             # unshift n highest values shortening @sorted
190 3         17 unshift @partial, shift @sorted for 1..$ref->{res_mod_val};
191             # consume what left in sorted to modify description
192 3         10 while ( my $tobedropped = shift @sorted ){
193 24         39 foreach my $ele( @descr ){
194 172 100       290 if ( $ele eq $tobedropped ){
195 24         34 $ele = "($ele)";
196 24         48 last;
197             }
198             }
199             }
200 3 100       8 @descr = reverse @descr if $ref->{res_mod} eq 'kl';
201             } # end kh kl check
202            
203             # dh and dl
204 6 100 100     25 if ( $ref->{res_mod} eq 'dh' or $ref->{res_mod} eq 'dl'){
205             # reverse if lowest are needed
206 3 100       9 @sorted = reverse @sorted if $ref->{res_mod} eq 'dl';
207             # reset partial result array
208 3         6 undef @partial;
209             # unshift n highest values shortening @sorted
210 3         18 unshift @partial, shift @sorted for 1 .. ( scalar @sorted - $ref->{res_mod_val} );
211             # consume what left in sorted to modify description
212 3         7 while ( my $tobedropped = shift @sorted ){
213 20 100       40 foreach my $ele( $ref->{res_mod} eq 'dl' ? reverse @descr : @descr ){
214 79 100       138 if ( $ele eq $tobedropped ){
215 20         26 $ele = "($ele)";
216 20         40 last;
217             }
218             }
219             }
220 3 100       8 @descr = reverse @descr if $ref->{res_mod} eq 'dh';
221             } # end dh dl check
222            
223             } # end of result modifiers processing
224            
225             # RESULT SUMMATION
226 28 100       54 if ( $ref->{res_sum} ){
227 12         32 push @descr, $ref->{res_sum};
228 12         22 push @partial, $ref->{res_sum};
229             }
230            
231             # COMPUTE RESULT AND DESCRIPTION
232             # add them to the $ref detailed result hasref
233 28         120 $ref->{result} += $_ for @partial;
234 28         139 $ref->{result_description} = join ' ', @descr;
235            
236 28 50       61 print "Description: $ref->{result_description}\nResult : $ref->{result}\n\n" if $debug;
237            
238 28         144 return ($ref->{result}, $ref->{result_description}, $ref);
239             } # end of single dice expression evaluation
240             }
241            
242             sub single_die{
243 10261     10261 0 24714 my $self = shift;
244 10261         12639 my $sides = shift;
245 10261 100       16684 croak "single_die expect one argument" unless $sides;
246 10260 100       26007 croak "Invalid side [$sides]" unless $sides =~/^(\d+)$/;
247 10256         17871 $sides = $1;
248 10256         16450 return 1 + int( $self->{sub_rand}($sides) );
249             }
250            
251             sub _validate_expr{
252 98     98   146 my $result = shift;
253            
254             # NB: see ./t/04-validate-expr.t
255             # many of the following check are never reached
256            
257             # die_mod = avg
258 98 100 100     286 if ( $result->{die_mod} and $result->{die_mod} eq 'avg' ){
259 7 50       16 croak "with avg no result modification (k|d) are admitted. OK: 3d8avg NO: 3d8avgkh" if $result->{res_mod};
260 7 100       25 croak "with avg no comparison modifiers (gt|lt) are admitted. OK: 3d8avg NO: 3d8avglt" if $result->{comp_mod};
261 6 100       40 croak "with avg no modification value (number) is admitted. OK: 3d8avg NO: 3d8avg3" if $result->{die_mod_val};
262             }
263             # die_mod = cs
264 96 100 100     251 if ( $result->{die_mod} and $result->{die_mod} eq 'cs' ){
265 9 100       31 croak "with cs no result modification (k|d) are admitted. OK: 3d8cs3 NO: 3d8cs3kl" if $result->{res_mod};
266 8 100       51 croak "with cs a number must be also specified. OK: 3d8cs2 NO: 3d8cs" unless $result->{die_mod_val};
267 6 100       26 croak "with cs no sum are permitted. OK: 3d8cs2 NO: 3d8cs2+12" if $result->{res_sum};
268             }
269             # die_mod = x
270 92 100 100     258 if ( $result->{die_mod} and $result->{die_mod} eq 'x' ){
271 12 100       42 croak "with x no result modification (k|d) are admitted. OK: 3d8x8 NO: 3d8x8kl" if $result->{res_mod};
272 11 100       37 croak "with x a number must be also specified. OK: 3d8x8 NO: 3d8x" unless $result->{die_mod_val};
273             }
274             # die_mod = r
275 90 100 100     238 if ( $result->{die_mod} and $result->{die_mod} eq 'r' ){
276 9 50       23 croak "with r a number must be also specified. OK: 3d8r1 NO: 3d8r" unless $result->{die_mod_val};
277             }
278             # comp_mod = gt|lt
279 90 100 66     226 if ( $result->{comp_mod} and $result->{comp_mod} =~/^(?:gt|lt)$/ ){
280 10 50       33 croak "a comparison modifier (lt or gt) can only be used with r x and cs. OK: 3d8rlt2 NO: 3d8avglt4" unless $result->{die_mod} =~ /^(?:r|x|cs)$/;
281             }
282             # res_mod = kh|kl|dh|dl
283 90 100 66     291 if ( $result->{res_mod} and $result->{res_mod} =~/^(?:kh|kl|dh|dl)$/ ){
284 27 50       66 croak "a result modifier (kh, kl, dh and dl) can only be used with number after it. OK: 3d8kh2 NO: 3d8kl" unless $result->{res_mod_val};
285 27 50       51 croak "a result modifier (kh, kl, dh and dl) cannot be used with a die modifier (r, x, cs or avg) OK: 3d8kh2 NO: 3d8x7kh3" if $result->{die_mod};
286 27 50       62 croak "a result modifier (kh, kl, dh and dl) cannot be used with a comparison modifier (lt or gt). OK: 3d8kh2 NO: 3d8khlt2" if $result->{comp_mod};
287 27 50       128 my $dice_num = $1 if $result->{dice_exp}=~ /^(\d+)d/;
288 27 100       108 croak "too many dice to keep or drop ($dice_num) in $result->{dice_exp}" if $result->{res_mod_val} >= $dice_num;
289             }
290             # res_sum = +3|-3
291 89 100 66     349 if ( $result->{res_sum} and $result->{res_sum} =~ /^[+-]\d+$/){
292 49 50 66     153 croak "a result sum cannot be used when cs is used" if defined $result->{die_mod} and $result->{die_mod} eq 'cs';
293             }
294             }
295            
296             sub _validate_pool{
297 6     6   12 my @args = @_;
298             # type => 'number'
299             # type => 'global_modifier'
300             # type => 'dice_expression'
301            
302 6 100       13 croak "too many bare number in dice pool" if 1 < grep{ $_->{type} eq 'number' }@args;
  23         69  
303 5 100       7 croak "too many global modifiers (kh or kl) in dice pool" if 1 < grep{ $_->{type} eq 'global_modifier' }@args;
  19         52  
304             # deafult to kh
305 4 100       6 push @args, { type => 'global_modifier', value => 'kh' } if 0 == grep{ $_->{type} eq 'global_modifier' }@args;
  15         32  
306 4 50       9 croak "global modifiers (kh or kl) must be the last element in a dice pool" unless $args[-1]->{type} eq 'global_modifier';
307 4         11 return @args;
308             }
309            
310             sub _identify_type{
311 122     122   29892 my $exp = shift;
312 122 100       291 croak "_validate_type expects one argument" unless $exp;
313            
314 121 50       247 print "\nEvaluating [$exp]\n" if $debug;
315            
316             # we got a dice expression, complex at will
317            
318             # dice_exp 1d6
319             # res_mod kh kl dh dl
320             # res_mod_val \d+
321             # die_mod r x cs avg
322             # comp_mod gt lt (null stands for eq)
323             # die_mod_val \d+
324             # res_sum +3 -13
325            
326 121 100       917 if( $exp =~ /
    100          
    100          
327             ^
328             (?\d+d\d+) # a mandatory dice expression as start 1d6
329             ( # an optional res_mod group
330             (?(?:kh|kl|dh|dl)) # with a res_mod kh|kl|dh|dl
331             (?\d+) # and with a mod_val 3
332             )?
333             ( # an optional die_mod
334             (?(?:r|x|cs|avg)) # with a die_mod r|x|cs|avg
335             (?(?:gt|lt))? # an optional comp_mod gt|lt
336             (?\d{0,}) # and an optional die_mod_val 3
337             )?
338             ( # an optional res_sum
339             (?[+-]{1}\d+) # with a res_mod +|-3
340             )?
341            
342             /x
343             ){
344 106 50       256 if ( $debug ){
345 0         0 print "\toriginal : [$exp]\n";
346 0         0 print "\ttype : [dice_expression]\n";
347 10     10   5224 print "\tdice expression : [$+{dice_exp}]\n";
  10         3889  
  10         5219  
  0         0  
348 0 0       0 print "\tresult modifier : [$+{res_mod}]\n" if $+{res_mod};
349 0 0       0 print "\tresult val modifier: [$+{res_mod_val}]\n" if $+{res_mod_val};
350 0 0       0 print "\tdie modifier : [$+{die_mod}]\n" if $+{die_mod};
351 0 0       0 print "\tdie comp modifier : [$+{comp_mod}]\n" if $+{comp_mod};
352 0 0       0 print "\tdie val modifier : [$+{die_mod_val}]\n" if $+{die_mod_val};
353 0 0       0 print "\tresult sum : [$+{res_sum}]\n" if $+{res_sum};
354             }
355            
356             # save the hashref output ( $+{KEY} cannot be reused inside a later s/// )
357             my $result = {
358             type => 'dice_expression',
359             original => $exp,
360             dice_exp => $+{dice_exp},
361             res_mod => $+{res_mod},
362             res_mod_val => $+{res_mod_val},
363             die_mod => $+{die_mod},
364             comp_mod => $+{comp_mod},
365             die_mod_val => $+{die_mod_val},
366             res_sum => $+{res_sum},
367 106         1831 };
368            
369             # remove everything matched from original expression..
370 106         418 my $tobenull = $exp;
371 106 50       215 print "Cleaning the expression to spot garbage:\n" if $debug;
372             # 'type' key unuseful, dice_exp must be the first to be removed or a lone number can modify it
373 106         195 foreach my $key ( qw( dice_exp res_mod res_mod_val die_mod comp_mod die_mod_val res_sum) ){
374 742 50 66     2016 print "\tremoving: $result->{$key}\n" if defined $result->{$key} and $debug;
375 742 100       3773 $tobenull =~ s/\Q$result->{$key}\E// if defined $result->{$key};
376             }
377 106 50       219 print "Left in the expression: [$tobenull]\n" if $debug;
378             # ..to spot unwanted remaining crumbles
379 106 100       290 croak "unexpected string [$tobenull] in expression [$exp]" if length $tobenull;
380            
381 98         246 _validate_expr( $result );
382 89         275 return $result;
383             }
384             # we got a bare number (can be used in dice pool)
385             elsif ( $exp =~ /^\d+$/ ){
386 8 50       17 print "received a bare number [$exp] used in dice pools\n" if $debug;
387 8         37 return { type => 'number', result => $exp };
388             }
389             # we got a global dice pool modifier
390             elsif( $exp =~ /^kh|kl$/){
391 4 50       8 print "received a global dice modifier [$exp] used in dice pools\n" if $debug;
392 4         15 return { type => 'global_modifier', value => $exp };
393             }
394             else{
395 3         29 croak "unrecognized expression [$exp]";
396             }
397             }
398            
399             my $wanted=<<'EOT';
400            
401             #########################
402             # multiple dice expression:
403             #########################
404             Dice pools; {2d8, 1d6} (qw( 2d8 1d6 )) -> sum
405             Dice pools with modifiers; {1d20+7, 10}kh1 (qw( 2d8+7 1d20 ))
406             (qw( 2d8+7 33 ))
407             (qw( 2d8 1d6 kh ))
408             (qw( 2d8 1d6 kl ))
409             (qw( 2d8 33 kh ))
410             (qw( 2d8 33 kl ))
411            
412            
413             Rounding; floor(1.5), ceil(1.5), round(1.5)
414             Average; avg(8d6)
415            
416            
417             EOT
418            
419            
420            
421             =head1 NAME
422            
423             Games::Dice::Roller - a full featured dice roller system
424            
425             =head1 VERSION
426            
427             Version 0.01
428            
429             =cut
430            
431             =head1 SYNOPSIS
432            
433             use Games::Dice::Roller;
434            
435             my $dice = Games::Dice::Roller->new();
436            
437             # simple dice expressions
438             my @simple = (qw( 3d6 4d8+4 1d100-5 ));
439            
440             # average results
441             my @average = (qw(4d4avg 4d8avg+2 4d12avg-7));
442            
443             # reroll if equal (default), lesser than or greater than N
444             my @reroll = (qw(6d4r1 5d6rlt3 5d6rgt4 6d4r1+10 6d4r1-5));
445            
446             # explode if equal (default), lesser than or greater than N
447             my @explode = (qw( 3d6x6 3d6xlt3 3d6xgt4 3d6x6+3 3d6x6-4 ));
448            
449             # just count succesful rolls
450             my @succes = (qw( 3d6cs1 3d6cslt3 3d6csgt4 ));
451            
452             # keep and drop dice from final results
453             my @keep_and_drop = (qw( 4d6kh3 4d6kh2 4d6kl2+3 4d6kl2-3 4d12dh1 4d12dl3 4d12dl3+3 4d12dl1-4 ));
454            
455            
456             foreach my $dice_expression ( @simple , @average, @reroll, @explode, @succes, @keep_and_drop ){
457            
458             my ($res, $descr) = $dice->roll( $dice_expression );
459             print "$res [$dice_expression] $descr\n";
460             }
461            
462            
463            
464             # 10 [3d6] 5 2 3
465             # 22 [4d8+4] 7 7 1 3 +4
466             # 14 [1d100-5] 19 -5
467             # 10 [4d4avg] 2.5 2.5 2.5 2.5
468             # 20 [4d8avg+2] 4.5 4.5 4.5 4.5 +2
469             # 19 [4d12avg-7] 6.5 6.5 6.5 6.5 -7
470             # 18 [6d4r1] 4 (1r) 3 2 3 (1r) 2 4
471             # 19 [5d6rlt3] 3 4 3 4 (2r) (2r) (2r) 5
472             # 11 [5d6rgt4] 2 4 1 (5r) 2 (5r) 2
473             # 25 [6d4r1+10] (1r) 2 (1r) 2 2 4 3 2 +10
474             # 13 [6d4r1-5] (1r) (1r) 2 (1r) 2 4 4 (1r) 4 2 -5
475             # 7 [3d6x6] 1 1 5
476             # 17 [3d6xlt3] 6 5 1x 1x 1x 3
477             # 11 [3d6xgt4] 4 3 4
478             # 11 [3d6x6+3] 2 2 4 +3
479             # 8 [3d6x6-4] 5 3 4 -4
480             # 1 [3d6cs1] (5) (3) 1
481             # 1 [3d6cslt3] 2 (6) (6)
482             # 2 [3d6csgt4] 6 (3) 5
483             # 14 [4d6kh3] (2) 6 4 4
484             # 9 [4d6kh2] 3 6 (2) (2)
485             # 8 [4d6kl2+3] (6) 4 1 (6) +3
486             # 1 [4d6kl2-3] (5) 1 3 (3) -3
487             # 13 [4d12dh1] 2 6 5 (6)
488             # 12 [4d12dl3] (9) (10) (9) 12
489             # 9 [4d12dl3+3] (1) 6 (3) (6) +3
490             # 26 [4d12dl1-4] 9 (1) 9 12 -4
491            
492            
493            
494             =head1 METHODS
495            
496            
497             =head2 new
498            
499             The constructor accept only one option, an anonymous hash and the only valid key is C holding as value an anonymous sub to be invoked instead of the core function L
500            
501            
502             =head2 roll
503            
504             This method expects a single string to be passed as argument. This string can be a C or a C (see below).
505            
506             It returns the final result and a string representing the roll.
507            
508            
509             my $result = $dice->roll('3d6+3');
510             print "result of the dice roll was: $result";
511             # result of the dice roll was: 16
512            
513             my ($res, $descr) = $dice->roll('3d6+3');
514             print "$descr\nResult: $res";
515             # 5 2 6 +3
516             # Result: 16
517            
518             In the descriptive string some die result can be modified by modifiers: dropped ones will be inside parens, rerolled dice result will be inside parens and with a C following them and exploded dice results will be marked by a C
519            
520             A third element is returned too: a hash reference intended to be used mainly internally and for debug purposes, with the internal carateristics of the dice expression. Dont rely on this because it can be changed or removed in future releases.
521            
522             =head3 die mofiers
523            
524             =head4 avg - average
525            
526             No dice are rolled, but the die average will be used instead. For C<1d6> the average will be C<3.5> so C<4d6avg> will always result in C<14>
527            
528             =head4 r - reroll
529            
530             Reroll dice equal, lesser than (C) or greater than (C) C as in C<3d6r1 3d6rlt3 3d6rgt4>
531             Each die rerolled will be not part of the final result and in the descriptive string will be inside parens and followed by C as in C<(1r)>
532            
533             =head4 x - explode
534            
535             Each die roll equal, lesser than (C) or greater than (C) C (as in C<3d6x6 3d6xlt3 3d6xgt4>) will add another die of the same type.
536             An exploded die will be added to final result and will be marked with C as in C<6x> in the descriptive string.
537            
538             For example C<8d6xgt4> can lead to a result of C<42> and a description like: C<6x 4 6x 4 5x 3 5x 3 2 2 1 1>
539            
540            
541             =head4 cs - count successes
542            
543             If a die roll is equal, lesser than (C) or greater than (C) C (as in C<3d6cs1 3d6cslt3 3d6gt4>) then it will count as a success.
544             The final result will be the succes count.
545             In the decription string unsuccesfull rolls will be inside parens.
546            
547            
548            
549            
550             =head3 result modifiers
551            
552             =head4 keep and drop
553            
554             With the result modifiers C you can choose how many dice will be used to compute the final result, keeping or dropping highest or lowest C dice.
555             For example C<4d6kh3> will roll C<4d6> but only best three ones will be used.
556             The descriptive string in this case will be always ordered in ascending or descending order, without representing the real occurence of numbers.
557            
558            
559             =head4 result sum
560            
561             An optional sum C can be added to the final result as positive or negative modifier. This must be the last element of the dice expression like in: C<3d8+4>
562             This option cannot be used with C
563            
564            
565            
566            
567            
568             =head3 dice pools
569            
570            
571             If to the C method is passed a string containing different things (separated by spaces) this string will be treated as a C
572            
573             A C must contain at least two elements. It can contains one or more C (explained above), no or one and only one C and no, one and only one C ( C for keep highest or C for keep lowest).
574            
575             All results of C are computed and compared each other and with an eventual C and the result of the C will be the highest (if no C then C will be the default) or lowest one (if C is specified) roll among them.
576            
577             For example: C<$dice-Eroll('4d4+6 3d6+2 2d8+1 12')> can lead to the following results (default C is C):
578            
579             # Result: 14
580             # Description: 4d4+6 = 1 2 1 4 +6, ( 3d6+2 = 4 3 4 +2 = 13 ), ( 12 ), ( 2d8+1 = 1 8 +1 = 10 )
581            
582             As you can see descriptions of discarded C or eventual C (C<12> in the example) lower than the higher result are represented inside parens.
583            
584            
585             =head2 about rand
586            
587             Some ancient perl on some unfortunate OS has proven to have problem with the core C even if nowadays this is very rare to appear. In this case you can provide your own C function during the constructor, for example using L as in the following example:
588            
589             my $gen = Math::Random::MT->new();
590             my $mt_dicer = Games::Dice::Roller->new(
591             sub_rand => sub{
592             my $sides = shift;
593             return $gen->rand( $sides );
594             },
595             );
596            
597             See the thread at L where this argument was discussed.
598            
599             =head1 DEBUG
600            
601             This module can produce debug informations if C environment variable is set to C<1>
602            
603             Under debug rolling a dice expression will produce something like:
604            
605             Evaluating [12d6kh4+3]
606             original : [12d6kh4+3]
607             type : [dice_expression]
608             dice expression : [12d6]
609             result modifier : [kh]
610             result val modifier: [4]
611             result sum : [+3]
612             Cleaning the expression to spot garbage:
613             removing: 12d6
614             removing: kh
615             removing: 4
616             removing: +3
617             Left in the expression: []
618             Description: 6 6 5 5 (4) (4) (3) (3) (2) (2) (1) (1) +3
619             Result : 25
620            
621            
622            
623             =head1 AUTHOR
624            
625             LorenzoTa, C<< >>
626            
627             =head1 BUGS
628            
629             Please report any bugs or feature requests to C, or through
630             the web interface at L. I will be notified, and then you'll
631             automatically be notified of progress on your bug as I make changes.
632            
633            
634            
635            
636             =head1 SUPPORT
637            
638             The main support site for the present module is L where I can be found as Discipulus
639            
640             You can find documentation for this module with the perldoc command.
641            
642             perldoc Games::Dice::Roller
643            
644             You can also look for information at:
645            
646             =over 4
647            
648             =item * RT: CPAN's request tracker (report bugs here)
649            
650             L
651            
652             =item * AnnoCPAN: Annotated CPAN documentation
653            
654             L
655            
656             =item * CPAN Ratings
657            
658             L
659            
660             =item * Search CPAN
661            
662             L
663            
664             =back
665            
666            
667             =head1 ACKNOWLEDGEMENTS
668            
669            
670             =head1 LICENSE AND COPYRIGHT
671            
672             Copyright 2021 LorenzoTa.
673            
674             This program is free software; you can redistribute it and/or modify it
675             under the terms of the the Artistic License (2.0). You may obtain a
676             copy of the full license at:
677            
678             L
679            
680             Any use, modification, and distribution of the Standard or Modified
681             Versions is governed by this Artistic License. By using, modifying or
682             distributing the Package, you accept this license. Do not use, modify,
683             or distribute the Package, if you do not accept this license.
684            
685             If your Modified Version has been derived from a Modified Version made
686             by someone other than you, you are nevertheless required to ensure that
687             your Modified Version complies with the requirements of this license.
688            
689             This license does not grant you the right to use any trademark, service
690             mark, tradename, or logo of the Copyright Holder.
691            
692             This license includes the non-exclusive, worldwide, free-of-charge
693             patent license to make, have made, use, offer to sell, sell, import and
694             otherwise transfer the Package with respect to any patent claims
695             licensable by the Copyright Holder that are necessarily infringed by the
696             Package. If you institute patent litigation (including a cross-claim or
697             counterclaim) against any party alleging that the Package constitutes
698             direct or contributory patent infringement, then this Artistic License
699             to you shall terminate on the date that such litigation is filed.
700            
701             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
702             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
703             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
704             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
705             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
706             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
707             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
708             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
709            
710            
711             =cut
712            
713             1; # End of Module