Building Skills in Object-Oriented Design

Step-by-Step Construction of A Complete Application

Steven F. Lott

Creative Commons License; some rights reserved.

This work is licensed under a Creative Commons License. You are free to copy, distribute, display, and perform the work under the following conditions:

  • Attribution. You must give the original author, Steven F. Lott, credit.

  • Noncommercial. You may not use this work for commercial purposes.

  • No Derivative Works. You may not alter, transform, or build upon this work.

For any reuse or distribution, you must make clear to others the license terms of this work.

6/16/2008


Table of Contents

Preface
Why Read This Book?
Audience
Organization of This Book
Why This Subject?
Programming Style
Conventions Used in This Book
Acknowledgements
1. Foundations
Problem Statement
The Use Case
Solution Approach
Methodology, Technique and Process
On Quality
On Rework
On Decision-Making
On Reuse
On Design Patterns
Deliverables
I. Roulette
2. Roulette Details
Roulette Game
Available Bets
Some Betting Strategies
3. Roulette Solution Overview
Preliminary Survey of Classes
A Walkthrough of Roulette
Questions and Answers
4. Outcome Class
Overview
Design
Deliverables
5. Bin Class
Overview
Questions and Answers
Java Design
Python Design
Deliverables
6. Wheel Class
Overview
Design
Deliverables
7. Bin Builder Class
Overview
Algorithms
Design
Deliverables
8. Bet Class
Overview
Questions and Answers
Design
Deliverables
9. Roulette Table Class
Overview
Design
Deliverables
10. Roulette Game Class
Overview
Design
Questions and Answers
Deliverables
11. Review of Testability
Overview
Questions and Answers
Design
Deliverables
12. Player Class
Overview
Design
Deliverables
13. Overall Simulation Control
Overview
Design
Deliverables
14. Player SevenReds
Overview
Design
Deliverables
15. Statistical Measures
Overview
Some Foundations
Statistical Algorithms
Design
Deliverables
16. Player Random
Overview
Design
Deliverables
17. Player 1-3-2-6
Overview
Questions and Answers
Design
Deliverables
Advanced Exercise
18. Player Cancellation
Overview
Design
Deliverables
19. Player Fibonacci
Overview
Design
Deliverables
20. Conclusion
II. Craps
21. Craps Details
Craps Game
Available Bets
Some Betting Strategies
22. Craps Solution Overview
Preliminary Survey of Classes
A Walkthrough of Craps
Questions and Answers
23. Outcome Class
Overview
Design
Deliverables
24. Throw Class
Overview
Design
Deliverables
25. Dice Class
Overview
Design
Deliverables
26. Throw Builder Class
Overview
Questions and Answers
Design Light
Design Heavy
Deliverables
27. Bet Class
Overview
Design
Deliverables
28. CrapsTable Class
Overview
Design
Deliverables
29. CrapsGame Class
Overview
Design
Deliverables
30. CrapsPlayer Class
Overview
Design
Deliverables
31. Design Cleanup and Refactoring
Overview
Design
Deliverables
32. Simple Craps Players
Overview
Design
Deliverables
33. Roll-Counting Player
Overview
Design
Deliverables
34. Conclusion
III. Blackjack
35. Blackjack Details
Blackjack Game
Available Bets and Choices
Betting Strategies
36. Blackjack Solution Overview
Preliminary Survey of Classes
A Walkthrough
Questions and Answers
37. Card, Deck and Shoe Classes
Overview
Questions and Answers
Design
Deliverables
38. Hand and Outcome Classes
Overview
Design
Deliverables
39. Blackjack Table Class
Overview
Design
Deliverables
40. BlackjackGame Class
Overview
Design
Deliverables
41. Simple Blackjack Player Class
Overview
Design
Deliverables
42. Variant Game Rules
Overview
Design
Deliverables
43. Conclusion
A. Python unittest Testing
B. Python doctest Testing
Develop the Class
Exercise the Class
Update the Docstrings
Add the Test Framework
Mixed unittest and doctest
C. Java JUnit Testing
D. Python Epydoc Documentation
Basic Epytext Markup
Epytext Field Markup
Epydoc Example
E. Java javadoc Documentation
Basic Javadoc Markup
Javadoc Tags
Javadoc Example
Bibliography

List of Tables

17.1. 1-3-2-6 Betting States
21.1. Craps Game States
40.1. Blackjack Overall Collaboration
40.2. Blackjack Insurance Collaboration
40.3. Blackjack Fill-Hand Collaboration
41.1. Blackjack Player Strategy

List of Examples

1. Typical Python Example
1.1. Sample Java Execution
1.2. Informal Python Unit Test
1.3. Informal Java Unit Test
4.1. Object Identity
4.2. Java Simple toString Implementation
4.3. Java StringBuffer toString Implementation
4.4. Java MessageFormat toString Implementation
5.1. Java Bin Construction
5.2. Python Bin Construction
5.3. Python Appending Outcomes to A Tuple
5.4. Python String Conversion of a Tuple of Outcomes
6.1. Java Subclass Declaration
6.2. Python Subclass Declaration
7.1. Python Localization
13.1. Java Explicit Iteration Through a List of Integers
13.2. Java Compressed Iteration Through a List of Integers
14.1. Java Main Program
15.1. Java Sigma Iteration
15.2. Python Sigma Iteration
15.3. Java Sample Values by Iterator
15.4. Python Sample Values by Iterator
17.1. Java instanceof
17.2. Python player Module
17.3. Python Singleton With Class Variables
21.1. Python Frequency Distribution
21.2. Java Frequency Distribution
23.1. Java Default Method Arguments via Overloading
23.2. Python Default Method Arguments
23.3. Python Default Mutable Method Arguments
33.1. Java Creation of A Player
37.1. Python Constant Declaration
A.1. testCard.py
A.2. card.py
B.1. card.py - Initial
B.2. card.py - Revised
B.3. testCards.py
C.1. TestCard.java
C.2. Card.java
D.1. card.py
E.1. Card.py

List of Equations

15.1. Basic Summation
15.2. Summation with Half-Open Interval
15.3. Summing Elements of an Array, x
15.4. Mean
15.5. Standard Deviation

Preface

The present letter is a very long one, simply because I had no leisure to make it shorter.

--BLAISE PASCAL , Pensées, The Provincial Letters , provincial letter 16, p. 571.

Why Read This Book?

The coffee-shop answer is to provide the beginning designer with a sequence of interesting and moderately complex exercises in OO design.

Some software developers find themselves stalled when trying to do object-oriented (OO) design. As programmers, they've understood the syntax of a programming language, and pieced together small examples. However, it is often difficult to take the next step to becoming a designer. Because this transition from guided learning of language features to self-directed design work is often ignored, programmers are left to struggle through their first design projects without appropriate skills or support. While it is critically important to examine examples of good design, a finished product doesn't reveal the author's decision-making process that created the design.

The most notable consequence of this skills gap is the creation of software that is far more complex than necessary to effectively solve a given problem. This, in turn, leads to software with high maintenance costs stemming from the low quality. It also leads to an unfair indictment of OO technology; this is usually voiced as “we tried OO programming and it failed.

As programming team leaders, educators and consultants, we find that software development training is focused on the programming tools, but does not expose the process of creating a design. In the building trades, we would neither expect nor allow apprentice plumbers to design the sanitary sewage system for an urban office building. Yet, in too many Information Technology (IT) departments, software developers are expected to leap from basic training in their tools to application design.

To continue this rant, we also find that some managers are entrusted with significant projects, but are uncomfortable with OO design on modern high-performance hardware. They tend to focus their design energies on the kinds of software architectures that were appropriate when the enterprise owned a single computer, when 64 megabytes of memory was all the enterprise would ever need, and centralized disk storage was charged back to end user departments at a rate of pennies per track per month. In some organizations, there are a few enduring symptoms of this mind set in some of the ways that “end-user computing” is separated from “enterprise computing”; we relegate everything non-mainframe to second class status.

Management discomfort with OO technology surfaces in many ways. One shocking comment was that “no application needs more than six classes.” A consequence of this management attitude is an unrealistic expectation for schedule and the evolution of the deliverables.

The intent of this book is to help the beginning designer by giving you a sequence of interesting and moderately complex exercises in OO design. This book can also help managers develop a level of comfort with the process of OO software development. The applications we will build are a step above trivial, and will require some careful thought and design. Further, because the applications are largely recreational in nature, they are interesting and engaging. This book allows the reader to explore the processes and artifacts of OO design before project deadlines make good design seem impossible.

We hope to prevent managers from saying the following: “We had a good design, but were forced to compromise it to meet our schedule.” As consultants, we find this to be a sad statement of management's emphasis of one near-term goal over long-term value. In one case, this was the result of a series of poorly-informed management decisions compounded on weak design skills. One of the root causes was the inability of the designers and managers to agree to a suitable course of action when a new kind of requirement made devastating changes to an existing design. We believe that more informed managers would have made a decision that created better long-term value.

Audience

Our primary audience is programmers who are new to OO programming. Have you found your first exposure to objects to be more distasteful than empowering? Why does this happen? Some instructors launch into extensive presentations on object orientation before getting to the language fundamentals, leaving students lost as to how they will accomplish the noble and lofty goals of OO. Other instructors leave OO for last, exposing the procedural side of the language first, and treating objects as a kind of add-on. This leaves students feeling that objects are optional. Additionally, some very skilled instructors are not skilled developers, and will often show examples that don't reflect currently accepted best practices.

Our audience has an exposure to the language, but needs more time to understand objects and object-orientation. We want to provide exercises that have four key features: just complex enough to require careful design work, just fun enough to be engaging, easy enough that results are available immediately, and can be built in simple stages.

In our effort to support the beginning student, we'll provide a few additional details on language features. We'll mark these as “Tips” for the new programmer, and qualify the tip by language. For more advanced students, these tips will be review material. We will not provide a thorough background in any programming language. The student is expected to know the basics of the language and tools.

Helpful additional skills include using one of the various unit test and documentation frameworks available. We've included information in the appendices.

Instructors are always looking for classroom projects that are engaging, comprehensible, and focus on perfecting language skills. Many real-world applications require considerable explanation of the problem domain; the time spent reviewing background information detracts from the time available to do the relevant programming. While all application programming requires some domain knowledge, the idea behind these exercises is to pick a domain that many people know a little bit about. This allows an instructor to use some or all of these exercises without wasting precious classroom time on incidental details required to understand the problem.

This book assumes an introductory level of skill in an OO programming language. We provide specific examples in Java (at least version 1.4) and Python (at least version 2.5). Student skills we expect include the following.

  • Create source files, compile and run application programs. While this may seem obvious, we don't discuss any integrated development environment (IDE). We have to assume these basic skills are present.
  • Use of the core procedural programming constructs: variables, statements, exceptions, functions. We will not, for example, spend any time on design of loops that terminate properly.
  • Some exposure to class definitions and subclasses. This includes managing the basic features of inheritance, as well as overloaded method names. We will avoid Python-unique features like multiple inheritance and callable objects, and focus on that subset of Python features that map directly to Java. For the Python equivalent of overloaded methods, we will assume that Python programmers can make use of default parameter values and named parameters.
  • Some exposure to the various collections frameworks. For Java programmers, this means the classes in the java.util package. For Python programmers, this means the built-in sequence and mapping types.
  • Optionally, some experience with a unit testing framework. See the appendices for supplemental exercises if you aren't familiar with Python's unittest or doctest or Java's JUnit.

  • Optionally, some experience writing formal documentation. For Java programmers, this means javadoc comments. For Python programmers, this often means Epydoc or a similar documentation package. See the appendices for supplemental exerises if you aren't familiar with formal, deliverable documentation.

Organization of This Book

This book presents a series of exercises to build simulations of the common, popular casino table games: Roulette, Craps and Blackjack. Each simulation can be extended to include variations on the player's betting system. With a simple statistical approach, we can show the realistic expectations for any betting system. Each of these games has a separate part in this book. Each part consists of a number of individual exercises to build the entire simulation. The completed project results in an application that can provide simple tabular results that shows the average losses expected from each betting strategy.

The interesting degree of freedom in each of the simulations is the player's betting strategy. The design will permit easy adaptation and maintenance of the player's strategies. The resulting application program can be extended by inserting additional betting systems, which allows exploration of what (if any) player actions can minimize the losses.

For Roulette, we proceed slowly, building up the necessary application one class at a time. Since this is the simplest game, the individual classes reflect that simplicity. We focus on isolation of responsibilities, creating a considerable number of classes. The idea is to build skills in object design by applying those skills to a number of classes.

The first chapter of the part provides details on the game of Roulette and the problem that the simulation solves. The second chapter is an overview of the solution, setting out the highest-level design for the application software. This chapter includes a technique for doing a walk-through of the design to be confident that the design will actually solve the problem.

Each of the remaining sixteen chapters is a design and programming exercise to be completed by the student. Plus or minus a Frequently Asked Questions (FAQ) section, each chapter has the same basic structure: an overview of the components being designed, some design details, and a summary of the deliverables to be built. The overview section presents some justification and rationale for the design. This material should help the student understand why the particular design was chosen. The design section provides a more detailed specification of the class or classes to be built. This will include some technical information on Java or Python implementation techniques.

For Craps, we build on the design patterns from Roulette. Craps, however, is a stateful game, so there is a more sophisticated design to handle the interactions between dice, game state and player. We exploit the State design pattern to show how the design pattern can be applied to this simple situation.

The first chapter is background information on the game of Craps, and the problem that the simulation solves. The second chapter is an overview of the solution, setting out the highest-level design for the application software. This chapter also provides a walk-through of the design.

Each of the remaining eleven chapters is an exercise to be completed by the student. Each chapter has the same basic structure: an overview of the component being designed, some design details, and a summary of the deliverables to be built.

The most sophisticated game that we cover here is Blackjack. The game states are more sophisticated than Craps since the available betting opportunities can change with split hands. Further, the player has two kinds of choices: betting opportunities, plus play opportunities. This makes the player's strategy considerably more complex. In casino gift shops, you can buy small summary cards that enumerate all possible game states and responses. The more advanced student can tackle these sophisticated betting strategies. For the less advanced student we will simplify the strategies down to some simpler conditions.

The first two chapters are background information on the game of Blackjack, the problem that the simulation solves, and an overview of the solution, setting out the highest-level design for the application software. Each of the remaining six chapters is an exercise to be completed by the student. Since this is more advanced material, and builds on previous work, this part has many simple deliverables compressed into the individual chapters.

Why This Subject?

Casino table games may seem like an odd choice of subject matter for programming exercises. We find that casino games have a number of advantages for teaching OO design and OO programming.

  • Casino games have an almost ideal level of complexity. If they were too simple, the house edge would be too obvious and people would not play them. If they were too complex, people would not enjoy them as simple recreation. Years (centuries?) of experience in the gaming industry has fine-tuned the table games to fit nicely with the limits of our human intellect.

  • Simulation of discrete phenomena lies at the origin of OO programming. We have found it easier to motivate, explain and justify OO design when solving simulation problems. The student can then leverage this insight into other applications of OO programming for more common transactional applications.

  • The results are sophisticated but easy to interpret. Probability theory has been applied by others to develop precise expectations for each game. These simulations should produce results consistent with the known probabilities. This book will skim over the probability theory in order to focus on the programming. For a few exercises, the theoretical results will be provided to serve as checks on the correctness of the student's work.

This book does not endorse casino gaming. Indeed, one of the messages of this book is that all casino games are biased against the player. Even the most casual study of the results of the exercises will allow the student to see the magnitude of the house edge in each of the games presented.

Programming Style

We have to adopt a style for each of the languages we're presenting. We won't present a complete set of coding standards; we will omit a number of issues that should be standardized. Some IT shops have documents they call “coding standards”, but are little more than descriptive style guides. What follows is not this kind of style guide; instead, it is some justification of the style we use for the examples in this book.

Just to continune this rant, we find that source code examples speak louder than any gratuitously detailed “specification” of the desired style. We find that some IT organizations waste time trying to write definitions of the preferred style. A good example trumps the description of the example. In particular, as consultants, we are often asked to provide standards to an inexperienced team of programmers. While the programmers only look at the examples (often cutting and pasting them), some managers prefer to spend money on empty verbiage peripheral to the useful example.

We use Java-centric terminology -- “field” and “method” -- throughout the book. Occaisionally, we will emphasize the differences between Java and Python by using the Python terms “attribute”, “instance variable” or “method function”.

We avoid using complex prefixes for variable names. In particular, we find prefixes to be little more than visual clutter. For example, an integer parameter with the amount of a bet might be called pi_amount where the prefix indicates the scope (p for a parameter) and type (i for an integer).

This style of name is only appropriate for primitive types, and doesn't address complex data structures well at all. How does one name a parameter that is a LinkedList of Sets of Outcomes? In Java programs, the variables are formally declared, therefore, we find that we don't need additional cues for their data type.

In some cases, prefixes are used to denote the scope of an instance variables. Variable names might include a cryptic one-letter prefix like “f” to denote an instance variable; sometimes programmers will use “my” or “the” as an English-like prefix. We prefer to reduce clutter. In Java, we have the qualifier this. available to disambiguate parameter from instance variable references. In Python, instance variables are always qualified, typically by self., making the scope very clear.

Conventions Used in This Book

Code examples will be minimal, and will include Python and Java. Here is a Python example.

Example 1. Typical Python Example

combo = { } 1
for i in range(1,7):
    for j in range(1,7):
        roll= i+j
        combo.setdefault( roll, 0 ) 2
        combo[roll] += 1
for n in range(2,13):
    print "%d %.2f%%" % ( n, combo[n]/36.0 ) 3
1

This creates a Python dictionary, a map from key to value. If we initialize it with something like the following: combo = dict( [ (n,0) for n in range(2,13) ] ), we don't need the setdefault function call below.

2

This assures that the rolled number exists in the dictionary with a default frequency count of 0.

3

Print each member of the resulting dictionary. Something more obscure like [ (n,combo[n]/36.0) for n in range(2,13)] is certainly possible.

The output from the above program will be shown as follows:

2 0.03%
3 0.06%
4 0.08%
5 0.11%
6 0.14%
7 0.17%
8 0.14%
9 0.11%
10 0.08%
11 0.06%
12 0.03%

Tool completed successfully

We will use the following type styles for references to a specific Class, method, or variable.

Most of the design specifications will provide Java-style method and variable descriptions. Python doesn't use type specifications, and Python programmers will have to translate the Java specifications into Python by removing the type names.

Tip

There will be design tips, and warnings, in the material for each exercise. These reflect considerations and lessons learned that aren't typically clear to starting OO designers.

Acknowledgements

We would like to thank Chuck Pyrak for putting us up to this. His idea of a One Room Schoolhouse to teach Java to an audience at multiple skill levels was a great idea. Additionally, our colleagues who collaborated through BLOKI brought infinte wisdom and insight to a complex and difficult project.

Thanks to Dion Dock for detailed, thoughtful comments.

Chapter 1. Foundations

We'll set our goal by presenting several elements that make up a complete problem statement: a context in which the problem arises, the problem, the forces that influence the choice of solution, the solution that balances the forces, and some consequences of the chosen solution.

Based on the problem statement, we'll present the high-level use case that this software implements. The use case is almost too trivial to bother defining. However, we have seen many projects run aground because they lacked even the most rudimentary description of the actor, the system and how the system helps the actor create value.

