Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Dice::Die

by coreolyn (Parson)
on Jan 06, 2001 at 01:55 UTC ( [id://50152]=sourcecode: print w/replies, xml ) Need Help??
Category: Fun
Author/Contact Info coreolyn
Description:

UPDATE: Version 0.07 Changes the return on faceless die to 0 instead of croaking, and cleans up the POD documentation a bit.


I sat down to play with OO to try to see how well it and efficient Perl is getting under my fingers. The node What the other half does inspired me to play with a die.

Please point out anything that could be done more efficiently.

Ref to Dice::Dice
package Die;

use strict;
use vars qw($VERSION);

$VERSION = '0.07';

{
   sub _set_face { (shift)->{face} = $_[1] }
}

# Constructor->new sucks.  ->get a Die.
sub get {
   my ( $init, $type, $faced)  = @_;
   my $class = ref($init) || $init;
    
   # To store a face value or not to store . . .
   if ( $faced && $faced == 1 ) {
      return bless { type => $type, face => 0 }, $class;
   } else {
      return bless { type => $type }, $class;
   }
}

sub roll {
   my ( $self, $keeper ) = @_;

   my $type  = $self->{type};
   my $roll  = int( rand($type) + 1 );

   # Optionally stores face value
   if (  defined($self->{face}) && defined($keeper) && $keeper == 1 )
   {
      $self->_set_face($roll);
   }
   return $roll;
}
 
sub face {
   my $self = shift;

   if ( $self->{face} ) {
      return $self->{face}; 
   } else {
      return 0;
   }
}

1;
__END__

=head1 NAME

   Die - Perl module for a base die object  

=head1 SYNOPSIS

   use Die;

   Die->get($type, 1) Where $type is a mandantory parameter 
                      that specifies the number of sides to 
                      the die, and '1' is an optional 
                      parameter used to create a die with a
                      'face' attribute to store the value of
                      the die roll. At creation Die->{face}=0
                      The 0 signifies that the Die has yet to 
                      be rolled.

   Die->roll(1)       Where '1' is an optional parameter
                      that forces the 'face' value to be 
                      stored in a Die object that has been 
                      created with a 'face'. (See Die->get).

=head1 DESCRIPTION 

   This module is a base class for the more robust Dice 
   class.  Although Die methods can be called directly 
   via this module, perfomance begs the question as to 
   why not to just use: 

   int(rnd($num)+1 

   However one may have need of the Dice class and desire 
   to maintain code continuity, and still need a die roll.
   By default Die creates itself as lightweight as possible
   by not creating a 'face' attribute, and by not storing 
   the 'roll'.  Both of these abilities can be enabled by 
   specifiying a an optional parameter of 1.
 
   A comparison of a random number subroutine versus the 
   various incantations of Die->get and Die->roll:

                       Rate    FacedDieObjStored FacedDieObjNotStored 
FacedDieObjStored     8514/s          --                -13%         
FacedDieObjNotStored  9821/s          15%                --           
FacelessDieObj       10110/s          19%                 3%         
RndSubRoutine        54455/s          540%              454%        
   
                               FacelessDieObj     rndSubRoutine
FacedDieObjStored                     -16%              -84%
FacedDieObjNotStored                   -3%              -82%
FacelessDieObj                          --              -81%
RndSubRoutine                         439%                --

=head1 AUTHOR 

coreolyn@bereth.com

=head1 SEE ALSO 

perldoc Dice

=cut
Replies are listed 'Best First'.
Re: Dice::di
by chipmunk (Parson) on Jan 06, 2001 at 02:11 UTC
    It seems like a lot of work to create an entirely new object for each execution of what is effectively int rand($dieType) + 1, especially with the length of the new() method. Why not allow a die object to be created once and then rolled as many times as desired?

    One other point; the calculation of theoretical average should be: my $theoreticalAve = ($dieType + 1) / 2; which is (max + min) / 2. Consider a two-sided die (marked 1 and 2); the expected average is 1.5, rather than 1. :)

      This comment is altered

      I agree. Each type of die should be storable as an instance. For example, my $six_sider = Dice::Di->new( SIDES => 6 ). And useful methods would be
      my $roll = $six_sider->roll(); my @rolls = $six_sider->rolls(6); #note plural, although #with wantarr +ay() this can probably be folded into roll() my @char_trait_set = map {$six_sider->roll()} (1..3) # ?
        The point I was making is that, as the module is written, he can't make an object $six_sider and then call $six_sider->roll() as many times as he wants. The value for the roll is set in the new() method, and the module does not provide a method for generating a new roll on an already created object. Each die roll requires creating an entirely new object, calling its get_roll() method to find out what the die roll was, and then discarding the object.

        I agree with you that a roll() method, as you described, would be very useful.

      Consider a two-sided die (marked 1 and 2)

      You mean a coin? ;-)
      I agree with chipmunk. I think you should create the object once, and then call a get_roll method from there.

      ryddler

        Consider a two-sided die (marked 1 and 2)

        You mean a coin? ;-)

        Yes, except I was trying to avoid ending up with (Heads + Tails) / 2. :)

      Update: The following post is an example of what happens when you don't read posts closely. I'd delete it, but somehow that doesn't seem right..


      While it might seem like overkill, when you start assembling RPG Apps and need to hold and compare 5-12 dice rolls against 3-4 different charts (arrays) a dice object becomes kinda handy. This is just the very base to be inherited into a more robust Dice::Dice object. Besides, it's simplicity makes for easier understanding of the basic construction which was actually the real objective :)

      Thanks for the fix on the run script

      coreolyn

