Bar bets and Games::Dice::Advanced

brian_d_foy on 2006-09-10T16:21:42

Going through some old papers yesterday, I found a statistics problem I had to prove:

This bar game uses three standard six-sided dice. The player rolls all three dice at once. If he rolls three 4s, the bar pays him $3. If he rolls two 4s, the bar pays him $2. If he rolls a single 4, the bar pays him $1. If he rolls no 4s, he pays the bar $1. Prove that the average loss per roll is about 7 cents for a large number of rolls.
I had done all the math and so on, but I wanted to write a little simulation. The Games::Dice::Advanced can handle all sorts of die things.
#!/usr/bin/perl

use Games::Dice::Advanced;

my $total = 0;

my $iterations = $ARGV[0] || 100_000;

print "Roll  | Dice  | Win    Total    Win/Roll \n" .
      "------|-------|---------------------------\n";

foreach ( 1 .. $iterations )
	{
	my @n = map { Games::Dice::Advanced->roll() } 1 .. 3;
	
	my( $change ) = pay_up( \@n );
	
	$total += $change;
	
	printf "%5d | %d %d %d |  %2d   %5d     % 5.3f\n", 
		$_,
		@n, $change, $total, $total / $_
			if $_ % ( $iterations / 10 ) == 0;	
	}

sub pay_up 
	{
	my $count = grep { $_ == 4 } @{ $_[0] };
	
	# return the payout based on the number of rolled 4s
	if(    $count == 3 ) {  3 }
	elsif( $count == 2 ) {  2 }
	elsif( $count == 1 ) {  1 }
	else                 { -1 }
	}

The "large number of rolls" tends to be pretty large. If you want to stick around a bar to roll dicce 4,000 times, you probably deserve to lose all of your money. Remember, you lose seven cents per roll.

Roll  | Dice  | Win    Total    Win/Roll 
------|-------|---------------------------
 1000 | 1 1 2 |  -1       9      0.009
 2000 | 1 4 1 |   1     -74     -0.037
 3000 | 3 5 6 |  -1    -132     -0.044
 4000 | 1 3 6 |  -1    -251     -0.063
 5000 | 2 2 4 |   1    -345     -0.069
 6000 | 5 2 1 |  -1    -433     -0.072
 7000 | 2 2 3 |  -1    -463     -0.066
 8000 | 1 2 3 |  -1    -584     -0.073
 9000 | 1 2 2 |  -1    -618     -0.069
10000 | 4 2 3 |   1    -761     -0.076
I figured that if I was going to play this at any Perl Mongers meeting I was likely to run into unusual dice, so I wanted to see how much I'd get out of people using twenty sided dice.
use Games::Dice::Advanced;

my $total      = 0;
my $sides      = $ARGV[0] || 6;
my $iterations = $ARGV[1] || 100_00;

print "Roll  | Dice     | Win    Total    Win/Roll \n" .
      "------|----------|---------------------------\n";

foreach ( 1 .. $iterations )
	{
	my @n = map { Games::Dice::Advanced->roll( "d$sides" ) } 1 .. 3;
	
	my( $change ) = pay_up( \@n );
	
	$total += $change;
	
	printf "%5d | %2d %2d %2d |  %2d   %5d     % 5.3f\n", 
		$_,
		@n, $change, $total, $total / $_
			if $_ % ( $iterations / 10 ) == 0;	
	}
	}

Doing this, I discovered a bug in Games::Dice::Advanced. There's a closing paren in the wrong place such that die with more than nine sides don't work correctly:

--- Advanced.pm 2006-09-10 10:45:33.000000000 -0500
+++ Advanced.pm-new     2006-09-10 10:46:51.000000000 -0500
@@ -132,7 +132,7 @@
            if($recipe !~ /\D/) {                       # constant
                # $self = eval("sub { $recipe * $mul }");
                $self = sub { $recipe * $mul };
-           } elsif($recipe =~ /^d(\d)+$/) {            # dINT
+           } elsif($recipe =~ /^d(\d+)$/) {            # dINT
             # $self = eval("sub { (1 + int(rand($1))) * $mul }");
             my $faces = $1;
             $self = sub { (1 + int(rand($faces))) * $mul };                    
With the little change, I figure I'd win about 75 cents per throw. Of course, if I do this with the London Perl Mongers, I'd win even more because of the exchange rate (they'd have to play with pounds).


unusual sided dice

jmm on 2006-09-11T04:12:00

Any old-time D&D gamer will have some 4-sided dice to use, if you're allowing non-standard dice. You'll be losing money to them.

loaded dice

drhyde on 2006-09-12T17:59:58

Games::Dice::Advanced supports loaded dice too, so you're doomed if you play against a perl programmer.

And thanks for the patch - version 1.1 is on its way to the CPAN as we speak.