We will summarize the approach to the solution, describing the overall strategy that we will follow. This is a kind of overall design pattern that we'll use to establish some areas of responsibility.

We will also describe the technical foundations. In this case, they are not terribly complex, but this is an important part of describing any software solution, no matter how simple.

We will dance around the methodology issue. Our intent is not to sell a particular methodology, but to provide some perspective on how we broke the work into manageable pieces.

Finally, we'll present some important parts of getting started on the solution. These are more specific, technical considerations that define common aspects of our approach.

Problem Statement

Context. Our context is the “classic” casino table games played against the house, including Roulette, Craps and Blackjack. We want to explore the consequences of various betting strategies for these casino games. Questions include “How well does the Cancellation strategy work?” “How well does the Martingale strategy works for the Come Line odds bet in Craps?” “How well does this Blackjack strategy I found on the Internet compare with the strategy card I bought in the gift shop?

A close parallel to this is exploring variations in rules and how these different rules have an influence on outcomes. Questions include “What should we do with the 2x and 10x odds offers in Craps?” “How should we modify our play for a single-deck Blackjack game with 6:5 blackjack odds?

Our context does not include exploring or designing new casino games. Our context also excludes multi-player games like poker. We would like to be able to include additional against-the-house games like Pai Gow Poker, Caribbean Stud Poker, and Baccarat.

Problem. Our problem is to answer the following question: For a given game, what player strategies produce the best results?

Forces. There are a number of forces that influence our choice of solution. First, we want an application that is relatively simple to build. Instead of producing an interactive user interface, we will produce raw data and statistical summaries. If we have little interaction, a command-line interface will work perfectly. We can have the user specify a player strategy and the application respond with a presentation of the results. If the results are tab-delimited, they can be pasted into a spreadsheet for further analysis.

Another force that influences our choice of solution is the need to be platform and language agnostic. In this case, we have selected an approach that works well on POSIX-compliant operating systems (i.e., Linux, MacOS, and all of the proprietary UNIX variants), and also works on non-compliant operating systems (i.e., all of the Windows versions). We have chosen two OO languages that work identically on both platform families: Java and Python.

We also need to strike a balance between interesting programming, probability theory and statistics. On one hand, the simplicity of these games means that complete analyses have been done using probability theory. However, that's not a very interesting programming exercise, so we will ignore the pure probability theory route in favor of learning OO design and programming.

Another force is the desire to reflect actual game play. While a long-running simulation of thousands of invidual cycles of play will approach the theoretical results, people typically don't spend more than a few hours at a table game. If, for example, a Roulette wheel is spun once each minute, a player is unlikely to see more that 480 spins in an 8-hour evening at a casino. Additionally, many players have a fixed budget, and the betting is confined by table limits. Finally, we need to address the subject of “money management”: a player may elect to stop playing when they are ahead. This structures our statistical analysis: we must simulate sessions of play that are limited in time, the amount lost and the amount won.

Solution. Our overall goal is to produce an application that allows us to experiment with different casino game betting strategies. We'll build a simple, command-line simulator that provides a reliable, accurate model of the game. We need to be able to easily pick one of a variety of player betting strategies, play a number of simulated rounds of the game, and produce a statistical summary of the results of that betting strategy.

Consequences. One of the most important consequences of our solution is that we will build an application into which new player betting strategies can be inserted. Clever gamblers invent new strategies all the time. We will not know all of the available strategies in advance, so we will not be able to fully specify all of the various design details in advance. Instead, we will find ourselves reworking some parts of the solution, to support a new player betting strategy. This forces us to take an agile approach to the design and implementation.

The Use Case

We have a single, small use case. We have some opinions on what makes a useful use case in our Soapbox on Use Cases. There is a single actor, the “investigator”. The actor's goal is to see the expected results of using a particular strategy for a particular game. The typical scenario is the following.

Procedure 1.1. Basic Use Case

  1. Actor

    Specifies which game and betting strategy to test. The strategy may need additional parameters, like an initial budget, or stake. The game may require additional parameters, like betting limits.

  2. System

    Responds with a statistical summary of the outcomes after a fixed number of cycles (spins, or throws or hands). The number of cycles needs to be small (on the order of 200, to reflect only a few hours of play).

This system use case is embedded in an overall cycle of investigation that forms a kind of business use case. From this overall view, the actor's goal is to find an optimal strategy for a given game. The procedure includes the following steps.

Procedure 1.2. Business Use Case

  1. Actor

    Researches alternative strategies. Uses IDE to build new classes.

  2. IDE

    Creates new classes for the simulator.

  3. Actor

    Uses the Basic Use Case. Runs the simulator with selection of game and strategy.

  4. Simulator

    Responds with statistical results.

  5. Actor

    Evaluates the results. Uses a spreadsheet or other tool for analysis and visualization.

We are addressing parts of this larger business use case. While not describing the detailed “how-to” of using the IDE to build the new classes, we will address the design of those new classes. Additionally, we won't address how to analyze the results.

Solution Approach

From reading the problem and use case information, we can identify at least the following four general elements to our application.

  • The game being simulated. This includes the various elements of the game: the wheel, the dice, the cards, the table and the bets.
  • The player being simulated. This includes the various decisions the player makes based on the state of the game, and the various rules of the betting system the player is following.
  • The statistics being collected.
  • An overall control component which processes the game, collects the statistics and writes the details or the final summary.

When we look at common design patterns, the Model-View-Control pattern often helps to structure applications. A more sophisticated, transactional application may require a more complex structure. However, in this case, the game, the player, and the statistics are the model. The command line selection of player and the reporting of raw data is the view. The overall control component creates the various objects to start the simulation.

While interesting, we will not pursue the design of a general-purpose simulation framework. Nor will we use any of the available general frameworks. While these are handy and powerful tools, we want to focus on developing application software “from scratch” as a learning exercise.

Our solution will depend heavily on desktop integration: the actor will use their IDE to create a strategy and build a new version of the application program. Once the application is built, the actor can run the application from the command line, collecting the output file. The statistical results file can be analyzed using a spreadsheet application. There are at least three separate application programs involved: the IDE (including editor and compiler), the simulator, the spreadsheet used for analysis.

A typical execution of the simulator will look like the following example.

Example 1.1. Sample Java Execution

java 1 casino.MainCrapsSim 2 --Dplayer.name="Player1326" 3 >details.log
1

We select the main simulator control using the package casino and the class MainCrapsSim.

2

We define the player to use Player1326. The main method will use this parameter to create objects and execute the simulation.

3

We collect the raw data in a file named details.log.

We are intentionally limiting our approach to a simple command-line application using the default language libraries. Avoiding additional libraries assures a “lowest-common denominator” multi-platform application. For Java, this standard is the J2SE set of libraries; we won't use any J2EE extensions. For Python, it is the base installation.

There are a number of more technical considerations that we will expand in the section called “Deliverables”. These include the use of an overall simulation framework and an approach for unit testing.

Among the topics this book deals with in a casual -- possibly misleading -- manner are probability and statitics. Experts will spot a number of gaps in our exposition. For example, there isn't a compelling need for simulation of the simpler games of Craps and Roulette, since they can be completely analyzed. However, our primary objective is to study programming, not casino games, therefore we don't mind solving known problems again. We are aware that our statistical analysis has a number of deficiencies. We will avoid any deeper investigation into statistics.

Methodology, Technique and Process

We want to focus on technical skills; we won't follow any particular software development methodology too closely. We hesitate to endorse a specific methodology; doing so inevitably alienates readers who embrace a different methodology. To continue this rant, we find that almost everyone has an existing notion of the proper way to organize software development work. This leads to the common practice of customizing methodologies, in most cases without a deep background in the methodology or the changes being made.

We prefer to lift up a few techniques which have a great deal of benefit.

  • Incremental Development. Each chapter is a "sprint" that produces some collection of deliverables. Each part is a complete release.

  • Unit Testing. We don't dwell on test-driven development, but each chapter explicitly requires unit tests for the classes built.

  • Embedded Documentation. We provide appendices on how to use Epydoc or javadoc to create usable API documents.

The exercises are presented as if we are doing a kind of iterative design with very, very small deliverables. We present the exercises like this for a number of reasons.

First, we find that beginning designers work best with immediate feedback on their design decisions. While we present the design in considerable detail, we do not present the final code. Programmers new to OO design will benefit from repeated exposure to the transformation of problem statement through design to code.

Second, for iterative or agile methodologies, this presentation parallels the way software is developed. A project manager may use larger collections of deliverables. However, the actual creation of functional source eventually decomposes into classes, fields and methods. For project managers, this exposition will help them see where and how rework can occur; giving them a chance to plan for the kind of learning that occur in most projects.

Third, for project teams using a strict waterfall methodology -- with all design work completed before any programming work -- the book can be read in a slightly different order. From each exercise chapter, read only the overview and design sections. From that information, integrate the complete design. Then proceed through the deliverables sections of each chapter, removing duplicates and building only the final form of the deliverables based on the complete design. This will show how design rework arises as part of a waterfall methodology.

We try to embrace a variety of deliverables in addition to working source code. In particular, we expect unit test classes in addition to the functional classes. Further, we expect Javadoc comments or Python docstrings in each class. Additionally, we will need to write some demonstration classes in order to determine the exact sequence of random numbers that are created from a specific seed; these are not deliverable as part of the final application, but will be necessary to construct proper unit tests.

This section addresses a number of methodology or process topics:

  • Quality, in general

  • Rework

  • Technical Decision-Making

  • Reuse

  • Design Patterns

On Quality

Our approach to overall quality assurance is relatively simple. We feel that a focus on unit testing and documetation covers most of the generally accepted quality factors. The Software Engineering Institute (SEI) published a quality measures taxonomy. While officially "legacy", it still provides an exhaustive list of quality attributes. These are broadly grouped into five categories. Our approach covers most of those five categories reasonably well.

  • Need Satisfaction. Does the software meet the need? We start with a problem statement, define the use case, and then write software which is narrowly focused on the actor's needs. By developing our application in small increments, we can ask ourself at each step, "Does this meet the actor's needs?" It's fairly easy to keep a software development project focused when we have use cases to describe our goals.

  • Performance. We don't address this specifically in this book. However, the presence of extensive unit tests allows us to alter the implemention of classes to change the overall performance of our application. As long as the resulting class still passes the unit tests, we can develop numerous alternative implementations to optimize speed, memory use, input/output, or any other resource.

  • Maintenance. Software is something that is frequently changed. It changes when we uncover bugs. More commonly, it changes when our understanding of the problem, the actor or the use case changes. In many cases, our initial solution merely clarifies the actor's thinking, and we have to alter the software to reflect a deeper understanding of the problem.

    Maintenance is just another cycle of the iterative approach we've chosen in this book. We pick a feature, create or modify classes, and then create or modify the unit tests. In the case of bug fixing, we often add unit tests to demonstrate the bug, and then fix our classes to pass the revised unit tests.

  • Adaptation. Adaptation refers to our need to adapt our software to changes in the environment. The environment includes interfaces, the operating system or platform, even the number of users is part of the environment. When we address issues of interoperability with other software, portability to new operating systems, scalability for more users, we are addressing adaptation issues.

    We chose Python and Java to avoid having interoperability and portability issues — these platforms give admirable support for many scalability issues. Generally, a well-written piece of software can be reused. While this book doesn't focus on reuse, Java and Python are biased toward writing reusable software.

  • Organizational. There are some organizational quality factors: cost of ownership and productivity of the developers creating it. We don't address these directly. Our approach, however, of developing software incrementally often leads to good developer productivity.

Our approach (Incremental, Unit Testing, Embedded Documentation) assures high quality in four of the five quality areas. Incremental development is a way to focus on need satisfaction. Unit testing helps us optimize resource use, and do maintenance well. Our choices of tools and platforms help us address adaptation.

The organizational impact of these techniques isn't so clear. It is easy to mis-manage a team and turn incremental development into a quagmire of too much planning for too little delivered software. It is all too common to declare that the effort spent writing unit test code is "wasted".

Ultimately, this is a book on OO design. How people organize themselves to build software is beyond our scope.

On Rework

In the section called “Problem Statement”, we described the problem. In the section called “Solution Approach”, we provided an overview of the solution. The following parts will guide you through an incremental design process; a process that involves learning and exploring. This means that we will coach you to build classes and then modify those classes based on lessons learned during later steps in the design process. See our Soapbox on Rework for an opinion on the absolute necessity for design rework.

We don't simply present a completed design. We feel that it is very important follow a realistic problem-solving trajectory so that beginning designers are exposed to the decisions involved in creating a complete design. In our experience, all problems involve a considerable amount of “learn as you go”. We want to reflect this in our series of exercises. In many respects, a successful OO design is one that respects the degrees of ignorance that people have when starting to build software. We will try to present the exercises in a way that teaches the reader how to manage ignorance and still develop valuable software.

On Decision-Making

Many of the chapters will include some lengthy design decisions that appear to be little more than hand-wringning over nuances. While this is true to an extent, we need to emphasize our technique for doing appropriate hand-wringing over OO design. We call it “Looking For The Big Simple”, and find that managers don't often permit the careful enumeration of all the alternatives and the itemization of the pros and cons of each choice. We have worked with managers who capriciously play their “schedule” or “budget” trump cards, stopping useful discussion of alternatives. This may stem from a fundamental discomfort with the technology, and a consequent discomfort of appearing lost in front of team members and direct reports. Our suggestion in this book can be summarized as follows:

Important

Good OO design comes from a good process for technical decision-making.

First, admit what we don't know, and then take steps to reduce our degrees of ignorance.

Which means not saying “work smarter not harder” unless we also provide the time and budget to actually get smarter. The learning process, as with all things, must be planned and managed. Our lesson learned from Blaise Pascal is that a little more time spent on design can result in considerable simplification, which will reduce development and maintenance costs.

It's also important to note that no one in the real world is omniscient. Some of the exercises include intentional dead-ends. As a practical matter, we can rarely foresee all of the consequences of a design decision.

On Reuse

While there is a great deal of commonality among the three games, the exercises do not start with an emphasis on constructing a general framework. We find that too much generalization and too much emphasis on reuse is not appropriate for beginning object designers. See Soapbox on Reuse for an opinion on reuse. Additionally, we find that projects that begin with too-lofty reuse goals often fail to deliver valuable solutions in a timely fashion. We prefer not to start out with a goal that amounts to boiling the ocean to make a pot of tea.

On Design Patterns

These exercises will refer to several of the “Gang of Four” design patterns in Gamma95. The Design Patterns book is not a prerequisite; we use it as reference material to provide additional insight into the design patterns used here. We feel that use of common design patterns significantly expands the programmer's repertoire of techniques. We note where they are appropriate, and provide some guidance in their implementation.

In addition, we reference several other design patterns which are not as well documented. These are, in some cases, patterns of bad design more than patterns of good design.

Deliverables

Each chapter defines the classes to be built and the unit testing that is expected. A third deliverable is merely implied. The purpose of each chapter is to write the source files for one or more classes, the source files for one or more unit tests, and assure that a minimal set of API documentation is available.

Source Files. The source files are the most important deliverable. In effect, this is the working application program. Generally, you will be running this application from within your Integrated Development Environment (IDE). You may want to create a stand-alone program.

In the case of Java, we might also deliver the collection of class files. Additionally, we might bundle the class files into an executable JAR file. The source is the principle deliverable; anyone should be able to produce class and JAR files using readily available tools.

In the case of Python, it's the packages of .py files. There really isn't much more to deliver. The interested student might want to look at the Python distutils and setuptools to create a distribution kit, or possibly a Python egg file.

Unit Test Files. The deliverables section of each chapter summarizes the unit testing that is expected, in addition to the classes to be built. We feel that unit testing is a critical skill, and emphasize it throughout the inividual exercises. We don't endorse a particular technology for implementing the unit tests. There are several approaches to unit testing that are in common use.

  • Formal Unit Tests. For formal testing of some class, X, we create a separate class, TestX, which creates instances of X and exercises those instances to be sure they work. In Java, this is often done with JUnit. In Python, the unittest module is the mechanism for doing formal unit tests. Additionally, many Python developers also use the doctest module to assure that the sample code in the docstrings is actually correct. We cover these technologies in the appendices.

  • Informal Unit Tests. This is often done by include a main program in the class definition file. We'll show two examples of this. By following the standard unit test naming conventions, these informal tests can be easily upgraded to more formal JUnit or PyUnit tests.

In Python, an informal is done by including the following block of code section in a module file. Each test method must have a name that starts with “test” to be compatible with the Python PyUnit module.

Example 1.2. Informal Python Unit Test

def testX():
    test procedure X goes here
def testY():
    test procedure Y goes here

if __name__ == "__main__":
    testX()
    testY()

In Java, this is done by putting public static void main(String[] args); in the class source file. Each test method must have a name that starts with “test” to be compatible with the Java JUnit package.

Example 1.3. Informal Java Unit Test

class SomeClass {

    public static void main( String[] args ) {
        SomeClass x= new SomeClass();
        x.testX();
        x.testY();
    }

    public void testX() {
        test procedure X goes here
    }
    public void testY() {
        test procedure Y goes here
    }

}

Documentation. The job isn't over until the paperwork is done. In the case of Java and Python, the internal documentation is generally built from specially formatted blocks of comments within the source itself. Java programmers can use the javadoc tool to create documentation from the program source. Python programmers can use Epydoc to create similar documentation.

Roulette

This part describes the game of Roulette. Roulette is a stateless game with numerous bets and a very simple process for game play.

The chapters of this part present the details on the game, an overview of the solution, and a series of sixteen exercises to build a complete simulation of the game, plus a variety of betting strategies. Each exercise chapter builds at least one class, plus unit tests; in some cases, this includes rework of previous deliverables.

Table of Contents

2. Roulette Details
Roulette Game
Available Bets
Some Betting Strategies
3. Roulette Solution Overview
Preliminary Survey of Classes
A Walkthrough of Roulette
Questions and Answers
4. Outcome Class
Overview
Design
Fields
Constructors
Methods
Deliverables
5. Bin Class
Overview
Java Collections
Python Collections
Questions and Answers
Java Design
Fields
Constructors
Methods
Python Design
Fields
Constructors
Methods
Deliverables
6. Wheel Class
Overview
Design
Fields
Constructors
Methods
Deliverables
7. Bin Builder Class
Overview
Algorithms
Design
Constructors
Methods
Deliverables
8. Bet Class
Overview
Questions and Answers
Design
Fields
Constructors
Methods
Deliverables
9. Roulette Table Class
Overview
Design
InvalidBet Exception
Table Class
Deliverables
10. Roulette Game Class
Overview
Design
Passenger57 Class
Roulette Game Class
Questions and Answers
Deliverables
11. Review of Testability
Overview
Questions and Answers
Design
Wheel Rework
Java NonRandom Class
Python NonRandom Class
Deliverables
12. Player Class
Overview
Design
Player superclass
Martingale Player
Deliverables
13. Overall Simulation Control
Overview
Design
Fields
Constructors
Methods
Deliverables
14. Player SevenReds
Overview
Design
Fields
Methods
Deliverables
15. Statistical Measures
Overview
Some Foundations
Statistical Algorithms
Design
Constructors
Methods
Deliverables
16. Player Random
Overview
Design
Fields
Constructors
Methods
Deliverables
17. Player 1-3-2-6
Overview
Questions and Answers
Design
Player1326 State
Player1326 No Wins
Player1326 One Win
Player1326 Two Wins
Player1326 No Wins
Player1326
Deliverables
Advanced Exercise
Singleton Design in Java
Singleton Design in Python
18. Player Cancellation
Overview
Design
Fields
Constructors
Methods
Deliverables
19. Player Fibonacci
Overview
Design
Fields
Constructors
Methods
Deliverables
20. Conclusion

