Roulette Game Class

Between Player and Game, we have a chicken-and-egg design problem: it’s not clear which we should do first. In this chapter, we’ll describe the design for Game in detail. However, in order to create the deliverables, we have to create a version of Player that we can use just to get started.

In the long run, we’ll need to create a sophisticated hierarchy of players. Rather than digress too far, we’ll create a simple player, Passenger57 (they always bet on black), which will be the basis for further design in later chapters.

The class that implements the game will be examined in Roulette Game Analysis.

Once we’ve defined the game, we can also define a simple player class to interact with the game. We’ll look at this in Passenger57 Design.

After looking at the player in detail, we can look at the game in detail. We’ll examine the class in Roulette Game Design.

We’ll provide some additional details in Roulette Game Questions and Answers. The Roulette Game Deliverables section will enumerate the deliverables.

There are a few variations on how Roulette works. We’ll look at how we can include these details in the Appendix: Roulette Variations section.

Roulette Game Analysis

The RouletteGame‘s responsibility is to cycle through the various steps of a defined procedure. We’ll look at the procedure in detail. We’ll also look at how we match bets in The Bet Matching Algorithm. This will lead us to define the player interface, which we’ll look at in Player Interface.

This is an active class that makes use of the classes we have built so far. The hallmark of an active class is longer or more complex methods. This is distinct from most of the classes we have considered so far, which have relatively trivial methods that are little more than getters and setters of instance variables.

The procedure for one round of the game is the following.

A Single Round of Roulette

  1. Place Bets. Notify the Player to create Bets. The real work of placing bets is delegated to the Player class. Note that the money is committed at this point; they player’s stake should be reduced as part of creating a Bet.

  2. Spin Wheel. Get the next spin of the Wheel, giving the winning Bin, w. This is a collection of individual Outcome‘s which will be winners. We can say w = {o_0, o_1, o_2, ..., o_n}: the winning bin is a set of outcomes.

  3. Resolve All Bets.

    For each Bet, b, placed by the Player:

    1. Winner? If Bet b Outcome is in the winning Bin, w, then notify the Player that Bet b was a winner and update the Player‘s stake.
    2. Loser? If Bet b Outcome is not in the winning Bin, w, then notify the Player that Bet b was a loser. This allows the Player to update the betting amount for the next round.

The Bet Matching Algorithm

This Game class will have the responsibility for matching the collection of Outcomes in the Bin of the Wheel with the collection of Outcomes of the Bets on the Table. We’ll need to structure a loop to compare individual elements from these two collections.

  • Driven by Bin. We could use a loop to visit each Outcome in the winning Bin.

    For each Outcome in the winning Bin, o(w):

    For each Bet contained by the Table, b:

    If the Outcome of the Bet matches the Outcome in the winning Bin, this bet, b, is a winner and is paid off.

    After this examination, all Bets which have not been paid off are losers.

    This is unpleasantly complex because we can’t resolve a Bet until we’ve checked all outcomes in the winning Bin.

  • Driven by Table. The alternative is to visit each Bet contained by the Table.

    For each Bet in the Table, b:

    If the Outcome of b is in the Outcomes in the winning Bin, the bet is a winner. If the Outcome is not in the Bin, the bet is a loser.

    Since the winning Bin is a frozenset of Outcomes, we can exploit set membership methods to test for presence or absence of the Outcome for a Bet in the winning Bin.

Player Interface

The Game and Player collaboration involves mutual dependencies. This creates a “chicken and egg” problem in decomposing the relationship between these classes. The Player depends on Game features. The Game depends on Player features.

Which do we design first?

We note that the Player is really a complete hierarchy of subclasses, each of which provides a different betting strategy. For the purposes of making the Game work, we can develop our unit tests with a stub for Player that simply places a single kind of Bet. We’ll call this player “Passenger57” because it always bets on Black.

Once we have a simplistic player, we can define the Game more completely.

After we have the Game finished, we can then revisit this design to make more sophisticated subclasses of Player. In effect, we’ll bounce back and forth between Player and Game, adding features to each as needed.

For some additional design considerations, see Appendix: Roulette Variations. This provides some more advanced game options that our current design can be made to support. We’ll leave this as an exercise for the more advanced student.

Passenger57 Design

class Passenger57

Passenger57 constructs a Bet based on the Outcome named "Black". This is a very persistent player.

We’ll need a source for the Black outcome. We have several choices; we looked at these in Roulette Bet Class. We will query the Wheel for the needed Outcome object.

In the long run, we’ll have to define a Player superclass, and make Passenger57 class a proper subclass of Player. However, our focus now is on getting the Game designed and built.

Fields

Passenger57.black

This is the outcome on which this player focuses their betting.

This Player will get this from the Wheel using a well-known bet name.

Passenger57.table

The Table that is used to place individual Bets.

Constructors

Passenger57.__init__(self, table, wheel)
Parameters:

Constructs the Player with a specific table for placing bets. This also creates the “black” Outcome. This is saved in a variable named Passenger57.black for use in creating bets.

Methods

Passenger57.placeBets(self)

Updates the Table with the various bets. This version creates a Bet instance from the black Outcome. It uses Table placeBet() to place that bet.

