Craps
November 4, 2011
We play a single game of craps using the (crap) function:
(define (crap)
(define (dice) (+ (randint 1 7) (randint 1 7)))
(let loop ((roll (dice)) (point 0) (rolls '()))
(if (zero? point)
(cond ((member roll (2 3 12)) (list #f roll))
((member roll (7 11)) (list #t roll))
(else (loop (dice) roll (cons roll rolls))))
(cond ((= roll point) (cons #t (reverse (cons roll rolls))))
((= roll 7) (cons #f (reverse (cons roll rolls))))
(else (loop (dice) point (cons roll rolls)))))))
We use the (crap) function to generate a list of craps games, suitable for calculating statistics:
(define (craps n)
(do ((n n (- n 1)) (cs '() (cons (crap) cs))) ((zero? n) cs)))
Now, with some help from the Standard Prelude, we can calculate some statistics. Here is a histogram of the rolls seen in a thousand games:
> (map cdr (sort (lambda (a b) (< (car a) (car b)))
(uniq-c = (sort (length (filter car (craps 1000)))
(98 184 253 358 438 565 435 382 231 176 100)
About half of games are winners:
> (length (filter car (craps 1000)))
504
The average game is about three-and-a-half rolls of the dice. There is of course no theoretical maximum, but we found one game with 26 rolls:
> (- (/ (apply + (map length (craps 1000))) 1000) 1.0)
3.579
> (- (apply max (map length (craps 1000))) 1)
26
We used filter, flatten, uniq-c and randint from the Standard Prelude. You can run the program at http://programmingpraxis.codepad.org/J9Tg9jZP.
Tried my hand at a Common Lisp solution.
n<-50000 #number of games aroll<-NA win<-0 loss<-0 groll<-NA ploss<-0 pwin<-0 for (y in 1:n) { a<-sample(1:6,2,replace=T) roll<-a[1]+a[2] aroll<-c(aroll,a[1]+a[2]) numroll<-1 if ((roll==2)||(roll==3)||(roll==12)) { loss<-loss+1 } if ((roll==7)||(roll==11)) { win<-win+1 } z<-(roll==2)||(roll==3)||(roll==12)||(roll==7)||(roll==11) if (z==FALSE) { point<-roll repeat { a<-sample(1:6,2,replace=T) roll<-a[1]+a[2] aroll<-c(aroll,a[1]+a[2]) numroll<-numroll+1 if ((roll==7)) { loss<-loss+1 ploss<-ploss+1 break } if ((roll==point)) { win<-win+1 pwin<-pwin+1 break } } } groll<-c(groll,numroll) } print("MOST COMMON ROLL") arollt <- table(aroll) as.numeric(names(arollt[arollt == max(arollt)])) print("WIN PERCENTAGE") print(100*win/(win+loss)) print("AVERAGE NUMBER OF ROLLS PER GAME") groll<-groll[-1] mean(groll) print("MAXIMUM NUMBER of ROLLS IN A GAME") max(groll) print("Probability of Winning a Game with More than 1 Roll") print(100*pwin/(pwin+ploss))Wow, that might be the first R solution I’ve seen posted here. Makes sense, though, if you’re working with random variates and statistics. If there’s a way to vectorize the code, I don’t see it; so far the only thing I came up with was
roll <- sum(sample(1:6, 2, replace=T))(which isn’t very impressive).Graham, I think it does help though.. That solution is approaching novel lengths..
Do you guys see GDean’s R code as properly indented? For me it’s flush left in both Chromium and Firefox.
This happened to me previously even though I used the advertised sourcecode tags that used to work.
The HTML source for the R code is indented but the whitespace is not preserved.
Jussi: as far as indentation goes, I usually post longer solutions offsite (github, codepad, etc.) and for shorter ones follow the instructions in the HOWTO link at the top of this page.
I fixed the comment formatting. I changed < and " to their ascii equivalents, added a [sourcecodx lang=”css”] to the top of the code, and [/sourcecodx] at the bottom of the code (note that I purposely mangled the word sourcecodx to prevent WordPress from writing my comment incorrectly). Looks fine to me.
Jussi: Only a small number of languages are directly supported by the sourcecodx tag. You may have said something like [sourcecodx lang=”fortran”]. I just looked at the WordPress support page linked in the HOWTO, and it appears they have added more languages since I wrote the HOWTO, and also added a feature for “plain text” source code: just say [sourcecodx] with no language parameter. I just checked by editing GDean’s code — that works, too. It’s too bad WordPress doesn’t support the <pre> tag.
Graham: Unless the code is extremely long, it is better to have it here than elsewhere. In the first place it is more easily seen (I checked the stats — 110 people saw your comment, but only 9 clicked the link), and in the second place you can interrupt the code with annotations, as I frequently do in my suggested solutions.
Thank you, praxis, GDean’s R code looks good now. My unindented Python code is in the McCarthy thread. I said [sourcecodx lang=”python”] — [/sourcecodx] unless I mistyped something (clever “x”) and python is listed as supported in the “how topost source code” page.
Here, let me just try to see if it works:
def evaluate(exp, env): return ( exp if isconstant(exp) else lookup(exp, env) if isidentifier(exp) else error() if not isnonemptylist(exp) else call(exp, env) if not isidentifier(exp[0]) else special(exp, env) if isspecial(exp[0], env) else evaluate(expand(exp, env), env) if ismacroword(exp[0], env) else call(exp, env) )(Sorry about the off-topic code.)
Ok, looks good. Probably I have mistyped something when I submitted the McCarthy entry. Sorry for the noise.
I fixed your McCarthy entry.
It doesn’t look like you mistyped anything. I changed “lang” to “language” and it worked fine.
I’ve seen this problem before, and don’t know how to deal with it. Sometimes “lang” works, and sometimes “language” works. I have no idea why.
The workaround is probably to just ignore the language parameter and bracket your code with [sourcecodx] … [/sourcecodx] tags. You will lose the clever language-based formatting, but that should work with anything.
The other approach is to format your code for html using the “foo-to-html” translator on the HOWTO page. That’s what I do for all the code presented in the suggested solutions. It’s inconvenient, but makes the code more integral to the running text.
Private emails to me have one overwhelming complaint: readers can’t edit their own comments. I’ve considered moving from the WordPress comment forums to DISQUS, but haven’t done it because managing one blog is hard enough, and managing two things would doubtless be harder. Does anyone have experience with DISQUS?
The HOWTO page seems to suggest that HTML code tags work in this comment box. I just experimented with HTML style attributes, where
style="white-space: pre"should preserve all whitespace. It worked in my tests.If this comment box allows both the code tags and the attribute, the following test code appears properly indented without any special markup in the code itself:
(define (gcd m n)
(cond ((< m n) (gcd m (- n m)))
((< n m) (gcd (- m n) n))
(else m)))
If not, not.
I think it stripped the attribute out. Just one repeat to test that I did not forget to type it in. And why would it preserve linebreaks then? Also, I’m now replacing the less-than signs with the entity, which I did not do above.
(define (gcd m n)
(cond ((< m n) (gcd m (- n m)))
((< n m) (gcd (- m n) n))
Without the style attribute but with the non-breaking spaces suggested in the HOWTO, and nothing about linebreaks:
(define (gcd m n)
(cond ((< m n) (gcd m (- n m)))
((< n m) (gcd (- m n) n))
Ok, it seems like specifying the white-space style as pre or pre-wrap for the code element should work since the software recognizes the code tags. It does not preserve the style attribute, however, and I failed to find out how it manages to obey line breaks in code while ignoring indentations.
Wait. I did not fail to find out. It adds br-elements at line breaks inside code elements. It’s doing too much and too little at the same time. So we continue to indent the hard way. Ok.
When I first started the blog, I spent a day experimenting, as you have done. I’m pretty sure the choices are what I listed in the HOWTO, except that now WordPress seems to handle a [sourcecodx] . . . [/sourcecodx] without a language designator as plain text, which it did not do back then. I’ll add that to the HOWTO.
Thanks for trying,
Phil
Here’s some on-topic code, indented with sed as in the HOWTO page.
The rules of the game are encoded in a generator that produces one
game.
>>> seed(exp(1))
>>> list(game())
[(6, None), (9, None), (4, None), (4, None), (6, 'win')]
The log odds (meant to be to the base that Turing and Good used)
thing is sometimes positive, sometime negative, which seems to
suggest that the game is more or less fair.
>>> logodds(100)
0.17374096069422723
>>> logodds(100)
-0.6963592814139442
This is Python 3. The magic keyword is
yield. A couple of the generator things below produce indefinitely long sequences. In theory,gamecould do so but in practice it of course never does. It is safe to ask for the next game, or the next grade, or the 100 next games as above.from random import seed, randrange
from math import exp, log10
from itertools import islice
def die():
return 1 + randrange(6)
def game(): # graded shots of a single game
shot = die() + die()
grade = ( "loss" if shot in (2, 3, 12)
else "win" if shot in (7, 11)
else None )
yield shot, grade
point = shot
while not grade:
shot = die() + die()
grade = ( "win" if shot == point
else "loss" if shot == 7
else None )
yield shot, grade
def games(): # graded games, indefinitely
while True:
shots = list(game())
yield ( [ shot for shot, grade in shots ],
shots[-1][1] )
def grades(): # grades, indefinitely
for shots, grade in games():
yield grade
def logodds(n): # 10 log10 (wins / losses) in n games
iswin = lambda g : g == "win"
wins = list(filter(iswin, islice(grades(), n)))
return 10 * log10(len(wins) / (n - len(wins)))
This can be done in Scheme but it would be a much longer story. Python has these built-in.
Since we’re discussing it, and just to put us back off topic again (sorry), I would like to point out that perl is another supported language by whatever is handling the attribute.
The HOWTO seems to leave it out, but I’ve posted several perl sources this way
I agree with Jussi on the usefulness of
yieldand generators in Python; if I have time, I’ll post a Python version myself later.A Clojure solution:
(defn dice [] (+ (inc (rand-int 6)) (inc (rand-int 6)))) (def win? #{7 11}) (def lose? #{2 3 12}) (defn game [] (loop [rolls 1 result (dice)] (cond (win? result) (list true rolls) (lose? result) (list false rolls) :else (recur (inc rolls) (dice))))) (defn games [n] (loop [i 0 results ()] (if (= i n) results (recur (inc i) (conj results (game)))))) (defn stats [results] (let [rolls (map #(second %1) results) ngames (count results)] (println "Wins:" (count (filter #(first %1) results)) "/" ngames) (println "Avg rolls:" (/ (double (apply + rolls)) ngames)) (println "Max rolls:" (reduce max rolls)))) (stats (games 1000))