Chapter 2. Roulette Details

In the first section we will present a summary of the game of Roulette as played in most American casinos.

We will follow this with a review the various bets available on the Roulette table in some depth. The definition of the various bets is an interesting programming exercise, and the first four exercise chapters will focus on this.

Finally, we will describe some common betting strategies that we will simulate. The betting strategies are interesting and moderately complex algorithms for changing the amount that is used for each bet in an attempt to recoup losses.

Roulette Game

The game of Roulette centers around a wheel with thirty-eight numbered bins. The numbers include 0, 00 (double zero), 1 through 36. The table has a surface marked with spaces on which players can place bets. The spaces include the 38 numbers, plus a variety of additional bets, which will be detailed below. After the bets are placed by the players, the wheel is spun by the house, a small ball is dropped into the spinning wheel; when the wheel stops spinning, the ball will come to rest in one of the thirty-eight numbered bins, defining the winning number. The winning number and all of the related winning bets are paid off; the losing bets are collected. Roulette bets are all paid off using odds, which will be detailed with each of the bets, below.

The numbers from 1 to 36 are colored red and black in an arbitrary pattern. They fit into various ranges, as well as being even or odd, which defines many of the winning bets related to a given number. The numbers 0 and 00 are colored green, they fit into none of the ranges, and are considered to be neither even nor odd. There are relatively few bets related to the zeroes. The geometry of the betting locations on the table defines the relationships between number bets.

Note

There are slight variations in Roulette between American and European casinos. We'll focus strictly on the American version.

Available Bets

There are a variety of bets available on the Roulette table. Each bet has a payout, which is stated as n:1 where n is the multiplier that defines the amount won based on the amount bet.

A $5 bet at 2:1 will win $10. After you are paid, there will be $15 sitting on the table, your original $5 bet, plus your $10 additional winnings.

Note

Not all games state their odds using this convention. Some games state the odds as “2 for 1”. This means that the total left on the table after the bets are paid will be two times the original bet. So a $5 bet will win $5, there will be $10 sitting on the table.

Roulette Table Layout

Roulette Table Layout

The table is divided into two classes of bets. The “inside” bets are the 38 numbers and small groups of numbers; these bets all have relatively high odds. The “outside” bets are large groups of numbers, and have relatively low odds. If you are new to casino gambling, see Odds and Payouts for more information on odds and why they are offered.

  • A “straight bet” is a bet on a single number. There are 38 possible bets, and they pay odds of 35 to 1. Each bin on the wheel pays one of the straight bets.
  • A “split bet” is a bet on an adjacent pair of numbers. It pays 17:1. The table layout has the numbers arranged sequentially in three columns and twelve rows. Adjacent numbers are in the same row or column. The number 5 is adjacent to 4, 6, 2, 8; the number 1 is adjacent to 2 and 4. There are 114 of these split bet combinations. Each bin on the wheel pays from two to four of the available split bets. Any of two bins can make a split bet a winner.
  • A “street bet” includes the three numbers in a single row, which pays 11:1. There are twelve of these bets on the table. A single bin selects one street bet; any of three bins make a street bet a winner.
  • A square of four numbers is called a “corner bet” and pays 8:1. There are 72 of these bets available.
  • At one end of the layout, it is possible to place a bet on the Five numbers 0, 00, 1, 2 and 3. This pays 6:1. It is the only combination bet that includes 0 or 00.
  • A “line bet” is a six number block, which pays 5:1. It is essentially two adjacent street bets. There are 11 such combinations.

The following bets are the “outside” bets. Each of these involves a group of twelve to eighteen related numbers. None of these outside bets includes 0 or 00. The only way to bet on 0 or 00 is to place a straight bet on the number itself, or use the five-number combination bet.

  • Any of the three 12-number ranges (1-12, 13-24, 25-36) pays 2:1. There are just three of these bets.
  • The layout offers the three 12-number columns at 2:1 odds. All of the numbers in a given column have the same remainder when divided by three. Column 1 contains 1, 4, 7, etc., all of which have a remainder of 1 when divided by 3.
  • There are two 18-number ranges: 1-18 is called low, 19-36 is called high. These are called even money bets because they pay at 1:1 odds.
  • The individual numbers are colored red or black in an arbitrary pattern. Note that 0 and 00 are colored green. The bets on red or black are even money bets, which pay at 1:1 odds.
  • The numbers (other than 0 and 00) are also either even or odd. These bets are also even money bets.

Some Betting Strategies

Perhaps because Roulette is a relatively simple game, elaborate betting systems have evolved around it. Searches on the Internet turn up a many copies of the same basic descriptions for a number of betting systems. Our purpose is not to uncover the actual history of these systems, but to exploit them for simple OO design exercises. Feel free to research additional betting systems or invent your own.

The Martingale system starts with a base wagering amount, w, and a count of the number of losses, c, initially 0. Each loss doubles the bet; any given spin will place an amount of w*2 c on a 1:1 proposition (for example, red). When a bet wins, the loss count is reset to zero; resetting the bet to the base amount, w. This assures that a single win will recoup all losses.

Note that the casinos effectively prevent successful use of this system by imposing a table limit. At a $10 Roulette table, the limit may be as low as $1,000. A Martingale bettor who lost six times in a row would be facing a $640 bet, and after the seventh loss, their next bet would exceed the table limit. At that point, the player is unable to recoup all of their losses. Seven losses in a row is only a 1 in 128 probability; making this a relatively likely situation.

Another system is to wait until some number of losses have elapsed. For example, wait until the wheel has spun seven reds in a row, and then bet on black. This can be combined with the Martingale system to double the bet on each loss as well as waiting for seven reds before betting on black.

Another betting system is called the 1-3-2-6 system. The idea is to avoid the doubling of the bet at each loss and running into the table limit. Rather than attempt to recoup all losses in a single win, this system looks to recoup all losses by waiting for four wins in a row. The sequence of numbers (1, 3, 2 and 6) are the multipliers to use when placing bets after winning. At each loss, the sequence resets to the multiplier of 1. At each win, the multiplier is advanced through the sequence. After one win, the bet is now 3w. After a second win, the bet is reduced to 2w, and the winnings of 4w are “taken down” or removed from play. In the event of a third win, the bet is advanced to 6w. Should there be a fourth win, the player has doubled their money, and the sequence resets.

Another method for tracking the lost bets is called the Cancellation system or the Labouchere system. The player starts with a betting budget allocated as a series of numbers. The usual example is 1, 2, 3, 4, 5, 6, 7, 8, 9. Each bet is sum of the first and last numbers in the last. In this case 1+9 is 10. At a win, cancel the two numbers used to make the bet. In the event of all the numbers being cancelled, reset the sequence of numbers and start again. For each loss, however, add the amount of the bet to the end of the sequence as a loss to be recouped.

Here's an example of the cancellation system using 1, 2, 3, 4, 5, 6, 7, 8, 9.

  1. Bet 1+9. A win. Cancel 1 and 9 leaving 2, 3, 4, 5, 6, 7, 8.

  2. Bet 2+8. A loss. Add 10 leaving 2, 3, 4, 5, 6, 7, 8, 10.

  3. Bet 2+10. A loss. Add 12 leaving 2, 3, 4, 5, 6, 7, 8, 10, 12.

  4. Bet 2+12. A win. Cancel 2 and 12 leaving 3, 4, 5, 6, 7, 8, 10.

  5. Next bet will be 3+10.

A player could use the Fibonacci Sequence to structure a series of bets in a kind of cancellation system. The Fibonacci Sequence is 1, 1, 2, 3, 5, 8, 13, .... At each loss, the sum of the previous two bets -- the next number in the sequence -- becomes the new bet amount. In the event of a win, the last two numbers in the sequence are removed. This allows the player to easily track our accumulated losses, with bets that could recoup those losses through a series of wins.

Chapter 3. Roulette Solution Overview

The first section is a survey of the classes gleaned from the general problem statement in Problem Statement as well as the problem details in Roulette Details. This survey is drawn from a quick overview of the key nouns in these sections.

Given this survey of the candidate classes, the second section is a walkthrough of the possible design that will refine the definitions, and give us some assurance that we have a reasonable architecture. We will make some changes to the preliminary class list, revising and expanding on our survey.

We will also include a number of questions and answers about this preliminary design information. This should help clarify the design presentation and set the stage for the various development exercises in the chapters that follow.

Preliminary Survey of Classes

To provide a starting point for the development effort, we have to identify the objects and define their responsibilities. The central principle behind the allocation of responsibility is encapsulation; we do this by attempting to isolate the information or isolate the processing that must be done. Encapsulation assures that the methods of a class are the exclusive users of the fields of that class. It also makes each class very loosely coupled with other classes; this permits change without a ripple through the application. For example, each Outcome contains both the name and the payout odds. That way each Outcome can be used to compute a winning amount, and no other element of the simulation needs to share the odds information or the payout calculation.

In a few cases, we have looked forward to anticipate some future considerations. One such consideration is the house rake, also known as the vigorish, vig, or commission. In some games, the house makes a 5% deduction from some payouts. This complexity is best isolated in the Outcome class. Roulette doesn't have any need for a rake, since the presence of the 0 and 00 on the wheel gives the house a little over 5% edge on each bet. We'll design our class so that this can be added later when we implement Craps.

In reading the background information and the problem statement, we noticed a number of nouns that seemed to be important parts of the game we are simulating.

Wheel
Bet
Bin
Table
Red
Black
Green
Number
Odds
Player
House

One common development milestone is to be able to develop a class model in the Unified Modeling Language (UML) to describe the relationships among the various nouns in the problem statement. Building (and interpreting) this model takes some experience with OO programming. In this first part, we'll avoid doing extensive modeling. Instead we'll simply identify some basic design principles. We'll focus in on the most important of these nouns and describe the kinds of classes that you will build.

The following table summarizes some of the classes and responsibilities that we can identify from the problem statement. This is not the complete list of classes we need to build. As we work through the exercises, we'll discover additional classes and rework some of these preliminary classes more than once.

Preliminary Roulette Class Structure
ClassResponsibilitiesCollaborations
OutcomeA name for the bet and the payout odds. This isolates the calculation of the payout amount. Example: "Red", "1:1".Collected by Wheel into the bins that reflect the bets that win; collected by Table into the available bets for the Player; used by Game to compute the amount won from the amount that was bet.
WheelSelects the Outcomes that win. This isolates the use of a random number generator to select Outcomes; and it encapsulates the set of winning Outcomes that are associated with each individual number on the wheel. 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”.Collects the Outcomes into bins; used by the overall Game to get a next set of winning Outcomes.
TableA collection of bets placed on Outcomes by a Player. This isolates the set of possible bets and the management of the amounts currently at risk on each bet. This also serves as the interface between the Player and the other elements of the game.Collects the Outcomes; used by Player to place a bet amount on a specific Outcome; used by Game to compute the amount won from the amount that was bet.
PlayerPlaces bets on Outcomes, updates the stake with amounts won and lost.Uses Table to place bets on Outcomes; used by Game to record wins and losses.
GameRuns the game: gets bets from Player, spins Wheel, collects losing bets, pays winning bets. This encapsulates the basic sequence of play into a single class.Uses Wheel, Table, Outcome, Player. The overall statistical analysis will play a finite number of games and collect the final value of the Player's stake.

The class Player has the most important responsibility in the application, since we expect to update the algorithms this class uses to place different kinds of bets. Clearly, we need to cleanly encapsulate the Player, so that changes to this class have no ripple effect in other classes of the application.

A Walkthrough of Roulette

A good preliminary task is to review these responsibilities to confirm that a complete cycle of play is possible. This will help provide some design details for each class. It will also provide some insight into classes that may be missing from this overview.

A good way to structure this task is to do a Class-Reponsibility-Collaborators (CRC) walkthrough. As preparation, get some 5" x 8" notecards. On each card, write down the name of a class, the responsibilities and the collaborators. Leave plenty of room around the responsibilities and collaborators to write notes. We've only identified five classes, so far, but others always show up during the walkthrough.

During the walkthrough, we identify areas of responsibility, allocate them to classes of objects and define any collaborating objects. An area of responsibility is a thing to do, a piece of information, a result. Sometimes a big piece of responsibility can be broken down into smaller pieces, and those smaller pieces assigned to other classes. There are a lot of reasons for decomposing, the purpose of this book is to explore many of them in depth. Therefore, we won't justify any of our suggestions until later in the book. For now, follow along closely to get a sense of where the exercises will be leading.

The basic processing outline is the responsibility of the Game class. To start, locate the Game card.

  1. Our preliminary note was that this class “Runs the game.” The responsibilities section has a summary of four steps involved in running the game.

  2. The first step is “gets bets from Player.” Find the Player card.

  3. Does a Player collaborate with a Game to place bets? If not, update the cards as necessary to include this.

  4. One of the responsibilities of a Player is to place bets. The step in the responsibility statement is merely “Places bets on Outcomes.” Looking at the classes, we note that the Table contains the amounts placed on the Bets. Fix the collaboration information on the Player to name the Table class. Find the Table card.

  5. Does a Table collaborate with a Player to accept the bets? If not, update the cards as necessary to include this.

  6. What card has responsibility for the amount of the bet? It looks like Table. We note one small problem: the Table contains the collection of amounts bet on Outcomes. What class contains the individual “amount bet on an Outcome?” This class appears to be missing. We'll call this new class Bet and start a new card. We know one responsibility is to hold the amount bet on a particular Outcome. We know three collaborators: the amount is paired with an Outcome, all of the Bets are collected by a Table, and the Bets are created by a Player. We'll update all of the existing cards to name their collaboration with Bet.

  7. What card has responsibility for keeping all of the Bets? Does Table list that as a responsibility? We should update these cards to clarify this collaboration.

You should continue this tour, working your way through spinning the Wheel to get a list of winning Outcomes. From there, the Game can get all of the Bets from the Table and see which are based on winning Outcomes and which are based on losing Outcomes. The Game can notify the Player of each losing Bet, and notify the Player of each winning Bet, using the Outcome to compute the winning amount.

This walkthrough will give you an overview of some of the interactions among the objects in the working application. You may uncover additional design ideas from this walkthrough. The most important outcome of the walkthrough is a clear sense of the responsibilities and the collaborations required to create the necessary application behavior.

Questions and Answers

Q: Why does the Game class run the sequence of steps? Isn't that the responsibility of some main program?
Q: Why is Outcome a separate class? Each object that is an instance of Outcome only has two attributes; why not use an array of Strings for the names, and a parallel array of integers for the odds?
Q: If Outcome encapsulates the function to compute the amount won, isn't it just a glorified subroutine?
Q: What is the distinction between an Outcome and a Bet?
Q: Why are the classes so small?
Q:

Why does the Game class run the sequence of steps? Isn't that the responsibility of some “main program?

A:

Coffee Shop Answer. We haven't finished designing the entire application, so we need to reflect our own ignorance of how the final application will be assembled from the various parts. Rather than allocate too many responsibilities to Game, and possibly finding conflicts or complication, we'd rather allocate too few responsibilities until we know more.

From another point of view, designing the main program is premature because we haven't finished designing the entire application. We anticipate a Game object being invoked from some statistical data gathering object to run one game. The data gathering object will then get the final stake from the player and record this. Game's responsibilities are focused on playing the game itself. We'll need to add a responsibility to Game to collaborate with the data gathering class to run a number of games as a “session”.

Deeper Answer. In procedural programming (especially in languages like COBOL), the “main program” is allocated almost all of the responsibilities. These procedural main programs usually contain a number of elements, all of which are very tightly coupled. We have seen highly skilled programmers who are able to limit the amount of work done in the main program, even in procedural languages. In OO languages, it becomes possible for even moderately skilled programmers to reduce the main program to a short list of object constructors, with the real work delegated to the objects. We find that “main program” classes are relatively hard to reuse, and prefer to keep them as short as possible.

Q:

Why is Outcome a separate class? Each object that is an instance of Outcome only has two attributes; why not use an array of Strings for the names, and a parallel array of integers for the odds?

A:

Representation. We prefer not to decompose an object into separate data elements. If we do decompose this object, we will have to ask which class would own these two arrays? If Wheel keeps these, then Table becomes very tightly coupled to these two arrays that should be Wheel's responsibility. If Table keeps these, then Wheel is priviledged to know details of how Table is implemented. If we need to change these arrays to another storage structure, two classes would change instead of one.

Having the name and odds in a single Outcome object allows us to change the representation of an Outcome. For example, we might replace the String as the identification of the outcome, with a collection of the individual numbers that comprise this outcome. This would identify a straight bet by the single winning number; an even money bet would be identified by an array of the 18 winning numbers.

Responsibility. he principle of isolating responsibility would be broken by this “two parallel arrays” design because now the Game class would need to know how to compute odds. In more complex games, there would be the added complication of figuring the rake. Consider a game where the Player's strategy depends on the potential payout. Now the Game and the Player both have copies of the algorithm for computing the payout. A change to one must be paired with a change to the other.

The alternative we have chosen is to encapsulate the payout algorithm along with the relevant data items in a single bundle.

Q:

If Outcome encapsulates the function to compute the amount won, isn't it just a glorified subroutine?

A:

In a limited way, yes. A class can be thought of as a glorified subroutine library that captures and isolates data elements along with their associated functions. For some new designers, this is a helpful summary of the basic principle of encapsulation. Inheritance and subclasses, however, make a class more powerful than a simple subroutine library with private data. Inheritance is a way to create a family of closely-related subroutine libraries in a simple way that is validated by the compiler.

Q:

What is the distinction between an Outcome and a Bet?

A:

We need to describe the propositions on the table on which you can place bets. The propositions are distinct from an actual amount of money wagered on a proposition. There are a lot of terms to choose from, including bet, wager, proposition, place, location, or outcome. We opted for using Outcome because it seemed to express the open-ended nature of a potential outcome, different from an amount bet on a potential outcome. In a way, we're considering the Outcome as an abstract possibility, and the Bet as a concrete action taken by a player.

Also, as we expand this simulation to cover other games, we will find that the randomized outcome is not something we can directly bet on. In Roulette, however, all outcomes are something we can be bet on, as well as a great many combinations of outcomes. We will revisit this design decision as we move on to other games.

Q:

Why are the classes so small?

A:

First-time designers of OO applications are sometimes uncomfortable with the notion of emergent behavior. In procedural programming languages, the application's features are always embodied in a few key procedures. Sometimes a single procedure, named main.

A good OO design partitions responsibility. In many cases, this subdivision of the application's features means that the overall behavior is not captured in one central place. Rather, it emerges from the interactions of a number of objects.

We have found that smaller elements, with very finely divided responsibilities, are more flexible and permit change. If a change will only alter a portion of a large class, it can make that portion incompatible with other portions of the same class. A symptom of this is a bewildering nest of if-statements to sort out the various alternatives. When the design is decomposed down more finely, a change can be more easily isolated to a single class. A much simpler sequence of if-statements can be focused on selecting the proper class, which can then simply carry out the desired functions.

