Table of Contents
The variations on Player, all of which
reflect different betting strategies, is the heart of this application.
In Chapter 10, Roulette Game Class, we roughed out a stub class for
Player. In this chapter, we will complete that
design. We will also expand on it to implement the Matingale betting
strategy.
We have now built enough infrastructure that we can begin to add a variety of players and see how their betting strategies work. Each player is betting algorithm that we will evaluate by looking at the player's stake to see how much they win, and how long they play before they run out of time or go broke.
The Player has the responsibility to
create bets and manage the amount of their stake. To create bets, the
player must create legal bets from known
Outcomes and stay within table limits. To
manage their stake, the player must deduct money when creating a bet,
accept winnings or pushes, report on the current value of the stake,
and leave the table when they are out of money.
We have an interface that was roughed out as part of the design
of Game and Table. In
designing Game, we put a
placeBets method in
Player to place all bets. We expected the
Player to create Bets
and use the placeBet method of
Table class to save all of the individual
Bets.
In an earlier exercise, we built a stub version of
Player in order to test
Game. See Passenger57
Class. When we finish creating the
final superclass, Player, we will also revise
our Passenger57 to be a subclass of
Player, and rerun our unit tests to be sure
that our more complete design still handles the basic test cases
correctly.
Our objective is to have a new abstract class,
Player, with two new concrete subclasses: a
revision to Passenger57 and a new player that
follows the Martingale betting system.
We'll defer some of the design required to collect detailed measurements for statistical analysis. In this first release, we'll simply place bets.
There are four design issues tied up in
Player: tracking stake, keeping within table
limits, leaving the table, and creating bets. We'll tackle them in
separate subsections.
Tracking the Stake. One of the more important features we need to add to
Player are the methods to track the player's
stake. The initial value of the stake is the player's budget. There
are two significant changes to the stake.
Each bet placed will deduct the bet amount from the
Player's stake. We are stopped from placing
bets when our stake is less than the table minimum.
Each win will credit the stake. The
Outcome will compute this amount for
us.
Additionally, a push will put the original bet amount back. This is a kind of win with no odds applied.
We'll have to design an interface that will create
Bets, reducing the stake. and will be used by
Game to notify the
Player of the amount won.
Additionally, we will need a method to reset the stake to the starting amount. This will be used as part of data collection for the overall simulation.
Table Limits. Once we have our superclass, we can then define the
Martingale player as a subclass. This player
doubles their bet on every loss, and resets their bet to a base
amount on every win. In the event of a long sequence of losses, this
player will have their bets rejected as over the table limit. This
raises the question of how the table limit is represented and how
conformance with the table limit is assured. We put a preliminary
design in place in Roulette Table Class. There are several places where we could
isolate this responsibility.
The Player stops placing bets when
they are over the Table limit. In this
case, we will be delegating responsibility to the
Player hierarchy. In a casino, a sign is
posted on the table, and both players and casino staff enforce
this rule. This can be modeled by providing a method in
Table that simply returns the table limit
for use by the Player to keep bets within
the limit.
The Table provides a “valid
bet” method. This reflects a more general situation where a
stateful game has bets that change. In Craps, for example, most
bets cannot be placed until a point is established.
The Table throws an “illegal
bet” exception when an illegal bet is placed. While
permissable, this kind of use for exceptions pushes the envelope
on clarity and simplicity. One viewpoint is that exceptions should
be reserved for situations that are truly unexpected. In this
case, we expect to run into the table limit situation fairly often
using Martigale betting.
We recommend the second choice: adding a
isValid method to the
Table class. This has the consequence of
allocating responsibility to Table, and permits
us to have more advanced games where some bets are not allowed during
some game states. It also obligates Player to
validate each bet with the Table. It also means
that at some point, the player may be unable to place a legal
bet.
We could also implement this by adding to the responsibilities
of the existing placeBet method. For example,
we could return true if the bet was accepted, and
false if the bet violated the limits or other game
state rules. We prefer to isolate responsibility and create a second
method rather than pile too much into a single method.
Leaving the Table. In enumerating the consequences of checking for legal bets, we
also uncovered the issue of the Player
leaving the game. We can identify a number of possible reasons for
leaving: out of money, out of time, won enough, and unwilling to
place a legal bet. Since this decision is private to the
Player, we need a way of alerting the
Game that the Player
is finished placing bets.
There are three mechanisms for alerting the
Game that the Player is
finished placing bets.
Expand the responsibilities of the
placeBets to also indicate if the player
wishes to continue or is withdrawing from the game. While most
table games require bets on each round, it is possible to step up
to a table and watch play before placing a bet. This is one
classic strategy for winning at blackjack: one player sits at the
table, placing small bets and counting cards, while a confederate
places large bets only when the deck is favorable. We really have
three player conditions: watching, betting and finished playing.
It becomes complex trying to bundle all this extra responsibility
into the placeBets method.
Add another method to Player that the
Game can use to determine if the
Player will continue or stop playing. This
can be used for a player who is placing no bets while waiting; for
example, a player who is waiting for the Roulette wheel to spin
red seven times in a row before betting on black.
The Player can throw an exception
when they are done playing. This is an exceptional situation: it
occurs exactly once in each simulation. However, it is a
well-defined condition, and doesn't deserve to be called
“exceptional”. It is merely a terminating condition
for the game.
We recommend adding a method to Player to
indicate when Player is done playing. This
gives the most flexibility, and it permits Game
to cycle until the player withdraws from the game.
A consequence of this decision is to rework the
Game class to allow the player to exit. This is
relatively small change to interrogate the
Player before asking the player to place
bets.
In this case, these were situations which we didn't discover
during the initial design. It helped to have some experience with
the classes in order to determine the proper allocation of
responsibilities. While design walkthroughs are helpful, an
alternative is a “prototype”, a piece of software that
is incomplete and can be disposed of. The earlier exercise created a
version of Game that was incomplete, and a
version of PlayerStub that will have to be
disposed of.
Creating Bets from Outcomes. Generally, a Player will have a few
Outcomes on which they are betting. Many
systems are similar to the Martingale system, and place bets on only
one of the Outcomes. These
Outcome objects are usually created during
player initialization. From these Outcomes,
the Player can create the individual
Bet instances based on their betting
strategy.