Player Class

The variations on Player, all of which reflect different betting strategies, is the heart of this application. In 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 a 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.

We’ll look at the player problem in Roulette Player Analysis.

In Player Design we’ll expand on our previous skeleton Player to create a more complete implementation. We’ll expand on that again in Martingale Player Design.

In Player Deliverables we’ll enumerate the deliverables for this chapter.

Roulette Player Analysis

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’ll look at a number of topics:

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 Design. 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.

Design Objectives

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 several 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 Bet s, 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.

  1. 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.
  2. The Table provides a “valid bet” method.
  3. The Table throws an “illegal bet” exception when an illegal bet is placed.

The first alternative is unpleasant because the responsibility to spread around: both Player and Table must be aware of a feature of the Table. This means that a change to the Table will also require a change to the Player. This is poor object-oriented design.

The second and third choices reflect two common approaches that are summarized as:

  • Ask Permission. The application has code wrapped in if permitted: conditional processing.
  • Ask Forgiveness. The application assumes that things will work. An exception indicates something unexpected happened.

The general advice is this:

It’s better to ask forgiveness than to ask permission.

Most of the time, validation should be handled by raising an exception.

Handling Game State. This raises a question about how we handle advanced games where some bets are not allowed during some game states.

There are two sources of validation for a bet.

  • The Table may reject a bet because it’s over (or under) a limit.
  • The Game may reject a bet because it’s illegal in the current state of the game.

Since these considerations are part of Craps and Blackjack, we’ll set them aside for now.

Leaving the Table

We also need to address 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.

  1. 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.
  2. 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.
  3. 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.

Note

Design Evolution

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.

Since we’re currently using the Wheel as a repository for all legal Outcome instances, we’ll need to provide the Wheel to the Player.

This doesn’t generalize well for Craps or Blackjack. We’ll need to revisit this design decision. In the long run, we’ll need to find another kind of factory for creating proper Outcome instances.

Player Design

class Player

We’ll design the base class of Player and a specific subclass, Martingale. This will give us a working player that we can test with.

Player places bets in Roulette. This an abstract class, with no actual body for the placeBets() method. However, this class does implement the basic win() method used by all subclasses.

Fields

Player.stake

The player’s current stake. Initialized to the player’s starting budget.

Player.roundsToGo

The number of rounds left to play. Initialized by the overall simulation control to the maximum number of rounds to play. In Roulette, this is spins. In Craps, this is the number of throws of the dice, which may be a large number of quick games or a small number of long-running games. In Craps, this is the number of cards played, which may be large number of hands or small number of multi-card hands.

Player.table

The Table used to place individual Bets. The Table contains the current Wheel from which the player can get Outcomes used to build Bets.

Constructors

Player.__init__(self, table)

Constructs the Player with a specific Table for placing Bets.

Parameters:table (Table) – the table to use

Since the table has access to the Wheel, we can use this wheel to extract :class`Outcome` objects.

Methods

Player.playing(self) → boolean

Returns true while the player is still active.

Player.placeBets(self)

Updates the Table with the various Bet s.

When designing the Table, we decided that we needed to deduct the amount of a bet from the stake when the bet is created. See the Table Roulette Table Analysis for more information.

Player.win(self, bet)
Parameters:bet (Bet) – The bet which won

Notification from the Game that the Bet was a winner. The amount of money won is available via bet method winAmount().

Player.lose(self, bet)
Parameters:bet (Bet) – The bet which won

Notification from the Game that the Bet was a loser. Note that the amount was already deducted from the stake when the bet was created.

Martingale Player Design

class Martingale

Martingale is a Player who places bets in Roulette. This player doubles their bet on every loss and resets their bet to a base amount on each win.

Fields

Martingale.lossCount

The number of losses. This is the number of times to double the bet.

Martingale.betMultiple

The the bet multiplier, based on the number of losses. This starts at 1, and is reset to 1 on each win. It is doubled in each loss. This is always equal to 2^{lossCount}.

Methods

Martingale.placeBets(self)

Updates the Table with a bet on “black”. The amount bet is 2^{lossCount}, which is the value of betMultiple.

Martingale.win(self, bet)
Parameters:bet (Bet) – The bet which won

Uses the superclass win() method to update the stake with an amount won. This method then resets lossCount to zero, and resets betMultiple to 1.

Martingale.lose(self, bet)
Parameters:bet (Bet) – The bet which won

Uses the superclass lose() to do whatever bookkeeping the superclass already does. Increments lossCount by 1 and doubles betMultiple.

Player Deliverables

There are six deliverables for this exercise. The new classes must have Python docstrings.

  • The Player abstract superclass. Since this class doesn’t have a body for the placeBets(), it can’t be unit tested directly.
  • A revised Passenger57 class. This version will be a proper subclass of Player, but still place bets on black until the stake is exhausted. The existing unit test for Passenger57 should continue to work correctly after these changes.
  • The Martingale subclass of Player.
  • A unit test class for Martingale. This test should synthesize a fixed list of Outcome s, Bin s, and calls a Martingale instance with various sequences of reds and blacks to assure that the bet doubles appropriately on each loss, and is reset on each win.
  • A revised Game class. This will check the player’s playing() method before calling placeBets(), and do nothing if the player withdraws. It will also call the player’s win() and lose() methods for winning and losing bets.
  • A unit test class for the revised Game class. Using a non-random generator for Wheel, this should be able to confirm correct operation of the Game for a number of bets.