Chapter 4. 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 which asks if we have two references to the same object.

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

Outcome contains a single outcome on which a bet can be placed. In Roulette, each spin of the wheel has a number of Outcomes. 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”.

Fields

  • String name ;

    Holds the name of the Outcome. Examples include "1", "Red".

  • int 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.

Constructors

  • Outcome(String name,
            int odds);

    Sets the local name and odds from the parameter name and odds.

Methods

  • int winAmount(int amount);

    Multiplies this Outcome's odds by the given amount. The product is returned.

  • An easy-to-read String output method is also very handy. This should return a String representation of the name and the odds. A form that looks like 1-2 Split (17:1) works nicely. In Java, this is done as follows.

    public String toString();

    In Python, the following declaration is used.

    def __str__(self):

    For some tips on how to do this, see Formatting in Java. Python formatting is done more simply, using the % operator.

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.

Chapter 5. Bin Class

This chapter will present the design for the Bin class. In addition to that, we'll examine the collection classes available in Java and Python. We'll also present present some questions and answers on this particular class and the process of creating a design.

Overview

The Roulette wheel has 38 bins, identified with a number and a color. Each of these bins defines a number of closely related winning Outcomes. At this time, we won't enumerate each of the 38 Bins of the wheel and each of the winning Outcomes (from two to fourteen in each Bin); we'll save that for a later exercise. At this time, we'll define the Bin class, and use this class to contain a number of Outcome objects.

Two of the Bins have relatively few Outcomes. Specifically, the 0 Bin only contains the basic “0Outcome and the “00-0-1-2-3Outcome. The 00 Bin, similarly, only contains the basic “00Outcome and the “00-0-1-2-3Outcome.

The other 36 Bins contain the straight bet, split bets, street bet, corner bets, line bets and various outside bets (column, dozen, even or odd, red or black, high or low) that will win if this Bin is selected. Each number bin has from 12 to 14 individual winning Outcomes.

Some Outcomes, like red or black, occur in as many as 18 individual Bins. Other Outcomes, like the straight bet numbers, each occur in only a single Bin. We will have to be sure that our Outcome objects are shared appropriately by the Bins.

Since a Bin is just a collection of individual Outcome objects, we have to select a collection class to contain the objects. In Java we have five basic choices, with some variations based on performance needs. In Python, we have two basic choices.

Java Collections

The Java collections framework offers us five basic choices for defining an object that is a container for other objects.

  • Simple Arrays. We can always declare a collection of Outcome objects as follows: Outcome[] bin1 = { Outcome("Red",1), Outcome("1",35) };. This does almost everything that we need it to do. Since our relationship between a Bin and the collection of Outcomes is relatively fixed, this kind of collection has some appeal.

  • java.util.VectorWith a Vector, we have the flexibility of easy addition of an Outcome to the collection. We also have the power of an Iterator to visit every member of the collection.

    A Vector has an implication of non-sequential access to the elements. In our case, we won't be using this; most of our collections will be visited in order, as we determine which individual bets on the table match the Bin's collection of winning Outcomes.

  • java.util.SetA Set offers easy addition of elements, an Iterator to visit the elements. Additionally, it assures that each element occurs only once. The semantics of a mathematical set are precisely what we want for each Bin on a wheel: it is an unordered collection of elements, each element occurs at most one time.

    The Set is appealing because the definition precisely matches our need. Additionally, it is possible that a Set performs faster than a Vector or List. The reason it can be faster is that it does not have to keep the elements in a particular order.

  • java.util.ListA List offers easy addition of elements, an Iterator to visit the elements. Like a Vector, a List keeps the elements in a particular order.

    A List has an implication of strictly sequential access to the elements. In our case, this is generally true. Most of our collections will be built in order and visited in order, as we determine which individual bets on the table match the Bin's collection of winning Outcomes. The order, however, isn't significant, so this feature is of relatively little value.

  • java.util.MapA map is used to associate a key with a value. This key-value pair is called an entry in the map. We don't need this feature at all. A map does more than we need for representing the bins on a wheel.

Having looked at the fundamental collection varieties, we will elect to use a Set. Our next step is to choose a concrete implementation. There are three alternatives.

  • java.util.HashSetThis uses a hash code to manage the elements in the set. The hash code can be computed almost instantly, allowing for rapid insert and retrieval. The hash code is mapped to buckets in an internal table. However, a large amount of storage is wasted by the need for empty buckets in the internal table. This trades off storage for speed.

  • java.util.LinkedHashSetThis adds a doubly-linked list that preserves the insertion order. This is not appropriate for what we are doing, as the insertion order is largely irrelevant.

  • java.util.TreeSetThis uses a binary red-black tree algorithm to store the elements of the set. The insertion time depends on the logarithm of the size of the tree, but the storage is minimized. This seems like the ideal candidate for representation of each Bin.

Python Collections

There are three basic Python types that are a containers for other objects.

  • Immutable Sequence or tuple. A tuple is immutable sequence of objects. This is the kind of collection we need, since the elements of a Bin don't change.

  • Mutable Sequence or list. A list is a sequence of objects that can be changed. While handy for the initial construction of the bin, this isn't really useful because the contents of a bin don't change once they have been enumerated.

  • Mapping or dictionary. A dictionary associated a key with a value. This key-value pair is called an item in the map. We don't need this feature at all. A map does more than we need for representing a Bin.

Having looked at the fundamental collection varieties, we will elect to use a tuple.

Questions and Answers

Q: Why wasn't Bin in the design overview?
Q: Why introduce an entire class for the bins of the wheel? Why can't the wheel be an array of 38 individual arrays?
Q: Isn't an entire class for bins a lot of overhead?
Q: How can you introduce Set, List, Vector when these don't appear in the problem?
Q:

Why wasn't Bin in the design overview?

A:

The definition of the Roulette game did mention the 38 bins of the wheel. However, when identifying the nouns, it didn't seem important. Then, as we started designing the Wheel class, the description of the wheel as 38 bins came more fully into focus. Rework of the preliminary design is part of detailed design. This is the first of several instances of rework.

Q:

Why introduce an entire class for the bins of the wheel? Why can't the wheel be an array of 38 individual arrays?

A:

There are two reasons for introducing Bin as a separate class: to improve the fidelity of our object model of the problem, and to reduce the complexity of the Wheel class. The definition of the game describes the wheel as having 38 bins, each bin causes a number of individual Outcomes to win. Without thinking too deeply, we opted to define the Bin class to hold a collection of Outcomes. At the present time, we can't foresee a lot of processing that is the responsibility of a Bin. But allocating a class permits us some flexibility in assigning responsibilities there in the future.

Additionally, looking forward, it is clear that the Wheel class will use a random number generator and will pick a winning Bin. In order to keep this crisp definition of responsibilities for the Wheel class, it makes sense to delegate all of the remaining details to another class.

Q:

Isn't an entire class for bins a lot of overhead?

A:

The short answer is no, class definitions are almost no overhead at all. Class definitions are part of the compiler's world; at run-time they amount to a few simple persistent objects that define the class. It's the class instances that cause run-time overhead.

In a system where were are counting individual instruction executions at the hardware level, this additional class may slow things down somewhat. In most cases, however, the extra few instructions required to delegate a method to an internal object is offset by the benefits gained from additional flexibility.

Q:

How can you introduce Set, List, Vector when these don't appear in the problem?

A:

We have to make a distinction between the classes that are uncovered during analysis of the problem in general, and classes are that just part of the implementation of this particular solution. This emphasizes the distinction between the problem as described by users and a solution as designed by software developers. The collections framework are part of a solution, and hinted at by the definition of the problem. Generally, these solution-oriented classes are part of frameworks or libraries that came with our tools, or that we can license for use in our application. The problem-oriented classes, however, are usually unique to our problem.

Java Design

Bin contains a collection of Outcomes which reflect the winning bets that are paid for a particular bin on a Roulette wheel. In Roulette, each spin of the wheel has a number of Outcomes. Example: A spin of 1, selects the “1” bin with 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”. These are collected into a single Bin.

Fields

  • Set outcomes ;

    Holds the connection of individual Outcomes.

For a discussion on the type declaration used for outcome, see Deferred Binding in Java.

Constructors

In addition to the default constructor that makes an empty Bin, Java programmers may find it helpful to create a two additional constructors to initialize the collection from other collections.

  • Bin();

    Creates an empty Bin. Outcomes can be added to it later.

  • Bin(Outcome[] outcomes);

    Creates an empty Bin using the this() statement to invoke the default constructor. It then loads that collection using elements of the given array.

  • Bin(Collection outcomes);

    Creates an empty Bin using the this() statement to invoke the default constructor. It then loads that collection using an iterator over the given Collection. This relies on the fact that all classes that implement Collection will provide the iterator; the constructor can convert the elements of the input collection to a proper Set.

The array constructor will initialize the outcomes Set from an array of Outcomes. This allows the following kind of constructor call from another class.

Example 5.1. Java Bin Construction

    Outcome five= new Outcome( "00-0-1-2-3", 6 );
    Outcome[] ocZero = {
        new Outcome("0", 35 ), five }; 1
    Outcome[] ocZeroZero = {
        new Outcome("00", 35 ), five }; 2
    Bin zero= new Bin( ocZero ); 3
    Bin zerozero= bew Bin( ocZeroZero );
1

This creates ocZero, which is an array of references to two objects: the “0Outcome and the “00-0-1-2-3Outcome.

2

This creates ocZeroZero, which is an array of references to two objects: the “00Outcome and the “00-0-1-2-3Outcome.

3

This creates zero, which is the final Bin, built from ocZero, which has both “0” and “five”.

Methods

  • void add(Outcome outcome);

    Adds an Outcome to this Bin. This can be used by a builder to construct all of the bets in this Bin. Since this class is really just a façade over the underlying collection object, this method can simply delegate the real work to the underlying collection.

  • public String toString();

    An easy-to-read String output method is also very handy. This should return a String representation of the list of Outcomes in this Bin.

    For some tips on how to implement this, see Formatting in Java.

Python Design

Bin contains a collection of Outcomes which reflect the winning bets that are paid for a particular bin on a Roulette wheel. In Roulette, each spin of the wheel has a number of Outcomes. Example: A spin of 1, selects the “1” bin with 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”. These are collected into a single Bin.

Fields

  • outcomes = () 

    Holds the connection of individual Outcomes.

Constructors

Python programmers should provide an initializer that uses the * modifier so that all of the individual arguments appear as a single list within the initializer.

  • def __init__(self, *outcomes):

This constructor can be used as follows.

Example 5.2. Python Bin Construction

    five= Outcome( "00-0-1-2-3", 6 )
    zero= Bin( Outcome("0",35), five ) 1
    zerozero= Bin( Outcome("00",35), five ) 2
1

This creates zero Bin, which is based on references to two objects: the “0Outcome and the “00-0-1-2-3Outcome.

2

This creates zerozero, which is based on references to two objects: the “00Outcome and the “00-0-1-2-3Outcome.

Methods

  • def  add(self, outcome):

    Adds an Outcome to this Bin. This can be used by a builder to construct all of the bets in this Bin. Since this class is really just a façade over the underlying collection object, this method can simply delegate the real work to the underlying collection. Note that a tuple is immutable; unlike a list it does not have an append method. Instead, a tuple is constructed by using the “+” operator which creates a new tuple from two input tuples.

    Example 5.3. Python Appending Outcomes to A Tuple

        self.outcomes = self.outcomes + outcome
        
  • def __str__(self):

    An easy-to-read String output method is also very handy. This should return a String representation of the list of Outcomes in this Bin. A handy technique for displaying collections is the following. This maps the str function to each element of self.outcomes, then joins the resulting string with “,” separators.

    Example 5.4. Python String Conversion of a Tuple of Outcomes

        def __str__( self ):
            return ', '.join( map(str,self.outcomes) )
        

Deliverables

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

  • The Bin class.

  • A class which performs a unit test of the Bin class. The unit test should create several instances of Outcome, two instances of Bin and establish that Bins can be constructed from the Outcomes.

    Programmers who are new to OO techniques are sometimes confused when reusing individual Outcome instances. This unit test is a good place to examine the ways in which object references are shared. A single Outcome object can be referenced by several Bins. We will make increasing use of this in later sections.

Chapter 6. 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. We'll introduce some basic subclass constructor techniques, also.

Overview

The wheel has two responsibilities: it is a container for the Bins and it picks one of the Bins at random. We'll also have to look at how to initialize the various Bins.

Container Implementation. Since the Wheel is 38 Bins, it is a collection. We can review our survey of the Java collections in Java Collections and the Python collections in Python Collections for some guidance here. In this case, the choice of Bin will be selected by a random numeric index. For Java programmers, this makes the java.util.Vector very appealing. Python programmers will find that a List will do very nicely.

One consequence of using a Vector is that we have to choose an index for the various Bins. While each Bin contains a variety of individual Outcomes, the single number Outcome is unique to each Bin. The numbers 1 to 36 become the index to their respective Bins. We have a small problem, however, with 0 and 00: we need two separate indexes. While 0 is a valid index into a Vector, what do we do with 00?

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 in the Vector. This gives us a unique place for all 38 Bins.

Random Selection. 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 number generator in java.util.Random does everything we need. We can use the generator's public int nextInt(int n); method to return integers.

Python programmers can use the random module. This module offers a choice function which picks a random value from a sequence. This is ideal for returning a randomly selected Bin from our list of Bins. For some cautions on casual use of modules in Python, see Using Python Modules.

Initialization. Each instance of Bin has a list of Outcomes. The 0 and 00 Bins only have two Outcomes. The other numbers have anywhere from twelve to fourteen Outcomes. Constructing the entire collection of Bins would be a tedious undertaking. We'll apply a common OO design technique of deferred binding and work on that algorithm later, after we have the basic design for the Wheel finished.

Design

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.

For those new to Java, see Java Subclass Constructors.

For those new to Python, see Python Subclass Constructors.

Fields

  • Java: java.util.Vector bins = new Vector( 38 );

    Python: bins = 38*[None] 

    Contains the individual Bin instances.

  • Java: java.util.Random rng = new java.util.Random();

    Python: rng = random.Random() 

    Generates the next random number, used to select a Bin from the bins collection.

Constructors

  • Wheel();

    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

  • addOutcome(int number,
               Outcome outcome);

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

  • next();

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

    Java: Be sure to note that the java.util.Random nextInt method uses the size of the bins collection to return values from 0 to the size of the collection. Typically there are 38 values, numbered from 0 to 37.

    Python: the choice function of the random module will select one of the available Bins from the bins list.

    In later chapters, we'll revisit ways to test this method. For now, it returns a random result that's difficult to unit test.

  • Bin getBin(int aBin);

    Returns the given Bin from the internal collection.

Deliverables

There are three deliverables for this exercise. The new class and the unit test will have Javadoc comments or 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 performs a demonstration of selecting random values from the Wheel class. Since the Bins will be returned in a random order, this is difficult to call a formal test. The demonstration should include the following.

    1. Create several instances of Outcome.

    2. Create two instances of Bin that use the available Outcomes.

    3. Create one instance of Wheel that uses the two Bins.

    4. A number of calls to the next method should return randomly selected Bins.

    Note that the sequence of random numbers is fixed by the seed value. The default constructor for a random number generator creates a seed based on the system clock. Your unit test can set a particular seed value for the random number generator within the instance of Wheel; this will deliver a fixed sequence of numbers that can be used to get a consistent result.

Chapter 7. Bin Builder Class

This is a rather long chapter, which describes a number of design considerations surrounding Outcome, Bin and Wheel. The second section presents a number of algorithms to group Outcomes based on the geometry of the table. These algorithms are presented at a high level, leaving much of the detailed design to the student.

Overview

It is clear that enumerating each Outcome in the 38 Bins is a tedious undertaking. Most Bins contain about fourteen individual Outcomes. It is often helpful to create a class that is used to build an instance of another class. This is a design pattern sometimes called a Builder. We'll design an object that builds the various Bins and assigns them to the Wheel. This will fill the need left open in the design of the Wheel class.

Additionally, we note that the complex algorithms to construct the Bins are only tangential to the operation of the Wheel object. While not essential to the design of the Wheel class, we find it is often helpful to segregate these rather complex builder methods into a separate class.

The BinBuilder class will have a method that enumerates the contents of each of the 36 number Bins, building the individual Outcome instances. We can then assign these Outcome objects to the Bins of a Wheel instance. We will use a number of steps to create the various types of Outcomes, and depend on the Wheel to assign each Outcome object to the correct Bin.

Looking at the layout of a Roulette table gives us a number of geometric rules for determining the various Outcomes that are combinations of individual numbers. These rules apply to the numbers from one to thirty-six. A different -- and much simpler -- set of rules applies to 0 and 00. First, we'll survey the table geometry, then we'll develop specific algorithms for each kind of bet.

  • Split Bets. Each number is adjacent to two, three or four other numbers. The four corners (1, 3, 34, and 36) only participate in two split bets. The center column of numbers (5, 8, 11, ..., 32) each participate in four split bets. The remaining “edge” numbers participate in three split bets. While this is moderately complex, the bulk of the layout (from 4 to 32) can be handled with a simple rule to distinguish the center column from the edge columns. The ends (1, 2, 3, 34, 35 and 36) are a little more complex.

  • Street Bets. Each number is a member of one of the twelve street bets.

  • Corner Bets. Each number is a member of one, two or four corner bets. As with split bets, the bulk of the layout can be handled with a simple rule to distinguish the column, and hence the “corners”. A number in the center column (5, 8, 11, ..., 32) is a member of four corners. At the ends, 1, 3, 34, and 36, are members of just one corner. All of the remaining numbers are along an edge and are members of two corners.

  • Line Bets. Six numbers comprise a line; each number is a member of one or two lines. The ends (1, 2, 3 and 34, 35, 36) are each part of a single line. The remaining 10 rows are each part of two lines.

  • Dozen Bets. Each number is a member of one of the three dozens. The three ranges are from 1 to 12, 13 to 24 and 25 to 36, making it very easy to associate numbers and ranges.

  • Column Bets. Each number is a member of one of the three columns. Each of the columns has a number numeric relationship. The values are 3c+1, 3c+2, and 3c+3, where c varies from 0 to 11.

  • The Even-Money Bets. These include Red, Black, Even, Odd, High, Low. Each number has three of the six possible even money Outcomes. An easy way to handle these is to create the 6 individual Outcome instances. For each number, n, individual if-statements can be used to determine which of the Outcome objects are associated with the given Bin.

The Bins for zero and double zero can easily be enumerated. Each bin has a straight number bet Outcome, plus the Five Bet Outcome (00-0-1-2-3, which pays 6:1).

One other thing we'll probably want are handy names for the various kinds of odds. While can define an Outcome as Outcome( "Number 1", 35 ), this is a little opaque. A slightly nicer form is Outcome( "Number 1", RouletteGame.StraightBet ). These are specific to a game, not general features of all Outcomes. We haven't designed the game yet, but we can provide a stub class with these these outcome odds definitions.

Algorithms

Straight bet Outcomes are the easiest to generate.

Procedure 7.1. Generating Straight Bets

  • For All Numbers. For each number, n, from 1 to 36:

    1. Create Outcome. Create an Outcome from the number, n, with odds of 35:l.

    2. Assign to Bin. Assign this new Outcome to Bin n.

