Outcome Class

In addition to defining the fundamental Outcome on which all gambling is based, this chapter provides a sidebar discussion on the notion of object identity and object equality. This is important because we will be dealing with a number of individual Outcome objects, and we need to be sure we can test for equality of two different objects. This is different from the test for identity.

Outcome Overview

The first class we will tackle is a small class that encapsulates each outcome. This class will contain the name of the outcome as a String, and the odds that are paid as an integer. We will use these objects when placing a bet and also when defining the Roulette wheel.

There will be several hundred instances of this class on a given Roulette table. The bins on the wheel, similarly, collect various Outcomes together. The minimum set of Outcome instances we will need are the 38 numbers, Red and Black. The other instances add details to our simulation.

In Roulette, the amount won is a simple multiplication of the amount bet and the odds. In other games, however, there may be a more complex calculation because the house keeps 5% of the winnings, called the “rake”. While it is not part of Roulette, it is good to have our Outcome class designed to cope with these more complex payout rules.

Also, we know that other casino games, like Craps, are stateful. An Outcome may change the game state. We can foresee reworking this class to add in the necessary features to change the state of the game.

While we are also aware that some odds are not stated as x:1, we won’t include these other kinds of odds in this initial design. Since all Roulette odds are x:1, we’ll simply assume that the denominator is always 1. We can forsee reworking this class to handle more complex odds, but we don’t need to handle the other cases yet.

Design Decision – Object Identity

Our design will depend on matching Outcome objects. We’ll be testing objects for equality.

The player will be placing bets that contain Outcomes; the table will be holding bets. The wheel will select the winning Outcomes. We need a simple test to see if two objects of the Outcome class are the same.

Was the Outcome for a bet equal to the Outcome contained in a spin of the wheel?

It turns out that this comparison between objects has some subtlety to it.

Outcomes have Names. Since each instance of Outcome has a distinct Outcome.name attribute, it seems simple enough to compare names. This is one sense of “equal” that seems to be appropriate.

To do object comparison correclty in Python, we must provide the __eq__() and __ne__() method which compares the Outcome.name attribute.

However, simply comparing outcome names isn’t – in the long run – going to work out well.

More than equal. We’ll be creating collections of Outcome objects, and we may need to create sets or maps where hash codes are used in addition to the simple equality tests.

As we look forward, the Python set and dict depend on a __hash__() method and an __eq__() method of each object in the collection.

The Hash Code Problem. Every object has a hash code. The hash code is simply a unique integer, perhaps the address of the object, perhaps some summary of the bits that make up the object. The default rule is this:

Distinct objects will have distinct hash codes

The default rule makes it possible for us to accidentally create objects that appear to be the same thing, but – internally – have different hash codes.

In Python, if we do nothing special, the __eq__() test will simply compare the object id values. These object id values are unique to each distinct object, irrespective of the attribute values.

This default behavior of objects is shown by the following example:

Object Identity

>>> oc1= Outcome( "Any Craps", 8 )
>>> oc2= Outcome( "Any Craps", 8 )
>>> oc1 == oc2
False
>>> id(oc1)
6291728
>>> id(oc2)
6291760

Each individual Outcome object has a distinct distinct hashcode. This makes them not equal according to the default methods inherited from object. However, we would like to have two of these objects test as equal.

This example shows that we can have two objects that appear equal, but don’t compare as equal.

Layers of Meaning. The real issue is that we have three distinct layers of meaning for comparing objects to see if they are “equal”.

  • Compare as Equal. We can call this “attribute equality”.

    The __eq__() method returns True. When we use the == operator, this is evaluated by using the __eq__() method. This must be overridden by a class to implement attribute equality.

  • Have the same hash code. We can call this “hash equality”. This has to include attribute equality.

    The __hash__(self)() method for several objects that represent the same Outcome have the same hash code.

  • Are references to the same object. We can call this “identity”.

    We can test that two objects are the same by using the is comparison between two objects.

    When we use the is comparison, we’re asserting that the two variables are references to the same object. This is the identity comparison.

    If we pursue this as an implementation, we have to be sure we don’t casually create instances of Outcome. Rather than create them all over our application, we need to have a single source for the “official” instances of Outcome.

Problem. Which should we implement, hash equality or object identity?

Forces. There are two broad implementation choices for making sure that our objects both provide appropriate hash values and pass equality tests.

  1. Object Identity. We can make sure that all of the parts of our application share the same Outcome objects. Two references to the same object will pass any identity test and will also have the same hash code.

    This requires that we have a pool of outcomes and each Outcome is – effectively – a Singleton instance. We have to be sure that our application has only one instance of Outcome( "1", 35 ), and that object is shared by the Wheel, the Player and any Bets that are made.

    We have to be sure to never create additional Outcome objects; there would be confusion between two discrete instances that happened to have the same name and odds.

    We have to avoid casually creating new instances of the Outcome class.

    Assuring that we work with single instances of each Outcome fits with the Don’t Repeat Yourself (DRY) principle: we only create each Outcome once.

  2. Hash Equality. We can make sure that all instances of an Outcome act like they are the same object. That is, they have the same hash value and they test true for equality. They may not be two references to the same underlying object, but they pass the hash and equality tests, making them equal for all practical purposes.

    This allows us to create additional Outcome objects, confident they will behave appropriately.

    This, however, also requires that we include the odds when creating an outcome. This gets wordy and unpleasant because we have the various outcomes and odds stated in more than one place in our application. This would break the Don’t Repeat Yourself (DRY) principle.

    The DRY principle is so important that this alternative is really unacceptable.

