The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;
package Games::Dice;
# ABSTRACT: Perl module to simulate die rolls
$Games::Dice::VERSION = '0.045';
require Exporter;

our @ISA = qw(Exporter);
our @EXPORT_OK = qw( roll roll_array);

# Preloaded methods go here.

# Win32 has crummy built in rand() support
# So let's use something that's decent and pure perl
use if $^O eq "MSWin32", 'Math::Random::MT::Perl' => qw(rand);

sub roll ($) {
    my($line, $dice_string, $sign, $offset, $sum, @throws, @result);

    $line = shift;

    return $line if $line =~ /\A[0-9]+\z/;

    return undef unless $line =~ m{
                 ^              # beginning of line
                 (              # dice string in $1
                   (?:\d+)?     # optional count
                   [dD]         # 'd' for dice
                   (?:          # type of dice:
                      \d+       # either one or more digits
                    |           # or
                      %         # a percent sign for d% = d100
                    |           # pr
                      F         # a F for a fudge dice
                   )
                 )
                 (?:            # grouping-only parens
                   ([-+xX*/bB]) # a + - * / b(est) in $2
                   (\d+)        # an offset in $3
                 )?             # both of those last are optional
                 \s*            # possibly some trailing space (like \n)
                 $
              }x;               # whitespace allowed

    $dice_string = $1;
    $sign        = $2 || '';
    $offset      = $3 || 0;

    $sign        = lc $sign;

    @throws = roll_array( $dice_string );
    return undef unless @throws;

    if( $sign eq 'b' ) {
        $offset = 0       if $offset < 0;
        $offset = @throws if $offset > @throws;

        @throws = sort { $b <=> $a } @throws;   # sort numerically, descending
        @result = @throws[ 0 .. $offset-1 ];    # pick off the $offset first ones
    } else {
        @result = @throws;
    }

    $sum = 0;
    $sum += $_ foreach @result;
    $sum += $offset if  $sign eq '+';
    $sum -= $offset if  $sign eq '-';
    $sum *= $offset if ($sign eq '*' || $sign eq 'x');
    do { $sum /= $offset; $sum = int $sum; } if $sign eq '/';

    return $sum;
}

sub roll_array ($) {
    my($line, $num, $type, @throws);

    $line = shift;

    return $line if $line =~ /\A[0-9]+\z/;

    return undef unless $line =~ m{
                 ^      # beginning of line
                 (\d+)? # optional count in $1
                 [dD]   # 'd' for dice
                 (      # type of dice in $2:
                    \d+ # either one or more digits
                  |     # or
                    %   # a percent sign for d% = d100
                  |     # pr
                    F   # a F for a fudge dice
                 )
              }x;       # whitespace allowed

    $num    = $1 || 1;
    $type   = $2;

    my $throw = sub { int (rand $_[0]) + 1 };

    if ( $type eq '%' ) {
        $type = 100;
    } elsif ( $type eq 'F' ) {
        $throw = sub { int( rand 3 ) - 1 };
    }

    @throws = ();
    for( 1 .. $num ) {
        push @throws, $throw->($type);
    }

    return @throws;
}

1;

=pod

=encoding UTF-8

=head1 NAME

Games::Dice - Perl module to simulate die rolls

=head1 VERSION

version 0.045

=head1 SYNOPSIS

  use Games::Dice 'roll';
  $strength = roll '3d6+1';

  use Games::Dice 'roll_array';
  @rolls = roll_array '4d8';

=head1 DESCRIPTION

Games::Dice simulates die rolls. It uses a function-oriented (not
object-oriented) interface. No functions are exported by default. At
present, there are two functions which are exportable: C<roll> and
C<roll_array>. The latter is used internally by C<roll>, but can also be
exported by itself.

The number and type of dice to roll is given in a style which should be
familiar to players of popular role-playing games: I<a>dI<b>[+-*/b]I<c>.
I<a> is optional and defaults to 1; it gives the number of dice to roll.
I<b> indicates the number of sides to each die; the most common,
cube-shaped die is thus a d6. % can be used instead of 100 for I<b>;
hence, rolling 2d% and 2d100 is equivalent. If F is used for I<b> fudge
dice are used, which either results in -1, 0 or 1. C<roll> simulates I<a>
rolls of I<b>-sided dice and adds together the results. The optional end,
consisting of one of +-*/b and a number I<c>, can modify the sum of the
individual dice. +-*/ are similar in that they take the sum of the rolls
and add or subtract I<c>, or multiply or divide the sum by I<c>. (x can
also be used instead of *.) Hence, 1d6+2 gives a number in the range
3..8, and 2d4*10 gives a number in the range 20..80. (Using / truncates
the result to an int after dividing.) Using b in this slot is a little
different: it's short for "best" and indicates "roll a number of dice,
but add together only the best few". For example, 5d6b3 rolls five six-
sided dice and adds together the three best rolls. This is sometimes
used, for example, in role-playing to give higher averages.