Split bet Outcomes are more complex because of the various cases: corners, edges and down-the-middle.

We note that there are two kinds of split bets: left-right pairs and up-down pairs. The left-right pairs all have the form (n, n+1). The up-down paris have the form (n, n+3). We can look at the number 5 as being part of 4 different pairs: (4,4+1), (5,5+1), (2,2+3), (5,5+3). The corner number 1 is part of 2 split bets: (1,1+1), (1,1+3).

We can generate the “left-right” split bets by iterating through the left two columns; the numbers 1, 4, 7, ..., 34 and 2, 5, 8, ..., 35.

Procedure 7.2. Generating Split Bets

  • For All Rows. For each row, r, from 0 to 11:

    1. First Column Number. Compute number, n as 3r+1. This will create values 1, 4, 7, ..., 34.

    2. Column 1-2 Split. Create a “n, n+1” split Outcome with odds of 17:1.

    3. Assign to Bins. Associate this object with two Bins: n and n+1.

    4. Second Column Number. Compute number, n as 3r+2. This will create values 2, 5, 8, ..., 35.

    5. Column 2-3 Split. Create a “n, n+1” split Outcome.

    6. Assign to Bins. Associate this object to two Bins: n and n+1.

Similarly, for the numbers 1 through 33, we can generate the “up-down” split bets. For each number, n, we generate a “n, n+3” split bet. This Outcome belongs to two Bins: n and n+3.

Street bet Outcomes are very simple.

We can generate the street bets by iterating through the twelve rows of the layout.

Procedure 7.3. Generating Street Bets

  • For All Rows. For each row, r, from 0 to 11:

    1. First Column Number. Compute number, n as 3r+1. This will create values 1, 4, 7, ..., 34.

    2. Street. Create a “n, n+1, n+2” street Outcome with odds of 11:1.

    3. Assign to Bins. Associate this object to three Bins: n, n+1, n+2.

Corner bet Outcomes are as complex as split bets because of the various cases: corners, edges and down-the-middle.

Eacb corner has four numbers, n, n+1, n+3, n+4. We can generate the corner bets by iterating through the numbers 1, 4, 7, ..., 31 and 2, 5, 8, ..., 32. For each number, n, we generate four corner bets: “n, n+1, n+3, n+4” corner bet. This Outcome object belongs to four Bins.

We generate corner bets by iterating through the lines between rows and columns. There are two lines between the three columns, and 11 lines between the 12 rows. The column lines are to the right of the first two columns. Similarly the row lines are below the first 11 rows.

Procedure 7.4. Generating Corner Bets

  • For All Lines Between Rows. For each line below a row, r, from 0 to 10:

    1. First Column Number. Compute number, n as 3r+1. This will create values 1, 4, 7, ..., 31.

    2. Column 1-2 Corner. Create a “n, n+1, n+3, n+4” corner Outcome withs odds of 8:1.

    3. Assign to Bins. Associate this object to four Bins: n, n+1, n+3, n+4.

    4. Second Column Number. Compute number, n as 3r+2. This will create values 2, 5, 8, ..., 32.

    5. Column 2-3 Corner. Create a “n, n+1, n+3, n+4” corner Outcome withs odds of 8:1.

    6. Assign to Bins. Associate this object to four Bins: n, n+1, n+3, n+4.

Line bet Outcomes are similar to street bets. However, these are based around the 11 lines between the 12 rows.

For lines s numbered 0 to 10, the numbers on the line bet can be computed as follows: 3s+1, 3s+2, 3s+3, 3s+4, 3s+5, 3s+6. This Outcome object belongs to six individual Bins.

Procedure 7.5. Generating Line Bets

  • For All Lines Between Rows. For each row, r, from 0 to 10:

    1. First Column Number. Compute number, n as 3r+1. This will create values 1, 4, 7, ..., 31.

    2. Line. Create a “n, n+1, n+2, n+3, n+4, n+5” line Outcome withs odds of 5:1.

    3. Assign to Bins. Associate this object to six Bins: n, n+1, n+2, n+3, n+4, n+5.

Dozen bet Outcomes require enumerating all twelve numbers in each of three groups.

Procedure 7.6. Generating Dozen Bets

  • For All Dozens. For each dozen, d, from 0 to 2:

    1. Create Dozen. Create an Outcome for dozen d+1 with odds of 2:1.

    2. For All Numbers. For each number, m, from 0 to 11:

      1. Assign to Bin. Associate this object to Bin 12d+m+1.

Column bet Outcomes require enumerating all twelve numbers in each of three groups. While the outline of the algorithm is the same as the dozen bets, the enumeration of the individual numbers in the inner loop is slightly different.

Procedure 7.7. Generating Column Bets

  • For All Columns. For each column, c, from 0 to 2:

    1. Create Column. Create an Outcome for column c+1 with odds of 2:1.

    2. For All Rows. For each row, r, from 0 to 11:

      1. Assign to Bin. Associate this object to Bin 3r+c+1.

The even money bet Outcomes are relatively easy to generate.

Procedure 7.8. Generating Even-Money Bets

  1. Create the Red outcome, with odds of 1:1.

  2. Create the Black outcome, with odds of 1:1.

  3. Create the Even outcome, with odds of 1:1.

  4. Create the Odd outcome, with odds of 1:1.

  5. Create the High outcome, with odds of 1:1.

  6. Create the Low outcome, with odds of 1:1.

  7. For All Numbers. For each number, n, from 1 to 36:

    1. Low? If n is between 1 and 18, associate the low Outcome with Bin n.

      High? Otherwise, n is between 19 and 36, associate the high Outcome with Bin n.

    2. Even? If n % 2 is zero, associate the even Outcome with Bin n.

      Odd? Otherwise, n % 2 is 1, associate the odd Outcome with Bin n.

    3. Red? If n is one of 1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, or 36, associate the red Outcome with Bin n.

      Black? Otherwise, associate the black Outcome with Bin n.

Design

BinBuilder creates the Outcomes for all of the 38 individual Bin on a Roulette wheel.

Constructors

  • BinBuilder();

    Initializes the BinBuilder.

Methods

  • void buildBins(Wheel aWheel);

    Creates the Outcome instances and uses the addOutcome method to place each Outcome in the appropropriate Bin of aWheel.

    There should be nine separate methods to generate the straight bets, split bets, street bets, corner bets, line bets, dozen bets and column bets, even money bets and the special case of zero and double zero. Each of the methods will be relatively simple and easy to unit test. Details are provided in Algorithms.

Deliverables

There are three deliverables for this exercise. The new classes will have Javadoc comments or Python docstrings.

  • The BinBuilder class.

  • A class which performs a unit test of the BinBuilder class. The unit test invoke each of the various methods that create Outcome instances.

  • Rework the unit test of the Wheel class. The unit test should create and initialize a Wheel. It can use the getBin method to check selected Bins for the correct Outcomes.

Chapter 8. Bet Class

In addition to the design of the Bet class, this chapter also presents some additional questions and answers on the nature of an object, identity and state change. This continues some of the ideas from On Object Identity

Overview

A Bet is an amount that the player has wagered on a specific Outcome. This class has the responsibilty for maintaining the association between an amount, an Outcome, and a specific Player.

This is the classic record declaration: a passive association among data elements. The only methods we can see are three pairs of getters and setters to get and set each of the three attributes.

The general scenario is to have a Player construct a number of Bet instances. The wheel is spun to select a winning Bin. Then each of the Bet objects will be checked to see if they are winners or losers. A winning Bet has an Outcome that matches one in the winning Bin; winners will return money to the Player. All other bets are losers, which remove the money from the Player.

Building Bets. In the long run, we'll need to build a variety of bets. Building a Bet involves two parts: an Outcome and an amount. There are several places to get Outcomes. First, we can create an Outcome object as part of constructing a Bet. Here's what it might look like in Java.

Bet myBet= new Bet( new Outcome("red",2), 25 )

A better choice is to get Outcome obects from the Wheel. To do this, we'd have to make sure that we saved Outcomes from the BinBuilder. We'd also have add specific Outcome getters to the Wheel. We could, for example, include a getBlack method that returns an instance of the black Outcome. In Python, for example, we might have a method function like the following.

class Wheel( object ):
...
    def getBlack( self ):
        return self.black

A third approach is to keep a Map (in Python, a dictionary) that maps an Outcome's string name to the relevant Outcome object. Our BinBuilder can accumulate this Map, and we can get Outcomes out of the Map with a getter method like the following.

Outcome getOutcome( String name ) {
    return outcomeMap.get(name);
}

For now, we'll build Outcomes manually in order to produce a good test of the Bet class.

Questions and Answers

Q: Why not update each Outcome with the amount of the bet?
Q: Does an individual bet really have unique identity? Isn't it just anonymous money?
Q:

Why not update each Outcome with the amount of the bet?

A:

We are isolating the static definition of the Outcome from the presence or absence of an amount wagered. Note that the Outcome is shared by the wheel's Bins, and the available betting spaces on the Table, possibly even the Player class. Also, if we have multiple Player, then we need to distinguish bets placed by the individual players.

Changing a field's value has an implication that the thing has changed state. In Roulette, there isn't any state change in the definition of an Outcome. However, when we look at Craps, we will see that changes in the game's state will enable and disable whole sets of Outcomes.

Q:

Does an individual bet really have unique identity? Isn't it just anonymous money?

A:

Yes, the money is anonymous. In a casino, the chips all look alike. However, the placement of the bet, really does have unique identity. A Bet is owned by a particular player, it lasts for a specific duration, it has a final outcome of won or lost. When we want to create summary statistics, we could do this by saving the individual Bet objects. We could update each Bet with a won or lost indicator, then we can total the wins and losses.

This points up another reason why we know a Bet is an object in its own right: it changes state. A bet that has been placed can change to a bet that was won or a bet that was lost.

Design

Bet associates an amount and an Outcome. In a future round of design, we can also associate a it with a Player.

Fields

  • int amountBet ;

    The amount of the bet.

  • Outcome outcome ;

    The outcome on which the bet is placed.

Constructors

  • Bet(int amount,
        Outcome outcome);

    Initialize the instance variables of this bet.

    For these first exercises, we'll omit the player. We'll come back to this class when necessary, and add that capability back in to this class.

Methods

  • int winAmount();

    Uses the Outcome's winAmount to compute the amount won, given the amount of this bet. Note that the amount bet must also be added in. A 1:1 outcome (e.g. a bet on Red) pays the amount bet plus the amount won.

  • int loseAmount();

    Returns the amount bet as the amount lost. This is the cost of placing the bet.

  • Java: public String toString();

    Python: def __str__(self):

    Returns a string representation of this bet. Note that this method will delegate the real work to the toString (or __str__) method of the Outcome (and the Player, when that is added).

Deliverables

There are two deliverables for this exercise. The new classes will have Javadoc comments or Python docstrings.

  • The Bet class.

  • A class which performs a unit test of the Bet class. The unit test should create a couple instances of Outcome, and establish that the winAmount and loseAmount methods work correctly.

Chapter 9. Roulette Table Class

This section provides the design for the Table to hold the bets. It also introduces the concepts behind exception handling, and the proper role of exceptions.

Overview

The Table has the responsibility to keep the Bets created by the Player. Additionally, the house imposes table limits on the minimum amount that must be bet and the maximum that can be bet. Clearly, the Table has all the information required to evaluation these conditions.

Note

Casinos prevent the Martingale betting system from working by imposing a table limit on each game. To cover the cost of operating the table game, the casino also imposes a minimum bet. Typically, the maximum is a multiplier of the minimum bet, often in the range of 10 to 50; a table with a $5 minimum might have a $200 limit, a $10 minimum may have only a $300 limit.

It isn't clear where the responsibility lies for determining winning and losing bets. The money placed on Bets on the Table is “at risk” of being lost. If the bet is a winner, the house pays the Player an amount based on the Outcome's odds and the Bet's amount. If the bet is a loser, the amount of the Bet is forfeit by the Player. Looking forward to stateful games like Craps, we'll place the responsibility for determining winners and losers with the game, and not with the Table object.

Our second open question is the timing of the payment for the bet from the player's stake. In a casino, the payment happens when the bet is placed on the table. In our Roulette simulation, this is a subtlety that doesn't have any practical consequences. We could deduct the money as part of bet creation, or we could deduct the money as part of resolving the spin of the wheel. In other games, however, there may several events and several opportunities for additional bets. For example, splitting a hand in blackjack, or placing additional odds bets in Craps. Because we can't allow a player to bet more than their stake, we should deduct the payment as the bet is created.

A consequence of this is a change to our definition of the Bet class. We don't need to compute the amount that is lost. Rather than deduct the money when the bet resolved, we've decided that the money is deducted from the Player's stake as part of creating the Bet. This will become part of the design of Player and Bet.

Looking forward a little, a stateful game like Craps will introduce a subtle distinction that may be appropriate for a future subclass of Table. When the game is in the point off state, some of the bets on the table are not allowed, and others become inactive. When the game is in the point on state, all bets are allowed and active. In Craps parlance, some bets are “not working” or “working” depending on the game state. This does not apply to the version of Table that will support Roulette.

Container Implementation. A Table is a collection of Bets. We need to choose a concrete class for the collection of the bets. We can review our survey of the Java collections in Java Collections and the Python collections in Python Collections for some guidance here. In this case, the bets are placed in no particular order, and are simply visited in an arbitrary order for resolution. We can use a Java LinkedList for this. Since the number of bets varies, we can't use a Python tuple; a list will do.

Table Limits. Table limits can be checked by providing a public method isValid that compares the total of a new prospective amount plus all existing Bets to the table limit. This can be used by the Player to evaluate each potential bet prior to creating it.

In the unlikely event of the Player object creating an illegal Bet, we can also throw (or raise) an exception to indicate that we have a design error that was not detected via unit testing. This should be a subclass of Exception that has enough information to debug the problem with the Player that attempted to place the illegal bet.

Additionally, the game can check the overall state of a Player's Bets to be sure that the table minimum is met. We'll need to provide a public method isValid that is used by the game. In the event of the minimum not being met, there are serious design issues, and an exception should be thrown. Generally, this situation arises because of a bug where the Player should have declined to bet rather than placing incorrect bets that don't meet the table minimum.

Bet Resolution. An important consideration is the collaboration between Table and some potential game class for resolving bets. The Table has the collection of Bets, each of which has a specific Outcome. The Wheel selects a Bin, which has a collection of Outcomes. All bets with a winning outcome will be resolved as a winner.

Because some games are stateful, and the winning and losing bets depend on game state, we will defer the details of the collaboration design until we get to the Game class. For now, we'll simply collect the Bets.

Adding and Removing Bets. A Table contains Bets. Instances of Bet are added by a Player. Later, Bets will be removed from the Table by the Game. When a bet is resolved, it must be deleted. Some games, like Roulette resolve all bets with each spin. Other games, like Craps, involve multiple rounds of placing and resolving some bets, and leaving other bets in play.

For Bet deletion to work, we have to provide a method to remove a bet. When we look at Game and bet resolution we'll return to bet deletion. It's import not to over-design this class at this time; we will often add features as we develop designs for additional use cases.

Design

There are two classes to design: the InvalidBet exception and the Table class.

InvalidBet Exception

InvalidBet is thrown when the Player attempts to place a bet which exceeds the table's limit.

 InvalidBet extends Exception {
}

This class simply inherits all features of the superclass.

In Python, the declaration must have a body, and the pass statement is used for this purpose.

class  InvalidBet ( Exception ): 
    pass 

Table Class

Table contains all the Bets created by the Player. A table also has a betting limit, and the sum of all of a player's bets must be less than or equal to this limit. We assume a single Player in the simulation.

Fields

  • int limit ;

    This is the table limit. The sum of a Player's bets must be less than or equal to this limit.

  • List bets ;

    This is a LinkedList of the Bets currently active. These will result in either wins or losses to the Player.

Constructors

  • Table();

    Creates an empty LinkedList of bets.

Methods

  • boolean isValid(Bet bet);

    Validates this bet. If the sum of all bets is less than or equal to the table limit, then the bet is valid, return true. Otherwise, return false.

  • void placeBet(Bet bet)
        throws InvalidBet;

    Adds this bet to the list of working bets. If the sum of all bets is greater than the table limit, then an exception should be thrown (Java) or raised (Python). This is a rare circumstance, and indicates a bug in the Player more than anything else.

  • ListIterator iterator();

    Returns a ListIterator over the list of bets. This gives us the freedom to change the representation from LinkedList to any other Collection with no impact to other parts of the application.

    In Python, we can return an iterator over the available list of Bet instances. The traditional Python approach is to return the list object itself, rather than an iterator over the list. With the introduction of the generators in Python 2.3, however, it can be slightly more flexible to return an iterator rather than the list object.

    Note that we need to be able remove Bets from the table. Consequently, we have to update the list, which requires that we use a ListIterator, not an Iterator.

  • Java: public String toString();

    Python: def __str__(self):

    reports on all of the currently placed bets.

Deliverables

There are three deliverables for this exercise. Each of these will have complete Javadoc comments or Python docstring comments.

  • An InvalidBet exception class. This is a simple subclass of Exception.

  • The Table class.

  • A class which performs a unit test of the Table class. The unit test should create at least two instances of Bet, and establish that these Bets are managed by the table correctly.

Chapter 10. Roulette Game Class

Between Player and Game, we have a chicken-and-egg design problem. 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, Pssenger57 (they always bet on black), which will be the basis for further design in later chapters.

Overview

The RouletteGame's responsibility is to cycle through the various steps of the game procedure, getting bets from the player, spinning the wheel and resolving the bets. 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 sequence of operations in one round of the game is the following.

Procedure 10.1. 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.

  3. Resolve All Bets

    For each Bet, b placed by the Player:

    1. Winner?

      If Bet b's 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's 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.

Matching Algorithm. This class has 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 have two collections that must be matched: Bin and Table. We'll need to structure a loop or nested loops to compare individual elements from these two collections.

We could use a loop to visit each Outcome in the winning Bin. For each Outcome, we would then visit each of the Bets contained by the Table. A Bet's Outcome that matches the Bin's Outcome is a winner and is paid off. The other bets are losers. This involves two nested loops: one to visit the winning Outcomes of a Bin and one to visit the Bets of a Table.

The alternative is to visit each Bet contained by the Table. Since the winning Bin is defined by a Set, we can exploit set membership methods to test for presence or absence of an Bet's Outcome in that Bin's Set. If the Bin contains the Outcome, the Bet is a winner; otherwise the Bet is a loser. This only requires a single loop to visit the Bets of a Table.

Player Interface. The Game collaborates with Player. We have a “chicken and egg” problem in decomposing the relationship between these classes. 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. In several future exercises, we'll revisit this design to make more sophisticated players.

For some additional design considerations, see the sidebar Additional Design Considerations. 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.

Design

There are two classes to design: a stub Player that we can use for testing, and the actual Game.

Passenger57 Class

PlayerStub 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 Building Bets. First, we can build the Black outcome from scratch as a distinct object. This works well for a stub that will only participate in a unit test. However, it's annoying to have to specify the odds each time. Second, we can interrogate the Wheel or BinBuilder for a specific Outcome object. Third, we can generalize this approach by adding a Map to the Wheel, and getting Outcome objects from the Map.

For now, we can create the black Outcome manually.

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

