Introduction
Overview
This a detailed description a simulator for casino games. It allows someone to model
different player bettting strategies for common games. It gathers some
statistics that I find useful for comparing the betting strategies.
Introduction
This document describes the Architecture of the
simulator in general. The Architecture section has a roadmap through the various
modules. This document reviews the Design of each
module and class in detail. It also lists some Extensions
that can be applied to this simulator to create more value.
Technology
This simulator is a Python
application. You’ll need the Python interpreter appropriate for your
platform. This has been tested under Windows 98 and MacOS 8.x. I have
complete confidence that the various ports of Python are of such high quality
that this will run everywhere.
A quick note on the use of an interpreter. The “slowness” factor is
more than offset by the leverage from garbage collection, built-in data structure
features, reliability and debugging power.
A quick note on Python. While Python programs don’t have the robust
compile-time checking available in Java, they do permit a quick assembly of a
working application. For a reliable, robust, high-performance application,
Java may be a better choice. For a simulation like this, however, Python
permits rapid implementation.
The diagrams were produced with Visio 2000 in a few minutes; very little care
was taken to make them complete or easy-to-read. They were exported as JPEG’s,
by Visio, again, as rapidly as possible.
This document was done in BBEdit 5.
References
The basic rules and some of the player strategies are stolen without any
permission of any kind from The Wizard of Odds.
His site is a brilliant analysis of casino games.
Design
driver
The driver module is shown in the following static structure diagram.
The driver uses classes from the simulator module and subclasses of the casino
module to run a simulation.
-
driver.main()
- Decodes the command-line parameters.
-
driver.process()
- Requires a specific game name, a number
of samples to create, a limit on the number of rounds a player will play and a
list of player names; optionally a report file. It is a kind of Factory,
and decodes the game name to create the required GameTable, Game, and
PlayerFactory. It uses the PlayerFactory and list of player names to create
the actual pool of Player objects that will play the game. It creates an
Analyzer to hold raw data and do reporting. It creates a Simulator to
create the required samples, given the Game and Players.
Source: driver.
simulator
The simulator module is shown in the following static structure diagram.
A Simulator is associated with an Analyzer. The simulator appends
samples to an analyzer. The simulator requires a Player and a Game; it runs
the game with the player for the required number of samples.
An Analyzer collects raw data; creates summary statistics or frequency
distributions; and writes the final report.
A Statistic keeps a specific statistic for each named sample set. Typical
statistics include final stake. Sample sets are usually each player
strategy being simulated.
A Distribution is a subclass of statistic. It keeps a set of values are
broken into buckets to create a frequency distribution for each named sample set.
Source: simulator.
-
class simulator.Simulator
-
__init__(self, players, game, analyzer)
- Initialize this simulator with a specific set of Player instances, a
CasinoGame instance, an Analyzer instance and a Simlog instance.
-
oneRound(self)
- Run the Game for a single round of play
-
runSession(self)
- Run the Game for a player’s session - max rounds or out of money
-
sample(self, games, player)
- Collect statistics for a number of samples of a given player
-
generate(self, games, rounds= 100)
- Generate a database of stats for a number of players
-
performance(self)
- Games per second
-
class simulator.Statistic
-
value(self, sample, value)
- Save the value with the given sample name
-
report(self, samples)
- For the given set of sample names, report each sample of this statistic
as a tab-delimited string.
-
class simulator.Distribution
-
value(self, sample, value)
- Break the set of values into buckets and save with the given sample name
-
report(self, samples)
- For the given set of sample names, report each sample of this
distribution as a comma-delimited string.
-
reportCell(self, index, samples)
- For a given set of sample names and a bucket of the distribution, report
each sample of this distribution as a tab-delimited string.
-
class simulator.Analyzer
-
outcome(self, outMinMaxDoc)
- Given the tuple (final, minStake, maxStake, doc), log the outcome
-
report(self, file= sys.stdout)
- Report the collected data showing all samples for all statistics
The following statistics are analyzed. The finals are the final stakes
after the number of rounds of play (or bankruptcy). The minimums are the
lowest stake recorded during play. The maximums are the highest stake
recorded during play.
- “avg final” - average of final stake samples
- “sdv final” - standard deviation of final stake samples
- “min final” - minimum final stake sample
- “max final” - maximum final stake sample
- “avg minimum” - average of the minimum stakes recorded during play
- “min minimum” - lowest of the minimum stakes recorded during play
- “avg maximum” - average of the maximum stakes recorded during play
- “max maximum” - highest of the maximum stakes recorded during play
- “dist final” - distribution of final outcomes
- “dist minimum” - distribution of minimum stakes recorded during play
casino
The casino module is shown in the following static structure diagram.
A GameTable holds the population of Bets for a CasinoGame.
A Bet is a specific proposition, optionally with an amount wagered on that
proposition by a Player. If the bet wins, the player’s stake
increases. If the bet loses the player’s stake decreases.
A CasinoGame contains the mechanics of play for the game. A round of play
will present game state and wagering opportunities to the player. The player
responds with an instance of Command, which either makes a play choice (e.g.,
hit or stand in blackjack) or places a bet.
A Player is a betting strategy. Each game simulation can provide multiple
subclasses of Player to represent different betting strategies.
A PlayerFactory must be subclassed to create the various Player subclasses from
string names. This allows a list of strings to represent a set of player
strategies. A generic simulator can use an instance of a subclass of
PlayerFactory to generate the Players for a Game.
Source: casino.
-
class casino.Bet
-
__init__(self, name, odds)
- Define a bet as a name, an array of payout odds tuples and an instance of
the Simlog. The odds array has the form { roll : (lose,win), ... } for each
roll. The default (or only) roll value must be zero. Where there are
alternative winning payouts based on roll, this index is used by the win method
to select the payout odds.
-
place(self, player, amount)
- Place a specific amount on this bet
-
wins(self, roll)
- The bet is a winner, pay it; computing odds for this bet based on the
“roll”. For Craps the roll is used to simplify describing the bets.
For example, a “Pass Line Odds” bet is really “Pass Line Odds Point 4 or 10”,
“Pass Line Odds Point 5 or 9”, “Pass Line Odds Point 6 or 8”. Rather than
track each bet separately, these are collectively as “Pass Line Odds” with
different payouts depending on the winning roll. In Blackjack, this is used
to pay blackjack (21) at 2:1, and other wins at 1:1. In Caribbean Stud
poker, it allows paying the raise depending on the hand.
-
lose(self)
- The bet is a loser, reduce the player’s stake
-
push(self)
- The bet is a push - the money is returned to the player
-
working(self)
- Is this bet working?
-
clear(self)
- Clear the bet, this is part of moving a bet; usually used to move “Come”
bets when a point is established.
-
class casino.GameTable
-
clearAll(self, player)
- clear all bets and establish a player betting strategy object for this
table.
-
place(self, betName, amount)
- Place a specific bet with a specific amount
-
move(self, fromName, toName)
- Move bet from one place to another, typically used in Craps to move Come
Bet to a Point
-
combine(self, fromName, toName)
- Combine one bet’s amount into abother, typically used in Blackjack to
handle a double-down
-
win(self, betName, roll)
- this bet is a winner (delegates work to the bet)
-
lose(self, betName)
- this bet is a pitiful loser (delegates work to the bet)
-
push(self, betName)
- this bet is a push (delegates work to the bet)
-
working(self, betName)
- is this bet working? (delegates work to the bet)
-
state(self)
- return the current table state: a list of bets working
-
class casino.CasinoGame
-
seatPlayer(self, player)
- associate a player with the game, reset all bets on the table
-
state(self)
- return a tuple with the current game state
-
place(self, bet, amount)
- place the named bet and amount (delegates work to the game table)
-
working(self, bet)
- is this bet working? (delegates work to the game table)
-
play(self)
- play 1 round of this game, offering betting opportunities to the seated
player. Calls player’s startRound, inRound and endRound methods repeatedly
to start the game, offering in-game betting and play opportunities, and endRound
for any cleanup required.
-
class casino.Player
-
__doc__
- “short description of the betting strategy” - this is used as sample
identification for Statistics created by an Analyzer
-
__init__(self, limit)
- Initialize the player with a specific initial stake (or betting limit).
-
start(self, rounds)
- Initialize this player prior to running the game for a finite number of
rounds to gather a sample.
-
def status( self )
- Current status of the player.
-
name(self)
- Return the __doc__ string as the name of this player; subclasses will
override this if the __doc__ is not a complete description of the strategy.
-
canPlay(self)
- Is the player’s stake still positive and is the number of rounds left
still positive?
-
startRound(self, game)
- Return a command to place any start-of-round bets - ante in card games or place pass-line (or
don’t pass) bets for craps or roulette
-
inRound(self, game)
- Return a command to place in-round bets - raise, double or split in card games, come bets in
craps. No inRound bets in roulette.
-
endRound(self, game)
- Check table state or game state if the player that keeps history
-
wins(self, win)
- Add the win amount to the player’s stake.
-
lose(self, amount)
- Remove the loss amount from the player’s stake.
-
finalNet(self)
- return final stake less original stake; this is the net outcome of the
game
-
minNet(self)
- return the minimum stake recorded during play less original stake; this
is the lowest point of the game
-
maxNet(self)
- return the maximum stake recorded during play less original stake; this
is the highest point of the game
-
class casino.PlayerFactory
-
newPlayer(self, name)
- Return a Player instance with the given name
-
playerSet(self, names)
- Given a list of player names, map the newPlayer method, returning a list
of Player instances.
-
class casino.Command
-
do(self, game)
- Abstract method for placing bet or making play.
-
class casino.CommandBet
-
do(self, game)
- Place this bet (using game.place).
-
class casino.CommandSequence
-
addCommand(self, command)
- Add the given command to this sequence; this is used in Roulette or Craps to place
a number of bets simultaneously.
-
do(self, game)
Perform the do method of each command in this sequence.
cards
The Cards module is shown in the following static structure diagram.
A Card is a realtively lightweight object, it has a rank, suit and a number of
points. Poker cards have points from 2 to King (13) and Ace (14).
Blackjack cards have points from 2 to 10, J, Q, K (all 10), and Ace (11).
The Ace can also be one, depending on the hand.
A Deck is a set of cards. A standard Deck instance is 52 cards, created
using the Card Factory to get the right subclass of cards for the deck.
A Shoe is a subclass of Deck with multiple standard decks.
A Hand is a set of cards which can be scored. Scoring a blackjack hand
involves interpreting the Aces as 1’s or 11’s. Scoring a Poker hand
involves determining wether the hand is a straight flush, four of a kind,
straight, flush, full-house, three of a kind, two pair, pair, ace-king or stiff.
Source: ` card <../../_static/gamesim/cards.py>`_.
-
class cards.Card
- Card is a simplistic structure, with overrides for __str__, __cmp__ and
__rcmp__. This makes an object with attributes that can be transformed into
a string, and compared for purposes of sorting and equality testing.
-
class cards.PokerCard(Card)
- Subclass of Card; this adds a points attribute with values from 2 through Ace
(14)
-
class cards.BJCard(Card)
- Subclass of Card; this adds a points attribute with values from Ace (1)
through Ace (11), with 10 and face cards all being 10.
-
class cards.CardFactory
-
__init__(self, game)
- Initialize this factory to create cards of the appropriate subclass
(PokerCard or BJCard) of Card.
-
newCard(self, suit, rank)
- Return a new card of the given subclass (PokerCard or BJCard).
-
class cards.Deck
-
__init__(self, game)
- Initialize the deck to create cards of the appropriate subclass
(PokerCard or BJCard) or Card. Create the 52 cards of a standard deck.
-
shuffle(self)
- Shuffle the deck prior to play.
-
isMore(self)
- Are there undealt cards?
-
next(self)
- Return the next card from the deck.
-
class cards.PokerShoe(Deck)
Since this is a subclass of Deck, it inherits shuffle, isMore and next for
shuffling and dealing.
-
__init__(self, decks)
- Create a Deck, extending it with additional Decks.
-
burn(self)
- “Burn” a card somewhere in the last deck; this is the limit on dealing.
-
class cards.BJShoe(Deck)
Since this is a subclass of Deck, it inherits shuffle, isMore and next for
shuffling and dealing.
-
__init__(self, decks)
- Create a Deck, extending it with additional Decks.
-
burn(self)
- “Burn” a card somewhere in the last deck; this is the limit on dealing.
-
class cards.Hand
-
reset(self)
- Clear out the hand
-
addCard(self, card)
- Append a card to the hand
-
count(self)
- Count the number of cards in the hand.
-
card(self, number)
- Return a specific card
-
contains(self, rank)
- Does the hand contain a card of the given rank?
-
class cards.PokerHand(Hand)
-
flush(self)
- return 1 if this hand is a flush
-
straight(self)
- return 1 if this hand is a straight
-
royal(self)
- return 1 if this hand is a straight 10-J-Q-K-A
-
highestCard(self)
- return the highest ranking card (for tie-breaking)
-
hasRank(self, rank)
- locate another card with this rank (or return None)
-
hand(self)
- Score this hand, returning a tuple with (“name”, level, highestCard )
-
class cards.BJHand(Hand)
-
points(self)
- return the total number of points, changing the valuation of aces from 11
to 1 when necessary.
-
split(self)
- Split the hand; this hand becomes a 1-card hand, the hand which is
returned is another 1-card hand.
simlog
The Simlog module is shown in the following static structure diagram.
Source: simlog.
-
class simlog.Simlog
This is a singleton class - only one instance is ever needed.
-
instance()
- returns the one and only instance of this class.
-
summary()
- disables all logging.
-
detail()
- enables all logging.
-
watch()
- takes a list of search-pattern strings and watches
messages that match the search patterns.
-
msg()
- takes a message name and message payload. If
detail() was called and all messages are logged or if the message matches one of
the watch patterns, the payload is printed.
sim1 (Craps)
The Sim1 (Craps) module is shown in the following static structure diagram.
The elements of the Craps game are subclasses of general Casino classes.
Source: sim1.
-
class sim1.CrapsTable(casino.GameTable)
- CrapsTable is a subclass of casino.GameTable. It
contains a domain of all Bets.
-
class sim1.CrapsGame(casino.CasinoGame)
CrapsGame is a subclass of casino.CasinoGame. It
overrides state to return a state tuple of ( “state”, point, ( d1, d2 ) ).
The state is either “come out” or “point”. For a state of “come out” the
point is zero, otherwise the point is the point required to win the round.
The (d1,d2) tuple is the dice just rolled.
The game state is an instance of CrapsState. There are two subclass to
reflect the state change from come out roll to point roll.
-
class sim1.CrapsState
-
__init__(self, point= 0)
- Initialize the game state with a specific point; 0 indicates a come out
roll.
-
evalDice(self, game, d1, d2)
- Determine if the round is over; calling back to the Game as various bets
win or lose.
-
class sim1.CrapsStateComeOutRoll(CrapsState)
- Lose on 2, 3, 12. Win on 7 or 11. Change state to
CrapsStatePointRoll on any other roll. Pay 1-roll propositions.
-
class sim1.CrapsStatePointRoll(CrapsState)
- Lose on 7, win in point being rolled; change state to
CrapsStateComeOutRoll. No action on 2, 3, 11, 12. Pay 1-roll
propositions.
-
class sim1.CrapsPlayer(casino.Player)
The game is played by offering a startRound betting opportunity, the player
places their bets - typically a PassLine bet. The inRound betting
opportunity is used for bets prior to point rolls, odds bets and come bets.
Most of the bets on a Craps table are propositions that don’t pay correct
odds. The only bets that do pay correct odds are the “behind the line” odds
bets on the Come numbers and the Pass Line. The Don’t Come and the Don’t
Pass bets are similar, and can be ignored.
The variations on play are used to evaluate the number of Come bets that can be
working along with the Pass Line bets. The open issue is not how much can
be lost at one throw of the dice, but how likely is that kind of exposure.
A little bit of study seems to show a consistent standard deviation, leading to a
conclusion that a reasonable approach is to stop playing when the stake is plus
or minus one standard deviation.
sim2 (Roulette)
The Sim2 (Roulette) module is shown in the following static structure diagram.
The elements of the Roulette game are subclasses of general Casino classes.
Source: sim2.
-
class sim2.RouletteTable(casino.GameTable)
- RouletteTable is a subclass of casino.GameTable. It
contains a domain of all Bets. To simplify play, each number on the wheel
is represented as a list of all bets that pay out for that number. For
instance, 5 pays [ “number 5”, “red”, “odd”, “low”, “grp1”, “col2”, “pair 45”,
“pair 56”, “pair 25”, “pair 58”, “three 456”, “quad 1245”, “quad 2356”, “quad
4578”, “quad 5689”, “hex 123456”, “hex 456789” ].
-
class sim2.RouletteGame(casino.CasinoGame)
RouletteGame is a subclass of casino.CasinoGame. It
overrides state to return the last number rolled. Roulette is
stateless, so the state is more of a historical fact. If the number is 0 or
00, the list has the bet name and the color (“green”). If the number is
1-36, the list has a variable number of elements, including some of the
following:
- number n
- color (“red” or “black”)
- even (“even” or “odd”)
- hi-lo (<= 18 or >18)
- grp n, where 1 is 1-12, 2 is 13-24, and 3 is 25-36.
- col c, where c=1 for the 1,4,7,... column, c=2 for the 2,5,8,... column
and c=3 for the 3,6,9,... column.
- pair nm, for all pair bets between n and m. This includes
horizontal pairs (like 1-2) and vertical pairs (like 1-4). A number (like
5) can be part of at most four pairs (4-5, 5-6, 2-5 and 5-8). A number
(like 1) may be part of only two pairs (1-2 and 1-4).
- three nmo, for any of the 12 rows of three (1-2-3, 4-5-6, ...)
- quad mnop, for all quad bets between n, m, o and p. For
example, 5-6-8-9. Some numbers (like 5) are part of four quad bets.
Other numbers (like 1) are only part of one quad bet.
- hex mnopqr, for all hex bets. A hex bet is a double row
of three (1-2-3-4-5-6, 4-5-6-7-8-9, ...)
The state list will have between 12 and 17 elements, depending on how many
pair and quad bets are possible.
-
class sim2.RoulettePlayer(casino.Player)
The game is played by offering a startRound betting opportunity, the player
places their bets. There is no inRound betting opportunity. The
endRound method allows the Player to collect history on the various bets which
have won recently.
The roulette game is very heavily stacked against the player. It is not
possible to win, since each spin is completely independent. However, it is
fun to use a betting system, like “wait for 7 blacks in a row and bet red” and
hope to win. The important part of the simulation is to get a standard
deviation so you can limit your losses.
You can consider your stake and the number of game rounds to give you a location
on a two-dimensional grid. As the game round number increases, your stake
moves either down a little or up a lot. On average, it will hover right
around zero. Sometimes it will be positive, other times negative. If
you leave when it is positive, you are a winner. A negative swing will
eventually be counterbalanced by a positive swing. The two don’t balance
exactly, and this is the house edge. The question is, when will a negative
swing be unlikely to recover given a finite bankroll and finite time? It
appears that one standard deviation is a good working rule.
sim3 (Blackjack)
The Sim3 (Blackjack) module is shown in the following static structure
diagram.
The elements of the Blackjack game are subclasses of general Casino classes.
Source: sim3.
-
class sim3.BlackjackTable(casino.GameTable)
- BlackjackTable is a subclass of casino.GameTable. It
contains a domain of all Bets, including the player options hit and stand.
The insurance bet is not simulated, since it is heavily weighted against the
player.
-
class sim3.BlackjackGame(casino.CasinoGame)
- BlackjackGame is a subclass of casino.CasinoGame. It
overrides state to return the dealer’s up card and the player’s hand. In
the event of a split, multiple tables are used, one for each split hand.
The game issues cards to dealer and player, offers inRound betting opportunities
to the player. It handles the various player requests: hit, stand, split
and double. If the player is not bust or blackjack, it plays out the
dealer’s hand, paying off as appropriate.
-
class sim3.BlackjackPlayer(casino.Player)
- The Blackjack subclass of casino.Player makes decisions based
on the dealer’s card and their hand. The rules are relatively well
understood, and sources differ slightly on some subtle issues, like how to play
the soft hands A2, A3, A4 and A5. This simulator allows evaluation of
various subtle rule variations.
sim4 (CaribPoker)
The Sim4 (CaribPoker) module is shown in the following static structure
diagram.
The elements of the Caribbean Stud Poker game are subclasses of general Casino
classes.
Source: sim4.
-
class sim4.CaribPokerTable(casino.GameTable)
- CaribPokerTable is a subclass of casino.GameTable. It
contains a domain of all Bets, including the player options raise and fold.
The progressive jackpot bet is not simulated, since it is heavily weighted
against the player.
-
class sim4.CaribPokerGame(casino.CasinoGame)
- CaribPokerGame is a subclass of casino.CasinoGame. It
overrides state to return the dealer’s up card and the player’s hand. The
game issues cards to dealer and player, offers one inRound betting opportunity to
the player. It handles the player requests: raise or fold. If the
dealer has AK, it plays out the dealer’s hand, paying off as appropriate.
-
class sim4.CaribPokerPlayer(casino.Player)
The CaribPoker player subclass of casino.Player makes
decisions based on the dealer’s card and their hand. The rules are complex,
and sources differ on a useful subset of these rules. Variations involve
raising on pairs or better, raising on AK or better, raising on AKJ83 or better,
and the Wizard of Odds rules:
- Fold on stiff
- Raise on pairs or better
- AK, dealer has 2-Q and we hold a card that matches the dealer’s card
- AKx, dealer has A or K and x is J or Q
- AKQx, where x beats dealer’s up card
Extensions
Currently, extending the simulations to incorporate additional player
strategies is not very convenient. Each game module should be split into
two sections, one with the basic game rules as subclasses of GameTable and
CasinoGame; the other with the subclasses of Player.
The introspection capabilities of Python may make the PlayerFactories
superfluous.