Passenger57.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 a the winAmount() method of theBet.

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

Notification from the Game that the Bet was a loser.

Roulette Game Design

class Game

Game manages the sequence of actions that defines the game of Roulette. This includes notifying the Player to place bets, spinning the Wheel and resolving the Bets actually present on the Table.

Fields

wheel

The Wheel that returns a randomly selected Bin of Outcomes.

table

The Table which contains the Bets placed by the Player.

player

The Player which creates Bets at the Table.

Constructors

We based the Roulette Game constructor on a design that allows any of the fields to be replaced. This is the Strategy design pattern. Each of these collaborating objects is a replaceable strategy, and can be changed by the client that uses this game.

Additionally, we specifically do not include the Player instance in the constructor. The Game exists independently of any particular Player, and we defer binding the Player and Game until we are gathering statistical samples.

Game.__init__(self, wheel, table)
Parameters:
  • wheel (Wheel) – The Wheel instance which produces random events
  • table (Table) – The Table instance which holds bets to be resolved.

Constructs a new Game, using a given Wheel and Table.

Methods

cycle(self, player)
Parameters:player (Player) – the individual player that places bets, receives winnings and pays losses.

This will execute a single cycle of play with a given Player. It will

  1. call thePlayer‘s placeBets() method to get bets,
  2. call theWheel‘s next() method to get the next winning Bin,
  3. call theTable‘s iterator to get an Iterator over the Bets. Stepping through this Iterator returns the individual Bet objects. If the winning Bin contains the Outcome, call the thePlayer win() method otherwise call the thePlayer lose() method.

Roulette Game Questions and Answers

Why are Table and Wheel part of the constructor while Player is given as part of the cycle() method?

We are making a subtle distinction between the casino table game (a Roulette table, wheel, plus casino staff to support it) and having a player step up to the table and play the game. The game exists without any particular player. By setting up our classes to parallel the physical entities, we give ourselves the flexibility to have multiple players without a significant rewrite. We allow ourselves to support multiple concurrent players or multiple simulations each using a different player object.

Also, as we look forward to the structure of the future simulation, we note that the game objects are largely fixed, but there will be a parade of variations on the player. We would like a main program that simplifies inserting a new player subclass with minimal disruption.

Why do we have to include the odds with the Outcome ? This pairing makes it difficult to create an Outcome from scratch.

The odds are an essential ingredient in the Outcome . It turns out that we want a short-hand name for each Outcome. We have three ways to provide a short name.

  • A variable name. Since each variable is owned by a specific class instance, we need to allocate this to some class. The Wheel or the BinBuilder make the most sense for owning this variable.
  • A key in a mapping. In this case, we need to allocate the mapping to some class. Again, the Wheel or BinBuilder make the most sense for owning the mapping.
  • A method which returns the Outcome . The method can use a fixed variable or can get a value from a mapping.

Roulette Game Deliverables

There are three deliverables for this exercise. The stub does not need documentation, but the other classes do need complete Python docstrings.

  • The Passenger57 class. We will rework this design later. This class always places a bet on Black. Since this is simply used to test Game, it doesn’t deserve a very sophisticated unit test of its own. It will be replaced in a future exercise.
  • The RouletteGame class.
  • A class which performs a demonstration of the Game class. This demo program creates the Wheel, the stub Passenger57 and the Table. It creates the Game object and cycles a few times. Note that the Wheel returns random results, making a formal test rather difficult. We’ll address this testability issue in the next chapter.

Appendix: Roulette Variations

In European casinos, the wheel has a single zero. In some casinos, the zero outcome has a special en prison rule: all losing bets are split and only half the money is lost, the other half is termed a “push” and is returned to the player. The following design notes discuss the implementation of this additional rule.

This is a payout variation that depends on a single Outcome. We will need an additional subclass of Outcome that has a more sophisticated losing amount method: it would push half of the amount back to the Player to be added to the stake. We’ll call this subclass the the PrisonOutcome class.

In this case, we have a kind of hybrid resolution: it is a partial loss of the bet. In order to handle this, we’ll need to have a loss() method in Bet as well as a win() method. Generally, the loss() method does nothing (since the money was removed from the Player stake when the bet was created.) However, for the PrisonOutcome class, the loss() method returns half the money to the Player.

We can also introduce a subclass of BinBuilder that creates only the single zero, and uses this new PrisonOutcome subclass of Outcome for that single zero. We can call this the EuroBinBuilder . The EuroBinBuilder does not create the five-way Outcome of 00-0-1-2-3, either; it creates a four-way for 0-1-2-3.

After introducing these two subclasses, we would then adjust Game to invoke the loss() method of each losing Bet, in case it resulted in a push. For an American-style casino, the loss() method does nothing. For a European-style casino, the los() method for an ordinary Outcome also does nothing, but the los() for a PrisonOutcome would implement the additional rule pushing half the bet back to the Player . The special behavior for zero then emerges from the collaboration between the various classes.

We haven’t designed the Player yet, but we would have to bear this push rule in mind when designing the player.

The uniform interface between Outcome and PrisonOutcome is a design pattern called polymorphism. We will return to this principle many times.