Fields

  • Outcome black = null;

    This is the outcome on which this player focuses their betting. For now, we'll build this Outcome the hard way. In the long run, we should get this from some souce (Game, Wheel or Table) using a well-known bet name.

  • Table table ;

    Used to place individual bets.

Constructors

  • PlayerStub(Table aTable);

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

Methods

  • void placeBets();

    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.

  • void win(Bet theBet);

    Notification from the Game that the Bet was a winner. The amount of money won is available via theBet winAmount.

  • void lose(Bet theBet);

    Notification from the Game that the Bet was a loser.

Roulette Game Class

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 theWheel ;

    Contains the wheel that returns a randomly selected Bin of Outcomes.

  • Table theTable ;

    Contains the bets placed by the player.

  • Player thePlayer ;

    Places bets on the table.

Constructors

  • We based the Roulette constructor on a design that allows any of the fields to be replaced. This is the Strategy design pattern. Each of these 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(Wheel aWheel,
         Table aTable);

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

Methods

  • void cycle(Player aPlayer);

    This will execute a single cycle of play with a given Player. It will call thePlayer placeBets to get bets. It will call theWheel next to get the next winning Bin. It will then call theTable's bets 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, otherwise call the thePlayer lose.

Questions and Answers

Q: Why are Table and Wheel part of the constructor while Player is given as part of the cycle method?
Q: Why do we have to include the odds with the Outcome? This pairing makes it difficult to create an Outcome from scratch.
Q:

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

A:

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.

Q:

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

A:

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.

Deliverables

There are three deliverables for this exercise. The stub does not need documentation, but the other classes do need complete Javadoc or 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.

Chapter 11. Review of Testability

This chapter presents some design rework and implementation rework for testability purposes. While testability is very important, new programmers can be slowed to a crawl by the mechanics of building test drivers and test cases. We prefer to emphasize the basic design considerations first, and address testability as a feature to be added to a working class.

Additionally, we'll address some issues in construction of class instances and an idealized structure for the main procedure.

Overview

We have been studiously ignoring an elephant standing in the saloon. This is the problem of testing an application that includes a random number generator (RNG). There are two questions raised:

  1. How can we develop formalized unit tests when we can't predict the random outcomes? This is a serious testability issue in randomized simulations. This question also arises when considering interactive applications, particularly for performance tests of web applications where requests are received at random intervals.

  2. Are the numbers really random? This is a more subtle issue, and is only relevant for more serious applications. Cryptographic, statistical or actuarial applications may care about the randomness of random numbers. This is a large subject, and well beyond the scope of this book. We'll just assume that our random number generator is good enough.

We'll address this first issue by developing some scaffolding that permits controlled testing. There are three approaches to replacing the random behavior with something more controlled. One approach is to subclass Wheel to create a more testable version without the random number generator. An alternative to changing wheel is to define a subclass of java.util.Random (or Python's random) that isn't actually random. A third approach is to record the sequence of random numbers actually generated from a particular seed value and use this to define the exected test results. For more information on this third alternative, see On Random Numbers.

Good testability is achieved when there are no changes to the target software. For this reason, we don't want to have two versions of Wheel, one for testing and one for normal operations.

Instead of having two versions of Wheel, it's slightly better to have a random number generator that creates a known sequence with which we can test. To get this known sequence, we have a choice between creating a non-random subclass of java.util.Random or controlling the seed for the random number generator used by Wheel. Both will produce a known sequence of non-random values.

One consequence of either of these decisions is that we have to make the random number generator in Wheel more visible. Our favorite approach to making something more visible is to assign an object through an official interface method or possible as part of the constructor. In the case of Wheel, we'd jave our overall simulation or test assign an appropriate number generating object to the instance of Wheel rather than have Wheel privately create a generator.

When we are doing testing, we can associate a Wheel with an instance of NonRandom, or an instance of Random initialized with a known seed. For actual use, we can then associate a Wheel with an instance of java.util.Random; this will either use the RNG's default constructor to assure unpredictability.

We'll need an additional constructor for Wheel that allows us to provide an appropriately initialized generator. Our current constructor secretly creates an instance of a RNG, using the RNG's default constructor. We'll need an additional method that provides an RNG, already initialized with an appropriate value.

For Python programmers, this is handled as a second parameter with a default value.

Note that, consistent with our principle of deferred binding, we don't have to choose exactly which implementation we will use. By allowing Wheel to take either an instance of Random initialized with a given seed or an instance of a new subclass, NonRandom, we have given ourselves the flexibility to choose either implementation. We'll provide specifications for NonRandom, but using a fixed seed for Random is seen by some as simpler.

Questions and Answers

Q: Why are we making the random number generator more visible? Isn't object design about encapsulation?
Q: If setting the seed works so well, why make a non-random subclass?
Q:

Why are we making the random number generator more visible? Isn't object design about encapsulation?

A:

Encapsulation isn't the same thing as “information hiding”. For some people, the information hiding concept can be a useful way to begin to learn about encapsulation. However, information hiding is misleading because it is often taken to exremes. In this case, we want to encapsulate the bins of the wheel and the procedure for selecting the winning bin into a single object. However, the exact random-number generator (RNG) is a separate component, allowing us to bind any suitable RNG.

Consider the situation where we are generating random numbers for a cryptographic application. In this case, the built-in random number generator may not be random enough. In this case, we may have a third-party Super-Random-Generator that should replace the built-in generator. We would prefer to minimize the changes required to introduce this new class.

Our initial design has isolated the changes to the Wheel, but required us to change the constructor. Since we are changing the source code for a class, we must to unit test that change. Further, we are also obligated unit test all of the classes that depend on this class. Changing the source for a class deep within the application forces us to endure the consequence of retesting every class that depends on this deeply buried class. This is too much work to simply replace one object with another.

We do, however, have an alternative. We can change the top-level main method, altering the concrete object instances that compose the working application. By making the change at the top of the application, we don't need to change a deeply buried class and unit test all the classes that depend on the changed class. Instead, we are simply choosing among objects with the same superclass or interface.

This is why we feel that constructors should be made very visible using the various design patterns for Factories and Builders. Further, we look at the main method as a kind of master Builder that assembles the objects that comprise the current execution of our application.

See our Why does the Game class run the sequence of steps? Isn't that the responsibility of some “main program? FAQ for more on this subject.

Looking ahead, we will have additional notes on this topic as we add the SevenReds subclass of Player.

Q:

If setting the seed works so well, why make a non-random subclass?

A:

While setting the seed is an excellent method for setting up a unit test, not everyone is comfortable with random number generators. The presence of an arbitrary but predictable sequence of values looks too much like luck for some Quality Assurance (QA) managers. While the algorithms for both Python and Java random number generators are published and well-understood, this isn't always sufficiently clear and convincing for non-mathematicians. It's often seems simpler to build a non-random subclass than to control the existing class.

From another point of view, creating a subclass of a built-in class is a necessary skill. The random number generators are easy classes to extend. Extending the abstract collection classes is also a very useful skill, but somewhat more complex than extending the random number generator.

Design

We'll present the design modification for Wheel first. This will be followed by design information for NonRandom in both Java and Python.

Wheel Rework

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.

Note that we will be rewriting these methods to change their implementation. The definitions of the methods don't change, but the implementations can (and do) change. This is the real strength of OO design. Once we have a well-defined interface (defined by the public methods and attributes), we are free to change the implementation as necessary.

This kind of rework — making a fundamental change to the implementation without touching the interface — is essential to successful OO programming.

Fields

  • Java: java.util.Vector bins = new Vector( 38 );

    Python: bins = 38*[None] 

    Contains the individual Bin instances.

  • Java: java.util.Random rng = new java.util.Random();

    Python: rng = random.Random() 

    Generates the next random number, used to select a Bin from the bins collection.

Constructors

  • Wheel();

    This will also create a new random number generator instance. It will then use the other constructor to do the rest of the initialization.

  • Wheel(Random rng);

    Creates a new wheel with 38 empty Bins. It will use the given random number generator instance.

Methods

  • addOutcome(int number,
               Outcome outcome);

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

  • next();

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

    Java: Be sure to note that the java.util.Random nextInt method uses the size of the bins collection to return values from 0 to the size of the collection. Typically there are 38 values, numbered from 0 to 37.

    Python: the choice function will select one of the available Bins from the bins list.

  • Bin getBin(int aBin);

    Returns the given Bin from the internal collection.

Java NonRandom Class

We need a controlled kind of random number generation for testing purposes. We'll define a class NonRandom that extends java.util.Random, but provides a more testable sequence of values. One approach is to simply count through a range of integer vales.

The API documentation gives us the advice that the protected int next(int bits); generates the next pseudorandom number. It says that any subclass should override this, as this is used by all other methods.

Fields

  • int value = 0;

    This will be used to count through the non-random sequence of values.

Constructors

  • NonRandom();

    This is the default constructor, and will initialize value to zero.

  • NonRandom(long seed);

    This constructor ignores the seed value. It initializes value to zero.

Methods

  • next(int bits);

    This method is the heart of the generator. In our case, we simply add one to value and return the new value. This provides a predicable sequence of values.

Python NonRandom Class

Nonrandom module provides the same interface as random.Random, but merely counts through a range of integer vales.

The API documentation gives us the advice that a subclass should override the random(), seed(), getstate(), setstate() and jumpahead() methods. However, we can safely ignore all but the random() method itself.

Fields

  • value = 0 

    This will be used to count through the non-random sequence of values.

Constructors

  • def NonRandom(self):

    This is the default constructor, and will initialize value to zero.

  • def NonRandom(self, seed):

    This constructor ignores the seed value. It initializes value to zero.

Methods

  • def random(self):

    This method is the heart of the generator. In our case, we simply add 1 to the value and return (value % 38)/38.0 . This provides a predicable sequence of values that is suitable for our testing.

    The return value does two things. First, it computest the remainder when the value is divided by 38. This will be a value from 0 to 37. Then it divides this by 38 to return a floating-point number in the half-open range [0.0, 1.0), as required by the API. This means that 0.0 is included, but 1.0 is not included.

Deliverables

There are five deliverables for this exercise. All of these deliverables need appropriate Javadoc comments or Python docstrings.

  • A modified design for Wheel. This add the second constructor that allows us to pass in a particular random number generator to Wheel. Also, there will be considerable rewriting of the original Wheel.

  • The NonRandom class.

  • A demonstration program of the new wheel and the NonRandom class (or the Random class with a known seed) that shows the results of a dozen spins of the non-random wheel. In the case of seeding Random, the spins while be in an arbitrary order, but will be the same each time the program is run.

  • Revised unit tests for Wheel based on the fixed sequence of responses from the non-random number generator.

  • Revised unit tests for Game based on this revised version of Wheel based on the fixed sequence of responses from the non-random number generator.

Chapter 12. Player Class

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

Overview

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

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

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 two 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 Bets, 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. This reflects a more general situation where a stateful game has bets that change. In Craps, for example, most bets cannot be placed until a point is established.

  3. The Table throws an “illegal bet” exception when an illegal bet is placed. While permissable, this kind of use for exceptions pushes the envelope on clarity and simplicity. One viewpoint is that exceptions should be reserved for situations that are truly unexpected. In this case, we expect to run into the table limit situation fairly often using Martigale betting.

We recommend the second choice: adding a isValid method to the Table class. This has the consequence of allocating responsibility to Table, and permits us to have more advanced games where some bets are not allowed during some game states. It also obligates Player to validate each bet with the Table. It also means that at some point, the player may be unable to place a legal bet.

We could also implement this by adding to the responsibilities of the existing placeBet method. For example, we could return true if the bet was accepted, and false if the bet violated the limits or other game state rules. We prefer to isolate responsibility and create a second method rather than pile too much into a single method.

Leaving the Table. In enumerating the consequences of checking for legal bets, we also uncovered 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

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.

Design

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 superclass

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

  • int stake ;

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

  • int 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.

  • Table table ;

    Used to place individual Bets.

Constructors

  • Player(Table aTable);

    Constructs the Player with a specific Table for placing Bets.

Methods

  • boolean playing();

    Returns true while the player is still active.

  • void placeBets();

    Updates the Table with the various Bets.

    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 Overview for more information.

  • void win(Bet theBet);

    Notification from the Game that the Bet was a winner. The amount of money won is available via theBet winAmount.

  • void lose(Bet theBet);

    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

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

  • int lossCount ;

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

  • int 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 2lossCount.

Methods

  • void placeBets();

    Updates the Table with a bet on black. The amount bet is 2lossCount, which is the value of betMultiple.

  • void win(Bet theBet);

    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.

  • void lose(Bet theBet);

    Increments lossCount by 1 and doubles betMultiple.

Deliverables

There are six deliverables for this exercise. The new classes must have Javadoc comments or 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 Outcomes, Bins, 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.

Chapter 13. Overall Simulation Control

This section starts to address the overall “main program” that pulls the various classes together to create a finished application. Additionally, we'll also address some issues in how Java handles collections of primitive data types like integers.

Overview

We can now use our application to generate some more usable results. We can perform a number of simulation runs and evaluate the long-term prospects for the Martingale betting system. We want to know a few things about the game:

  • How long can we play with a given budget? In other words, how many spins before we've lost our stake.

  • How much we can realistically hope to win? How large a streak can we hope for? How far ahead can we hope to get before we should quit?

The Simulator will be an active class with a number of responsibilities

  • Create the Wheel, Table and Game objects.

  • Simulate a number of sessions (typically 100), saving the maximum stake and length of each session.

  • For each session: initialize the Player and Game, cycle the game a number of times, collect the size of the Player's stake after each cycle.

  • Write a final summary of the results.

At this point, we'll need for formalize some definitions, just to be crystal clear on the responisbility allocation.

Simulation Terms

cycle

A single cycle of betting and bet resolution. This depends on a single random event: a spin of the wheel or a throw of the dice. Also known as a round of play.

session

One or more cycles. The session begins with a player having their full stake. A session ends when the play elects to leave or can no longer participate. A player may elect to leave because of elapsed time (typically 250 cycles), or they have won a statistically significant amount. A player can no longer participate when their stake is too small to make the minimum bet for the table.

game

Some games have intermediate groupings of events between an individual cycles and an entire session. Blackjack has hands, where a number of player decisions and a number of random events contribute to the payoff. Craps has a game, which starts with the dice roll when the point is off, and ends when the point is made or the shooter gets Craps; consequently, any number of individual dice rolls can make up a game. Some bets are placed on the overall game, while others are placed on individual dice rolls.

The sequence of operations for the simulator looks like this.

Procedure 13.1. Controlling the Simulation

  1. Empty List of Maxima. Create an empty maxima list. This is the maximum stake at the end of each session.

  2. Empty List of Durations. Create an empty durations list. This is the duration of each session, measured in the number of cycles of play before the player withdrew or ran out of money.

  3. For All Sessions. For each of 100 sessions:

    1. Empty List of Stake Details. Create an empty list to hold the history of stake values for this session. This is raw data that we will summarize into two metrics for the session: maximum stake and duration. We could also have two simple variables to record the maximum stake and count the number of spins for the duratioon. However, the list allows us to gather other statistics, like maximum win or maximum loss.

    2. While The Player Is Active

      1. One Cycle. Play one cycle of the game. See the definition in Roulette Game Class.

      2. Save Detail. Save the player's current stake in the list of stake values for this session. The alternative is to update the maximum to be the larger of the current stake and the maximum, and increment the duration.

    3. Get Maximum. Get the maximum stake from the list of stake values. Save the maximum stake metric in the maxima list.

    4. Get Duration. Get the length of the list of stake values. Save the duration metric in the durations list.

  4. Statistical Description of Maxima. Compute the average and standard deviation of the values in the maxima list.

  5. Statistical Description of Durations. Compute the average and standard deviation of values in the durations list.

Both this overall Simulator and the Game collaborate with the Player. The Simulator's collaboration, however, initalizes the Player and then monitor's the changes to the Player's stake. We have to design two interfaces for this collaboration.

Player Initialization. The Simulator will initialize a Player for 250 cycles of play, assuming about one cycle each minute, and about four hours of patience. We will also initialize the player with a generous budget of the table limit, 100 betting units. For a $10 table, this is $1,000 bankroll.

Currently, the Player class is designed to play one session and stop when their duration is reached or their stake is reduced to zero. We have two alternatives for reinitializing the Player at the beginning of each session.

  1. Provide some setters that allow a client class like this overall simulator control to reset the stake and roundsToGo values of a Player.

  2. Provide a Factory that allows a client class to create new, freshly initialized instances of Player.

While the first solution is quite simple, there are some advantages to creating a PlayerFactory. If we create an Abstract Factory, we have a single place that creates all Players. Further, when we add new player subclasses, we introduce these new subclasses by creating a new subclass of the factory. In this case, however, only the main program creates instances of Player, reducing the value of the factory. While design of the factory is a good exercise, we can scrape by with adding setter methods to the Player class.

Player Interrogation. The Simulator will interrogate the Player after each cycle and capture the current stake. An easy way to manage this detailed data is to create a List that contains the stake at the end of each cycle. The length of this list and the maximum value in this list are the two metrics the Simulator gathers for each session.

Our list of maxima and durations are created sequentially during the session and summarized sequentially at the end of the session. A Java LinkedList will do everything we need. For a deeper discussion on the alternatives available in the collections framework, see Java Collections.

Statistical Summary. The Simulator will interrogate the Player after each cycle and capture the current stake. We don't want the sequence of stakes for each cycle, however, we want a summary of all the cycles in the session. We can save the length of the sequence as well as the maximum of the sequence to determine these two aggregate performance parameters for each session. Our objective is to run several session simulations to get averages and a standard deviations for duration and maximum stake. This means that the Simulator needs to retain these statistical samples. We will defer the detailed design of the statistical processing, and simply keep the duration and maximum values in Lists for this first round of design.

Design

Simulator exercises the Roulette simulation with a given Player placing bets. It reports raw statistics on a number of sessions of play.

Fields

  • int initDuration = 250;

    The duration value to use when initializing a Player for a session.

  • int initStake = 100;

    The stake value to use when initializing a Player for a session.

  • int samples ;

    The number of game cycles to simulate. A default value of 50 makes sense.

  • List durations ;

    A list of lengths of time the Player remained in the game. Each session of play producrs a duration metric, which are collected into this list.

  • List maxima ;

    A list of maximum stakes for each Player. Each session of play producrs a maximum stake metric, which are collected into this list.

  • Player thePlayer ;

    The betting strategy we are simulating.

  • Game theGame ;

    The casino game we are simulating.

Constructors

  • Simulator(Game game,
              Player player);

    Saves the Player and Game instances so we can gather statistics on the performance of the player's betting strategy.

Methods

  • List session();

    Executes a single game session. The Player is initialized with their initial stake and initial cycles to go. An empty List of stake values is created. The session loop executes until the Player playing returns false. This loop executes the Game cycle; then it gets the stake from the Player and appends this amount to the List of stake values. The List of individual stake values is returned as the result of the session of play.

  • gather();

    Executes the number of games sessions in samples. Each game session returns a List of stake values. When the session is over (either the play reached their time limit or their stake was spent), then the length of the session List and the maximum value in the session List are the resulting duration and maximum metrics. These two metrics are appended to the durations list and the maxima list.

    A client class will either display the durations and maxima raw metrics or produce statistical summaries.

Deliverables

