File Coverage

blib/lib/Games/Dice.pm
Criterion Covered Total %
statement 59 64 92.1
branch 22 30 73.3
condition 9 9 100.0
subroutine 10 11 90.9
pod 0 2 0.0
total 100 116 86.2


line stmt bran cond sub pod time code
1 1     1   71805 use strict;
  1         11  
  1         31  
2 1     1   7 use warnings;
  1         2  
  1         21  
3 1     1   36 use 5.010;
  1         4  
4             package Games::Dice 0.046;
5             # ABSTRACT: Perl module to simulate die rolls
6              
7             require Exporter;
8              
9             our @ISA = qw(Exporter);
10             our @EXPORT_OK = qw( roll roll_array);
11              
12             # Preloaded methods go here.
13              
14             # Win32 has crummy built in rand() support
15             # So let's use something that's decent and pure perl
16 1     1   679 use if $^O eq "MSWin32", 'Math::Random::MT::Perl' => qw(rand);
  1         13  
  1         6  
17              
18             sub _parse_spec {
19 12     12   15 my $line = shift;
20 12 50       71 return undef unless $line =~ m{
21             ^ # beginning of line
22             ( # dice string in $1
23             (?\d+)? # optional count
24             [dD] # 'd' for dice
25             (? # type of dice:
26             \d+ # either one or more digits
27             | # or
28             % # a percent sign for d% = d100
29             | # pr
30             F # a F for a fudge dice
31             )
32             )
33             (?: # grouping-only parens
34             (?[-+xX*/bB]) # a + - * / b(est) in $2
35             (?\d+) # an offset in $3
36             )? # both of those last are optional
37             \s* # possibly some trailing space (like \n)
38             $
39             }x; # whitespace allowed
40              
41 12         21 my %pr;
42 1     1   705 $pr{$_} = $+{$_} for keys %+;
  1         572  
  1         618  
  12         212  
43              
44 12   100     51 $pr{sign} ||= '';
45 12   100     35 $pr{offset} ||= 0;
46 12   100     26 $pr{count} ||= 1;
47              
48 12         24 $pr{sign} = lc $pr{sign};
49              
50 12         36 return \%pr;
51              
52             }
53              
54             sub roll ($) {
55 13     13 0 174 my $line = shift;
56 13         19 my @result;
57              
58 13 100       70 return $line if $line =~ /\A[0-9]+\z/;
59              
60 12         27 my $pr = _parse_spec($line);
61 12 50       26 return undef unless $pr;
62              
63 12         25 my @throws = _roll_dice($pr);
64 12 50       29 return undef unless @throws;
65              
66 12         26 my ( $sign, $offset ) = @$pr{qw(sign offset)};
67              
68 12 100       23 if ( $sign eq 'b' ) {
69 2 50       6 $offset = 0 if $offset < 0;
70 2 100       5 $offset = @throws if $offset > @throws;
71              
72 2         7 @throws = sort { $b <=> $a } @throws; # sort numerically, descending
  18         26  
73 2         7 @result = @throws[ 0 .. $offset - 1 ]; # pick off the $offset first ones
74             }
75             else {
76 10         18 @result = @throws;
77             }
78              
79 12         18 my $sum = 0;
80 12         26 $sum += $_ foreach @result;
81 12 100       23 $sum += $offset if $sign eq '+';
82 12 100       21 $sum -= $offset if $sign eq '-';
83 12 100 100     42 $sum *= $offset if ( $sign eq '*' || $sign eq 'x' );
84 12 100       19 do { $sum /= $offset; $sum = int $sum; } if $sign eq '/';
  1         3  
  1         2  
85              
86 12         65 return $sum;
87             }
88              
89             sub _roll_dice {
90 12     12   17 my $pr = shift;
91              
92 12         39 my ($type,$num) = @$pr{qw(type count)};
93              
94 12     24   42 my $throw = sub { int( rand $_[0] ) + 1 };
  24         48  
95              
96 12 100       41 if ( $type eq '%' ) {
    100          
97 1         2 $type = 100;
98             }
99             elsif ( $type eq 'F' ) {
100 2     8   10 $throw = sub { int( rand 3 ) - 1 };
  8         16  
101             }
102              
103 12         15 my @throws;
104 12         37 for ( 1 .. $num ) {
105 32         345 push @throws, $throw->( $type );
106             }
107 12         210 return @throws;
108             }
109              
110             sub roll_array ($) {
111 0     0 0   my $line = shift;
112              
113 0 0         return $line if $line =~ /\A[0-9]+\z/;
114              
115 0           my $pr = _parse_spec($line);
116 0 0         return unless $pr;
117              
118 0           return _roll_dice($pr);
119             }
120              
121             1;
122              
123             =pod
124              
125             =encoding UTF-8
126              
127             =head1 NAME
128              
129             Games::Dice - Perl module to simulate die rolls
130              
131             =head1 VERSION
132              
133             version 0.046
134              
135             =head1 SYNOPSIS
136              
137             use Games::Dice 'roll';
138             $strength = roll '3d6+1';
139              
140             use Games::Dice 'roll_array';
141             @rolls = roll_array '4d8';
142              
143             =head1 DESCRIPTION
144              
145             Games::Dice simulates die rolls. It uses a function-oriented (not
146             object-oriented) interface. No functions are exported by default. At
147             present, there are two functions which are exportable: C and
148             C. The latter is used internally by C, but can also be
149             exported by itself.
150              
151             The number and type of dice to roll is given in a style which should be
152             familiar to players of popular role-playing games: IdI[+-*/b]I.
153             I is optional and defaults to 1; it gives the number of dice to roll.
154             I indicates the number of sides to each die; the most common,
155             cube-shaped die is thus a d6. % can be used instead of 100 for I;
156             hence, rolling 2d% and 2d100 is equivalent. If F is used for I fudge
157             dice are used, which either results in -1, 0 or 1. C simulates I
158             rolls of I-sided dice and adds together the results. The optional end,
159             consisting of one of +-*/b and a number I, can modify the sum of the
160             individual dice. +-*/ are similar in that they take the sum of the rolls
161             and add or subtract I, or multiply or divide the sum by I. (x can
162             also be used instead of *.) Hence, 1d6+2 gives a number in the range
163             3..8, and 2d4*10 gives a number in the range 20..80. (Using / truncates
164             the result to an int after dividing.) Using b in this slot is a little
165             different: it's short for "best" and indicates "roll a number of dice,
166             but add together only the best few". For example, 5d6b3 rolls five six-
167             sided dice and adds together the three best rolls. This is sometimes
168             used, for example, in role-playing to give higher averages.
169              
170             Generally, C probably provides the nicer interface, since it does
171             the adding up itself. However, in some situations one may wish to
172             process the individual rolls (for example, I am told that in the game
173             Feng Shui, the number of dice to be rolled cannot be determined in
174             advance but depends on whether any 6s were rolled); in such a case, one
175             can use C to return an array of values, which can then be
176             examined or processed in an application-dependent manner.
177              
178             This having been said, comments and additions (especially if accompanied
179             by code!) to Games::Dice are welcome. So, using the above example, if
180             anyone wishes to contribute a function along the lines of roll_feng_shui
181             to become part of Games::Dice (or to support any other style of die
182             rolling), you can contribute it to the author's address, listed below.
183              
184             =head1 PERL VERSION
185              
186             This module should work on any version of perl still receiving updates from
187             the Perl 5 Porters. This means it should work on any version of perl released
188             in the last two to three years. (That is, if the most recently released
189             version is v5.40, then this module should work on both v5.40 and v5.38.)
190              
191             Although it may work on older versions of perl, no guarantee is made that the
192             minimum required version will not be increased. The version may be increased
193             for any reason, and there is no promise that patches will be accepted to lower
194             the minimum required perl.
195              
196             =head1 NAME
197              
198             =head1 AUTHORS
199              
200             =over 4
201              
202             =item *
203              
204             Philip Newton
205              
206             =item *
207              
208             Ricardo Signes
209              
210             =back
211              
212             =head1 CONTRIBUTORS
213              
214             =for stopwords Mario Domgoergen Mark Allen Ricardo Signes
215              
216             =over 4
217              
218             =item *
219              
220             Mario Domgoergen
221              
222             =item *
223              
224             Mark Allen
225              
226             =item *
227              
228             Ricardo Signes
229              
230             =back
231              
232             =head1 COPYRIGHT AND LICENSE
233              
234             This software is Copyright (c) 1999 by Philip Newton.
235              
236             This is free software, licensed under:
237              
238             The MIT (X11) License
239              
240             =cut
241              
242             __END__