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   80074 use 5.010;
  10         37  
4 10     10   54 use strict;
  10         19  
  10         218  
5 10     10   50 use warnings;
  10         15  
  10         280  
6            
7 10     10   70 use Carp;
  10         20  
  10         24871  
8            
9             our $VERSION = '0.02';
10             our $debug = $ENV{DICE_ROLLER_DEBUG} // 0;
11            
12             sub new{
13 12     12 1 9419 my $class = shift;
14 12         36 my %opts = @_;
15 12 100       47 if ( defined $opts{sub_rand} ){
16             croak "sub_rand must be a code reference meant to replace core rand function"
17 7 100       48 unless ref $opts{sub_rand} eq 'CODE';
18             }
19             return bless {
20 10017     10017   25746 sub_rand => $opts{sub_rand} // sub{ rand($_[0]) },
21 11   100     92 }, $class;
22             }
23            
24            
25             sub roll{
26 37     37 1 13755 my $self = shift;
27 37         62 my $arg = shift;
28 37 100       108 croak "roll method expects one argument" unless $arg;
29 36 100       88 croak "roll method expects a single string argument" if @_;
30            
31             # trim spaces
32 35         116 $arg =~ s/^\s+//;
33 35         92 $arg =~ s/\s+$//;
34            
35             # check if we received a dice pool
36 35         127 my @args = split /\s+/, $arg;
37            
38             # a dice pool
39 35 100       94 if ( scalar @args > 1 ){
40             # transform each one in resulting hashref returned by _identify_type
41 6         12 @args = map { _identify_type($_) } @args;
  23         45  
42 6         16 @args = _validate_pool( @args );
43             # transform each dice expression in its resulting format
44 4         9 foreach my $ele( @args ){
45 17 100       42 next unless $ele->{type} eq 'dice_expression';
46 9         40 my ($res, $descr) = $self->roll( $ele->{original} );
47 9         55 $ele = { result => $res, result_description => $descr, original => $ele->{original}};
48             }
49             # is the last element
50 4         6 my $global_modifier = pop @args;
51 4         16 my @sorted = sort{ $a->{result} <=> $b->{result} }@args;
  11         27  
52 4 100       11 @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       18 ($sorted[0]->{result_description} ? " = $sorted[0]->{result_description}": '')
    50          
57             );
58 4         6 shift @sorted;
59             push @global_descr, "( ".
60             ($_->{original} ? $_->{original} : '').
61             ($_->{result_description}?" = $_->{result_description} = ":'').
62             ($_->{result}?"$_->{result} ":'').
63 4 100       36 ")" for @sorted;
    100          
    50          
64            
65 4         30 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         70 my $ref = _identify_type( shift @args );
73            
74             # used to accumulate partial results and descriptive string elements
75 28         65 my ( @partial, @descr );
76            
77 28         94 my ($times, $sides) = split 'd', $ref->{dice_exp};
78 28         90 while( $times > 0 ){
79            
80 260         323 my $single_res;
81            
82             # BARE DICE EXPRESSION
83 260 100       469 unless ( $ref->{die_mod} ){
84 122         205 $single_res = $self->single_die( $sides );
85 122         875 push @partial, $single_res;
86 122         172 push @descr, $single_res;
87 122         156 $times--;
88 122         244 next;
89             }
90            
91             # DIE MODIFIERS #
92             # avg does not require further processing
93 138 100 66     385 if ( $ref->{die_mod} and $ref->{die_mod} eq 'avg' ){
94 5         8 $single_res = (1 + $sides) / 2;
95 5         10 push @partial, $single_res;
96 5         13 push @descr, $single_res;
97 5         7 $times--;
98 5         10 next;
99             }
100             # if r x cs roll the die
101             else{
102 133         237 $single_res = $self->single_die( $sides );
103             }
104             # process r x cs die modifiers
105             # if r
106 133 100 66     996 if ( $ref->{die_mod} and $ref->{die_mod} eq 'r' ){
107 44         75 my $comp_num = $ref->{die_mod_val};
108 44         54 my $comp_op = $ref->{comp_mod};
109             # check if it must be rerolled
110 44 100 100     342 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 12         29 push @descr,"($single_res"."r)";
117 12         26 next;
118             }
119             else{
120 32         54 push @descr, $single_res;
121 32         45 push @partial, $single_res;
122 32         42 $times--;
123 32         66 next;
124             }
125             } # end of r check
126             # if x
127 89 100 66     270 if ( $ref->{die_mod} and $ref->{die_mod} eq 'x' ){
128 53         71 my $comp_num = $ref->{die_mod_val};
129 53         74 my $comp_op = $ref->{comp_mod};
130             # check if it must be exploded
131 53 100 100     379 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 12         34 push @descr,$single_res."x";
138 12         32 push @partial, $single_res;
139 12         29 next;
140             }
141             else{
142 41         65 push @descr, $single_res;
143 41         70 push @partial, $single_res;
144 41         53 $times--;
145 41         80 next;
146             }
147            
148             } # end of x check
149            
150             # if cs
151 36 50 33     114 if ( $ref->{die_mod} and $ref->{die_mod} eq 'cs' ){
152 36         49 my $comp_num = $ref->{die_mod_val};
153 36         51 my $comp_op = $ref->{comp_mod};
154             # initialize partial with zero succes
155 36         53 push @partial, 0;
156             # check if it is success
157 36 100 100     256 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         16 push @descr,$single_res;
164 10         14 push @partial, 1;
165 10         13 $times--;
166 10         21 next;
167             }
168             else{
169 26         55 push @descr, "($single_res)";
170 26         30 $times--;
171 26         52 next;
172             }
173             } # end of cs check
174             } # end of while loop
175            
176             # RESULT MODIFIERS kh kl dh dl #
177 28 100 66     85 if ( $ref->{res_mod} and $ref->{res_mod} =~/^(?:kh|kl|dh|dl)$/ ){
178 6         13 my @wanted;
179             my @dropped;
180             # sort from lowest to highest partial, temporary results
181 6         25 my @sorted = sort{ $a <=> $b }@partial;
  120         169  
182            
183             # kh and kl
184 6 100 100     37 if ( $ref->{res_mod} eq 'kh' or $ref->{res_mod} eq 'kl'){
185             # reverse if highest are needed
186 3 100       11 @sorted = reverse @sorted if $ref->{res_mod} eq 'kh';
187             # reset partial result array
188 3         8 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         9 while ( my $tobedropped = shift @sorted ){
193 24         35 foreach my $ele( @descr ){
194 172 100       337 if ( $ele eq $tobedropped ){
195 24         38 $ele = "($ele)";
196 24         51 last;
197             }
198             }
199             }
200 3 100       10 @descr = reverse @descr if $ref->{res_mod} eq 'kl';
201             } # end kh kl check
202            
203             # dh and dl
204 6 100 100     24 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         7 undef @partial;
209             # unshift n highest values shortening @sorted
210 3         17 unshift @partial, shift @sorted for 1 .. ( scalar @sorted - $ref->{res_mod_val} );
211             # consume what left in sorted to modify description
212 3         9 while ( my $tobedropped = shift @sorted ){
213 20 100       39 foreach my $ele( $ref->{res_mod} eq 'dl' ? reverse @descr : @descr ){
214 79 100       144 if ( $ele eq $tobedropped ){
215 20         33 $ele = "($ele)";
216 20         36 last;
217             }
218             }
219             }
220 3 100       9 @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       58 if ( $ref->{res_sum} ){
227 12         29 push @descr, $ref->{res_sum};
228 12         24 push @partial, $ref->{res_sum};
229             }
230            
231             # COMPUTE RESULT AND DESCRIPTION
232             # add them to the $ref detailed result hasref
233 28         125 $ref->{result} += $_ for @partial;
234 28         152 $ref->{result_description} = join ' ', @descr;
235            
236 28 50       73 print "Description: $ref->{result_description}\nResult : $ref->{result}\n\n" if $debug;
237            
238 28         151 return ($ref->{result}, $ref->{result_description}, $ref);
239             } # end of single dice expression evaluation
240             }
241            
242             sub single_die{
243 10265     10265 0 25359 my $self = shift;
244 10265         13516 my $sides = shift;
245 10265 100       16789 croak "single_die expect one argument" unless $sides;
246 10264 100       25793 croak "Invalid side [$sides]" unless $sides =~/^(\d+)$/;
247 10260         17989 $sides = $1;
248 10260         17406 return 1 + int( $self->{sub_rand}($sides) );
249             }
250            
251             sub _validate_expr{
252 98     98   147 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       19 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       23 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     281 if ( $result->{die_mod} and $result->{die_mod} eq 'cs' ){
265 9 100       35 croak "with cs no result modification (k|d) are admitted. OK: 3d8cs3 NO: 3d8cs3kl" if $result->{res_mod};
266 8 100       42 croak "with cs a number must be also specified. OK: 3d8cs2 NO: 3d8cs" unless $result->{die_mod_val};
267 6 100       25 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     254 if ( $result->{die_mod} and $result->{die_mod} eq 'x' ){
271 12 100       39 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     241 if ( $result->{die_mod} and $result->{die_mod} eq 'r' ){
276 9 50       25 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     224 if ( $result->{comp_mod} and $result->{comp_mod} =~/^(?:gt|lt)$/ ){
280 10 50       34 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     273 if ( $result->{res_mod} and $result->{res_mod} =~/^(?:kh|kl|dh|dl)$/ ){
284 27 50       62 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       53 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       61 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       109 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     361 if ( $result->{res_sum} and $result->{res_sum} =~ /^[+-]\d+$/){
292 49 50 66     161 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   11 my @args = @_;
298             # type => 'number'
299             # type => 'global_modifier'
300             # type => 'dice_expression'
301            
302 6 100       11 croak "too many bare number in dice pool" if 1 < grep{ $_->{type} eq 'number' }@args;
  23         71  
303 5 100       10 croak "too many global modifiers (kh or kl) in dice pool" if 1 < grep{ $_->{type} eq 'global_modifier' }@args;
  19         50  
304             # deafult to kh
305 4 100       6 push @args, { type => 'global_modifier', value => 'kh' } if 0 == grep{ $_->{type} eq 'global_modifier' }@args;
  15         35  
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         10 return @args;
308             }
309            
310             sub _identify_type{
311 122     122   30165 my $exp = shift;
312 122 100       282 croak "_validate_type expects one argument" unless $exp;
313            
314 121 50       248 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       900 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       254 if ( $debug ){
345 0         0 print "\toriginal : [$exp]\n";
346 0         0 print "\ttype : [dice_expression]\n";
347 10     10   4976 print "\tdice expression : [$+{dice_exp}]\n";
  10         3803  
  10         4916  
  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         1879 };
368            
369             # remove everything matched from original expression..
370 106         399 my $tobenull = $exp;
371 106 50       220 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         211 foreach my $key ( qw( dice_exp res_mod res_mod_val die_mod comp_mod die_mod_val res_sum) ){
374 742 50 66     2020 print "\tremoving: $result->{$key}\n" if defined $result->{$key} and $debug;
375 742 100       3854 $tobenull =~ s/\Q$result->{$key}\E// if defined $result->{$key};
376             }
377 106 50       216 print "Left in the expression: [$tobenull]\n" if $debug;
378             # ..to spot unwanted remaining crumbles
379 106 100       305 croak "unexpected string [$tobenull] in expression [$exp]" if length $tobenull;
380            
381 98         285 _validate_expr( $result );
382 89         260 return $result;
383             }
384             # we got a bare number (can be used in dice pool)
385             elsif ( $exp =~ /^\d+$/ ){
386 8 50       13 print "received a bare number [$exp] used in dice pools\n" if $debug;
387 8         39 return { type => 'number', result => $exp };
388             }
389             # we got a global dice pool modifier
390             elsif( $exp =~ /^kh|kl$/){
391 4 50       9 print "received a global dice modifier [$exp] used in dice pools\n" if $debug;
392 4         39 return { type => 'global_modifier', value => $exp };
393             }
394             else{
395 3         44 croak "unrecognized expression [$exp]";
396             }
397             }
398            
399            
400             1; # End of Module
401            
402            
403             =head1 NAME
404            
405             Games::Dice::Roller - a full featured dice roller system
406            
407             =head1 VERSION
408            
409             Version 0.01
410            
411             =cut
412            
413             =head1 SYNOPSIS
414            
415             use Games::Dice::Roller;
416            
417             my $dice = Games::Dice::Roller->new();
418            
419             # simple dice expressions
420             my @simple = (qw( 3d6 4d8+4 1d100-5 ));
421            
422             # average results
423             my @average = (qw(4d4avg 4d8avg+2 4d12avg-7));
424            
425             # reroll if equal (default), lesser than or greater than N
426             my @reroll = (qw(6d4r1 5d6rlt3 5d6rgt4 6d4r1+10 6d4r1-5));
427            
428             # explode if equal (default), lesser than or greater than N
429             my @explode = (qw( 3d6x6 3d6xlt3 3d6xgt4 3d6x6+3 3d6x6-4 ));
430            
431             # just count succesful rolls
432             my @succes = (qw( 3d6cs1 3d6cslt3 3d6csgt4 ));
433            
434             # keep and drop dice from final results
435             my @keep_and_drop = (qw( 4d6kh3 4d6kh2 4d6kl2+3 4d6kl2-3 4d12dh1 4d12dl3 4d12dl3+3 4d12dl1-4 ));
436            
437            
438             foreach my $dice_expression ( @simple , @average, @reroll, @explode, @succes, @keep_and_drop ){
439            
440             my ($res, $descr) = $dice->roll( $dice_expression );
441             print "$res [$dice_expression] $descr\n";
442             }
443            
444            
445            
446             # 10 [3d6] 5 2 3
447             # 22 [4d8+4] 7 7 1 3 +4
448             # 14 [1d100-5] 19 -5
449             # 10 [4d4avg] 2.5 2.5 2.5 2.5
450             # 20 [4d8avg+2] 4.5 4.5 4.5 4.5 +2
451             # 19 [4d12avg-7] 6.5 6.5 6.5 6.5 -7
452             # 18 [6d4r1] 4 (1r) 3 2 3 (1r) 2 4
453             # 19 [5d6rlt3] 3 4 3 4 (2r) (2r) (2r) 5
454             # 11 [5d6rgt4] 2 4 1 (5r) 2 (5r) 2
455             # 25 [6d4r1+10] (1r) 2 (1r) 2 2 4 3 2 +10
456             # 13 [6d4r1-5] (1r) (1r) 2 (1r) 2 4 4 (1r) 4 2 -5
457             # 7 [3d6x6] 1 1 5
458             # 17 [3d6xlt3] 6 5 1x 1x 1x 3
459             # 11 [3d6xgt4] 4 3 4
460             # 11 [3d6x6+3] 2 2 4 +3
461             # 8 [3d6x6-4] 5 3 4 -4
462             # 1 [3d6cs1] (5) (3) 1
463             # 1 [3d6cslt3] 2 (6) (6)
464             # 2 [3d6csgt4] 6 (3) 5
465             # 14 [4d6kh3] (2) 6 4 4
466             # 9 [4d6kh2] 3 6 (2) (2)
467             # 8 [4d6kl2+3] (6) 4 1 (6) +3
468             # 1 [4d6kl2-3] (5) 1 3 (3) -3
469             # 13 [4d12dh1] 2 6 5 (6)
470             # 12 [4d12dl3] (9) (10) (9) 12
471             # 9 [4d12dl3+3] (1) 6 (3) (6) +3
472             # 26 [4d12dl1-4] 9 (1) 9 12 -4
473            
474            
475            
476             =head1 METHODS
477            
478            
479             =head2 new
480            
481             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
482            
483            
484             =head2 roll
485            
486             This method expects a single string to be passed as argument. This string can be a C or a C (see below).
487            
488             It returns the final result and a string representing the roll.
489            
490            
491             my $result = $dice->roll('3d6+3');
492             print "result of the dice roll was: $result";
493             # result of the dice roll was: 16
494            
495             my ($res, $descr) = $dice->roll('3d6+3');
496             print "$descr\nResult: $res";
497             # 5 2 6 +3
498             # Result: 16
499            
500             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
501            
502             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.
503            
504             =head3 die modifiers
505            
506             =head4 avg - average
507            
508             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>
509            
510             =head4 r - reroll
511            
512             Reroll dice equal, lesser than (C) or greater than (C) C as in C<3d6r1 3d6rlt3 3d6rgt4>
513             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)>
514            
515             =head4 x - explode
516            
517             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.
518             An exploded die will be added to final result and will be marked with C as in C<6x> in the descriptive string.
519            
520             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>
521            
522            
523             =head4 cs - count successes
524            
525             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.
526             The final result will be the succes count.
527             In the decription string unsuccesfull rolls will be inside parens.
528            
529            
530            
531            
532             =head3 result modifiers
533            
534             =head4 keep and drop
535            
536             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.
537             For example C<4d6kh3> will roll C<4d6> but only best three ones will be used.
538             The descriptive string in this case will be always ordered in ascending or descending order, without representing the real occurence of numbers.
539            
540            
541             =head4 result sum
542            
543             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>
544             This option cannot be used with C
545            
546            
547            
548            
549            
550             =head3 dice pools
551            
552            
553             If to the C method is passed a string containing different things (separated by spaces) this string will be treated as a C
554            
555             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).
556            
557             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.
558            
559             For example: C<$dice-Eroll('4d4+6 3d6+2 2d8+1 12')> can lead to the following results (default C is C):
560            
561             # Result: 14
562             # Description: 4d4+6 = 1 2 1 4 +6, ( 3d6+2 = 4 3 4 +2 = 13 ), ( 12 ), ( 2d8+1 = 1 8 +1 = 10 )
563            
564             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.
565            
566            
567             =head2 about rand
568            
569             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:
570            
571             my $gen = Math::Random::MT->new();
572             my $mt_dicer = Games::Dice::Roller->new(
573             sub_rand => sub{
574             my $sides = shift;
575             return $gen->rand( $sides );
576             },
577             );
578            
579             See the thread at L where this argument was discussed.
580            
581             =head1 DEBUG
582            
583             This module can produce debug informations if C environment variable is set to C<1>
584            
585             Under debug rolling a dice expression will produce something like:
586            
587             Evaluating [12d6kh4+3]
588             original : [12d6kh4+3]
589             type : [dice_expression]
590             dice expression : [12d6]
591             result modifier : [kh]
592             result val modifier: [4]
593             result sum : [+3]
594             Cleaning the expression to spot garbage:
595             removing: 12d6
596             removing: kh
597             removing: 4
598             removing: +3
599             Left in the expression: []
600             Description: 6 6 5 5 (4) (4) (3) (3) (2) (2) (1) (1) +3
601             Result : 25
602            
603            
604            
605             =head1 AUTHOR
606            
607             LorenzoTa, C<< >>
608            
609             =head1 BUGS
610            
611             Please report any bugs or feature requests to C, or through
612             the web interface at L. I will be notified, and then you'll
613             automatically be notified of progress on your bug as I make changes.
614            
615            
616            
617            
618             =head1 SUPPORT
619            
620             The main support site for the present module is L where I can be found as Discipulus
621            
622             You can find documentation for this module with the perldoc command.
623            
624             perldoc Games::Dice::Roller
625            
626             You can also look for information at:
627            
628             =over 4
629            
630             =item * RT: CPAN's request tracker (report bugs here)
631            
632             L
633            
634            
635             =item * Search CPAN
636            
637             L
638            
639             =back
640            
641            
642             =head1 ACKNOWLEDGEMENTS
643            
644            
645             =head1 LICENSE AND COPYRIGHT
646            
647             Copyright 2021 LorenzoTa.
648            
649             This program is free software; you can redistribute it and/or modify it
650             under the terms of the the Artistic License (2.0). You may obtain a
651             copy of the full license at:
652            
653             L
654            
655             Any use, modification, and distribution of the Standard or Modified
656             Versions is governed by this Artistic License. By using, modifying or
657             distributing the Package, you accept this license. Do not use, modify,
658             or distribute the Package, if you do not accept this license.
659            
660             If your Modified Version has been derived from a Modified Version made
661             by someone other than you, you are nevertheless required to ensure that
662             your Modified Version complies with the requirements of this license.
663            
664             This license does not grant you the right to use any trademark, service
665             mark, tradename, or logo of the Copyright Holder.
666            
667             This license includes the non-exclusive, worldwide, free-of-charge
668             patent license to make, have made, use, offer to sell, sell, import and
669             otherwise transfer the Package with respect to any patent claims
670             licensable by the Copyright Holder that are necessarily infringed by the
671             Package. If you institute patent litigation (including a cross-claim or
672             counterclaim) against any party alleging that the Package constitutes
673             direct or contributory patent infringement, then this Artistic License
674             to you shall terminate on the date that such litigation is filed.
675            
676             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
677             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
678             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
679             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
680             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
681             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
682             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
683             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
684            
685            
686             =cut