There are five deliverables for this exercise. Each of these classes needs complete Javadoc or Python docstring comments.

  • Revision to the Player class to add two new methods: one will set the stake and the other will set the roundsToGo.

    void setStake(int initStake);
    void setRounds(int initRounds);
  • The Simulator class.

  • The expected outcomes from the non-random wheel can be rather complex to predict. Because of this, one of the deliverables is a demonstration program that enumerates the actual sequence of non-random spins. From this we can derive the sequence of wins and losses, and the sequence of Player bets. This will allow us to predict the final outcome from a single session.

  • A unit test of the Simulator class that uses the non-random generator to produce the predictable sequence of spins and bets.

  • A main application class that creates the necessary objects, runs the Simulator's gather method, and writes the available outputs to System.out In this case, it will print the list of maxima, and the list of session lengths. This raw data can be redirected to a file, loaded into a spreadsheet and analyzed.

Chapter 14. Player SevenReds

This section introduces an additional specialization of the Martingale strategy. Additionally, we'll also address some issues in how an overall application is composed of individual class instances. Adding this new subclass should be a small change to the main application class.

Overview

The SevenReds player waits for seven red wins in a row before betting black. This is a subclass of Player. We can create a subclass of our main Simulator to use this new SevenReds class.

We note that our previous players (Passenger57 and Martingale) are stateless: they place the same bets over and over until they are cleaned out or their playing session ends. This SevenReds player has two states: waiting and betting. In the waiting state, they are simply counting the number of reds. In the betting state, they have seen seven reds and are now playing the Martingale system on black. We will defer serious analysis of this stateful betting until some of the more sophisticated subclasses of Player. For now, we will simply use an integer to count the number of reds.

Currently, the player is not informed of the final outcome unless they place a bet. We designed the Game to evaluate the Bet instances and notify the Player of just their Bets that were wins or losses. We will need to add a method to Player to be given the overall list of winning Outcomes even when the Player has not placed a bet.

Once we have updated the design of Game to notify Player, we can add the new SevenReds class. Note that we are can intoduce each new betting strategy via creation of new subclasses. A relatively straightforward update to our simulation main program allows us to use these new subclasses. The previously working subclasses are left in place, allowing graceful evolution by adding features with minimal rework of existing classes.

In addition to waiting for the wheel to spin seven reds, we will also follow the Martingale betting system to track our wins and losses, assuring that a single win will recoup all of our losses. This makes SevenReds a further specialization of Martingale. We will be using the basic features of Martingale, but doing additional processing to determine if we should place a bet or not.

Introducing a new subclass should be done by upgrading the main program. See Soapbox on Composition for comments on the ideal structure for a main program. Additionally, see our Why does the Game class run the sequence of steps? Isn't that the responsibility of some “main program? FAQ entry and Why are we making the random number generator more visible? Isn't object design about encapsulation? FAQ entry for more discussion.

Design

SevenReds is a Martingale player who places bets in Roulette. This player waits until the wheel has spun red seven times in a row before betting black.

Fields

  • int redCount ;

    The number of reds yet to go. This starts at 7, is reset to 7 on each non-red outcome, and decrements by 1 on each red outcome.

  • Note that we inherit betMultiple. This is initially 1, doubles with each loss and is reset to one on each win.

Methods

  • void placeBets();

    If redCount is zero, this places a bet on black, using the bet multiplier.

  • void winners(Bin outcomes);

    This is notification from the Game of all the winning outcomes. If this vector includes red, redCount is decremented. Otherwise, redCount is reset to 7.

Deliverables

There are five deliverables from this exercise. The new classes will require complete Javadoc comments or Python docstrings.

  • A revision to the Player to add the following method. The superclass version doesn't do anything with this information. Some subclasses, however, will process this.

    void winners(Bin outcomes);
  • The SevenReds subclass of Player.

  • A revision to the Game class. This will call the winners with the winning Bin instance before paying off the bets.

  • A unit test of the SevenReds class. This test should synthesize a fixed list of Outcomes, Bins, and calls a SevenReds instance with various sequences of reds and blacks. One test cases can assure that no bet is placed until 7 reds have been seen. Another test case can assure that the bets double (following the Martingale betting strategy) on each loss.

  • A main application class that creates the necessary objects, runs the Simulator's gather method, and writes the available outputs to System.out In this case, it will simply print the list of maxima, and the list of session lengths.

Chapter 15. Statistical Measures

This section presents two ordinary statistical algorithms: mean and standard deviation. In one sense, this section could be treated as optional. Instead of having the simulator present useful summary statistics, raw data can be put into a spreadsheet for creating the statistics.

However, the process of designing these statistical processing classes is very interesting. This chapter examines some ways to add features to the built-in collection classes.

Overview

Currently, the Simulator class collects two Lists. One has the length of a session, the other has the maximum stake during a session. We need to create some descriptive statistics to summarize these stakes and session lengths.

We will design a Statistics class with responsibility to retain and summarize a list of numbers and produce the average (also known as the mean) and standard deviation. The Simulator can then use this this Statistics class to get an average of the maximum stakes from the list of session-level measures. The Simulator can also apply this Statistics class to the list of session durations to see an average length of game play before going broke.

We can encapsulate this statistical processing in two ways: we could extend an existing class or we could delegate the statistical functions to a separate class. If we extend the library java.util.List, we can add the needed statistical summary features; given this new class, we can replace the original List of sample values with a StatisticalList that both saves the values and computes descriptive statistics. In addition to delegation, we have two choices for implementation of this extended version of List, giving us three alternative designs.

  1. Extend LinkedList. In this case, we are simply adding two new methods to the existing LinkedList implementation. However, our new methods would not apply to ArrayList or Vector.

  2. Extend AbstractList. However, in doing this we would need to provide the implementation details of the list structure itself. In effect, we would reimplement LinkedList or ArrayList.

  3. Create a separate class that computes statistics on a List given as an argument to the methods of the statistical class. In this case, our statistical methods will work with any subclass that implements the interface List. This can be applied to any of LinkedList, ArrayList or Vector.

Alternative three -- a separate statistical class -- has the most reuse potential. We'll create a simple class with two methods: mean and stdev to compute the mean and standard deviation of a List of Integer objects. This can be used in a variety of contexts, and allows us the freedom to switch List implementation classes without any other changes.

The detailed algorithms for mean and standard deviation are provided in Statistical Algorithms.

Since our processes are relative simple, stateless algorithms, we don't need any instance variables. Consequently, we'll never need to create an instance of this class. As with Java.lang.Math, all of these methods can be declared as static, making them features of the class itself.

Some Foundations

For those programmers new to statistics, this section covers the Sigma operator, Σ.

Equation 15.1. Basic Summation

f(i) for 0 <= i < n

The Σ operator has three parts to it. Below it is a bound variable, i and the starting value for the range, written as i=0. Above it is the ending value for the range, usually something like n. To the right is some function to execute for each value of the bound variable. In this case, a generic function, f(i). This is read as “sum f(i) for i in the range 0 to n”.

One common definition of Σ uses a closed range, including the end values of 0 and n. However, since this is not a helpful definition for software, we will define Σ to use a half-open interval. It has exactly n elements, including 0 and n-1; mathematically, 0 <= i < n.

Consequently, we prefer the following notation, but it is not often used. Since statistical and mathematical texts often used 1-based indexing, some care is required when translating formulae to programming languages that use 0-based indexing.

Equation 15.2. Summation with Half-Open Interval

f(i) for 0 <= i < n

Our two statistical algorithms have a form more like the following function. In this we are applying some function, f(), to each value, xi of an array. When computing the mean, there is no function. When computing standard deviation, the function involves subtracting and multiplying.

Equation 15.3. Summing Elements of an Array, x

f(x[i]) for 0 <= i < n

We can transform this definition directly into a for loop that sets the bound variable to all of the values in the range, and does some processing on each value of the List of Integers. This is the Java implementation of Sigma. This computes two values, the sum, sum and the number of elements, n.

Example 15.1. Java Sigma Iteration

int sum= 0;
for( int i= 0; i != theList.size(); ++i ) { 1
    int xi= ((Integer)theList.get(i)).intValue(); 2
    // int fxi = processing of xi 3
    sum += fxi;
}
int n= theList.size();
1

Get the size of theList. Execute the body of the loop for all values of i in the range 0 to the number of elements-1.

2

Fetch item i from theList. This is an Object, which we cast to be an Integer. We then get the int value of this item and assign it to xi.

3

For simple mean calculation, this statement does nothing. For standard deviation, however, this statement computes the measure of deviation from the average.

This is the Python implemention of Sigma. This computes two values, the sum, sum and the number of elements, n.

Example 15.2. Python Sigma Iteration

sum= 0
for i in range(len(theList)): 1
    xi= theList[i] 2
    # fxi = processing of xi 3
    sum += fxi
n= len(theList)
1

Get the length of theList. Execute the body of the loop for all values of i in the range 0 to the number of elements-1.

2

Fetch item i from theList and assign it to xi.

3

For simple mean calculation, this statement does nothing. For standard deviation, however, this statement computes the measure of deviation from the average.

More advanced Python programmers may be aware that there are several ways to shorten this loop down to a single expression using the sum function as well as list displays.

In the usual mathematical notation, an integer index, i is used. In Java or Python it isn't necessary to use the formal integer index. Instead, an iterator can be used to visit each element of the list, without actually using an explicit numeric counter. In Java, the use of an iterator is shown below.

Example 15.3. Java Sample Values by Iterator

int sum= 0;
for( Iterator i= theList.iterator(); i.hasNext();  ) { 1
    int xi= ((Integer)i.next()).intValue(); 2
    // int fxi = processing of xi 3
    sum += fxi;
}
int n= theList.size();
1

Get an Interator over all items of theList. Execute the body of the loop for all items in the iterator.

2

Fetch the next item from the Interator, i. This is an Object, which we cast to be an Integer. We then get the int value of this item and assign it to xi.

3

For simple mean calculation, this statement does nothing. For standard deviation, however, this statement computes the measure of deviation from the average.

In Python, the iterator is implied, and the processing simplifies to the following.

Example 15.4. Python Sample Values by Iterator

for xi in theList: 1
    # fxi = processing of xi 2
    sum += fxi
n= len(theList)
1

Execute the loop assigning each item of of theList to xi.

2

For simple mean calculation, this statement does nothing. For standard deviation, however, this statement computes the measure of deviation from the average.

These iterator-based formulations can be slightly faster when using a LinkedList because this class is optimized for exactly this kind of sequential access.

Statistical Algorithms

Computing the mean of a list of values is relatively simple. The mean is the sum of the values divided by the number of values in the list. Since the statistical formula is so closely related to the actual loop, we'll provide the formula, followed by an overview of the code.

Equation 15.4. Mean

sum( [ x[i] for i in range(n) ] )/ n

The definition of the Σ mathematical operator leads us to the following method for computing the mean:

Procedure 15.1. Computing Mean

  1. intialize sum, s, to zero

  2. for each value, i, in the range 0 to the number of values in the list, n:

    1. add element xi to s

  3. return s divided by the number of elements, n

The standard deviation can be done a few ways, but we'll use the formula shown below. This computes a deviation measurement as the square of the difference between each sample and the mean. The sum of these measurements is then divided by the number of values times the number of degrees of freedom to get a standardized deviation measurement. Again, the formula summarizes the loop, so we'll show the formula followed by an overview of the code.

Equation 15.5. Standard Deviation

sqrt( sum( [ (x[i]-meanx)^2 ) for i in range(n) ] ) /
              (n-1)

The definition of the Σ mathematical operator leads us to the following method for computing the standard deviation:

Procedure 15.2. Computing Standard Deviation

  1. compute the mean, m

  2. intialize sum, s, to zero

  3. for each value, xi in the list:

    1. compute the difference from the mean, d as xi - m

    2. add d2 to s. This is typically done as d*d in Java, since there is no “squared” operator. In Python, this can be d**2.

  4. compute the variance, v, as s divided by (n - 1). This n - 1 value reflects the statistical notion of “degrees of freedom”, which is beyond the scope of this book.

  5. return the square root of the variance, v.

Tip

For programmers new to Java, the java.lang.Math contains the Math.sqrt, which computes square roots.

For programmers new to Python, the math module contains the math.sqrt, which computes square roots.

Programmers who have the Python numeric module installed will find that this module provides some alternatives to the designs shown in this section. Since this module is optional, we won't cover it any any depth here. However, it can simplify the methods for computing mean and standard deviation.

Design

IntegerStatistics computes several simple descriptive statistics of Integer values in a List.

Constructors

  • IntegerStatistics();

Methods

  • static double mean(List aList);

    Computes the mean of the List of Integer values.

  • static double stdev(List aList);

    Computes the standard deviation of the List of Integer values.

Deliverables

There are three deliverables for this exercise. These classes will include the complete Javadoc comments or Python dostring.

  • The IntegerStatistics class.

  • A unit test of the IntegerStatistics class that some simple LinkedList, ArrayList, and Vector test data. The results can be checked with a spreadsheet.

  • An update to the overall Simulator that gets uses an IntegerStatistics object to compute the mean and standard deviation of the peak stake. It also computest the mean and standard deviation of the length of each session of play.

Here is some standard deviation unit test data, some intermediate results and the correct answers given to 6 significant digits. Your answers should be the same to the precision shown.

Sample Value
9
8
5
9
9
4
5
8
10
7
8
8
sum90
mean7.5
sum d*d608.668
stdev1.88293

Chapter 16. Player Random

This section will introduce a simple subclass of Player who bets at random.

Overview

One possible betting strategy is to bet completely randomly. This serves as an interesting benchmark for other betting strategies.

We'll write a subclass of Player which steps through all of the bets available on the Wheel, selecting one or more of the available outcomes at random. This Player, like others, will have a fixed initial stake and a limited amount of time to play.

The Wheel class can provide an Iterator over the collection of Bin instances. We could revise Wheel to provide a binIterator method that we can use to return all of the Bins. From each Bin, we will need an iterator we can use to return all of the Outcomes.

To collect a list of all possible Outcomes, we would use the following algorithm:

Procedure 16.1. Locating all Outcomes

  1. Empty List of Outcomes. Create an empty list of all Outcomes, allOC.

  2. Get Bin Iterator. Get the Iterator from the Wheel that lists all Bins.

    1. For Each Bin

      1. Get Outcome Iterator. Get the Iterator that lists all Outcomes.

      2. For Each Outcome

        1. Save Outcome. Save a reference to each Outcome in the list of all known outcomes, allOC.

To place a random bet, we would use the following algorithm:

Procedure 16.2. Placing a Random Bet

  1. Get the size of the pool of all possible Outcomes, s.

  2. Get a random number, u, from zero to the total size-1. That is, 0 <= u <= s-1.

  3. Return element u from the pool of Outcomes.

Design

PlayerRandom is a Player who places bets in Roulette. This player makes random bets around the layout.

Fields

  • Java: java.util.Random rng ;

    Python: rng  

    Generates the next random number.

Constructors

  • PlayerRandom(Table aTable,
                 Random aGenerator);

    This uses the super construct to invoke the superclass constructor using the Table. Then it saves the random number generator. This could be an instance of either Random or NonRandom.

    For those new to Java, see Java Subclass Constructors. For those new to Python, see Python Subclass Constructors.

Methods

  • void placeBets();

    Updates the Table with a randomly placed bet.

Deliverables

There are five deliverables from this exercise. The new classes need Javadoc comments or Python docstrings.

  • Updates to the Bin to return an iterator over available Outcomes

  • Updates to the Wheel to return an iterator over available Bins

  • The PlayerRandom class.

  • A unit test of the PlayerRandom class. This should use the NonRandom number generator to iterate through all possible Outcomes.

  • An update to the overall Simulator that uses the PlayerRandom.

Chapter 17. Player 1-3-2-6

This section will describe a player who has a complex internal state. We will also digress on the way the states can be modeled using a group of polymorphic classes. This section also has an advanced exercise that shows some alternative implementations for the state objects, using the Singleton design pattern.

Overview

On the Internet, we found descriptions of a betting system called the “1-3-2-6” system. This system looks to recoup losses by waiting for four wins in a row. The sequence of numbers (1, 3, 2 and 6) are the multipliers to use when placing bets after winning. At each loss, the sequence resets to the multiplier of 1. At each win, the multiplier is advanced. After one win, the bet is now 3x. After a second win, the bet is reduced to 2x, and the winnings of 4x are “taken down” or removed from play. In the event of a third win, the bet is advanced to 6x. Should there be a fourth win, the player has doubled their money, and the sequence resets.

This betting system makes our player more stateful than in previous betting systems. When designing SevenReds, we noted that this player was stateful, but deferred any more serious design consideration until now.

In this case, the description of the betting system seems to identify four states: no wins, one win, two wins, and three wins. In each of these states, we have specific bets to place, and state transition rules that tell us what to do next. The following table summarizes the states, the bets and the transition rules.

Table 17.1. 1-3-2-6 Betting States
    Next State
State Bet On Win On Loss
No Wins 1 One Win No Wins
One Win 3 Two Wins No Wins
Two Wins 2 Three Wins No Wins
Three Wins 6 No Wins No Wins

When we are in a given state, the table gives us the amount to bet in the Bet column. If this bet wins, we transition to the state named in the Win column, otherwise, we transition to the state named in the Lost column. We always start in the No Wins state.

We can exploit the State design pattern to design this more sophisticated player. This pattern suggests that we design a hierarchy of classes to represent these four states. Each state will have a slightly different bet amount, and a different next state when a bet wins. Each individual state class will be relatively simple, but we will have isolated the processing unique to each state into separate classes.

One of the consequences of the State design pattern is that it obligates us to define the interface between the Player and the object that holds the Player's current state. It seems best to have the state object follow our table and provide three methods: currentBet, nextWon, and nextLost. The Player can use these methods of the state object to place bets and pick a new state.

The state's currentBet method will construct a Bet from an Outcome that the Player keeps, and the multiplier that is unique to the state. As the state changes, the multiplier moves between 1, 3, 2 and 6.

The state's nextWon method constructs a new state object based on the state transition table when the last bet was a winner.

The state's nextLost method constructs a new state based on the state transition table when the last bet was a loser. In this case, all of the various states create a new instance of the NoWins object, resetting the multiplier to 1 and starting the sequence over again.

Questions and Answers

Q: Why code the state as objects?
Q: Isn't it simpler to code the state as a number? We can just increment when we win, and reset to zero when we lose.
Q: Doesn't this create a vast number of state objects?
Q: Is Polymorphism necessary?
Q:

Why code the state as objects?

A:

The reason for encoding the states as objects is to encapsulate the information or behavior associated with that state. In this case, we have both the bet amount and the rules for transition to next state. While simple, these are still unique to each state.

Since this is a book on design, we feel compelled to present the best design. In games like blackjack, the player's state may have much more complex information or behavior. In those games, this design pattern will be very helpful. In this one case only, the design pattern appears to be over-engineered.

We will use this same design pattern to model the state changes in the Craps game itself. In the case of the Craps game, there is additional information as well as behavior changes. When the state changes, bets are won or lost, bets are working or not, and outcomes are allowed or prohibited.

Q:

Isn't it simpler to code the state as a number? We can just increment when we win, and reset to zero when we lose.

A:

The answer to all “isn't it simpler” questions is “yes, but...”. In this case, the full answer is “yes, but what happens when you add a state or the states become more complex?

