Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Dice::Dice

by coreolyn (Parson)
on Jan 07, 2001 at 05:36 UTC ( [id://50302]=sourcecode: print w/replies, xml ) Need Help??
Category: Fun
Author/Contact Info Coreolyn
Description:

Creates, stores and reuses customized sets of random integers.

Version .08 eliminates the dependency for ::Die class and adds two new (unrefined methods) qroll and reRoll.

I look forward to any wisdom bestowed

package Dice;

use strict;
use Carp;

use vars qw($VERSION @EXPORTER @ISA);

@ISA        = qw(Exporter);
@EXPORTER  = qw(rolls reRoll qroll grab faces);
$VERSION   = '0.08';

sub grab {
   my $class = shift;
   unless( shift =~ m/(^\d+)[dD](\d+)$/ ) {
      croak "Invalid parameters passed to $class->grab";
   }

   return bless { type => $2, qty => $1 }, $class;
}

sub rolls {
   my $self = shift;

   my @faces = map { $self->roll } 1..$self->{qty};
   $self->{faces} = \@faces; 

   return $self->total;
}

sub qroll {
   my $self = shift;
   return map { $self->roll } 1..shift;
}

sub roll {
   return int( rand( shift->{type} ) + 1 );
}

sub total {
   my $total = 0;
   foreach my $face ( (shift->faces) ) {
      $total += $face;
   }
   return $total;
}

sub faces {
   my $diRef = shift->{faces};
   return @$diRef;
}

sub reRoll {
   my ( $self, $type, @dice ) = @_;
    
   sub numeric { $a <=> $b }
   my @faces = $self->faces;

   my $i = 1;
   unless ( $type =~ m/^die$/i ) {
      @faces = sort numeric ($self->faces);

      while ( $i <= $dice[0] ) {
         if ( $type =~m/^high$/i ) {
            pop(@faces);
         } elsif ( $type =~ m/^low$/i ) {
            shift(@faces);
         }
         $i++; 
      }
      while ( $i > 1 ) {
         push(@faces, $self->roll );
         $i--;
      }
   } 
   if ( $type =~ m/^die$/i ) {
      VALUE : foreach my $value (@dice) {
         FACE : foreach my $face (@faces) {
            if ( $value == $face ) {
               $face = $self->roll;
               last FACE;
            }
         }
      }
   } 

   $self->{faces} = \@faces;
   return $self->faces;
}

1;
__END__

=head1 NAME

Dice - Perl extension for creating reusable sets of Dice

=head1 SYNOPSIS

  use Dice;

  Dice->grab($dietype)    Constructor takes arguments in the form 
                          of '$dieQty d $dieType' as in the 
                          expression '3d6'. ( Reads 3 six sided 
                          die)

   Dice->rolls()          Rolls new random values for each die in 
                          the Dice object. 

   Dice->faces()          Returns an array with the Dice values

   

   Dice->reRoll('die'|'high'|'low', @faceValues|$qty);

                          The reRoll() method supports three types
                          of re-rolls. The first parameter specifies
                          the type of re-roll.

                   'die'  Signifies that defined face values will be 
                          supplied in @faceValues to be rerolled.

                          [Note: Still needs an exception generated
                           if supplied value is not found in the
                           objects @faces]
 
                  'high'  Signifies that highest dies as specified by 
                          the quantity of dice specified in $qty will 
                          be reRolled.

                  'low'   Same as 'high' except lowest dice.


   Dice->qroll($qty)      Returns a non stored array of dice of the 
                          same 'type' as the object. The number of 
                          dice rolled = $qty.
                            

=head1 DESCRIPTION

   Dice objects are extensible and reusable.  They are of value in 
   programatic re-use, maintenance, and convenience but NOT performanc
+e. 

=head1 AUTHOR

   coreolyn@bereth.com

=head1 SEE ALSO

   

=cut

Test Script
#! /usr/bin/perl -w
use strict;
use lib ('./');
use Dice;

my $dieType = 10;
my $rollQty  = 6;
my $dice = "$rollQty\d$dieType";

system("clear");
print "\n\$dice = $dice\n";

my $MagicMissles = Dice->grab("$dice");
my $total = $MagicMissles->rolls(1);
my @rolls = $MagicMissles->faces;

my $theoreticalAve = ($dieType)  / 2;
my $average = $total / $rollQty;

sub numeric { $a <=> $b }
@rolls = sort numeric (@rolls);
print "\nHere are the di rolls\n@rolls\n";
$total = $MagicMissles->total;
print "Total = $total\n";

@rolls = sort numeric ( $MagicMissles->reRoll('high', '3') );
print "\nAfter re-rolling the top 3 die\n@rolls\n";
$total = $MagicMissles->total;
print "Total = $total\n";

@rolls = sort numeric ( $MagicMissles->reRoll('low', '2') );
print "\nAfter re-rolling the bottom 2 die\n@rolls\n";
$total = $MagicMissles->total;
print "Total = $total\n";

print "\nInput the value of the Die you wish to re-roll: ";
my $values = <STDIN>;
my @values = split(/ /,$values);
@rolls = sort numeric ( $MagicMissles->reRoll('die', @values ) );
print "\nNew rolls now look like this\n@rolls\n";

$total = $MagicMissles->total;
print "\nTotal damage = $total\n";

print "\nQuick rolls (unremembered any qty): ";
my $qty = <STDIN>;
@rolls = $MagicMissles->qroll($qty);
print "You rolled\n@rolls\n";

print "\nAverage should equal $theoreticalAve\nActual Average = $avera
+ge\n\n";
Replies are listed 'Best First'.
Re (tilly) 1: Dice::Dice
by tilly (Archbishop) on Jan 07, 2001 at 23:02 UTC
    I think you should do some more thinking about your interfaces.

    I know it is tempting when turning loose on the OO way of thinking to go overboard. For instance writing get/set methods for everything. Having dice that know everything about themselves. So on and so forth.

    But such over-engineering is one of the major (IMHO) pitfalls of OO design. To avoid it, stop and walk through how you expect your stuff to be used. Do it by hand if need be. (An interesting extreme programming exercise is to have people stand in a room, each one being another object, and walk through the algorithm, watching as people make requests.)

    What you want to do is export a simple interface that can be implemented internally efficiently. For instance most of your get/set routines can be replaced by hash interfaces that you declare cannot be accessed externally. In one stroke you throw away a large amount of your interface and get a huge performance win.

    For instance why should a die keep track of its last roll? Almost never is that needed. OTOH you are constantly going to want to roll a particular type of die many times in a row. Why require a ton of objects for that? I maintain that in practice the simpler interface of saying that a die has a size and can be rolled to be rolled many times is just as flexible and far more efficient:

    package Dice::Die; $VERSION = 0.04; use strict; sub get_size { (shift)->{size}; } sub new { my $init = shift; my $class = ref($init) || $init; my $size = shift; return bless {size => $size}, $class; } sub roll { my $self = shift; my $count = shift || 1; my $size = $self->{size}; return map {int(rand($size) + 1)} 1..$count; } 1;
    (Note that while I provide an external accessor, I don't use it internally.)

    Another point is be lazy about doing work. For instance in your Dice::Dice::roll method you do a tremendous amount of work keeping track of totals etc. I have to ask why. After all my experience with role-playing games suggests strongly that people will want to start rolling 5 and taking the top 3 very shortly. Just do a roll and return results. Allow the work of massaging that data to be done by someone else. (Do something simple, and do as little as possible.) After all that is why we have List::Util and friends.

    Oh, also think twice before tracking stuff yourself. For instance if you kept your current design I would modify your roll method in Dice::Dice to look more like this:

    sub roll { my ($self) = shift; my $diRef = $self->get_di; my ($total, @roll); foreach my $di ( @$diRef ) { my $rolled = $di->roll(); push( @roll, $rolled ); $total += $rolled; } $self->set_total($total); $self->set_dice(\@roll); return $total; }
    Oops, where did your quantity go? I don't need it because every array in Perl already knows how long it is. Besides which there is less room for me to make a mistake (and there is somewhat better performance) if I don't try to use explicit for loops.

    Another warning is that the entire idea of a pseudo-hash is flawed. There is a real possibility that when Perl 5.8 comes out it will no longer be supported. It is virtually certain that it is an idea that will not survive into Perl 6.0 due to implementation issues. In other words code that depends on it will be in the acceptable 5% breakage. Which means that no matter what the performance benefit may be, it is unwise to start using it.

    Unfortunately there is no good way to learn that other than by paying attention to development discussions. But after the first attempt at Perl 6 (the Topaz effort) crashed and burned on that issue, the value of supporting it has to be questioned. Oh, something else with the same idea likely will be introduced in time. But not with that interface.

    Besides which the performance benefit is probably not that big for you. While they make accesses faster, they make construction more expensive. Given that your design results in constructing lots of objects that are accessed very few times each, this may well not be a net win. (Hey, as another benefit of not using them you get to do less typing! :-)

      I don't see it as a problem to have a method for remembering how many sides the die has, especially since it's two real lines of code and the instance has to have that in order to useful (so it's persistent data anyway). Since he is doing this as a learning exercise, it's a good point to be made though.

      As far as dice objects go, I prefer to have a Face attribute, which does hold the most recent roll. Completely useless in many circumstances, but what if this is for a game of Yahtzee? Then the number on the top of the die is very important. Also, as an amusing trick, I'd like to see an OppositeFace () method.

      By carefully abusing list vs. scalar context, the same method can be made to return a single roll or a list of several rolls (of the same die). Also useful would be a sum method to total up several rolls into a single scalar result.

      Gee, I'm glad I have no idea what a pseudo-hash is given it's shaky future. I would have used "our" to create a default hash if it were really needed, but prefer to simply set the defaults in the new constructor.
        Thank you for reminding me. Default values are something that is good to make a method from if you want them inherited in an override. His implementation of defaults tries to do that but will blow up because he will try to be using a symbolic reference and has strict on.

        As for the accessor method, I provided one accessor where he provided 2 setters and 2 getters. Then I didn't use mine internally, while he used his a lot. I think that the difference is substantial.

        You do have a good point about the needs of Yahtzee vs the needs of role-playing games. However if you wanted to reuse this module for Yahtzee, why not provide a subclass that adds the face attribute? Just because something is sometimes useful doesn't mean that you want to always do it. Instead be reasonably efficient by default but provide an interface that can be extended easily.

        As for a sum method, depending on your situation that might or might not fit in the class. In role-playing games you often see rolling a lot of dice and summing them up. But you also see a lot of rolling several dice and summing up some subselection. (Usually the top m of n.) In games such as backgammon you really don't want to sum them, but you do care about doubles. In short it doesn't need to happen by default though it may be convenient often enough to provide it.

        As for what a pseudo-hash is, it is an anonymous array whose first element is a hash saying what the following entries are. In short it acts a lot like a C struct. And if you do a lot of declarations Perl will actually resolve the lookup through the hash element at compile time and the accesses will be faster as well.

        Unfortunately it looks to be history. But you might as well remember this for reuse as a future trivia question.

        What feature was left out of Perl 6 because it caused serious performance problems in the first attempt to write Perl 6?

        By carefully abusing list vs. scalar context, the same method can be made to return a single roll or a list of several rolls

        Why would this be abusing context? That is exactly what wantarray is for ...

        Also, as an amusing trick, I'd like to see an OppositeFace () method.

        sub opposite_face { my $self = shift; return $self->{size} - $self->{face} + 1; }

        Tony

        Update: DOH!

        Call me dense but I'm not understanding the difference between a 'face' attribute and the array held in the 'Results' attribute.

        As for an OppositeFace() method I don't think I'll be chasing that one for a while :)

Re: Dice::Dice
by coreolyn (Parson) on Jan 08, 2001 at 20:09 UTC

    Thanks everybody for your comments. I think this little exersize has finally shown me the most fundamental problem with my approach to coding, that being, that the real coding begins after I get the code to work. My standard approach has always been just to go to the next chunk of code as soon as I have something that achieves the functionality I'm after. Lesson well learned.

    The psuedo-hash information alone shows that there needs to be a total re-write. Because of the performance gain I had planned on using the construct as my default OO structure for the majority of my OO projects. Furthermore, I had not considered the construction costs of providing all the accessor methods that may or may not be used. I simply provided them because that's what the book does. (And if Damian, or many of the perlmonks here, said to jump off a cliff, I'd at least give the suggestion serious consideration :)

    This is intended to be a universal base class for dice, so that anyone could derive a "Dice::Yahtzee" or "Dice::BarDice", "Dice::DnD", "Dice::Craps" etc.

    Storing the value of each di is fundamental to the purpose to the class. That storage is what makes it a 'di' and not just a random integer. However providing an alternative constructor without storage may have value in many situations. Without storage, utilizing the int(Rand( $num)+1) would be the smarter way to go. Providing it would simply give the calling code continuity, but would not be worth the performance hit.

    After much meditation I don't believe this is a case of 'over-enginneering'. Di are an object unto themselves that are as old as Egypt, and the patterns derived from thier differing applications of randomness are the key to programming chaos into simulations that make them seem more 'real'. Not to mention the bulk of programmers who would just simply like the 'shortcuts' that the object could provide.

    I'm going to keep persuing this here and all the way to CPAN. Why not? It'll probably get more use than Bone::easy. I need to learn POD and the route to publishing on CPAN and this basic class simplifies the learning process on all levels. It has a certain 'Zen' apeal for me.

    coreolyn - Heading back to the drawing board.
    UPDATE: Look for Dice::di VERSION .04

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: sourcecode [id://50302]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having a coffee break in the Monastery: (3)
As of 2024-03-29 07:22 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found