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.076I 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).
And thanks for the patch - version 1.1 is on its way to the CPAN as we speak.