This question arises frequently in OO programming. Variations on this question include “Why is this an entire object?” and “Isn't an object over-engineering this primitive type?” See Why is Outcome a separate class? Each object that is an instance of Outcome only has two attributes; why not use an array of Strings for the names, and a parallel array of integers for the odds? FAQ entry on the Outcome class for additional background on this.

Our goal in OO design is to isolate responsibility. First, and most important, we can unambiguously isolate the responsibilities for each individual state. Second, we find that it is very common that only one state changes, or new states get added. Given these two conditions, the best object model is separate state objects.

Q:

Doesn't this create a vast number of state objects?

A:

Yes.

There are two usual follow-up questions: “Aren't all those objects a lot of memory overhead?” or “...a lot of processing overhead?

Since Java and Python remove unused objects, the old state definitions are cleaned up by the garbage collection thread. A few tests will demonstrate that the Java memory management is efficient and reliable.

Object creation is an overhead that we can control. One common approach is to use the Singleton design pattern. In this case, this should be appropriate because we only want a single instance of each of these state classes.

Note that using the Singleton design pattern doesn't change any interfaces except the initialization of the Player1326 object with the starting state.

Q:

Is Polymorphism necessary?

A:

In some design patterns, like State and Command, it is essential that all subclasses have the same interface and be uniform, indistinguishable, almost anonymous instances. Because of this polymorphic property, the objects can be invoked in a completely uniform way.

In our exercise, we will design a number of different states for the player. Each state has the same interface. The actual values for the instance variables and the actual operation implemented by a subclass method will be unique. Since the interfaces are uniform, however, we can trust all state objects to behave properly.

There are numerous designs where polymorphism doesn't matter at all. In many cases, the anonymous uniformity of subclasses isn't relevant. When we move on to example other casino games, we will see many examples of non-polymorphic class hierarchies. This will be due to the profound differences between the various games and their level of interaction with the players.

Design

There are a number of design elements: the superclass for the states, plus the individual state classes, and the actual subclass of Player that uses these states.

This version of the design will use a simpler design that constructs individual state objects on every state change. Once this is working, it can be replaced with a proper Singleton design to see how much that improves performance.

Player1326 State

Player1326State is the superclass for all of the states in the 1-3-2-6 betting system.

Fields

  • Player1326 player ;

    The player who is currently in this state. This player will be used to provide the Outcome on which we will bet.

Constructors

  • Player1326State(Player1326 player);

    The constructor for this class saves the Player1326 which will be used to provide the Outcome on which we will bet.

Methods

  • abstract Bet currentBet();

    Constructs a new Bet from the player's outcome information. Each subclass has a different multiplier used when creating this bet.

    In Java, this method can be declared as abstract. Each subclass will provide a unique implementation for this method.

    In Python, this method should raise the NotImplementedException. This is a big debugging aid, it helps us locate subclasses which did not provide a method body. This raise statement is the functional equivalent of the Java abstract declaration.

  • abstract Player1326State nextWon();

    Constructs the new Player1326State instance to be used when the bet was a winner.

    In Java, this method can be declared as abstract. Each subclass will provide a unique implementation for this method.

    In Python, this method should raise the NotImplementedException.

  • Player1326State nextLost();

    Constructs the new Player1326State instance to be used when the bet was a loser. This method is the same for each subclass: it creates a new instance of Player1326NoWins. It is not abstract, but actually defined in the superclass to assure that it is available for each subclass.

Player1326 No Wins

Player1326NoWins defines the bet and state transition rules in the 1-3-2-6 betting system. When there are no wins, the base bet value of 1 is used.

Methods

  • Bet currentBet();

    Constructs a new Bet from the player's outcome information. The bet multiplier is 1.

  • Player1326State nextWon();

    Constructs the new Player1326OneWin instance to be used when the bet was a winner.

Player1326 One Win

Player1326NoWins defines the bet and state transition rules in the 1-3-2-6 betting system. When there is one wins, the base bet value of 3 is used.

Methods

  • Bet currentBet();

    Constructs a new Bet from the player's outcome information. The bet multiplier is 3.

  • Player1326State nextWon();

    Constructs the new Player1326TwoWins instance to be used when the bet was a winner.

Player1326 Two Wins

Player1326NoWins defines the bet and state transition rules in the 1-3-2-6 betting system. When there are two wins, the base bet value of 2 is used.

Methods

  • Bet currentBet();

    Constructs a new Bet from the player's outcome information. The bet multiplier is 2.

  • Player1326State nextWon();

    Constructs the new Player1326ThreeWins instance to be used when the bet was a winner.

Player1326 No Wins

Player1326NoWins defines the bet and state transition rules in the 1-3-2-6 betting system. When there are three wins, the base bet value of 6 is used.

Methods

  • Bet currentBet();

    Constructs a new Bet from the player's outcome information. The bet multiplier is 6.

  • Player1326State nextWon();

    Constructs the new Player1326NoWins instance to be used when the bet was a winner.

Player1326

Player1326 follows the 1-3-2-6 betting system. The player has a preferred Outcome, an even money bet like red, black, even, odd, high or low. The player also has a current betting state that determines the current bet to place, and what next state applies when the bet has won or lost.

Fields

  • Outcome outcome ;

    This is the player's preferred outcome.

  • Player1326State myState ;

    This is the current state of the 1-3-2-6 betting system. It will be an instance of one of the four states: No Wins, One Win, Two Wins or Three Wins.

Constructors

  • Player1326();

    Initializes the state and the outcome. The myState is set to the initial state of an instance o0f Player1326NoWins. The outcome is set to some even money proposition, for example "Black".

Methods

  • void placeBets();

    Updates the Table with a bet created by the current state. This method delegates the bet creation to myState currentBet.

  • void win(Bet bet);

    Uses the superclass method to update the stake with an amount won. Uses the current state to determine what the next state will be by calling myState's nextWon method and saving the new state in myState

  • void lose(Bet bet);

    Uses the current state to determine what the next state will be. This method delegates the next state decision to myState's nextLost, method saving the result in myState.

Deliverables

There are eight deliverables for this exercise. Additionally, there is an optional, more advanced design exercise in a separate seciotn.

  • The five classes that make up the Player1326State class hierarchy.

  • The Player1326 class.

  • A unit test of the Player1326 class. This test should synthesize a fixed list of Outcomes, Bins, and calls a Player1326 instance with various sequences of reds and blacks. There are 16 different sequences of four winning and losing bets. These range from four losses in a row to four wins in a row.

  • An update to the overall Simulator that uses the Player1326.

Advanced Exercise

The object creation for each state change can make this particular player rather slow. Using the Singleton design pattern can make this application run faster. The difference in performance is small for this application, but can be huge for other applications. We note that the Singleton design pattern assures that only a single instance of a given class exists, guaranteeing a single centralized object which can have a number of consequences. First, it can prevent problems from duplicate use of precious resources. Second, it can improve performance and reduce memory consumption.

The general idea behind the Singleton design pattern is to assure that only a single instance of a given class ever exists. This is done by avoiding the use of an explicit constructor. Instead, a static method is provided by the class that will return the one-and-only instance of the object. Because of the technical differences in Java and Python, we'll cover each implementation in some detail.

The deliverables for this exercise are revisions to the Player1326State class hierarchy to implement the Singleton design pattern. This will not change any of the existing unit tests, demonstrations or application programs.

Singleton Design in Java

To implement the Singleton design pattern for the entire state class hierarchy, we must add a static variable, theInstance, initially null, to each individual subclass of Player1326State. Additionally, we have to add a getInstance method to each subclass. Finally, we can make the constructor protected in the superclass to prevent any other class from using it via the new operator.

Note that we can't inherit a single superclass definition of theInstance variable or getInstance method. Being static, these would be shared by all objects of all four subclasses. We would only have a single Player1326State object, and it would be the initially constructed object. Consequently, we need to have a distinct instance variable and method in each subclass, so that an instance of each distinct subclass gets created.

This aspect of this class hierarchy has to be included by a tedious cut-and-paste process. It might be nice to specify that this piece of programming is copied out of a template into each class, assuring consistent implementation of the Singleton design pattern. There are a few automated approaches to this, including tools for aspect-oriented programming and literate programming. Both of these are areas for further investigation, well outside the scope of this book.

The Player1326 constructor would be changed to use the getInstance method of Player1326NoWins to create the initial state. We have to remove the creation a new instance via new Player1326NoWins() and replace this with Player1326NoWins.getInstance().

Each state's nextWon and nextLost methods would have to use the getInstance method to get the next state instance. By removing all of the individual new operations, we assure that objects are only made available through getInstance, controlling the number of instances actually created.

We note that the compile-time binding in Java forces us to repeat these two static elements in each class. There is some administrative overhead in assuring that this aspect of the Singleton pattern is repeated in each subclass. In Python, however, the late binding makes this slightly simpler.

Singleton Design in Python

This class can be sped up by using the Singleton design pattern for the entire state class hierarchy. In Python, we have two choices for dealing with the various singleton instances. The most common solution is to create module-level variables at import time; Python assures that each module is only important once. A second solution is to implement a Java-like singleton design in the classes.

Because of the deferred binding in Python, we can easily have each class contain references module-level variables that won't be created until after the class is defined. The variables don't exist when the class is defined, but they will exist when the object methods are actually executed. Each class can then expect a module-level variable with a name like theNoWinsState. These four variables are created by the module after the definitions of the classes.

The Java-style declaration means that we have a theInstance variable which contains the one-and-only instance for this class. Note that we have two choices for handling the object creation. We can override the __new__ method, or we can provide a getInstance method. We'll focus on the getInstance method because it's most like Java.

For programmers new to Python, there are two small syntax changes that make a variable or method part of the class and not part of each individual instance. First, instance variables must be created by __init__ and qualified with the instance name (usually self). If, instead, we simply place a variable in the class definition, this variable belongs to the class, and therefore it belongs to every instance of the class. Second, if we call a method using an object name, the method is called with that given instance as the value for self. If, instead, we call a method using the class name, the method is called for the class as a whole.

Chapter 18. Player Cancellation

This section will describe a player who has a complex internal state that can be modeled using existing library classes.

Overview

One method for tracking the lost bets is called the “cancellation” system or the “Labouchere” system. The player starts with a betting budget allocated as a series of numbers. The usual example is 1, 2, 3, 4, 5, 6. Each bet is sum of the first and last numbers in the last. In our example, the end values of 1+6 leads the player to bet 7. When the player wins, they cancel the two numbers used to make the bet. In the event that all the numbers are cancelled, the player resets the sequence of numbers and starts again. For each loss, however, the player adds the amount of the bet to the end of the sequence; this is a loss to be recouped. This adds the loss to the amount bet to assure that the next winning bet both recoups the most recent loss and provides a gain. Multiple winning bets will recoup multiple losses, supplemented with small gains.

Here's an example of the cancellation system using 1, 2, 3, 4, 5, 6.

  1. Bet 1+6. A win. Cancel 1 and 6 leaving 2, 3, 4, 5.

  2. Bet 2+5. A loss. Add 7 leaving 2, 3, 4, 5, 7.

  3. Bet 2+7. A loss. Add 9 leaving 2, 3, 4, 5, 7, 9.

  4. Bet 2+9. A win. Cancel 2 and 9 leaving 3, 4, 5, 7.

  5. Next bet will be 3+7.

State. The player's state is a list of individual bet amounts. This list grows and shrinks; when it is empty, the player leaves the table. We can keep a List of individual bet amounts. The total bet will be the first and last elements of this list. Wins will remove elements from the collection; losses will add elements to the collection. Since we will be accessing elements in an arbitrary order, we will want to use an ArrayList. We can define the player's state with a simple list of values.

Design

PlayerCancellation uses the cancellation betting system. This player allocates their available budget into a sequence of bets that have an accelerating potential gain as well as recouping any losses.

Fields

  • List sequence = new List();

    This List keeps the bet amounts; wins are removed from this list and losses are appended to this list. THe current bet is the first value plus the last value.

  • Outcome outcome ;

    This is the player's preferred outcome.

Constructors

  • PlayerCancellation();

    This uses the resetSequence method to initalize the sequence of numbers used to establish the bet amount. This also picks a suitable even money Outcome, for example, black.

Methods

  • resetSequence();

    Puts the initial sequence of six Integer instances into the sequence variable. These Integers are built from the values 1 through 6.

  • void placeBets();

    Creates a bet from the sum of the first and last values of sequence. The first value is at index 0. The last value's position depends on the size of the List.

  • void win(Bet bet);

    Uses the superclass method to update the stake with an amount won. It then removes the fist and last element from sequence.

  • void lose(Bet bet);

    Uses the superclass method to update the stake with an amount lost. It then appends the sum of the first and list elements of sequence to the end of sequence as a new Integer value.

Deliverables

There are three deliverables for this exercise.

  • The PlayerCancellation class.

  • A unit test of the PlayerCancellation class. This test should synthesize a fixed list of Outcomes, Bins, and calls a PlayerCancellation instance with various sequences of reds and blacks. There are 16 different sequences of four winning and losing bets. These range from four losses in a row to four wins in a row. This should be sufficient to exercise the class and see the changes in the bet amount.

  • An update to the overall Simulator that uses the PlayerCancellation.

Chapter 19. Player Fibonacci

This section will describe a player who has an internal state that can be modeled using methods and simple values instead of state objects.

Overview

A player could use the Fibonacci Sequence to structure a series of bets in a kind of cancellation system. The Fibonacci Sequence is 1, 1, 2, 3, 5, 8, 13, .... At each loss, the sum of the previous two bets is used, which is the next number in the sequence. In the event of a win, the last two numbers in the sequence are removed. This allows us to easily track our accumulated losses with bets that could recoup those losses.

In order to compute the Fibonacci sequence, we need to retain the two previous bets as the player's state. In the event of a win, we can compute the previous number in the sequence as the difference in the two bets. In the event of a loss, we can update the two numbers to show the next step in the sequence. The player's state is just these two numeric values.

Design

PlayerFibonacci uses the Fibonacci betting system. This player allocates their available budget into a sequence of bets that have an accelerating potential gain.

Fields

  • int recent = 1;

    This is the most recent bet amount.

  • int previous = 0;

    This is the bet amount previous to the most recent bet amount.

Constructors

  • PlayerFibonacci();

Methods

  • void win(Bet bet);

    Uses the superclass method to update the stake with an amount won. This will go “backwards” in the sequence. It updates recent and previous as follows.

    1. newRecent = recent - previous

    2. previous = recent - newRecent

    3. recent = newRecent

  • void lose(Bet bet);

    Uses the superclass method to update the stake with an amount lost. This will go “forwards” in the sequence. It updates recent and previous as follows.

    1. newRecent = recent + previous

    2. previous = recent

    3. recent = newRecent

Deliverables

There are three deliverables for this exercise.

  • The PlayerFibonacci class.

  • A unit test of the PlayerFibonacci class. This test should synthesize a fixed list of Outcomes, Bins, and calls a PlayerFibonacci instance with various sequences of reds and blacks. There are 16 different sequences of four winning and losing bets. These range from four losses in a row to four wins in a row. This should be sufficient to exercise the class and see the changes in the bet amount.

  • An update to the overall Simulator that uses the PlayerFibonacci.

Chapter 20. Conclusion

The game of Roulette has given us an opportunity to build an application with a considerable number of classes and objects. It is comfortably large, but not complex; we have built tremendous fidelity to a real-world problem. Finally, this program produces moderately interesting simulation results.

We note that a great many of our design decisions were not easy to make without exploring a great deal of the overall application's design. There are two ways to do this exploration: design everything in advance or design just enough but remain tolerant of our own ignorance.

Experienced designers can often design an entire, complex application before building any software. The process of doing this design, however, is internally iterative. Some parts are designed in detail, with tolerance for future changes; then other parts are designed in detail and the two design elements reconciled.

For new designers, we can't give enough emphasis to the importance of creating a trial design, exploring the consequences of that design, and then doing rework of that design. Too often, we have seen trial designs finalized into deliverables with no opportunity for meaningful rework. In Chapter 11, Review of Testability, we presented one common kind of rework to support more complete testing. In Chapter 12, Player Class, we presented another kind of rework to advance a design from a stub to a complete implementation.

We also feel compelled to point out the distinction between relatively active and passive classes in this design. We had several passive classes, like Outcome, Bet and Table, which had few responsibilities beyond collecting a number of related attributes and providing simple functions. We also had several complex, active classes, like Game, BinBuilder and all of the variations on Player. These classes, typically, had fewer attributes and more complex methods. In the middle of the spectrum is the Wheel. We find this distinction to be an enduring feature of OO design: there are things and actors; the things tend to be passive, acted upon by the actors. The overall system behavior emerges from the collaboration among all of the objects in the system; primarily -- but not exclusively -- the behavior of the active classes.

Craps

This part describes parts of the more complex game of Craps. Craps is a game with two states and a number of state-change rules. It has a variety of kinds of bets, some of which are quite complex.

The chapters of this part presents the details on the game, an overview of the solution, and a series of eleven exercises to build a complete simulation of the game, with a variety of betting strategies. The exercises in this part are more advanced; unlike Part I, “Roulette”, we will often combine several classes into a single batch of deliverables.

There are several examples of rework in this part, some of them quite extensive. This kind of rework reflects three more advanced scenarios: refactoring to generalize and add features, renaming to rationalize the architecture, and refactoring to extract features. Each of these is the result of learning; they are design issues that can't easily be located or explained in advance.

Table of Contents

21. Craps Details
Craps Game
Available Bets
Some Betting Strategies
22. Craps Solution Overview
Preliminary Survey of Classes
A Walkthrough of Craps
Questions and Answers
23. Outcome Class
Overview
Design
Outcome Rework
Deliverables
24. Throw Class
Overview
Design
Throw Design
Natural Throw Design
Craps Throw Design
Eleven Throw Design
Point Throw Design
Craps Game Design
Deliverables
25. Dice Class
Overview
Design
NumberPair Class
Dice Class
Deliverables
26. Throw Builder Class
Overview
Questions and Answers
Design Light
Outcome Rework
OutcomeField Design
OutcomeHorn Design
ThrowBuilder class
Design Heavy
RandomEvent class
Bin and BinBuilder Rework
Dice and Wheel Rework
Throw Rework
Outcome Rework
OutcomeField Design
OutcomeHorn Design
ThrowBuilder class
Deliverables
27. Bet Class
Overview
Design
Bet Rework
CommissionBet
Deliverables
28. CrapsTable Class
Overview
Design
Craps Game Stub
CrapsTable Design
Deliverables
29. CrapsGame Class
Overview
Game State
Bet Resolution
Moving Bets
Additional Craps Design
Design
Throw Rework
ThrowBuilder Rework
Bet Rework
CrapsPlayer Class Stub
CrapsGameState Class
CrapsGamePointOff Class
CrapsGamePointOn Class
CrapsGame Class
Deliverables
30. CrapsPlayer Class
Overview
Design
CrapsPlayer Superclass
CrapsPlayerPass Subclass
Craps Martingale Subclass
Deliverables
31. Design Cleanup and Refactoring
Overview
Design
RandomEventFactory class
Wheel Class
Dice Class
Table Class
Player class
Game Class
RouletteGame Class
CrapsGame Class
Deliverables
32. Simple Craps Players
Overview
Design
CrapsSimplePlayer superclass
Craps Martingale Player
Player1326 State
Craps1326 Player
CrapsCancellation Player
Deliverables
33. R