Roulette Game Class

Between the Player class and the Game class, 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 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 Game’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 Algorithms. 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 collections of related instance variables.

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

A Single Round of Roulette

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

  2. Spin Wheel. Get the next spin of the Wheel instance, giving the winning Bin object, w. This is a collection of individual Outcome instances. Any Bet instance with a winning outcome is a winner. 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 the Outcome object of Bet, b, is in the winning Bin, w, then notify the Player object that Bet b was a winner and update the Player object’s stake.

    2. Loser? If the Outcome object of Bet, b, 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 Algorithms

This Game class will have the responsibility for matching the collection of Outcome instances in the Bin object of the Wheel object with the Outcome attributes of the collection of Bet instances on the Table object. We’ll need to structure a loop to compare individual elements from these two collections. There are two common ways to iterate through the bets and outcomes:

  • Driven by Bin object contents. We can visit each Outcome instance in the winning Bin object.

  • Driven by Table object contents. We can to visit each Bet instance contained by the Table object.

We’ll look at each algorithm to select the best choice.

To examine each Outcome object in the winning Bin object, we’ll use code like the following.

For each Outcome instance in the winning Bin object, o:

For each Bet instance contained by the Table object, b:

If the Outcome instance of the Bet instance matches the Outcome instance in the winning Bin object (o = b), this bet, b, is a winner and is paid off. The winnings are the amount of the bet multiplied by the odds of the outcome.

After this examination, all Bet instances which have not been paid off are losers. This is unpleasantly complex because we can’t resolve a Bet instance until we’ve checked all Outcome instances in the winning Bin object.

To examine each Outcome object in the Bet instances within the Table object, we’ll use code like the following.

For each Bet instance in the Table object, b:

If the Outcome attribute of b is in the Outcome instances in the winning Bin, the bet is a winner. The winnings are the amount of the bet multiplied by the odds of the outcome. If the Outcome is not in the Bin, the bet is a loser.

This is simpler because the winning Bin instance is a frozenset of Outcome instances, we can exploit set membership methods to test for presence or absence of the Outcome instance for a Bet object in the winning Bin instance.

Player Interface

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

Which do we design first?

We note that the Player class is really a complete hierarchy of subclasses, each of which provides a different betting strategy. For the purposes of making the Game class work, we can develop our unit tests with a stub for the Player class 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 class more completely.

After we have the Game class finished, we can then revisit this design to make more sophisticated subclasses of Player class. In effect, we’ll bounce back and forth between Player class and Game class, 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 instance based on the Outcome object 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 object 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 class. Since our focus is on getting the Game class designed and built, we’ll set this consideration aside until later.

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 Bet instances.

Constructors

Passenger57.__init__(self, table: Table, wheel: Wheel) → None
Parameters
  • table (Table) – The Table instance on which bets are placed.

  • wheel (Wheel) – The Wheel instance which defines all Outcome instances.

Constructs the Player instance 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) → None

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

Passenger57.win(self, bet: Bet) → None
Parameters

bet (Bet) – The bet which won.

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

Passenger57.lose(self, bet: Bet) → None
Parameters

bet (Bet) – The bet which won.

Notification from the Game object that the Bet instance 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 object to place bets, spinning the Wheel object and resolving the Bet instances actually present on the Table object.

Fields

wheel

The Wheel instance that returns a randomly selected Bin object of Outcome instances.

table

The Table object which contains the Bet instances placed by the Player object.

player

The Player object which creates Bet instances at the Table object.

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: Wheel, table: Table) → None
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

Game.cycle(self, player: Player) → None
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 execute the following steps:

  1. Call Player.placeBets() method to create bets.

  2. Call Wheel.choose() method to get the next winning Bin object.

  3. Call iter() on the table to get all of the Bet instances. For each Bet instance, if the winning Bin contains the Outcome, call Player.win() method, otherwise, call the Player.lose() method.

Roulette Game Simplification

The essence of the Game class includes a large number of complex methods, but relatively few fields and a very simple constructor.

It may make sense to consider using a @dataclass definition for this class. It’s not completely clear that all of the various dataclass features are particularly useful here. The principle benefit seems to be eliminating the need to write the tiny Game.__init__() method.

Roulette Game Questions and Answers

Why are a Table object and Wheel object part of the constructor for Game class, while a Player object is given as a parameter for the cycle() method? Why not provide all of the objects as part of the constructor?

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 instance, perhaps with different strategies.

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 for the players. 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 class? This pairing makes it difficult to create an Outcome object from scratch.

The odds are an essential ingredient in the Outcome instance. It’s not clear where else they can possibly go.

Creating a new Outcome instance to create a Bet object is really a request for a simplified name of each Outcome alternative. We have three ways to provide a short name:

  • A variable name. We also need to put the variable in some kind of namespace. The Wheel or the BinBuilder make the most sense for owning this variable.

  • A key in a mapping. We also need to allocate the mapping to some object. Again, the Wheel or BinBuilder make the most sense for owning the mapping of name to Outcome instance.

  • A method which returns an Outcome object. The method can use a fixed variable or can get a value from a mapping.

The Wheel class shows up most often as a place to track the Outcome instances. A method (or a mapping) in this class is an elegant way to track down the Outcome instance required to build a Bet object.

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 Game class.

  • A class which performs a demonstration of the Game class. This demo program creates the Wheel object, the stub Passenger57 object and the Table object. It creates the Game object and cycles a few times. Note that the Wheel instance 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 loss() method for an ordinary Outcome also does nothing, but the loss() 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.

Looking Forward

We’ve almost got enough software to create detailed simulations of play. What can be a struggle is creating appropriate unit tests for the fairly complex collection of classes developed up to this point.

In the next chapter, we’ll address the testing considerations required to be sure the software works correctly. We’ll also look at some of the static analysis considerations that stem from using type hints and the mypy tool.