;;; martingale.ss -- three systems for betting at roulette ;;; John David Stone ;;; Department of Mathematics and Computer Science ;;; Grinnell College ;;; stone@math.grin.edu ;;; Created February 5, 1998 ;;; Last revised February 9, 1998 ;;; Roulette is a game of chance in which a small ivory ball is dropped ;;; onto a wheel, mounted horizontally in a stationary table and rotating ;;; around a vertical axis. Near the rim of the wheel, the circumference ;;; is divided by short metal partitions into thirty-seven (sometimes ;;; thirty-eight) equal parts. The wheel is not quite flat, but slopes ;;; downwards slightly from the center; in addition, the rim sections ;;; separated by the metal partitions are slightly recessed, to form small ;;; pockets, each labelled with a different number and colored either red, ;;; black, or green. When the ball is dropped onto the wheel, it bounces ;;; around irregularly for a while before settling into one of these ;;; pockets. Players bet on which pocket the ball will end up in, whether ;;; the label for that pocket is an even or an odd number, whether it is ;;; colored red or black, and so on. ;;; Roulette is often played in casinos, with a hired professional (the ;;; ``croupier'') at the table to spin the wheel, drop the ball onto it, ;;; announce the results, collect the losing bets, and pay off on the ;;; winning ones. The casinos are able to provide this service and still ;;; make money, because they permit the player to make only bets that have ;;; a negative ``mathematical expectation'' -- on the average, the player ;;; receives less in winnings than she pays out in losses. The casino ;;; collects the difference. ;;; For instance, suppose a player bets twenty dollars that the ball will ;;; settle into a red pocket. There are eighteen red pockets on the wheel, ;;; so her chance of winning is 18/37 (or 18/38 if an extra pocket is ;;; used). If she wins, she gets to keep her original bet, and the ;;; croupier gives her twenty dollars more -- an amount equal to her ;;; original bet. If she loses, the croupier takes her original bet, so ;;; she is down twenty dollars. Her mathematical expectation is ($20 * ;;; 18/37) - ($20 * 19/37), or -$0.54; over a long series of such ;;; twenty-dollar bets, she will, on the average, lose money at the rate of ;;; about fifty-four cents per bet. (If a thirty-eight-pocket wheel is ;;; used, she will lose about $1.05 per twenty-dollar bet. Some European ;;; casinos have an additional rule that complicates the calculation: If ;;; the ball lands in the pocket numbered 0, the player neither wins nor ;;; loses; instead, the original bet is ``imprisoned'' until the next spin ;;; of the wheel. If the ball lands in a red pocket the next time, the ;;; original bet is returned to the player, though without any additional ;;; payoff; if it lands in 0 again, the bet remains ``imprisoned''; and ;;; otherwise the croupier takes the original bet. In this case the ;;; mathematical expectation is about -$0.27 per twenty-dollar bet.) ;;; The procedures that make up this program permit the user to simulate a ;;; session of play at roulette, under casino rules, using any of three ;;; well-known and widely used betting systems: the martingale, the great ;;; martingale, and the cancellation method. ;;; (Important note: These systems do not work, as you will quickly ;;; discover if you run the simulation. If you play roulette by casino ;;; rules, you will probably lose money no matter what strategy or system ;;; you adopt.) ;;; The SPIN procedure simulates the operation of the roulette wheel; it ;;; returns a number in the range from 0 to 36, each number about 1/37 of ;;; the time. (define spin (lambda () (random 37))) ;;; The COLOR-OF-POCKET procedure takes a number in the range from 0 to 36 ;;; and returns the color of the pocket on a roulette wheel that is ;;; labelled with that number. Only the pocket numbered 0 is green. (define color-of-number (lambda (number) (cond ((member number '(1 3 5 7 9 12 14 16 18 19 21 23 25 27 30 32 34 36)) 'red) ((member number '(2 4 6 8 10 11 13 15 17 20 22 24 26 28 29 31 33 35)) 'black) (else 'green)))) ;;; A player using the martingale begins by wagering a minimum amount on ;;; one of the bets that pays off at even money: red, black, even, or odd. ;;; (It makes no difference which of these bets one places; advocates of ;;; the system seem to prefer the color bets, so we'll assume that the ;;; player always bets on red.) If she wins, she wagers the minimum amount ;;; again on the next spin of the wheel; if she loses, however, she doubles ;;; the amount on the next spin, in order to recover the amount lost. If ;;; she loses on the next spin as well, she doubles the amount of the wager ;;; again (to four times the original stake) for the spin after that, and ;;; so on, doubling the stake after each loss. As soon as she wins, she ;;; will have recovered all of the money she has lost; moreover, she will ;;; have won an amount equal to the original minimum bet. On the spin ;;; after the win, she starts all over again with the minimum bet. ;;; Very occasionally, after several losses, the martingale may require the ;;; player to increase the stake to an amount that exceeds either her ;;; entire remaining bankroll or the casino's limit on the maximum size of ;;; a bet. In such cases, most players bet as much as they can and accept ;;; the resulting loss as a necessary accommodation to the circumstances of ;;; the real world, rather than seeing it as a defect of the system. (In ;;; fact, however, the player is more likely to go broke as a result of ;;; this limitation than to win an amount equal to her original bankroll by ;;; a succession of ``normal'' runs. Running up against the casino's limit ;;; is a rare event, but not rare enough to compensate for the large loss ;;; one sustains when it happens.) ;;; The minimum and maximum betting limits are determined by the casino and ;;; are essentially constant as far as the player is concerned; here are ;;; some typical values. (define minimum-bet 20) ; in American dollars (define maximum-bet 3000) ; in American dollars ;;; The MARTINGALE procedure takes as its arguments the size of the ;;; player's original bankroll (in American dollars) and the maximum number ;;; of spins that she can play without collapsing in exhaustion. It ;;; simulates the use of the martingale system until either the casino has ;;; all of the player's money or the specified number of spins has been ;;; reached. It returns the size of the player's bankroll at the end of ;;; the simulation. ;;; MARTINGALE itself is the husk of a husk-and-kernel pair, mainly devoted ;;; to precondition testing; the actual simulation is done by the procedure ;;; that follows it, MARTINGALE-KERNEL. (define martingale (lambda (bankroll spins-remaining) ;; Make sure that BANKROLL is a positive integer. (if (or (not (integer? bankroll)) (not (positive? bankroll))) (error 'martingale "The bankroll must be given as a positive integer")) ;; Make sure that SPINS-REMAINING is a non-negative integer. (if (or (not (integer? spins-remaining)) (negative? spins-remaining)) (error 'martingale (string-append "The number of spins remaining must be " "given as a non-negative integer"))) ;; Call the MARTINGALE-KERNEL procedure to simulate the play, starting ;; with a minimum bet on the first spin. (martingale-kernel bankroll minimum-bet 0 spins-remaining))) ;;; The MARTINGALE-KERNEL procedure carries out the actual simulation, spin ;;; by spin, once the initial arguments have been validated. (define martingale-kernel (lambda (bankroll stake spins-so-far spins-remaining) (cond ((zero? bankroll) ;; In this case, the player has no money left; give up. (display "The player went broke after ") (display spins-so-far) (display " spin") (if (not (= 1 spins-so-far)) (display "s")) (display ".") (newline) bankroll) ((zero? spins-remaining) ;; The number of spins specified in the original call to ;; MARTINGALE has been simulated; interrupt play. (display "The game is over.") (newline) bankroll) ((eq? 'red (color-of-number (spin))) ;; The player wins; add the current stake to the bankroll and ;; start over with a new minimum bet. (martingale-kernel (+ bankroll stake) minimum-bet (+ spins-so-far 1) (- spins-remaining 1))) (else ;; The player loses; subtract the current stake from the ;; bankroll and double the stake for the next round if possible. (martingale-kernel (- bankroll stake) (min (* 2 stake) ; doubled stake (- bankroll stake) ; left in bankroll maximum-bet) ; casino limit (+ spins-so-far 1) (- spins-remaining 1)))))) ;;; To start the simulation, call the MARTINGALE procedure, specifying the ;;; number of American dollars you're prepared to wager and the number of ;;; rounds you're prepared to play. A typical interaction looks like this: ;;; ;;; > (martingale 12500 10000) ;;; The player went broke after 3222 spins. ;;; 0 ;;; ;;; This simulated player managed to lose $12500 during the first third of ;;; his holiday in Monte Carlo. The record of a more successful player ;;; looks like this: ;;; ;;; > (martingale 12500 10000) ;;; The game is over. ;;; 12460 ;;; ;;; This simulated player made it to the end of her ten thousand spins of ;;; the wheel while losing only $40 -- a spectacularly good result. ;;; ===== ;;; If you've reached this point in the reading, you're done with step 2. ;;; ===== ;;; Let's move on now to the ``great martingale.'' In the view of the ;;; advocates of this system, the problem with the martingale is that ;;; merely doubling the size of one's bet after a loss does not provide ;;; enough compensation; the player needs not only to recoup the loss that ;;; he actually sustained, but also the amount that he would have won if he ;;; had been luckier in those earlier rounds. The great martingale ;;; rectifies this mistake by adding the payoff for the ``missing win'' -- ;;; the size of the minimum bet -- to the result of doubling the previous ;;; bet after a loss. If the minimum bet is again $20, the ;;; great-martingale player who loses on the first spin bets $60 (= 2 * $20 ;;; + $20) on the next one; if this too loses, his next bet is $140 (= 2 * ;;; $60 + $20), then $300, and so on. ;;; Here are the procedures for simulating the great martingale: (define great-martingale (lambda (bankroll spins-remaining) ;; Make sure that BANKROLL is a positive integer. (if (or (not (integer? bankroll)) (not (positive? bankroll))) (error 'great-martingale "The bankroll must be given as a positive integer")) ;; Make sure that SPINS-REMAINING is a non-negative integer. (if (or (not (integer? spins-remaining)) (negative? spins-remaining)) (error 'great-martingale (string-append "The number of spins remaining must be " "given as a non-negative integer"))) ;; Call the GREAT-KERNEL procedure to simulate the play, starting with ;; a minimum bet on the first spin. (great-kernel bankroll minimum-bet 0 spins-remaining))) ;;; The GREAT-KERNEL procedure carries out the actual simulation, spin by ;;; spin, once the initial arguments have been validated. (define great-kernel (lambda (bankroll stake spins-so-far spins-remaining) (cond ((zero? bankroll) ;; In this case, the player has no money left; give up. (display "The player went broke after ") (display spins-so-far) (display " spin") (if (not (= 1 spins-so-far)) (display "s")) (display ".") (newline) bankroll) ((zero? spins-remaining) ;; The number of spins specified in the original call to ;; GREAT-MARTINGALE has been simulated; interrupt play. (display "The game is over.") (newline) bankroll) ((eq? 'red (color-of-number (spin))) ;; The player wins; add the current stake to the bankroll and ;; start over with a new minimum bet. (great-kernel (+ bankroll stake) minimum-bet (+ spins-so-far 1) (- spins-remaining 1))) (else ;; The player loses; subtract the current stake from the ;; bankroll, double the stake for the next round and add in the ;; minimum bet once more if possible. (great-kernel (- bankroll stake) (min (*-REPLACE-THIS-WITH-THE-CORRECT-EXPRESSION-*) ; doubled stake (and then some) (- bankroll stake) ; left in bankroll maximum-bet) ; casino limit (+ spins-so-far 1) (- spins-remaining 1)))))) ;;; The cancellation system is another variation on the same idea. The ;;; player starts out by making a list of the first ten multiples of the ;;; minimum bet, in descending order -- in this case, (200 180 160 140 120 ;;; 100 80 60 40 20). On each spin of the wheel, the player adds the first ;;; and last elements of the list and places a bet for the sum. If she ;;; wins, the two addends are struck off the list; those items are now ;;; ``firmly won''. If the player loses, she adds the amount lost to the ;;; beginning of the list as a new element, which must be won back on some ;;; subsequent round. ;;; When the list is reduced to one element, that element is the size of ;;; the next bet. When all the items on the original list have been ;;; ``firmly won'' and struck off the list, the player starts over with a ;;; fresh copy of the first ten multiples of the original bet. ;;; Here are the procedures for simulating the cancellation system. Note ;;; that the second parameter of the CANCELLATION-KERNEL procedure keeps ;;; track of the player's list of amounts to be won. (define cancellation (lambda (bankroll spins-remaining) ;; Make sure that BANKROLL is a positive integer. (if (or (not (integer? bankroll)) (not (positive? bankroll))) (error 'cancellation "The bankroll must be given as a positive integer")) ;; Make sure that SPINS-REMAINING is a non-negative integer. (if (or (not (integer? spins-remaining)) (negative? spins-remaining)) (error 'cancellation (string-append "The number of spins remaining must be " "given as a non-negative integer"))) ;; Call the CANCELLATION-KERNEL procedure to simulate the play, ;; starting with a minimum bet on the first spin. (cancellation-kernel bankroll (descending-multiples minimum-bet 10) 0 spins-remaining))) ;;; The CANCELLATION-KERNEL procedure carries out the actual simulation, ;;; spin by spin, once the initial arguments have been validated. (define cancellation-kernel (lambda (bankroll plan spins-so-far spins-remaining) ;; The following expressions print out a report about the state of the ;; player's bankroll and of the plan list, enabling the user to observe ;; the simulation spin by spin. To activate them, remove the ;; semicolons. ;; (display "Bankroll = ") ;; (display bankroll) ;; (display ", plan = ") ;; (display plan) ;; (newline) ;; The simulation proper begins here. (cond ((zero? bankroll) ;; In this case, the player has no money left; give up. (display "The player went broke after ") (display spins-so-far) (display " spin") (if (not (= 1 spins-so-far)) (display "s")) (display ".") (newline) bankroll) ((zero? spins-remaining) ;; The number of spins specified in the original call to ;; CANCELLATION has been simulated; interrupt play. (display "The game is over.") (newline) bankroll) ((eq? 'red (color-of-number (spin))) ;; The player wins; add the current stake to the bankroll and ;; strike out the first and last elements of the PLAN list, ;; replacing it with a new copy of the original PLAN if it ;; becomes empty. (cancellation-kernel (+ bankroll (amount-to-bet plan bankroll)) (all-but-first-and-last plan) (+ spins-so-far 1) (- spins-remaining 1))) (else ;; The player loses; subtract the current stake from the ;; bankroll and double the stake for the next round if possible. (cancellation-kernel (- bankroll (amount-to-bet plan bankroll)) (cons (amount-to-bet plan bankroll) plan) (+ spins-so-far 1) (- spins-remaining 1)))))) ;; The DESCENDING-MULTIPLES procedure takes two arguments, BASE and LENGTH, ;; and returns a list consisting of the first LENGTH multiples of BASE, in ;; descending numerical order. The preconditions of this procedure are ;; that BASE is a positive real number and LENGTH a non-negative integer. (define descending-multiples (lambda (base length) ;; Make sure that BASE is a positive real number. (if (or (not (real? base)) (not (positive? base))) (error 'descending-multiples "The base must be a positive real number")) ;; Make sure that LENGTH is a non-negative integer. (if (or (not (integer? length)) (negative? length)) (error 'descending-multiples "The length must be a non-negative integer")) ;; Call a kernel procedure to construct the actual list. (descending-multiples-kernel base length))) ;;; The DESCENDING-MULTIPLES-KERNEL procedure carries out the recursion by ;;; which the list of multiples is actually constructed, once the arguments ;;; have been validated. (define descending-multiples-kernel (lambda (base length) ;; You get to write this one. Fortunately, it follows a pattern that ;; we've seen several times already. (*-REPLACE-ME-*) )) ;;; The AMOUNT-TO-BET procedure takes the current ``plan list'' and the ;;; size of the player's bankroll as arguments and determines how much the ;;; player should wager on the outcome of the current spin. (define amount-to-bet (lambda (plan bankroll) ;; Make sure that PLAN is a non-empty list of positive integers. (if (or (null? plan) (not (list-of-positive-integers? plan))) (error 'amount-to-bet "The ``plan list'' must be a non-empty list")) ;; Make sure that BANKROLL is a positive integer. (if (or (not (integer? bankroll)) (not (positive? bankroll))) (error 'amount-to-bet "The bankroll must be a positive integer")) ;; If there is only one element in PLAN, that element is the bet ;; prescribed by cancellation; otherwise, the prescribed bet is the sum ;; of the first and last elements. Return the prescribed bet, the size ;; of the current bankroll, or the maximum bet permitted by the casino, ;; whichever is least. (min (if (null? (cdr plan)) (car plan) (+ (car plan) (last plan))) bankroll maximum-bet))) ;;; The LIST-OF-POSITIVE-INTEGERS? predicate determines whether its ;;; argument is a (possibly empty) list in which each element is a positive ;;; integer. (define list-of-positive-integers? (lambda (object) (or (null? object) (and (pair? object) (integer? (car object)) (positive? (car object)) (list-of-positive-integers? (cdr object)))))) ;;; The LAST procedure finds and returns the last element of a non-empty ;;; list. This version does no precondition testing, because it is invoked ;;; only recursively and by the AMOUNT-TO-BET procedure; in both contexts, ;;; the precondition is known to be met. (define last (lambda (ls) ;; If you have read the textbook carefully, you'll recall that this ;; procedure is one of the early examples, so you may be able to look ;; it up. However, it's not difficult to rewrite it from scratch if ;; you need to. (*-REPLACE-ME-*) )) ;;; The ALL-BUT-FIRST-AND-LAST procedure takes a non-empty list as ;;; argument. If this list has exactly one or exactly two elements, the ;;; procedure constructs and returns a copy of the original ``plan list'' ;;; -- namely, the first ten multiples of the minimum bet, in descending ;;; order. Otherwise, this procedure returns a list exactly like the one ;;; it is given, except that the first and last elements have been removed. (define all-but-first-and-last (lambda (ls) (if (or (null? (cdr ls)) (null? (cddr ls))) (descending-multiples minimum-bet 10) (all-but-last (cdr ls))))) ;;; The ALL-BUT-LAST procedure takes a non-empty list as argument and ;;; returns a near-copy of that list, differing only in that it lacks the ;;; last element. (define all-but-last (lambda (ls) ;; Again, you get to write this one. (*-REPLACE-ME-*) ))