Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

check my logic & a random number issue

by rmckillen (Novice)
on Oct 11, 2002 at 01:54 UTC ( [id://204384]=perlquestion: print w/replies, xml ) Need Help??

rmckillen has asked for the wisdom of the Perl Monks concerning the following question:

I need someone to check my logic on this script. I am trying to simulate the net win/loss resulting from 100 games of craps. In this script, a passline bet is always made, and if there is no craps on the comeout roll, a 1X odds bet is made before the next roll. Those are the only bets. If you don't know how craps works, check out http://thewizardofodds.com/game/craps.html and read the intro, and about "the passline" and "taking the odds". It's simple and only a couple of paragraphs.

The reason I think something is wrong, is because when I simulated just 100 trials, there were a number of identical net win/loss results that occured twice. Is my logic, or random number generation the problem? THANKS!

#!/usr/bin/perl use Math::Random; use DBI; $username = "username"; $password = "password"; $database = "database"; $dbh = DBI->connect("DBI:mysql:$database", $username, $password); for($k=1;$k<=100;$k++) { $netwinnings = 0; $counttwos = 0; for($i=1;$i<=100;$i++) { $winnings = 0; $gameover = 0; $numrolls = 0; while($gameover == 0) { $numrolls++; ##if($numrolls == 2) { print "THE POINT IS $roll\n"; } $roll = rolldice(); ##print "ROLL #$numrolls $roll\n"; if($numrolls == 1) { $point = $roll; if($roll == 7 || $roll == 11) { $gameover = 1; $winnings += 1; ##print "WIN $winnings\n\n"; } elsif($roll == 2 || $roll == 3 || $roll == 12) { $gameover = 1; $winnings -= 1; ##print "LOSE $winnings\n\n"; } } else { if($roll == $point) { $gameover = 1; if($roll == 6 || $roll == 8) { $gameover = 1; $winnings += 1.2 + 1; ##print "WIN $winnings\n\n"; } elsif($roll == 5 || $roll == 9) { $gameover = 1; $winnings += 1.5 + 1; ##print "WIN $winnings\n\n"; } elsif($roll == 4 || $roll == 10) { $gameover = 1; $winnings += 2 + 1; ##print "WIN $winnings\n\n"; } } elsif($roll == 7) { $gameover = 1; $winnings -= 2; ##print "LOSE $winnings\n\n"; } } } $winnings = sprintf "%.2f", $winnings; $netwinnings += $winnings; $netwinnings = sprintf "%.2f", $netwinnings; } $sth = $dbh->prepare("SELECT value,qty FROM craps WHERE value = $netwi +nnings"); $sth->execute(); ($value,$qty) = $sth->fetchrow_array(); ##print "\n\n$value - $netwinnings\n\n"; if($value == $netwinnings) { $sth = $dbh->prepare("UPDATE craps SET qty = qty + 1 WHERE value = $ne +twinnings"); $sth->execute(); } else { $sth = $dbh->prepare("INSERT INTO craps VALUES($netwinnings,1)"); $sth->execute(); } print "NET WIN $netwinnings\n"; } $dbh->disconnect; sub rolldice() { $d1 = random_uniform_integer(1,1,6); $d2 = random_uniform_integer(1,1,6); $roll = $d1 + $d2; return $roll; }

Replies are listed 'Best First'.
Re: check my logic & a random number issue
by jarich (Curate) on Oct 11, 2002 at 03:44 UTC
    The reason I think something is wrong, is because when I simulated just 100 trials, there were a number of identical net win/loss results that occured twice. Is my logic, or random number generation the problem?
    Other than just eyeballing the results, what statistical analyses have you performed? Have you checked that your average dice roll is 7? When I added code in to print the average dice roll, I noticed that there's a condition that you haven't covered.
    if ($roll == $point) { $gameover = 1; if ($roll == 6 || $roll == 8) { $gameover = 1; #not necessary $winnings += 1.2 + 1; print "WIN $winnings\n"; } elsif ($roll == 5 || $roll == 9) { $gameover = 1; # not necessary $winnings += 1.5 + 1; print "WIN $winnings\n"; } elsif ($roll == 4 || $roll == 10) { $gameover = 1; # not necessary $winnings += 2 + 1; print "WIN $winnings\n"; } else { print "what happens here?"; # loss? draw? } }
    Because you put $gameover = 1 at the start of that conditional block you don't need to continue to do so for each specific condition. Unless, of course, you had intended to remove that first $gameover = 1.

    Is it these kind of pairs that are bothering you?

    LOSE -1 average dice roll: 7 LOSE -1 average dice roll: 8 LOSE -2 average dice roll: 7 LOSE -2 average dice roll: 7 LOSE -1 average dice roll: 7 LOSE -1 average dice roll: 7
    because chances are high that these are just statistical fluctuations. If you look at the dice rolls that generate these losses you'll find that they're all different. The game of craps may just be weighted to encourage losses. ;)

    You'll notice that 8 in my average dice roll. This is a running average and those 5 losses just happened to be results 5 - 10 of a simulation. The average ought to stay around 7 but will fluctuate around it through about 3 standard errors. ;)

    So, in conclusion, yes there is a logic error in your code, your game occasionally ends without setting wins or losses. Had you kept a total of games played you might have spotted this. Whether or not your random number generator is non-random or your use of it is flawed can only really be determined by analysing it in the same using something similar to what dws suggested.

    Your logic may contain other errors but as I've never studied the game of craps I have no idea what the frequency of certain results should be. Should you lose more often than you win, but win bigger? Should you lose 1 unit twice as often as you lose 2 units? I'm 100% certain that there are statistic tables out there for this game just as there are for other casino games. Once you have that data, you'll be able to analyse your results better and check whether they're in line with what they should be.

    Hope it helps

    jarich

    PS: for the interests of simplifying your code, you can easily drop the database stuff.

    PPS: FWIW in the interests of not installing more stuff, my roll function came out to be:

    sub rolldice() { # was: my $roll = int(rand(12)) + 1; my $d1 = int(rand(6)) + 1; my $d2 = int(rand(6)) + 1; my $roll = $d1 + $d2; return $roll; }

    Update: Didn't think hard enough about my rolldice function. Fixed now.

      While it can't hurt to add the "else... what happens here" block that you suggest for checking errors, I don't think it would ever be reached. All of that code is inside a block where $roll == $point. If $point is ever set to anything other than 4,5,6,8,9,10, the game is over on the first roll.

      Another thing you might want to do is round the winnings down for each game. I'm pretty sure that's what the casinos do. And maybe make the minimum bet something more typical, like $5. In addition to making it more realistic, these two things combined might make the results very different than a simple $1 bet with no rounding. I don't feel like doing the math, though, so that's just a gut feeling.

      When I've played I've won a lot by betting the minimum on the pass line and then doubling the minimum for the odds. Watch out for those bets in the middle -- they're not worth it!

        Very good catch.

        My modifications to the code changed the way it worked and therefore the responses I got. My roll function allowed "1" to be returned, which ought to be impossible (with two dice). A mistake I made when making my version strict compliant prevented this mistake from showing up in my version, so I assumed it was a bug in the original code (using my roll function though).

        In which case I can't see any logic errors in the code (except perhaps for not validating the roll dice value and catching errors that shouldn't happen in that extra else statement).

        What I said about doing proper statistical analyses is still valid. ;)

        Oh, and don't take the fact that I made mistakes in a quick and dirty strict compliancy conversion to be an argument for not using strict. ;) This (as well as formatting) was sorely missed in the original version of this code.

        jarich

      Greetings!!

      PPS: FWIW in the interests of not installing more stuff, my roll function came out to be:

      sub rolldice() { my $roll = int(rand(12)) + 1; return $roll; }
      After reading your node, jarich, I began wondering how different Math::Random, int(rand(6)) and int(rand(12)) might be... I noted your suggested rolldice() subroutine quoted above. To appropriately spread the roll result to mimic a table craps game, there should be a curve peaking at seven. I decided to slap together a quick test...

      #!c:\perl\bin\perl.exe use strict; use warnings; use diagnostics; use Math::Random; my $num_rolls = 10000; my @rollhashes; for (my $i=0; $i<4; $i++) { $rollhashes[$i] = { 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0, 7 => 0, 8 => 0, 9 => 0, 10 => 0, 11 => 0, 12 => 0 }; } for (1 .. $num_rolls) { my $m1 = random_uniform_integer(1,1,6); my $m2 = random_uniform_integer(1,1,6); my $math = $m1 + $m2; $rollhashes[2]{$math}++; my $m0 = random_uniform_integer(2,1,12); $rollhashes[0]{$m0}++; my $r1 = (int(rand(6) + 1)); my $r2 = (int(rand(6) + 1)); my $rand = $r1 + $r2; $rollhashes[3]{$rand}++; my $r0 = (int(rand(12) + 2)); $rollhashes[1]{$r0}++; } print "\nUSING TWO DICE:\n"; print "\nMath::Random\t\tint(rand)\n"; for (my $i = 2; $i <= 12; $i++) { my $n = sprintf("%02d",$i); print "$n = $rollhashes[2]{$i}\t\t$n = $rollhashes[3]{$i}\n"; } print "\nUSING ONE DIE:\n"; print "\nMath::Random\t\tint(rand)\n"; for (my $i = 2; $i <= 12; $i++) { my $n = sprintf("%02d",$i); print "$n = $rollhashes[0]{$i}\t\t$n = $rollhashes[1]{$i}\n"; }
      And here is the output:

      USING TWO DICE: Math::Random int(rand) 02 = 271 02 = 270 03 = 533 03 = 606 04 = 876 04 = 849 05 = 1129 05 = 1172 06 = 1364 06 = 1362 07 = 1640 07 = 1632 08 = 1390 08 = 1402 09 = 1101 09 = 1079 10 = 876 10 = 838 11 = 540 11 = 535 12 = 280 12 = 255 USING ONE DIE: Math::Random int(rand) 02 = 862 02 = 852 03 = 853 03 = 835 04 = 795 04 = 865 05 = 849 05 = 876 06 = 817 06 = 772 07 = 801 07 = 836 08 = 822 08 = 801 09 = 809 09 = 814 10 = 856 10 = 821 11 = 891 11 = 841 12 = 823 12 = 848
      So, using one die rather than two has a tremendous impact on the spread of the numbers! Additionally, I noted that the differences between using Math::Random and int(rand(6)) were fairly slight. After running the above code over numerous tests, I found them to be quite comparable. In the interest of not installing more than is necessary, I would go with the int(rand(6)) rather than slurping in the Math::Random goodies.

      Craps is my favorite Vegas game... I find it to be the most fun and exciting of all the games I've tried. (Probably more fun than it should be!)

      -Daruma

      Update: formatting changes and fixed reference to jarich's rand(12) usage... Thanks, jarich!!
      Update2:Reworked my unnecessarily long code with a few loops...
        You're quite right. When you use a single dice (twelve sided) the probability of getting any number (1 to 12 inclusive) is just 1/12. When you use two dices of 6 sides each the probability of each number is markedly different.
        Value Probability Combinations 1 0 2 1/36 1:1 3 2/36 1:2, 2:1 4 3/36 1:3, 2:2, 3,1 5 4/36 1:4, 2:3, 3:2, 4:1 6 5/36 1:5, 2:4, 3:3, 4:2, 5:1 7 6/36 1:6, 2:5, 3:4, 4:3, 5:2, 6:1 8 5/36 2:6, 3:5, 4:4, 5:3, 6:2 9 4/36 3:6, 4:5, 5:4, 6:3 10 3/36 4:6, 5:5, 6:4 11 2/36 5:6, 6:5 12 1/36 6:6
        Of course, if you're not using a truely random or very, very convincing pseudo-random generator you probably won't quite get these probabilities.

        Considering that 7 is a bad number to get in Craps, it'd be nicer to use just a 12 sided dice, but then the Casinos would have to rig the game another way to get their money. ;)

        It's good to know that for this kind of example rand() is as good as Math::Random.

        jarich

Re: check my logic & a random number issue
by dws (Chancellor) on Oct 11, 2002 at 02:30 UTC
    When I simulated just 100 trials, there were a number of identical net win/loss results that occured twice. Is my logic, or random number generation the problem?

    One way to approach this is to strip out everything extraneous. This bisects the problem. If the symptoms go away, you have a pretty good idea which side of the line the problem lies on.

    Something like the following may help you gain some insight:

    use Math::Random; for ( 1 .. 100 ) { my $d1 = random_uniform_integer(1,1,6); my $d2 = random_uniform_integer(1,1,6); print $d1 + $2, "\n"; }
    Does this produce results consistent with what you're seeing?

    Is also helps, when asking people to review your code, to indent. Unindented code is hard to read, and many people will take one look at it and walk away.

    Oh, and use strict; to avoid accidental globals like $roll.

      Here is the indented code. Just stripping down to the one simple for loop doesn't help because it will just print a number 1-12, 100 times. That won't really allow me to observe any sort of pattern or inconsistencies, while the output with all of the other code thrown in there gives values like xx.xx, which can range from negative hundreds of dollars to positive hundreds of dollars.
      #!/usr/bin/perl use Math::Random; use DBI; use strict; $username = "username"; $password = "password"; $database = "database"; $dbh = DBI->connect("DBI:mysql:$database", $username, $password); for($k=1;$k<=100;$k++) { $netwinnings = 0; $counttwos = 0; for($i=1;$i<=100;$i++) { $winnings = 0; $gameover = 0; $numrolls = 0; while($gameover == 0) { $numrolls++; ##if($numrolls == 2) { print "THE POINT IS $roll\n"; } $roll = rolldice(); ##print "ROLL #$numrolls $roll\n"; if($numrolls == 1) { $point = $roll; if($roll == 7 || $roll == 11) { $gameover = 1; $winnings += 1; ##print "WIN $winnings\n\n"; } elsif($roll == 2 || $roll == 3 || $roll == 12) { $gameover = 1; $winnings -= 1; ##print "LOSE $winnings\n\n"; } } else { if($roll == $point) { $gameover = 1; if($roll == 6 || $roll == 8) { $gameover = 1; $winnings += 1.2 + 1; ##print "WIN $winnings\n\n"; } elsif($roll == 5 || $roll == 9) { $gameover = 1; $winnings += 1.5 + 1; ##print "WIN $winnings\n\n"; } elsif($roll == 4 || $roll == 10) { $gameover = 1; $winnings += 2 + 1; ##print "WIN $winnings\n\n"; } } elsif($roll == 7) { $gameover = 1; $winnings -= 2; ##print "LOSE $winnings\n\n"; } } } $winnings = sprintf "%.2f", $winnings; $netwinnings += $winnings; $netwinnings = sprintf "%.2f", $netwinnings; } $sth = $dbh->prepare("SELECT value,qty FROM craps WHERE value = $n +etwinnings"); $sth->execute(); ($value,$qty) = $sth->fetchrow_array(); ##print "\n\n$value - $netwinnings\n\n"; if($value == $netwinnings) { $sth = $dbh->prepare("UPDATE craps SET qty = qty + 1 WHERE val +ue = $netwinnings"); $sth->execute(); } else { $sth = $dbh->prepare("INSERT INTO craps VALUES($netwinnings,1) +"); $sth->execute(); } print "NET WIN $netwinnings\n"; } $dbh->disconnect; sub rolldice() { $d1 = random_uniform_integer(1,1,6); $d2 = random_uniform_integer(1,1,6); $roll = $d1 + $d2; return $roll; }
Re: check my logic & a random number issue
by BrowserUk (Patriarch) on Oct 11, 2002 at 17:43 UTC

    I've probably got the logic wrong somewhere, but if I haven't, it appears as though it might worthwhile (but not by much:^) taking up craps using a simple strategy and sticking to it.

    #! perl -sw use strict; use vars qw($runs $games $pot $bet); $runs ||= 100; $games ||= 100; $pot ||= 100; $bet ||= 1; #srand(1); #! for consistant results whilst testing. sub rolldice() { return int(1+rand(6)) + int(1+rand(6)); } #! 0 1 2 3 4 5 6 7 8 9 10 11 +12 my @passline = ( 0, 0, -1, -1, 0, 0, 0, +1, 0, 0, 0, +1, - +1 ); my @odds = ( 0, 0, 0, 0, 2, 1.5, 1.2, -1, 1.2, 1.5, 2, 0, 0 + ); my %results; for(1 .. $runs) { my $pot = $pot; #! Start with som money in the kitty for (1 .. $games) { my $bet = $bet; my $point = rolldice(); # print "pot:$pot bet:$bet point:$point"; <>; if (my $pays = $passline[$point]) { $pot += $bet * $pays; next; #! Game over } my $roll; do { $bet++ if ($pot-$bet); $roll = rolldice(); # print "pot:$pot bet:$bet roll:$roll"; <>; } until ($roll == $point or $roll == 7); $pot += int($bet * $odds[$roll]); #! Round $pot = 0,last if $pot <= 0; #! Can't continue if we've lost it + all } $results{$pot-$::pot}++; } print "Over $runs runs of up to $games games produced ", scalar keys % +results, " different outcomes\n"; my @outcomes = sort{$a<=>$b} keys %results; my ($start, $end) = ( 10*int($outcomes[0]/10), 10*(int($outcomes[-1]/1 +0)+1) ); my ($lost,$even,$won)= (0,0,0); print ' : 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 + | 9 |'; for (my $i=$start; $i <=$end; $i+=10 ) { printf "$/% 7.2f :", $i; for(my $result=$i; $result<=($i+9); $result++){ print ' -- |' and next unless exists $res +ults{$result}; printf '*%4d*|', $results{$result} and next if $result == -$po +t;; $lost += $results{$result} if $result < 0; $won += $results{$result} if $result > 0; printf '%4d |', $results{$result}; } } printf "\n\nBusted:%6.2f%% lost:%6.2f%% even:%6.2f%% won:%6.2f%%\n", $results{-$pot}*100/$runs, $lost*100/$runs, $results{0}*100/$runs, $won*100/$runs; __END__ c:\test>craps -runs=1000 -games=100 -pot=100 -bet=1 Over 1000 runs of up to 100 games produced 216 different outcomes : 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 + | 9 | -100.00 :* 43*| -- | 1 | 3 | 1 | 1 | 2 | 2 | 2 + | 1 | -90.00 : 2 | 1 | 5 | 2 | 2 | -- | 1 | 1 | 2 + | -- | -80.00 : 3 | -- | 2 | 1 | 1 | 1 | 5 | 3 | 3 + | 3 | -70.00 : 1 | 3 | -- | 3 | 5 | 2 | 5 | 7 | 1 + | 7 | -60.00 : 3 | 1 | 3 | 4 | 2 | 3 | 1 | 9 | 5 + | 1 | -50.00 : 4 | 3 | 10 | 7 | 6 | 7 | 6 | 8 | 12 + | 7 | -40.00 : 3 | 6 | 6 | 11 | 10 | 10 | -- | 7 | 8 + | 1 | -30.00 : 1 | 8 | 4 | 6 | 9 | 4 | 6 | 8 | 4 + | 8 | -20.00 : 7 | 7 | 7 | 4 | 6 | 3 | 6 | 7 | 7 + | 13 | -10.00 : 7 | 9 | 10 | 10 | 7 | 5 | 10 | 1 | 13 + | 9 | 0.00 : 9 | 4 | 7 | 11 | 8 | 6 | 4 | 8 | 10 + | 11 | 10.00 : 10 | 9 | 3 | 9 | 7 | 8 | 6 | 5 | 10 + | 6 | 20.00 : 9 | 9 | 9 | 7 | 10 | 3 | 4 | 4 | 10 + | 5 | 30.00 : 4 | 8 | 6 | 4 | 7 | 5 | 4 | 4 | 12 + | 2 | 40.00 : 3 | 2 | 5 | 4 | 11 | 3 | 3 | 2 | 4 + | 4 | 50.00 : 4 | 5 | 6 | 1 | 5 | 8 | 7 | 2 | -- + | 3 | 60.00 : 3 | 3 | 3 | 5 | 5 | 2 | 5 | 4 | -- + | 2 | 70.00 : 3 | 1 | 2 | 4 | 3 | 5 | 3 | 3 | 1 + | 3 | 80.00 : 2 | 2 | 1 | 5 | 1 | 4 | 1 | 3 | 3 + | 1 | 90.00 : 3 | 1 | 1 | 2 | 4 | 3 | 3 | 4 | -- + | 1 | 100.00 : 2 | 1 | 1 | 3 | 1 | -- | 2 | 2 | 2 + | -- | 110.00 : -- | 3 | -- | 1 | -- | -- | -- | -- | 1 + | 3 | 120.00 : -- | 1 | -- | 2 | 1 | 2 | 1 | 1 | 1 + | 1 | 130.00 : -- | -- | -- | -- | -- | 1 | -- | 1 | -- + | 1 | 140.00 : -- | -- | -- | -- | -- | 1 | -- | -- | -- + | 2 | 150.00 : -- | -- | -- | -- | -- | -- | -- | -- | -- + | -- | Busted: 4.30% lost: 45.50% even: 0.90% won: 49.30% c:\test>

    Cor! Like yer ring! ... HALO dammit! ... 'Ave it yer way! Hal-lo, Mister la-de-da. ... Like yer ring!

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others musing on the Monastery: (5)
As of 2024-04-19 21:11 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found