Solution. We have to assure that we’re sharing single instances of Outcome objects. We have several choices for assuring that we have exactly one instance of each Outcome.

  • Global Outcome Objects. We can declare global variables for the various outcomes and use those global objects as needed.

    Generally, globals variables are often undesirable because changes to those variables can have unexpected consequences in a large application.

    Global constants are no problem at all. The pool of Outcome instances are proper constant values used to create bins and bets.

  • Outcome Factory Object. We can create a class which is a Factory for individual Outcome objects. When some part of the application asked for an Outcome which didn’t yet exist, the Factory would create it, save it, and return a reference to it. When some part of the application asked for an Outcome which already exists, the Factory would return a reference to the existing object.

    This centralizes the pool of global objects into a single object, the Factory.

  • Singleton Outcome Class. A Singleton class creates and maintains a single instance of itself. This requires that the class have a static getInstance() method that is a reference to the one-and-only instance of the class.

    This saves us from creating global variables. Instead, each class definition contains it’s own private reference to the one-and-only object of that class.

    However, this has the profound disadvantage that each distinct outcome would need to be a distinct subclass of Outcome. This is an unappealing level of complexity.

All of these are acceptable techniques for implementing object identity. We don’t have to choose one at the current time.

Consequences. The most important consequence of this design decision is that we’re going to evenually create some global definitions of Outcomes, or a Factory object that centralizes Outcome creation.

We may also need to add the necessary static variable and static getInstance() method.

Outcome Design

class Outcome

Outcome contains a single outcome on which a bet can be placed.

In Roulette, each spin of the wheel has a number of Outcomes with bets that will be paid off.

For example, the “1” bin has the following winning Outcomes: “1”, “Red”, “Odd”, “Low”, “Column 1”, “Dozen 1-12”, “Split 1-2”, “Split 1-4”, “Street 1-2-3”, “Corner 1-2-4-5”, “Five Bet”, “Line 1-2-3-4-5-6”, “00-0-1-2-3”, “Dozen 1”, “Low” and “Column 1”. All of these bets will payoff if the wheel spins a “1”.

Fields

Outcome.name
Holds the name of the Outcome. Examples include "1", "Red".
Outcome.odds
Holds the payout odds for this Outcome. Most odds are stated as 1:1 or 17:1, we only keep the numerator (17) and assume the denominator is 1.

We can use name to provide hash codes and do equality tests.

Constructors

Outcome.__init__(self, name, odds)
Parameters:
  • name (str) – The name of this outcome
  • odds (int) – The payout odds of this outcome.
Sets the instance name and odds from the parameter name and odds.

Methods

For now, we’ll assume that we’re going to have global instances of each Outcome.

Outcome.winAmount(self, amount) → amount

Multiply this Outcome‘s odds by the given amount. The product is returned.

Parameter:amount (number) – amount being bet
Outcome.__eq__(self, other) → boolean

Compare the name attributes of self and other.

Parameter:other (Outcome) – Another Outcome to compare against.
Returns:True if this name matches the other name.
Return type:bool
Outcome.__ne__(self, other) → boolean

Compare the name attributes of self and other.

Parameter:other (Outcome) – Another Outcome to compare against.
Returns:True if this name does not match the other name.
Return type:bool
Outcome.__str__(self) → string

Easy-to-read representation of this outcome.

Python formatting is most easily done with the % operator. For some tips on how to do this, see Message Formatting.

Returns:String of the form name (odds:1).
Return type:str

This easy-to-read String output method is essential. This should return a String representation of the name and the odds. A form that looks like 1-2 Split (17:1) works nicely.

Outcome Deliverables

There are two deliverables for this exercise. Both will have Javadoc comments or Python docstrings.

  • The Outcome class.
  • A class which performs a unit test of the Outcome class. The unit test should create a three instances of Outcome, two of which have the same name. It should use a number of individual tests to establish that two Outcome with the same name will test true for equality, have the same hash code, and establish that the winAmount() method works correctly.

Message Formatting

For the very-new-to-Python, there are few variations on creating a formatted string.

Generally, we simply use something like this.

def __str__( self ):
    return "%s (%d:1)" % ( self.name, self.odds )

Table Of Contents

Previous topic

Roulette Solution Overview

Next topic

Bin Class

This Page