Generally, C<roll> probably provides the nicer interface, since it does
the adding up itself. However, in some situations one may wish to
process the individual rolls (for example, I am told that in the game
Feng Shui, the number of dice to be rolled cannot be determined in
advance but depends on whether any 6s were rolled); in such a case, one
can use C<roll_array> to return an array of values, which can then be
examined or processed in an application-dependent manner.

This having been said, comments and additions (especially if accompanied
by code!) to Games::Dice are welcome. So, using the above example, if
anyone wishes to contribute a function along the lines of roll_feng_shui
to become part of Games::Dice (or to support any other style of die
rolling), you can contribute it to the author's address, listed below.

=head1 NAME

=head1 AUTHORS

=over 4

=item *

Philip Newton <pne@cpan.org>

=item *

Ricardo Signes <rjbs@cpan.org>

=back

=head1 CONTRIBUTORS

=for stopwords Mario Domgoergen Mark Allen

=over 4

=item *

Mario Domgoergen <mdom@taz.de>

=item *

Mark Allen <mrallen1@yahoo.com>

=back

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 1999 by Philip Newton.

This is free software, licensed under:

  The MIT (X11) License

=cut

__END__

#pod =head1 NAME
#pod
#pod
#pod =head1 SYNOPSIS
#pod
#pod   use Games::Dice 'roll';
#pod   $strength = roll '3d6+1';
#pod
#pod   use Games::Dice 'roll_array';
#pod   @rolls = roll_array '4d8';
#pod
#pod =head1 DESCRIPTION
#pod
#pod Games::Dice simulates die rolls. It uses a function-oriented (not
#pod object-oriented) interface. No functions are exported by default. At
#pod present, there are two functions which are exportable: C<roll> and
#pod C<roll_array>. The latter is used internally by C<roll>, but can also be
#pod exported by itself.
#pod
#pod The number and type of dice to roll is given in a style which should be
#pod familiar to players of popular role-playing games: I<a>dI<b>[+-*/b]I<c>.
#pod I<a> is optional and defaults to 1; it gives the number of dice to roll.
#pod I<b> indicates the number of sides to each die; the most common,
#pod cube-shaped die is thus a d6. % can be used instead of 100 for I<b>;
#pod hence, rolling 2d% and 2d100 is equivalent. If F is used for I<b> fudge
#pod dice are used, which either results in -1, 0 or 1. C<roll> simulates I<a>
#pod rolls of I<b>-sided dice and adds together the results. The optional end,
#pod consisting of one of +-*/b and a number I<c>, can modify the sum of the
#pod individual dice. +-*/ are similar in that they take the sum of the rolls
#pod and add or subtract I<c>, or multiply or divide the sum by I<c>. (x can
#pod also be used instead of *.) Hence, 1d6+2 gives a number in the range
#pod 3..8, and 2d4*10 gives a number in the range 20..80. (Using / truncates
#pod the result to an int after dividing.) Using b in this slot is a little
#pod different: it's short for "best" and indicates "roll a number of dice,
#pod but add together only the best few". For example, 5d6b3 rolls five six-
#pod sided dice and adds together the three best rolls. This is sometimes
#pod used, for example, in role-playing to give higher averages.
#pod
#pod Generally, C<roll> probably provides the nicer interface, since it does
#pod the adding up itself. However, in some situations one may wish to
#pod process the individual rolls (for example, I am told that in the game
#pod Feng Shui, the number of dice to be rolled cannot be determined in
#pod advance but depends on whether any 6s were rolled); in such a case, one
#pod can use C<roll_array> to return an array of values, which can then be
#pod examined or processed in an application-dependent manner.
#pod
#pod This having been said, comments and additions (especially if accompanied
#pod by code!) to Games::Dice are welcome. So, using the above example, if
#pod anyone wishes to contribute a function along the lines of roll_feng_shui
#pod to become part of Games::Dice (or to support any other style of die
#pod rolling), you can contribute it to the author's address, listed below.
#pod
#pod =cut