Wheel Class

This chapter builds on the previous two chapters, creating a more complete composite object from the Outcome and Bin classes we have already defined. In Wheel Analysis we’ll look at the responsibilities of a wheel and it’s collaboration.

In the Wheel Design we’ll provide the detailed design information. In the Test Setup we’ll address some considerations for testing a class which has random behavior. In Wheel Deliverables we’ll enumerate what must be built.

Wheel Analysis

The wheel has two responsibilities: it is a container for the Bins and it picks one Bin at random. We’ll also have to look at how to initialize the various Bins that comprise a standard Roulette wheel.

In The Container Responsibility we’ll look at the container aspect in detail.

In The Random Bin Selection Responsibility we’ll look at the random selection aspects.

Based on this, the Constructing a Wheel section provides a description of how we can build the Wheel instance.

The Container Responsibility

Since the Wheel is 38 Bins, it is a collection. We can review our survey of available collections in Design Decision – Choosing A Collection for some guidance here.

In this case, the choice of the winning Bin will be selected by a random numeric index. We need some kind of sequential collection.

This makes an immutable tuple very appealing. This is a subclass of collections.abc.Sequence and has the features we’re looking for.

One consequence of using a sequential collection is that we have to choose an index for the various Bins.

The index values of 1 to 36 are logical mappings to Bins. The Bin at index 1 would contain Outcome("1", 35) among several others. The Bin at index 2 would contain Outcome("2", 35). And so on through 36.

We have a small problem, however, with 0 and 00: we need two separate indexes. While 0 is a valid index, what do we do with 00?

Enumerate some possible solutions before reading on.


Since the index of the Bin doesn’t have any significance at all, we can assign the Bin that has the 00 Outcome to position 37. It doesn’t actually matter because we’ll never really use the index for any purpose other than random selection.

The Random Bin Selection Responsibility

In order for the Wheel to select a Bin at random, we’ll need a random number from 0 to 37 that we can use an an index.

The random module offers a Random.choice() function which picks a random value from a sequence. This is ideal for returning a randomly selected Bin from our list of Bins.

Testability. Note that testing a class using random numbers isn’t going to be easy. To do testing properly, we’ll need to create a non-random random number generator that we can use in place of the built-in random number generator.

To create a non-random random-number generator, we can do something like the following.

  1. When testing, we can then set a specific seed value that will generate a known sequence of values.
  2. Create a mock for the random number generator that returns a known, fixed sequence of values. We can leverage the unittest.mock module for this.

We’ll address this in detail in Review of Testability. For now, we’ll suggest using the first technique – set a specific seed value.

Constructing a Wheel

Each instance of Bin has a list of Outcomes. The zero (“0”) and double zero (“00”) Bin s only have two Outcomes. The other numbers have anywhere from twelve to fourteen Outcomes.

Clearly, there’s quite a bit of complexity in building some of the bins.

Rather than dwell on these algorithms, we’ll apply a common OO principle of deferred binding. We’ll build a very basic wheel first and work on the bin-building algorithms later.

It’s often simplest to build a class incrementally. This is an example where the overall structure is pretty simple, but some details are rather complex.

Wheel Design

class Wheel

Wheel contains the 38 individual bins on a Roulette wheel, plus a random number generator. It can select a Bin at random, simulating a spin of the Roulette wheel.

Fields

Wheel.bins

Contains the individual Bin instances.

This is a tuple of 38 elements. This can be built with tuple( Bin() for i in range(38) )

Wheel.rng

A random number generator to use to select a Bin from the bins collection.

Because of the central importance of this particular source of randomness, it seems sensible to isolate it from any other processing that might need random numbers. We can then set a seed value using os.urandom() or specific values for testing.

Constructors

Wheel.__init__(self)

Creates a new wheel with 38 empty Bins. It will also create a new random number generator instance.

At the present time, this does not do the full initialization of the Bins. We’ll rework this in a future exercise.

Methods

Bin.addOutcome(number, outcome)

Adds the given Outcome to the Bin with the given number.

Parameters:
  • bin (int) – bin number, in the range zero to 37 inclusive.
  • outcome (Outcome) – The Outcome to add to this Bin
Bin.next() → Bin

Generates a random number between 0 and 37, and returns the randomly selected Bin.

The Random.choice() function of the random module will select one of the available Bin s from the bins list.

Returns:A Bin selected at random from the wheel.
Return type:Bin
Bin.get(bin) → Bin

Returns the given Bin from the internal collection.

Parameters:bin (int) – bin number, in the range zero to 37 inclusive.
Returns:The requested Bin.
Return type:Bin

Test Setup

We need a controlled kind of random number generation for testing purposes. This is done with tests that look like the following:

Test Outline

class GIVEN_Wheel_WHEN_next_THEN_random_choice(unittest.TestCase):
    def setUp(self):
        self.wheel= Wheel()
        self.wheel.rng.seed(1)
    def runTest(self):
        etc.

The values delivered from this seeded random number generator can be seen from this experiment.

Fixed pseudo-random sequence

>>> x = random.Random()
>>> x.seed(1)
>>> [x.randint(0,37) for i in range(10)]
[8, 36, 4, 16, 7, 31, 28, 30, 24, 13]

This allows us to predict the output from the Wheel.next() method.

Wheel Deliverables

There are three deliverables for this exercise. The new class and the unit test will have Python docstrings.

  • The Wheel class.
  • A class which performs a unit test of building the Wheel class. The unit test should create several instances of Outcome, two instances of Bin, and an instance of Wheel. The unit test should establish that Bins can be added to the Wheel.
  • A class which tests the Wheel class by selecting “random” values from a Wheel object using a fixed seed value.