File Coverage

blib/lib/Games/Dice/Advanced.pm
Criterion Covered Total %
statement 48 51 94.1
branch 20 28 71.4
condition 6 12 50.0
subroutine 12 12 100.0
pod 2 4 50.0
total 88 107 82.2


line stmt bran cond sub pod time code
1             package Games::Dice::Advanced;
2              
3 1     1   17442 use strict;
  1         2  
  1         36  
4 1     1   5 use warnings;
  1         1  
  1         31  
5 1     1   4 use vars qw($VERSION);
  1         6  
  1         709  
6              
7             $VERSION = '1.1';
8              
9             =head1 NAME
10              
11             Games::Dice::Advanced - simulate dice rolls, including weird and
12             loaded dice
13              
14             =head1 SYNOPSIS
15              
16             print Games::Dice::Advanced->roll(); # roll a six-sided die
17             print Games::Dice::Advanced->roll('d4'); # roll a four-sided die
18              
19             # roll a four-sided die and a 6-sided die and return the total
20             $die1 = Games::Dice::Advanced->new('d4');
21             $die2 = Games::Dice::Advanced->new('d6');
22             print Games::Dice::Advanced->roll($die1, $die2);
23              
24             print $die1->roll(); # roll the d4 we created above
25              
26             # roll 2 four-sided dice and a 6-sided die and return the total
27             print Games::Dice::Advanced->roll('2d4', 'd6');
28              
29             # create a four-sided die with the squares of 1, 2, 3 and 4
30             Games::Dice::Advanced->new(sub { int(1+rand(4)) ** 2 });
31              
32             =head1 METHODS
33              
34             =over 4
35              
36             =item roll
37              
38             Roll one or more dice. If called as a class method, it first calls
39             appropriate constructors and creates objects before calling those objects'
40             roll() methods. When called on an object it simply rolls the die.
41              
42             When called as a class method, it takes a list of arguments defining a
43             'recipe' to roll. These are added
44             together to produce a result. Each item in the recipe must be a valid
45             argument to the constructor (see the description of the 'new' method below).
46             If no argument is given to a class method, we assume a six-sided die - 'd6'.
47             You will note that the multiplier constructor is not available when roll
48             is called in this way.
49              
50             When called as an object method, no arguments are permitted.
51              
52             =cut
53              
54             sub roll {
55 130045     130045 1 460639 my($self, @args) = @_;
56 130045 100 66     647450 if(ref($self) && $self->isa('Games::Dice::Advanced')) {
    50          
57             # called as object method
58 130030 50       277919 die("roll() called incorrectly") if(@args);
59 130030         143529 return &{$self};
  130030         211818  
60             } elsif($self eq 'Games::Dice::Advanced') {
61             # called as class method
62 15 50       32 @args = ('d6') unless(@args);
63 25 100 66     105 return sum(map {
64 15         23 (ref($_) && $_->isa('Games::Dice::Advanced')) ?
65             $_->roll() :
66             Games::Dice::Advanced->new($_)->roll()
67             } @args);
68             } else {
69 0         0 die("Out of cucumber error\n");
70             }
71             }
72              
73             =item new
74              
75             This method defines a die. You may call it yourself to create a die for
76             later rolling, or it may be called by the roll() method. It takes zero,
77             one or two
78             arguments. If no argument is given, we silently assume that the user
79             wants to create a six-sided die, a 'd6'. Valid arguments are:
80              
81             =over 4
82              
83             =item integer constant, eg '5'
84              
85             Creates a die that always returns that constant
86              
87             =item dN, where N is integer, eg 'd10'
88              
89             Creates a die that returns a random integer from 1 to N with results spread
90             evenly across the range.
91              
92             =item NdM, where N and M are integer, eg '2d10'
93              
94             Creates a die dM as above which is rolled N times to generate a result.
95             Note that the N is *not* just a multiplier.
96              
97             =item N and any other valid argument, where N is a number, eg (2, 'd4')
98              
99             Note that the two arguments may be in any order. Creates a die as specified,
100             and multiplies the results by N when it is rolled. Compare with NdM above.
101              
102             =back
103              
104             Leading and trailing whitespace is stripped, no other whitespace is allowed
105             in any of the above.
106              
107             =over 4
108              
109             =item SUBREF
110              
111             A reference to a subroutine, which is to be called whenever we need to generate
112             a result. It should take no parameters.
113              
114             =item HASHREF
115              
116             Use this to easily specify truly weird dice. NOT YET IMPLEMENTED, so use
117             a SUBREF for the moment.
118              
119             =back
120              
121             =cut
122              
123             sub new {
124 21     21 1 179649 my($class, @args) = @_;
125              
126 21 50       58 @args = ('d6') unless(@args);
127 21         36 @args = map { s/(^\s+|\s+$)//; $_; } @args;
  22         74  
  22         66  
128              
129 21         33 my $self = '';
130              
131 21 100       52 if(@args == 1) { push @args, 1; } # multiply by 1
  20         26  
132              
133 21 50       38 if(@args == 2) {
134 21         35 my($recipe, $mul) = @args;
135 21 50 33     107 ($recipe, $mul) = ($mul, $recipe) if(ref($mul) || $mul=~ /\D/);
136 21 50 33     89 die("Bad arguments to new()") if(ref($mul) || $mul=~ /\D/);
137              
138 21 100       76 if($recipe !~ /\D/) { # constant
    100          
    100          
    50          
139             # $self = eval("sub { $recipe * $mul }");
140 17     30   52 $self = sub { $recipe * $mul };
  30         106  
141             } elsif($recipe =~ /^d(\d+)$/) { # dINT
142             # $self = eval("sub { (1 + int(rand($1))) * $mul }");
143 2         7 my $faces = $1;
144 2     20000   13 $self = sub { (1 + int(rand($faces))) * $mul };
  20000         69683  
145             } elsif($recipe =~ /^(\d+)d(\d+)/) { # INTdINT
146 1         6 my($repeats, $faces) = ($1, $2);
147             $self = sub {
148 100000     100000   142924 $mul * sum(map { 1 + int(rand($faces)) } (1..$repeats))
  200000         398151  
149 1         11 };
150             } elsif(ref($recipe) eq 'CODE') {
151 10000     10000   9178 $self = sub { $mul * &{$recipe} }
  10000         17041  
152 1         7 } else {
153 0         0 die("$recipe isn't valid");
154             }
155             } else {
156 0         0 die("new() called incorrectly");
157             }
158              
159 21         99 bless($self, $class);
160             }
161              
162             =back
163              
164             =cut
165              
166 100010     100010 0 217340 sub sum { foldl(sub { shift() + shift(); }, @_); }
  100015     100015   317810  
167              
168             sub foldl {
169 100015     100015 0 148909 my($f, $z, @xs) = @_;
170 100015         205465 $z = $f->($z, $_) foreach(@xs);
171 100015         470169 return $z;
172             }
173              
174             =head1 BUGS
175              
176             For random, read 'pseudo-random'. Patches to work with sources of true
177             randomness are welcome.
178              
179             Doesn't support dice with fractional or complex numbers of sides :-)
180              
181             =head1 FEEDBACK
182              
183             I welcome feedback about my code, including constructive criticism. And,
184             while this is free software (both free-as-in-beer and free-as-in-speech) I
185             also welcome payment. In particular, your bug reports will get moved to
186             the front of the queue if you buy me something from my wishlist, which can
187             be found at L.
188              
189             =head1 AUTHOR
190              
191             David Cantrell EFE
192              
193             =head1 COPYRIGHT
194              
195             Copyright 2003 David Cantrell
196              
197             This module is free-as-in-speech software, and may be used, distributed,
198             and modified under the same terms as Perl itself.
199              
200             =cut
201              
202             1;