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   80795 use 5.010;
  10         37  
4 10     10   52 use strict;
  10         20  
  10         225  
5 10     10   50 use warnings;
  10         16  
  10         299  
6            
7 10     10   54 use Carp;
  10         19  
  10         24843  
8            
9             our $VERSION = '0.03';
10             our $debug = $ENV{DICE_ROLLER_DEBUG} // 0;
11            
12             sub new{
13 12     12 1 9377 my $class = shift;
14 12         38 my %opts = @_;
15 12 100       43 if ( defined $opts{sub_rand} ){
16             croak "sub_rand must be a code reference meant to replace core rand function"
17 7 100       43 unless ref $opts{sub_rand} eq 'CODE';
18             }
19             return bless {
20 10020     10020   25545 sub_rand => $opts{sub_rand} // sub{ rand($_[0]) },
21 11   100     98 }, $class;
22             }
23            
24            
25             sub roll{
26 37     37 1 13440 my $self = shift;
27 37         66 my $arg = shift;
28 37 100       94 croak "roll method expects one argument" unless $arg;
29 36 100       85 croak "roll method expects a single string argument" if @_;
30            
31             # trim spaces
32 35         113 $arg =~ s/^\s+//;
33 35         85 $arg =~ s/\s+$//;
34            
35             # check if we received a dice pool
36 35         119 my @args = split /\s+/, $arg;
37            
38             # a dice pool
39 35 100       90 if ( scalar @args > 1 ){
40             # transform each one in resulting hashref returned by _identify_type
41 6         12 @args = map { _identify_type($_) } @args;
  23         41  
42 6         13 @args = _validate_pool( @args );
43             # transform each dice expression in its resulting format
44 4         8 foreach my $ele( @args ){
45 17 100       44 next unless $ele->{type} eq 'dice_expression';
46 9         44 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         6 my $global_modifier = pop @args;
51 4         17 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         6 my $global_result = $sorted[0]->{result};
54             my @global_descr = (
55             ($sorted[0]->{original} ? $sorted[0]->{original} : $sorted[0]->{result}).
56 4 50       17 ($sorted[0]->{result_description} ? " = $sorted[0]->{result_description}": '')
    50          
57             );
58 4         5 shift @sorted;
59             push @global_descr, "( ".
60             ($_->{original} ? $_->{original} : '').
61             ($_->{result_description}?" = $_->{result_description} = ":'').
62             ($_->{result}?"$_->{result} ":'').
63 4 100       35 ")" 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         65 my $ref = _identify_type( shift @args );
73            
74             # used to accumulate partial results and descriptive string elements
75 28         64 my ( @partial, @descr );
76            
77 28         96 my ($times, $sides) = split 'd', $ref->{dice_exp};
78 28         86 while( $times > 0 ){
79            
80 263         335 my $single_res;
81            
82             # BARE DICE EXPRESSION
83 263 100       460 unless ( $ref->{die_mod} ){
84 122         193 $single_res = $self->single_die( $sides );
85 122         823 push @partial, $single_res;
86 122         154 push @descr, $single_res;
87 122         150 $times--;
88 122         236 next;
89             }
90            
91             # DIE MODIFIERS #
92             # avg does not require further processing
93 141 100 66     461 if ( $ref->{die_mod} and $ref->{die_mod} eq 'avg' ){
94 5         10 $single_res = (1 + $sides) / 2;
95 5         7 push @partial, $single_res;
96 5         7 push @descr, $single_res;
97 5         7 $times--;
98 5         10 next;
99             }
100             # if r x cs roll the die
101             else{
102 136         253 $single_res = $self->single_die( $sides );
103             }
104             # process r x cs die modifiers
105             # if r
106 136 100 66     1045 if ( $ref->{die_mod} and $ref->{die_mod} eq 'r' ){
107 47         83 my $comp_num = $ref->{die_mod_val};
108 47         71 my $comp_op = $ref->{comp_mod};
109             # check if it must be rerolled
110 47 100 100     358 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 15         35 push @descr,"($single_res"."r)";
117 15         32 next;
118             }
119             else{
120 32         54 push @descr, $single_res;
121 32         42 push @partial, $single_res;
122 32         43 $times--;
123 32         62 next;
124             }
125             } # end of r check
126             # if x
127 89 100 66     272 if ( $ref->{die_mod} and $ref->{die_mod} eq 'x' ){
128 53         82 my $comp_num = $ref->{die_mod_val};
129 53         71 my $comp_op = $ref->{comp_mod};
130             # check if it must be exploded
131 53 100 100     377 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         27 push @descr,$single_res."x";
138 12         30 push @partial, $single_res;
139 12         29 next;
140             }
141             else{
142 41         64 push @descr, $single_res;
143 41         67 push @partial, $single_res;
144 41         52 $times--;
145 41         80 next;
146             }
147            
148             } # end of x check
149            
150             # if cs
151 36 50 33     96 if ( $ref->{die_mod} and $ref->{die_mod} eq 'cs' ){
152 36         53 my $comp_num = $ref->{die_mod_val};
153 36         46 my $comp_op = $ref->{comp_mod};
154             # initialize partial with zero succes
155 36         50 push @partial, 0;
156             # check if it is success
157 36 100 100     263 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         13 push @partial, 1;
165 10         14 $times--;
166 10         22 next;
167             }
168             else{
169 26         59 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     83 if ( $ref->{res_mod} and $ref->{res_mod} =~/^(?:kh|kl|dh|dl)$/ ){
178 6         10 my @wanted;
179             my @dropped;
180             # sort from lowest to highest partial, temporary results
181 6         25 my @sorted = sort{ $a <=> $b }@partial;
  120         170  
182            
183             # kh and kl
184 6 100 100     39 if ( $ref->{res_mod} eq 'kh' or $ref->{res_mod} eq 'kl'){
185             # reverse if highest are needed
186 3 100       10 @sorted = reverse @sorted if $ref->{res_mod} eq 'kh';
187             # reset partial result array
188 3         7 undef @partial;
189             # unshift n highest values shortening @sorted
190 3         16 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         36 foreach my $ele( @descr ){
194 172 100       297 if ( $ele eq $tobedropped ){
195 24         40 $ele = "($ele)";
196 24         47 last;
197             }
198             }
199             }
200 3 100       9 @descr = reverse @descr if $ref->{res_mod} eq 'kl';
201             } # end kh kl check
202            
203             # dh and dl
204 6 100 100     23 if ( $ref->{res_mod} eq 'dh' or $ref->{res_mod} eq 'dl'){
205             # reverse if lowest are needed
206 3 100       7 @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         8 while ( my $tobedropped = shift @sorted ){
213 20 100       40 foreach my $ele( $ref->{res_mod} eq 'dl' ? reverse @descr : @descr ){
214 79 100       134 if ( $ele eq $tobedropped ){
215 20         32 $ele = "($ele)";
216 20         39 last;
217             }
218             }
219             }
220 3 100       7 @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       62 if ( $ref->{res_sum} ){
227 12         22 push @descr, $ref->{res_sum};
228 12         28 push @partial, $ref->{res_sum};
229             }
230            
231             # COMPUTE RESULT AND DESCRIPTION
232             # add them to the $ref detailed result hasref
233 28         121 $ref->{result} += $_ for @partial;
234 28         131 $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         166 return ($ref->{result}, $ref->{result_description}, $ref);
239             } # end of single dice expression evaluation
240             }
241            
242             sub single_die{
243 10268     10268 0 24838 my $self = shift;
244 10268         12301 my $sides = shift;
245 10268 100       16939 croak "single_die expect one argument" unless $sides;
246 10267 100       25007 croak "Invalid side [$sides]" unless $sides =~/^(\d+)$/;
247 10263         17458 $sides = $1;
248 10263         16311 return 1 + int( $self->{sub_rand}($sides) );
249             }
250            
251             sub _validate_expr{
252 98     98   149 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     280 if ( $result->{die_mod} and $result->{die_mod} eq 'avg' ){
259 7 50       17 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     252 if ( $result->{die_mod} and $result->{die_mod} eq 'cs' ){
265 9 100       30 croak "with cs no result modification (k|d) are admitted. OK: 3d8cs3 NO: 3d8cs3kl" if $result->{res_mod};
266 8 100       39 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     251 if ( $result->{die_mod} and $result->{die_mod} eq 'x' ){
271 12 100       52 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     237 if ( $result->{die_mod} and $result->{die_mod} eq 'r' ){
276 9 50       22 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     243 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     276 if ( $result->{res_mod} and $result->{res_mod} =~/^(?:kh|kl|dh|dl)$/ ){
284 27 50       60 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       54 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       46 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       135 my $dice_num = $1 if $result->{dice_exp}=~ /^(\d+)d/;
288 27 100       104 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     341 if ( $result->{res_sum} and $result->{res_sum} =~ /^[+-]\d+$/){
292 49 50 66     164 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       11 croak "too many bare number in dice pool" if 1 < grep{ $_->{type} eq 'number' }@args;
  23         70  
303 5 100       8 croak "too many global modifiers (kh or kl) in dice pool" if 1 < grep{ $_->{type} eq 'global_modifier' }@args;
  19         46  
304             # deafult to kh
305 4 100       7 push @args, { type => 'global_modifier', value => 'kh' } if 0 == grep{ $_->{type} eq 'global_modifier' }@args;
  15         31  
306 4 50       10 croak "global modifiers (kh or kl) must be the last element in a dice pool" unless $args[-1]->{type} eq 'global_modifier';
307 4         23 return @args;
308             }
309            
310             sub _identify_type{
311 122     122   30189 my $exp = shift;
312 122 100       298 croak "_validate_type expects one argument" unless $exp;
313            
314 121 50       243 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       848 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       241 if ( $debug ){
345 0         0 print "\toriginal : [$exp]\n";
346 0         0 print "\ttype : [dice_expression]\n";
347 10     10   5257 print "\tdice expression : [$+{dice_exp}]\n";
  10         3760  
  10         4900  
  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         1811 };
368            
369             # remove everything matched from original expression..
370 106         387 my $tobenull = $exp;
371 106 50       212 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         203 foreach my $key ( qw( dice_exp res_mod res_mod_val die_mod comp_mod die_mod_val res_sum) ){
374 742 50 66     1867 print "\tremoving: $result->{$key}\n" if defined $result->{$key} and $debug;
375 742 100       3757 $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       298 croak "unexpected string [$tobenull] in expression [$exp]" if length $tobenull;
380            
381 98         228 _validate_expr( $result );
382 89         370 return $result;
383             }
384             # we got a bare number (can be used in dice pool)
385             elsif ( $exp =~ /^\d+$/ ){
386 8 50       16 print "received a bare number [$exp] used in dice pools\n" if $debug;
387 8         38 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         16 return { type => 'global_modifier', value => $exp };
393             }
394             else{
395 3         29 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