Benchmarks: Costs of OO Perl
by coreolyn (Parson) on Jan 10, 2001 at 02:46 UTC

    Ah.. the cost/benifit ratio for perl 00

    I think I've got an apples to apples here, hope someone will correct me if I'm wrong. I ran the benchmarks two different ways one with object creation in the benchmark and one with it outside. I put object creation outside of one of the benchmarks, as a way to statistically add a numerical factor for the convenience, reuse, and managament of OO over non-OO code.

    Besides the lousy formating the results are pretty much what I expected, But I was suprised at the cost of just creating a stack for a simple subroutine, And, at where any benefits to the pseudo hash were.

    It appears to me that if your willing to pay the cost for an OO die that you might as well pay the extra 20% to have the extra abilities one may desire. However the default behavior should be to NOT store values or have a 'face' unless required.

    use Benchmark qw(cmpthese); use strict; use Dice::pureDieObj; use Dice::di; use Dice::diV02; #my $pureDieObj = Dice::pureDieObj->get(20); #my $FacelessDieObj = Dice::di->get(20,1); #my $FacedDieObj = Dice::di->get(20); #my $PsuedoHashDie = Dice::diV02->new( type=>20 ); system("clear"); my $loops; for $loops (55000) { cmpthese $loops, { pureSub => sub { my $roll = Die(20); }, purists => sub { my $roll = int( rand( 20 ) + 1 ); }, pureDieObj => sub { # No face/storage only a roll my $pureDieObj = Dice::pureDieObj->get(20); my $roll = $pureDieObj->roll; }, FacelessDieObj => sub { my $FacelessDieObj = Dice::di->get(20,1); my $roll = $FacelessDieObj->roll; }, FacedDieObjNotStored => sub { my $FacedDieObj = Dice::di->get(20); my $roll = $FacedDieObj->roll(1); }, FacedDieObjStored => sub { my $FacedDieObj = Dice::di->get(20); my $roll = $FacedDieObj->roll; }, PsuedoHashObj => sub { my $PsuedoHashDie = Dice::diV02->new( type=>20 ); my $roll = $PsuedoHashDie->roll; }, }; } sub Die { return int( rand( shift ) + 1 ); }

    And the results...

    With object creation:

    Benchmark: timing 55000 iterations of FacedDieObjNotStored, FacedDieOb +jStored, FacelessDieObj, PsuedoHashObj, pureDieObj, pureSub, purists. +.. FacedDieObjNotStored: 6 wallclock secs ( 6.14 usr + 0.00 sys = 6.14 + CPU) @ 8957.65/s (n=55000) FacedDieObjStored: 7 wallclock secs ( 6.52 usr + 0.00 sys = 6.52 CP +U) @ 8435.58/s (n=55000) FacelessDieObj: 5 wallclock secs ( 5.58 usr + 0.00 sys = 5.58 CPU) +@ 9856.63/s (n=55000) PsuedoHashObj: 16 wallclock secs (15.08 usr + 0.00 sys = 15.08 CPU) @ + 3647.21/s (n=55000) pureDieObj: 5 wallclock secs ( 5.26 usr + 0.00 sys = 5.26 CPU) @ 10 +456.27/s (n=55000) pureSub: 2 wallclock secs ( 1.09 usr + 0.00 sys = 1.09 CPU) @ 50 +458.72/s (n=55000) purists: 1 wallclock secs ( 0.55 usr + 0.00 sys = 0.55 CPU) @ 10 +0000.00/s (n=55000) Rate PsuedoHashObj FacedDieObjStored FacedDieObjNotStored FacelessDieO +bj pureDieObj pureSub purists PsuedoHashObj 3647/s + -- -57% -59% -63% -65% - +93% -96% FacedDieObjStored 8436/s 131% -- + -6% -14% -19% -83% -92% FacedDieObjNotStored 8958/s 146% 6% + -- -9% -14% -82% -91% FacelessDieObj 9857/s 170% 17% + 10% -- -6% -80% -90% pureDieObj 10456/s 187% 24% + 17% 6% -- -79% -90% pureSub 50459/s 1283% 498% + 463% 412% 383% -- -50% purists 100000/s 2642% 1085% + 1016% 915% 856% 98% --

    Without object creation factored in:

    Benchmark: timing 55000 iterations of FacedDieObjNotStored, FacedDieOb +jStored, FacelessDieObj, PsuedoHashObj, pureDieObj, pureSub, purists... FacedDieObjNotStored: 1 wallclock secs ( 2.32 usr + 0.00 sys = 2.32 + CPU) @ 23706.90/s (n=55000) FacedDieObjStored: 2 wallclock secs ( 2.77 usr + 0.00 sys = 2.77 CP +U) @ 19855.60/s (n=55000) FacelessDieObj: 1 wallclock secs ( 2.06 usr + 0.00 sys = 2.06 CPU) +@ 26699.03/s (n=55000) PsuedoHashObj: 3 wallclock secs ( 3.97 usr + 0.00 sys = 3.97 CPU) @ + 13853.90/s (n=55000) pureDieObj: 1 wallclock secs ( 1.72 usr + 0.00 sys = 1.72 CPU) @ 31 +976.74/s (n=55000) pureSub: 2 wallclock secs ( 1.12 usr + 0.00 sys = 1.12 CPU) @ 49 +107.14/s (n=55000) purists: 1 wallclock secs ( 0.51 usr + 0.00 sys = 0.51 CPU) @ 10 +7843.14/s (n=55000) Rate PsuedoHashObj FacedDieObjStored FacedDieObjNotStored FacelessDieO +bj pureDieObj pureSub purists PsuedoHashObj 13854/s + -- -30% -42% -48% -57% + -72% -87% FacedDieObjStored 19856/s 43% -- + -16% -26% -38% -60% -82% FacedDieObjNotStored 23707/s 71% 19% + -- -11% -26% -52% -78% FacelessDieObj 26699/s 93% 34% + 13% -- -17% -46% -75% pureDieObj 31977/s 131% 61% + 35% 20% -- -35% -70% pureSub 49107/s 254% 147% + 107% 84% 54% -- -54% purists 107843/s 678% 443% + 355% 304% 237% 120% --

    Of course I'm sure the purist are looking at the numbers wondering why anyone would even consider a Die object. But I still contend that for an ease of application development and management in large dice intensive applications, or just for ease of use in small ones it's worth the associated costs. Besides it is proving to be a great study in OO and Perl.

    coreolyn - Still waiting for hardware and software to catch up to my vision :)
      I didn't scan all your article, but if you realize that most of the work of an object is not during dispatch, then your "20%" will become more like "2%" for non-trivial objects. I remember Damian saying this at a talk he gave last summer, and I think he'd know, having written the book on Perl Objects.

      -- Randal L. Schwartz, Perl hacker

        Whether or not most of the work of an object is during dispatch depends on how your have designed your system. In the example that led coreolyn to do those benchmarks the majority of the work actually was in creation of objects and dispatch to them. In a sample run I doubled the performance by doing direct hash access to attributes of objects rather than calling get methods.

        But yes, I think it is generally true that experienced OO programmers come up with designs where dispatch is not a major bottleneck.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others chilling in the Monastery: (2)
As of 2024-04-20 03:04 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found