Building Skills in Object-Oriented Design 2.1.1 Python
Building Skills in Object-Oriented Design 2.1.1 Python
Release 2.1.1-Python
Steven F. Lott
CONTENTS
Front Matter
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
5 5 6 7 9 9 10 11 13 13 15 15 16 17 22
1 Preface 1.1 Why Read This Book? . . . . . 1.2 Audience . . . . . . . . . . . . . 1.3 Organization of This Book . . . 1.4 Why This Subject? . . . . . . . 1.5 Programming Style . . . . . . . 1.6 Conventions Used in This Book 1.7 Acknowledgements . . . . . . . .
2 Foundations 2.1 Problem Statement . . . . . . . . . . 2.2 Our Simulation Application . . . . . . 2.3 Soapbox on Use Cases . . . . . . . . . 2.4 Solution Approach . . . . . . . . . . . 2.5 Methodology, Technique and Process 2.6 Deliverables . . . . . . . . . . . . . .
II
Roulette
25
3 Roulette Details 29 3.1 Roulette Game . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.2 Available Bets in Roulette . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.3 Some Betting Strategies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 4 Roulette Solution Overview 4.1 Preliminary Survey of Classes . . . . . . 4.2 Preliminary Roulette Class Structure . . 4.3 A Walkthrough of Roulette . . . . . . . . 4.4 Roulette Solution Questions and Answers 5 Outcome Class 5.1 Outcome Overview . . . . . . . . 5.2 Design Decision Object Identity 5.3 Outcome Design . . . . . . . . . . 5.4 Outcome Deliverables . . . . . . . 5.5 Message Formatting . . . . . . . . 6 Bin Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 35 36 37 38 41 41 41 44 45 45 47 i
Bin Overview . . . . . . . . Design Decision Choosing Bin Questions and Answers Bin Design . . . . . . . . . Bin Deliverables . . . . . . . . . . . . . . . . . .
. . . . . . . . A Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
47 47 48 49 50 51 51 52 53 54 55 55 56 59 59 60 61 61 61 62 63 64 65 67 67 68 69 69 71 71 72 73 74 74 75 77 77 78 79 79 80 81 83 83 85 86 87
7 Wheel Class 7.1 Wheel Overview . . . 7.2 Wheel Design . . . . 7.3 Non-Random Design . 7.4 Wheel Deliverables . 8 Bin 8.1 8.2 8.3 8.4 8.5
Builder Class Bin Builder Overview . . . . . . . . . Bin Builder Algorithms . . . . . . . . BinBuilder Design . . . . . . . . . . . Bin Builder Deliverables . . . . . . . Internationalization and Localization
9 Roulette Bet Class 9.1 Roulette Bet Overview . . . . . . . . . . . . . . . . 9.2 Design Decision Create or Locate an Outcome . . 9.3 Design Decision Where to Keep the Outcome Map 9.4 Roulette Bet Questions and Answers . . . . . . . . 9.5 Roulette Bet Design . . . . . . . . . . . . . . . . . . 9.6 Roulette Bet Deliverables . . . . . . . . . . . . . . . 10 Roulette Table Class 10.1 Roulette Table Overview . . 10.2 InvalidBet Exception Design 10.3 Roulette Table Design . . . . 10.4 Roulette Table Deliverables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11 Roulette Game Class 11.1 Roulette Game Overview . . . . . . . . . . 11.2 Passenger57 Design . . . . . . . . . . . . . 11.3 Roulette Game Design . . . . . . . . . . . . 11.4 Roulette Game Questions and Answers . . 11.5 Roulette Game Deliverables . . . . . . . . . 11.6 Additional Roulette Design Considerations 12 Review of Testability 12.1 Testability Overview . . . . . . . . 12.2 Test-Driven Design . . . . . . . . 12.3 Capturing Pseudo-Radom Data . 12.4 Testability Questions and Answers 12.5 Testable Random Events Design . 12.6 Testability Deliverables . . . . . . 13 Player Class 13.1 Roulette Player Overview 13.2 Player Design . . . . . . 13.3 Martingale Player Design 13.4 Player Deliverables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ii
14 Overall Simulation Control 14.1 Simulation Control Overview . . 14.2 Simulation Terms . . . . . . . . 14.3 Simulator Design . . . . . . . . . 14.4 Player Rework . . . . . . . . . . 14.5 Simulation Control Deliverables 15 SevenReds Player Class 15.1 SevenReds Player Overview . . 15.2 SevenReds Design . . . . . . . 15.3 Player Rework . . . . . . . . . 15.4 Game Rework . . . . . . . . . 15.5 SevenReds Player Deliverables 16 Statistical Measures 16.1 Statistics Overview . . 16.2 Some Foundations . . . 16.3 Statistical Algorithms . 16.4 IntegerStatistics Design 16.5 Statistics Deliverables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
89 89 89 91 93 93 95 95 96 97 97 97 99 99 100 101 102 103 105 105 106 106 107 107 108 109 110 111 111 111 112 112 113 113 113
17 Random Player Class 17.1 Random Player Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.2 Random Player Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.3 Random Player Deliverables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 Player 1-3-2-6 Class 18.1 Player 1-3-2-6 Overview . . . . . . . . . . . 18.2 On Polymorphism . . . . . . . . . . . . . . 18.3 Player 1-3-2-6 Questions and Answers . . . 18.4 Player1326 State Design . . . . . . . . . . . 18.5 Player1326 No Wins Design . . . . . . . . . 18.6 Player1326 One Win Design . . . . . . . . 18.7 Player1326 Two Wins Design . . . . . . . . 18.8 Player1326 Three Wins . . . . . . . . . . . 18.9 Player1326 Design . . . . . . . . . . . . . . 18.10 Player 1-3-2-6 Deliverables . . . . . . . . . 18.11 Advanced Exercise Refactoring . . . . . . 18.12 Advanced Exercise Less Object Creation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19 Cancellation Player Class 115 19.1 Cancellation Player Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 19.2 PlayerCancellation Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 19.3 Cancellation Player Deliverables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 20 Fibonacci Player Class 20.1 Fibonacci Player Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20.2 PlayerFibonacci Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20.3 Fibonacci Player Deliverables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Conclusion 117 117 118 118 121
iii
III
Craps
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
123
127 127 128 128 131 131 133 133 134 134 135 137 137 139 139 140 140 141 141 142 143 144 144 145 146 147 149 149 150 150 151 152 152 155 155 156 157 158 159 161 161 163 163 164 166
22 Craps Details 22.1 Craps Game . . . . . . . . . . . . . . . 22.2 Creating A Dice Frequency Distribution 22.3 Available Bets . . . . . . . . . . . . . . 22.4 Some Betting Strategies . . . . . . . . . 22.5 Wrong Betting . . . . . . . . . . . . . . 23 Craps Solution Overview 23.1 Preliminary Survey of Classes . . . . . 23.2 Preliminary Class Structure . . . . . . . 23.3 A Walkthrough of Craps . . . . . . . . 23.4 Craps Solution Questions and Answers 24 Outcome Class 24.1 Outcome Overview . 24.2 Overloaded Methods . 24.3 Outcome Rework . . 24.4 Outcome Deliverables 24.5 Advanced Exercise . . 25 Throw Class 25.1 Throw Overview . . . 25.2 Throw Design . . . . 25.3 Natural Throw Design 25.4 Craps Throw Design . 25.5 Eleven Throw Design 25.6 Point Throw Design . 25.7 Craps Game Design . 25.8 Throw Deliverables . 26 Dice 26.1 26.2 26.3 26.4 26.5 26.6 Class Dice Overview . . . Throw Rework . . . NumberPair Design Dice Design . . . . . Dice Deliverables . . Dice Optimization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27 Throw Builder Class 27.1 Throw Builder Overview . . . . . . . . 27.2 Outcomes with Variable Odds . . . . . 27.3 Refactoring The Outcome Hierarchy . . 27.4 Soapbox on Subclasses . . . . . . . . . 27.5 Soapbox on Architecture . . . . . . . . 27.6 Throw Builder Questions and Answers 27.7 Soapbox on Justication . . . . . . . . 27.8 Design Light . . . . . . . . . . . . . . . 27.9 Design Heavy . . . . . . . . . . . . . . . 27.10 Common Design . . . . . . . . . . . . . 27.11 Throw-Builder Deliverables . . . . . . .
iv
28.2 Bet Rework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 28.3 CommissionBet Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 28.4 Bet Deliverables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 29 Craps Table Class 29.1 Craps Table Overview . . . . . . . . . . . . . . . . 29.2 Design Decision Table vs. Game Responsibility . 29.3 Design Decision Allowable Outcomes . . . . . . 29.4 CrapsGame Stub . . . . . . . . . . . . . . . . . . . 29.5 CrapsTable Design . . . . . . . . . . . . . . . . . . 29.6 Craps Table Deliverables . . . . . . . . . . . . . . 30 CrapsGame Class 30.1 Game State . . . . . . . . . . . . . . . . 30.2 Game State Class Hierarchy . . . . . . 30.3 Resolving Bets . . . . . . . . . . . . . . 30.4 Moveable Bets . . . . . . . . . . . . . . 30.5 Design Decision Win, Lose, Wait . . . 30.6 Additional Craps Design . . . . . . . . 30.7 Craps Game Implementation Overview 30.8 Throw Rework . . . . . . . . . . . . . . 30.9 ThrowBuilder Rework . . . . . . . . . . 30.10 Bet Rework . . . . . . . . . . . . . . . . 30.11 CrapsPlayer Class Stub . . . . . . . . . 30.12 CrapsGameState Class . . . . . . . . . 30.13 CrapsGamePointO Class . . . . . . . . 30.14 CrapsGamePointOn Class . . . . . . . . 30.15 CrapsGame Class . . . . . . . . . . . . 30.16 Craps Game Deliverables . . . . . . . . 30.17 Optional Working Bets . . . . . . . . . 31 CrapsPlayer Class 31.1 Craps Player Overview . . . . 31.2 CrapsPlayer Superclass Design 31.3 CrapsPlayerPass Subclass . . . 31.4 Craps Martingale Subclass . . 31.5 Craps Player Deliverables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 169 170 170 171 171 172 173 173 175 176 177 178 179 180 180 182 183 183 184 185 186 188 190 190 191 191 192 193 193 194 195 195 199 200 201 203 204 204 205 207 207 208 208 209 210 211 v
32 Design Cleanup and Refactoring 32.1 Design Review . . . . . . . . . . 32.2 RandomEventFactory Design . . 32.3 Wheel Class Design . . . . . . . 32.4 Table Class . . . . . . . . . . . . 32.5 Game Class . . . . . . . . . . . . 32.6 RouletteGame Class . . . . . . . 32.7 CrapsGame Class . . . . . . . . 32.8 Refactoring Deliverables . . . . . 33 Simple Craps Players 33.1 Simple Craps Players Overview . 33.2 CrapsPlayer Design . . . . . . . 33.3 CrapsSimplePlayer superclass . . 33.4 Craps Martingale Player . . . . 33.5 Player1326 State . . . . . . . . . 33.6 Craps1326 Player . . . . . . . .
33.7 CrapsCancellation Player . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212 33.8 Simple Craps Players Deliverables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212 34 Roll-Counting Player Class 34.1 Roll-Counting Overview . . . 34.2 BettingStrategy Design . . . 34.3 NoChangeBetting Class . . . 34.4 MartingaleBetting Class . . . 34.5 Bet1326Betting Class . . . . 34.6 CrapsOneBetPlayer class . . 34.7 CrapsTwoBetPlayer class . . 34.8 CrapsSevenCountPlayer class 34.9 Roll-Counting Deliverables . 35 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 215 217 217 218 219 220 221 222 223 225
IV
Blackjack
227
36 Blackjack Details 231 36.1 Blackjack Game . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231 36.2 Available Bets and Choices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 36.3 Betting Strategies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 37 Blackjack Solution Overview 37.1 Preliminary Survey of Classes . . . . . . . 37.2 Preliminary Class Structure . . . . . . . . . 37.3 A Walkthrough . . . . . . . . . . . . . . . . 37.4 Blackjack Solution Questions and Answers 38 Card, Deck and Shoe Classes 38.1 Card, Deck and Shoe Overview . . . . . 38.2 Card-Deck-Shoe Questions and Answers 38.3 Card Superclass . . . . . . . . . . . . . 38.4 FaceCard Class . . . . . . . . . . . . . . 38.5 AceCard Class . . . . . . . . . . . . . . 38.6 Deck class . . . . . . . . . . . . . . . . 38.7 Shoe class . . . . . . . . . . . . . . . . . 38.8 Card-Deck-Shoe Deliverables . . . . . . 39 Hand and Outcome Classes 39.1 Hand Overview . . . . . . . . . 39.2 Hand Total Class Design . . . 39.3 Hand Hard Total Class Design 39.4 Hand Soft Total Class Design . 39.5 Card Class Updates . . . . . . 39.6 Hand Class Design . . . . . . . 39.7 Hand Deliverables . . . . . . . 40 Blackjack Table Class 40.1 Blackjack Table Overview . . 40.2 BlackjackTable Class . . . . 40.3 Hand Rework . . . . . . . . . 40.4 Blackjack Table Deliverables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235 235 236 236 238 239 239 240 241 243 243 243 244 245 247 247 250 250 250 251 251 253 255 255 255 256 256
vi
41 Blackjack Game Class 41.1 Blackjack Game Overview . 41.2 Blackjack Collaboration . . . 41.3 Dealer Rules . . . . . . . . . 41.4 BlackjackPlayer Class . . . . 41.5 Card Rework . . . . . . . . . 41.6 Hand Rework . . . . . . . . . 41.7 BlackjackGame Class . . . . 41.8 Blackjack Game Deliverables
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
42 Simple Blackjack Player Class 269 42.1 Blackjack Player Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269 42.2 SimpleBlackjackPlayer Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270 42.3 Blackjack Player Deliverables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271 43 Variant Game Rules 43.1 Variant Game Overview . . 43.2 BlackjackGame Rework . . 43.3 OneDeckGame Class . . . . 43.4 Variant Game Deliverables 44 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273 273 274 274 274 275
277
45 Python unittest Testing 281 45.1 Dependencies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282 45.2 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282 46 Python doctest Testing 285 46.1 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286 46.2 Add the Test Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288 46.3 Mixed unittest and doctest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288 47 Python Documentation 291 47.1 Basic RST Markup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292 47.2 RST Field Markup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294 47.3 Class Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
VI
Back Matter
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
299
301 301 301 301 301 301 301 301 303 305
48 Bibliography 48.1 Use Cases . . . . . 48.2 Computer Science 48.3 Design Patterns . 48.4 Statistics . . . . . 48.5 Python . . . . . . 48.6 Java . . . . . . . . 48.7 Casino Games . . 49 Toolset 50 Indices and tables
vii
Bibliography
307
viii
Legal Notice 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.
CONTENTS
CONTENTS
Part I
Front Matter
CHAPTER
ONE
PREFACE
Penss, The Provincial Letters: Provincial letter 16, p. 571. The present letter is a very long one, simply because I had no leisure to make it shorter. BLAISE PASCAL
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. Closing the Skills Gap. The deepeer answer on the intent of this book is to help you, the beginning designer, by giving you a sequence of interesting and moderately complex exercises in OO design. The exercises are not focused on a language, but on a design process. The exercises are not hypothetical, but must lead directly to working programs. The long answer is that this book will make you work. 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 nd this to be a sad statement of managements 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.
1.2 Audience
Our primary audience you includes programmers who are new to OO programming. You need to have an exposure to the language, but you need more time to understand objects and objectorientation. We will 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 eort to support you, well provide a few additional details on language features. Well mark these as Tips. 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. Weve included information in the appendices. OO Misdirection. Have you found your rst exposure to objects to be more distasteful than empowering? Why does this happen? Some instructors preface a course on a language (like Python or Java) with an extensive presentations on object orientation. Its not easy to grasp OO design concepts before getting to the language fundamentals. In some cases, this leaves 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 rst, 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 dont reect currently accepted best practices.
Chapter 1. Preface
Classroom Use. 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. Skills. This book assumes an introductory level of skill in an OO programming language. We provide specic examples in Python (at least version 2.5). Student skills we expect include the following. If you cant do these things, this book is too advanced. Close it now. Create source les, compile and run application programs. While this may seem obvious, we dont 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 denitions 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 arent familiar with Pythons unittest or doctest or Javas 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 arent familiar with formal, deliverable documentation. Language Details. There are two editions of this book, one emphasizing Python syntax and the other emphasizing Java syntax. Both books come from a common source, so that the OO Design issues are central and the language nuances are segregated.
Roulette. For those whove never been in a casino, or seen movies that have casinos in them, Roulette is the game with the big wheel. They spin the wheel and toss in a marble. When the wheel stops spinning, the bin in which the marble rests denes the winning outcomes. People who bet on the right things get money. People who bet on the wrong things lose money. Starting in Roulette, we proceed slowly, building up the necessary application one class at a time. Since this is the simplest game, the individual classes reect 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 rst 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 condent 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 justication 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 specication of the class or classes to be built. This will include some technical information on Java or Python implementation techniques. Craps. For those whove never been in a casino, or seen the play Guys and Dolls, Craps is the game with the dice. A player shoots the dice. Sometimes theres a great deal of shouting and clapping. A throw of the dice may or may not resolve bets. Additionally, a throw of the dice may also change the state of the game. A casino provides a number of visual cues as to the state of the game and the various bets. In 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 rst 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. Blackjack. For those whove never been in a casino, or seen a movie with Blackjack, Blackjack is a game with cards. The dealer deals two cards to themselves and each player. One of the dealers card is up and one is down, providing a little bit of information on the dealers hand. The players may ask for additional cards, or keep the hand theyve got. The idea is to build a hand thats close to 21 points, but not more than 21. In Craps and Roulette there are a lot of bets, but few player decisions. In Blackhjack, there are few bets, but really complex player decisions. In Blackjack, the game states are more sophisticated than Craps or Roulette. 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 playing strategies. For the less advanced student we will simplify the strategies down to a few key conditions. The rst 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.
Chapter 1. Preface
Fit and Finish. We include several t-and-nish issues in Fit and Finish. This includes more information and examples on unit testing and documentation. Additionally, we cover some main program issues required to knit all of the software components together into a nished whole.
This style of name is only appropriate for primitive types, and doesnt 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 nd that we dont need additional cues for their data type. In some cases, prexes are used to denote the scope of an instance variables. Variable names might include a cryptic one-letter prex like f to denote an instance variable; sometimes programmers will use my or the as an English-like prex. We prefer to reduce clutter. In Python, instance variables are always qualied, typically by self., making the scope very clear. Generally, method functions (and classes) should be short enough and simple enough that complex navigational aids are not necessary.
1. We create a Python dictionary, a map from key to value. We use the collections.defaultdict so that missing keys are created in the dictionary with an initial value created by the int() function. 2. We iterate through all combinations of two dice, using variables i and j to represent each die. 3. We sum the dice to create a roll. We increment the value in the dictionary based on the roll. 4. Finally, we print each member of the resulting dictionary. 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%
We will use the following type styles for references to a specic Class, method(), or variable. Most of the design specications will provide Java-style method and variable descriptions. Python doesnt use type specications, and Python programmers will have to translate the Java specications into Python by removing the type names.
10
Chapter 1. Preface
Sidebars When we do have a signicant digression, it will appear in a sidebar, like this. Tip: Tip There will be design tips, and warnings, in the material for each exercise. These reect considerations and lessons learned that arent typically clear to starting OO designers.
1.7 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 innte wisdom and insight to a complex and diicult project. Thanks to Dion Dock and Robert Lucente for comments and corrections.
1.7. Acknowledgements
11
12
Chapter 1. Preface
CHAPTER
TWO
FOUNDATIONS
Well 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 inuence the choice of solution, the solution that balances the forces, and some consequences of the chosen solution. Based on the problem statement, well present the high-level use case that this software implements. The use case is almost too trivial to bother dening. 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 well 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, well present some important parts of getting started on the solution. These are more specic, technical considerations that dene common aspects of our approach.
13
A close parallel to this is exploring variations in rules and how these dierent rules have an inuence on outcomes. Questions include What should we do with the 2x and 10x odds oers 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 multiplayer 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 inuence 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 inuences 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, thats 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 reect actual game play. While a long-running simulation of thousands of invidual cycles of play will approach the theoretical results, people typically dont 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 eight-hour evening at a casino. Additionally, many players have a xed budget, and the betting is conned 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. Use Case. The high-level use case is an overall cycle of investigation . From this overall view, the actors goal is to nd an optimal strategy for a given game. Heres the scenario were imagining.
14
Chapter 2. Foundations
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 nd 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.
15
A use case is not a specication, and does not replace ordinary design. We have had experiences with customers who simply retitle their traditional procedural programming specications as use cases. We hypothesize that this comes from an unwillingness to separate problem denition from solution denition. The consequence is a conation of use case, technical background, design and programming specications into gargantuan documents that defy the ability of programmers or users to comprehend them. There are a number of common problems with use cases that will make the design job more diicult. Each of these defects should lead to review of the use case with the authors to see what, if anything, they can do to rework the use case to be more complete. No Actor. Without an actor, its impossible to tell who is getting value from the interaction. A catch-all title like the user indicates a use case written from the point of view of the database or the application software, not an actual person. An actor can be an interface with other software, in which case, the actual software needs to be named. Without knowing the actor, you will have trouble deciding which classes are clients and which classes provide the lower-level services of the application. No Value Proposition. There are two basic kinds of value: information for decisionmaking or actions taken as the result of decision-making. People interact with software because there are decisions the software cannot make or there are actions the actor cannot make. Some use cases include value-less activities like logging in, or committing a transaction, or clicking Okay to continue. These are parts of operating scenarios, not statements of value that show how the actor is happier or more successful. Without a value proposition, you will have no clue as to what problem the software solves, or what it eventually does for the actor. No Interactions. If the entire body of the use case is a series of steps the application performs, we are suspicious of the focus. We prefer a use case to emphasize interaction with the actor. Complex algorithms or interface specications should be part of an appendix or supplemental document. Without any interaction, it isnt clear how the actor uses the software. We also try to make a distinction between detailed operating scenarios and use cases. We have seen customers write documents they call detailed use cases that describe the behavior of individual graphical user interface widgets or panels. We prefer to call these scenarios, since they dont describe measuable business value, but instead describe technical interactions.
16
Chapter 2. Foundations
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 (or de novo) 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 le. The statistical results le 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.
Sample Execution
python -m casino.craps --Dplayer.name="Player1326" >details.log
1. We select the main simulator control using the package casino and the module craps. 2. We dene the player to use, player.name="Player1326". The main method will use this parameter to create objects and execute the simulation. 3. We collect the raw data in a le 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 wont use any J2EE extensions. For Python, it is the base installation. There are a number of more technical considerations that we will expand in 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 isnt 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 dont mind solving known problems again. We are aware that our statistical analysis has a number of deciencies. We will avoid any deeper investigation into statistics.
17
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 nd 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 nal code. Programmers new to OO design will benet 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, elds 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 dierent 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 nal form of the deliverables based on the complete design. This will show how design rework arises as part of a waterfall methodology. This section addresses a number of methodology or process topics: Quality, in general Rework Technical Decision-Making Reuse Design Patterns
2.5.1 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 oicially legacy, it still provides an exhaustive list of quality attributes. These are broadly grouped into ve categories. Our approach covers most of those ve categories reasonably well. Need Satisfaction. Does the software meet the need? We start with a problem statement, dene the use case, and then write software which is narrowly focused on the actors needs. By developing our application in small increments, we can ask ourself at each step, Does this meet the actors needs? Its fairly easy to keep a software development project focused when we have use cases to describe our goals. Performance. We dont address this specically 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 claries the actors thinking, and we have to alter the software to reect a deeper understanding of the problem.
18
Chapter 2. Foundations
Maintenance is just another cycle of the iterative approach weve 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 xing, we often add unit tests to demonstrate the bug, and then x 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 doesnt 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 dont 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 ve 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 isnt 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 eort 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.
2.5.2 On Rework
In Problem Statement , we described the problem. In 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 dont simply present a completed design. We feel that it is very important follow a realistic problemsolving 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 reect 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. Soapbox on Rework We consider design rework to be so important, we will summarize the idea here. Important: The best way to learn is to make mistakes. Rework is a consequence of learning. All of software development can be described as various forms of knowledge capture. A project begins with many kinds of ignorance and takes steps to reduce that ignorance. Some of those steps should involve revising or consolidating previous learnings. A project without rework is suspiciously under-engineered.
19
For some, the word rework has a negative connotation. If you nd the word distasteful, please replace every occurance with any of the synonyms: adaptation, evolution, enhancement, mutation. We prefer the slightly negative connotation of the word rework because it helps managers realize the importance of incremental learning and how it changes the requirements, the design and the resulting software. Since learning will involve mistakes, good management plans for the costs and risks of those mistakes. Generally, our approach is to manage our ignorance; we try to create a design such that correcting a mistake only xes a few classes. We often observe denial of the amount of ignorance involved in creating IT solutions. It is sometimes very diicult to make it clear that if the problem was well-understood, or the solution was well-dened there would be immediately applicable o-the-shelf or open-source solutions. The absence of a ready-to-hand solution generally means the problem is hard. It also means that there are several degrees of ignorance: ignorance of the problem, solution and technology; not to mention ignorance of the amount of ignorance involved in each of these areas. We see a number of consequences of denying the degrees of ignorance. Programmers. For programmers, experienced in non-OO (e.g. procedural ) environments, one consequnece is that they nd learning OO design is diicult and frustrating. Our advice is that since this is new, you have to make mistakes or you wont learn eectively. Allow yourself to explore and make mistakes; feel free to rework your solutions to make them better. Above all, do not attempt to design a solution that is complete and perfect the very rst time. We cant emphasize enough the need to do design many times before understanding what is important and what is not important in coping with ignorance. Managers. For managers, experienced in non-object implementation, the design rework appears to be contrary to a fanciful expectation of reduced development eort from OO techniques. The usual form for the complaint is the following: I thought that OO design was supposed to be easier than non-OO design. Were not sure where the expectation originates, but good design takes time, and learning to do good design seems to require making mistakes. Every project needs a budget for making the necessary mistakes, reworking bad ideas to make them good and searching for simplications. Economics. Often, management attempts the false economy of attempting to minimize rework by resorting to a waterfall methodology. The idea is that having the design complete before attempting to do any development somehow magically prevents design rework. We dont see that this waterfall approach minimizes rework; rather, we see it shifting the rework forward in the process. There are two issues ignored by this approach: how we grow to understand the problem domain and providing an appropriate level of design detail. We nd that any initial high-level design can miss details of the problem domain, and this leads to rework. Forbidding rework amounts to mandating a full understanding of the problem. In most cases, our users do not fully understand their problem any more than our developers understand our users. Generally, it is very hard to understand the problem, or the solution. We nd that hands-on use of preliminary versions of software can help more than endless conversations about what could be built. In the programming arena, we nd that Java and Python (and their associated libraries) are so powerful that detailed design is done at the level of individual language statements. This leads us to write the program either in English prose or UML diagrams (sometimes both) before writing the program in the nal programming language. We often develop a strong commitment to the initial design, and subsequent revisions are merely transliterations with no time permitted for substantial revisions. While we have written the program two or three times over, the additional quality isnt worth doubling or tripling the workload. We feel that the original workload should be managed as planned cycles of work and rework.
20
Chapter 2. Foundations
War Story on Bad Design In one advanced programming course, we observed the following sad scenario. The instructor provided an excellent background in how to create abstract data type (ADT) denitions for the purely mathematical objects of scalar, vector and matrix. The idea was to leverage the ADTs to implement more complex operations like matrix multiplication, inversion and Gaussian elimination. The audience, primarily engineers, seemed to understand how this applied to things they did every day. The rst solution, presented after a week of work, began with the following statement: Rather than think it through from the basic denitions of matrix and vector, I looked around in my drawer and found an old FORTRAN program and rewrote that, basically transliterating the FORTRAN. We think that a lack of experience in the process of software design makes it seem that copying an inappropriate solution is more eective than designing a good solution from scratch.
2.5.3 On Decision-Making
Many of the chapters will include some lengthy design decisions that appear to be little more than handwringning 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 nd that managers dont 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 Design Good OO design comes from a good process for technical decision-making. First, admit what we dont 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 simplication, which will reduce development and maintenance costs. Its 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.
2.5.4 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 nd 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 nd 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.
21
Soapbox on Reuse While a promise of OO design is reuse, this needs to be tempered with some pragmatic considerations. There are two important areas of reuse: reusing a class specication to create objects with common structure and behavior, and using inheritance to reuse structure and behavior among multiple classes of objects. Beyond these two areas, reuse can create more cost than value. The rst step in reuse comes from isolating responsibilities to create classes of objects. Generally, a number of objects that have common structure and behavior is a kind of reuse. When these objects cooperate to achieve the desired results, this is sometimes called emergent behavior : no single class contains the overall functionality, it grew from the interactions among the various objects. This is sometimes called inversion of control. When the application grows and evolves, we can preserve some class declarations, reusing them in the next revision of the application. This reduces the cost and risks associated with software change. Class denitions are the most fundamental and valuable kind of reuse. Another vehicle for OO resuse is inheritance. The simple subclass-superclass relationship yields a form of reuse; a class hierarchy with six subclasses will share the superclass code seven times over. This, by itself, has tremendous benets. We caution against any larger scope of reuse. Sharing classes between projects may or may not work out well. The complexity of achieving inter-project reuse can be paralyzing to rst-time designers. Often, dierent projects reect dierent points of view, and the amount of sharing is limited by these points of view. As an example, consider a product in a business context. An external customers view of the product (shaped by sales and marketing) may be very dierent from the internal views of the same product. Internal views of the product (for example, nance, legal, manufacturing, shipping, support) may be very dierent from each other. Reconciling these views may be far more challenging than a single software development project. For that reason, we dont encourage this broader view of reuse.
2.6 Deliverables
Each chapter denes 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 les for one or more classes, the source les for one or more unit tests, and assure that a minimal set of API documentation is available. Source Files. The source les are the most important deliverable. In eect, 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 les. Additionally, we might bundle the class les into an executable JAR le. The source is the principle deliverable; anyone should be able to produce class and JAR les using readily available tools. In the case of Python, its the packages of .py les. There really isnt much more to deliver. The interested student might want to look at the Python distutils and setuptools to create a distribution 22 Chapter 2. Foundations
kit, or possibly a Python .egg le. 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 dont endorse a particular technology for implementing the unit tests. There are several approaches to unit testing that are in common use. 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. Documentation. The job isnt 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 (or sphinx) to create similar documentation.
2.6. Deliverables
23
24
Chapter 2. Foundations
Part II
Roulette
25
This part describes the game of Roulette. Roulette is the game with the big wheel. They spin the wheel, toss in a marble and wait for the wheel to stop spinning. Roulette is essentially 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.
27
28
CHAPTER
THREE
ROULETTE DETAILS
In the rst section, Roulette Game, 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 Available Bets in Roulette. The denition of the various bets is an interesting programming exercise, and the rst four exercise chapters will focus on this. In Some Betting Strategies, 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.
29
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: Odds 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.
Figure 3.1: 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 oered.
30
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 ve-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 oers 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.
31
Odds and Payouts Not all of the Roulette outcomes are equal probability. Lets compare a split bet on 1-2 and a even money bet on red. The split bet wins if either 1 or 2 comes up on the wheel. This is 2 of the 38 outcomes, or a 1/19 probability, 5.26%. The red bet wins if any of the 18 red numbers come up on the wheel. The is 18 of the 38 outcomes, or a 9/19 probability, 47.4%. Clearly, the red bet is going to win almost ten times more often than the 1-2 bet. As an inducement to place bets on rare occurences, the house oers a higher payout on those bets. Since the 1-2 split bet wins is so rarely, they will pay you 17 times what you bet. On the other hand, since the red bet wins so frequently, they will only pay back what you bet. Youll notice that the odds of winning the 1-2 split bet is 1 chance in 19, but they pay you 17 times your bet. Since your bet is still sitting on the table, it looks like 18 times your bet. It still isnt 19 times your bet. This discrepency between the actual probability and the payout odds is sometimes called the house edge. It varies widely among the various bets in the game of Roulette. For example, the 5-way bet has 5/38 ways of winning, but pays only 6:1. There is only a 13.2% chance of winning, but they pay you as if you had a 16.7% chance, keeping the 3.5% dierence. You have a 5.26% chance to win a split bet, but the house pays as if it were a 5.88% chance, a .62% discrepency in the odds. The smallest discrepency between actual chances of winning (47.4%) and the payout odds (50%) is available on the even money bets: red, black, even, odd, high or low. All the betting systems that we will look at focus on these bets alone, since the house edge is the smallest.
32
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. Cancellation. 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 rst 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. Heres 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 numbers in the sequence becomes the new bet amount. In the event of a win, we simply revert to the base betting amount. This allows the player to easily track our accumulated losses, with bets that could recoup those losses through a series of wins.
33
34
CHAPTER
FOUR
35
Black Green Number Odds Player House One common development milestone is to be able to develop a class model in the Unied 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 rst part, well avoid doing extensive modeling. Instead well simply identify some basic design principles. Well focus in on the most important of these nouns and describe the kinds of classes that you will build.
36
Collects the Outcomes; used by Player to place a bet amount on a specic Outcome; used by Game to compute the amount won from the amount that was bet. Player Responsibilities. Places bets on Outcomes, updates the stake with amounts won and lost. Collaborators. Uses Table to place bets on Outcomes; used by Game to record wins and losses. Game Responsibilities. Runs 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. Collaborators. Uses Wheel, Table, Outcome, Player. The overall statistical analysis will play a nite number of games and collect the nal value of the Players stake. The class Player has the most important responsibility in the application, since we expect to update the algorithms this class uses to place dierent kinds of bets. Clearly, we need to cleanly encapsulate the Player, so that changes to this class have no ripple eect in other classes of the application.
37
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. Well 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 Bet s are collected by a Table, and the Bet s are created by a Player. Well 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.
38
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 identication 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 identied 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 guring the rake. Consider a game where the Players 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. If Outcome encapsulates the function to compute the amount won, isnt it just a gloried subroutine? In a limited way, yes. A class can be thought of as a gloried 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. What is the distinction between an Outcome and a Bet? 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, dierent from an amount bet on a potential outcome. In a way, were 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 nd 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. Why are the classes so small? First-time designers of OO applications are sometimes uncomfortable with the notion of emergent behavior. In procedural programming languages, the applications 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 applications 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 nely divided responsibilities, are more exible 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 nely, a change can be more easily isolated to a single class. A much simpler sequence of ifstatements can be focused on selecting the proper class, which can then simply carry out the desired functions.
39
40
CHAPTER
FIVE
OUTCOME CLASS
In addition to dening 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 dierent objects. This is dierent from the test for identity.
To do object comparison correclty in Python, we must provide the __eq__() and __ne__() method which compares the Outcome.name attribute. However, simply comparing outcome names isnt in the long run going to work out well. More than equal. Well be creating collections of Outcome objects, and we may need to create sets or maps where hash codes are used in addition to the simple equality tests. As we look forward, the Python set and dict depend on a __hash__() method and an __eq__() method of each object in the collection. The Hash Code Problem. Every object has a hash code. The hash code is simply a unique integer, perhaps the address of the object, perhaps some summary of the bits that make up the object. The default rule is this: Distinct objects will have distinct hash codes The default rule makes it possible for us to accidentally create objects that appear to be the same thing, but internally have dierent hash codes. In Python, if we do nothing special, the __eq__() test will simply compare the object id values. These object id values are unique to each distinct object, irrespective of the attribute values. This default behavior of objects is shown by the following example:
Object Identity
>>> oc1= Outcome( "Any Craps", 8 ) >>> oc2= Outcome( "Any Craps", 8 ) >>> oc1 == oc2 False >>> id(oc1) 6291728 >>> id(oc2) 6291760
Each individual Outcome object has a distinct distinct hashcode. This makes them not equal according to the default methods inherited from object. However, we would like to have two of these objects test as equal. This example shows that we can have two objects that appear equal, but dont compare as equal. Layers of Meaning. The real issue is that we have three distinct layers of meaning for comparing objects to see if they are equal. Compare as Equal. We can call this attribute equality. The __eq__() method returns True. When we use the == operator, this is evaluated by using the __eq__() method. This must be overridden by a class to implement attribute equality. Have the same hash code. We can call this hash equality. This has to include attribute equality. The __hash__(self)() method for several objects that represent the same Outcome have the same hash code. Are references to the same object. We can call this identity. We can test that two objects are the same by using the is comparison between two objects. When we use the is comparison, were asserting that the two variables are references to the same object. This is the identity comparison.
42
If we pursue this as an implementation, we have to be sure we dont casually create instances of Outcome. Rather than create them all over our application, we need to have a single source for the oicial instances of Outcome. Problem. Which should we implement, hash equality or object identity? Forces. There are two broad implementation choices for making sure that our objects both provide appropriate hash values and pass equality tests. 1. Object Identity. We can make sure that all of the parts of our application share the same Outcome objects. Two references to the same object will pass any identity test and will also have the same hash code. This requires that we have a pool of outcomes and each Outcome is eectively a Singleton instance. We have to be sure that our application has only one instance of Outcome( "1", 35 ), and that object is shared by the Wheel, the Player and any Bets that are made. We have to be sure to never create additional Outcome objects; there would be confusion between two discrete instances that happened to have the same name and odds. We have to avoid casually creating new instances of the Outcome class. Assuring that we work with single instances of each Outcome ts with the Dont Repeat Yourself (DRY) principle: we only create each Outcome once. 2. Hash Equality. We can make sure that all instances of an Outcome act like they are the same object. That is, they have the same hash value and they test true for equality. They may not be two references to the same underlying object, but they pass the hash and equality tests, making them equal for all practical purposes. This allows us to create additional Outcome objects, condent they will behave appropriately. This, however, also requires that we include the odds when creating an outcome. This gets wordy and unpleasant because we have the various outcomes and odds stated in more than one place in our application. This would break the Dont Repeat Yourself (DRY) principle. The DRY principle is so important that this alternative is really unacceptable. Solution. We have to assure that were sharing single instances of Outcome objects. We have several choices for assuring that we have exactly one instance of each Outcome. Global Outcome Objects. We can declare global variables for the various outcomes and use those global objects as needed. Generally, globals variables are often undesirable because changes to those variables can have unexpected consequences in a large application. Global constants are no problem at all. The pool of Outcome instances are proper constant values used to create bins and bets. Outcome Factory Object. We can create a class which is a Factory for individual Outcome objects. When some part of the application asked for an Outcome which didnt yet exist, the Factory would create it, save it, and return a reference to it. When some part of the application asked for an Outcome which already exists, the Factory would return a reference to the existing object. This centralizes the pool of global objects into a single object, the Factory. Singleton Outcome Class. A Singleton class creates and maintains a single instance of itself. This requires that the class have a static getInstance() method that is a reference to the one-and-only instance of the class. This saves us from creating global variables. Instead, each class denition contains its own private reference to the one-and-only object of that class.
43
However, this has the profound disadvantage that each distinct outcome would need to be a distinct subclass of Outcome. This is an unappealing level of complexity. All of these are acceptable techniques for implementing object identity. We dont have to choose one at the current time. Consequences. The most important consequence of this design decision is that were going to evenually create some global denitions of Outcomes, or a Factory object that centralizes Outcome creation. We may also need to add the necessary static variable and static getInstance() method.
5.3.1 Fields
name Holds the name of the Outcome. Examples include "1", "Red". odds Holds the payout odds for this Outcome. Most odds are stated as 1:1 or 17:1, we only keep the numerator (17) and assume the denominator is 1. We can use name to provide hash codes and do equality tests.
5.3.2 Constructors
__init__(self, name, odds) Parameters name (str) The name of this outcome odds (int) The payout odds of this outcome. Sets the instance name and odds from the parameter name and odds.
5.3.3 Methods
For now, well assume that were going to have global instances of each Outcome. winAmount(self, amount ) Multiply this Outcomes odds by the given amount. The product is returned. Parameter amount (number) amount being bet __eq__(self, other ) Compare the name attributes of self and other.
44
Parameter other (Outcome) Another Outcome to compare against. Returns True if this name matches the other name. Return type bool __ne__(self, other ) Compare the name attributes of self and other. Parameter other (Outcome) Another Outcome to compare against. Returns True if this name does not match the other name. Return type bool __str__(self ) Easy-to-read representation of this outcome. Python formatting is most easily done with the % operator. For some tips on how to do this, see Message Formatting. Returns String of the form name (odds:1). Return type str This easy-to-read String output method is essential. This should return a String representation of the name and the odds. A form that looks like 1-2 Split (17:1) works nicely.
45
46
CHAPTER
SIX
BIN CLASS
This chapter will present the design for the Bin class. In addition to that, well examine the collection classes available in Java and Python. Well also present present some questions and answers on this particular class and the process of creating a design.
47
Mapping dict. A dict associates a key with a value. This key-value pair is called an item in the map. We dont need this feature at all. A map does more than we need for representing a Bin. Mutable Set set. A set is a mutable collection of objects. Duplicates arent allowed, and theres no inherent ordering. This looks close to what we need. Immutable Set frozenset. A frozenset is an immutable collection of objects. Duplicates arent allowed, and theres no inherent ordering. This looks close to what we need. Having looked at the fundamental collection varieties, we will elect to use a frozenset.
48
6.4.1 Fields
outcomes A frozenset that holds the collection of individual Outcomes.
6.4.2 Constructors
Python programmers should provide an initializer that uses the * modier so that all of the individual arguments appear as a single list within the initializer. __init__(self, * outcomes) Parameter outcomes any number of outcomes used to populate the Bin initially. This constructor can be used as follows.
1. zero is based on references to two objects: the 0 Outcome and the 00-0-1-2-3 Outcome. 2. zerozero is based on references to two objects: the 00 Outcome and the 00-0-1-2-3 Outcome.
6.4.3 Methods
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 faade over the underlying collection object, this method could simply delegate the real work to the underlying collection. Note that a frozenset is immutable; unlike a list it does not have an append() method. Instead, a new frozenset can be constructed by using the | operator which creates a new frozenset from a frozenset and a frozenset or set
>>> zero= Outcome("0",35) >>> zerozero= Outcome("00",35) >>> b = frozenset( [zero] ) >>> b frozenset([<roulette.Outcome object at 0x6002d0>])
49
>>> map(str,b) ['0 (35:1)'] >>> b |= set( [zerozero] ) >>> map(str,b) ['0 (35:1)', '00 (35:1)'] >>> b frozenset([<roulette.Outcome object at 0x6002d0>, <roulette.Outcome object at 0x600330>])
Parameter outcome (Outcome) An outcome to add to this Bin __str__(self ) An easy-to-read 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 Bin.outcomes, then joins the resulting string with ", " separators
', '.join( map(str,self.outcomes) )
Returns String of the form [outcome, outcome, ...]. Return type str
50
CHAPTER
SEVEN
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 dened. Well introduce some basic subclass constructor techniques, also.
Since the index of the Bin doesnt have any signicance 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.
51
7.2.1 Fields
bins Contains the individual Bin instances. This is a tuple of 38 elements. This can be built with tuple( Bin() for i in range(38) ) rng The random number generator to use to select a Bin from the bins collection. This is not always simply random.Random(). For testing, we need to inject a non-random random number generator in place of the system random number generator.
7.2.2 Constructors
__init__(self, rng)
52
Parameter rng (random.Random) A random number generator. For testing, this may be a non-random number generator. 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. Well rework this in a future exercise.
7.2.3 Methods
addOutcome(number, outcome) Adds the given Outcome to the Bin with the given number. Parameters bin (int) bin number, in the range zero to 37 inclusive. outcome (Outcome) The Outcome to add to this Bin next() Generates a random number between 0 and 37, and returns the randomly selected Bin. The Random.choice() function of the random module will select one of the available Bin s from the bins list. Returns A Bin selected at random from the wheel. Return type Bin get(bin) Returns the given Bin from the internal collection. Parameter bin (int) bin number, in the range zero to 37 inclusive. Returns The requested Bin. Return type Bin
7.3.1 Fields
value The non-random value to return.
7.3.2 Constructors
__init__(self ) Creates a non-random random number generator.
53
7.3.3 Methods
setSeed(self, value) Saves this value as the next value to return. Parameter value (long) the value to return next. choice(self, sequence) Use the given seed value as an index and return the requested item from the sequence. Parameter sequence the sequence of values from which a random value is selected.
54
CHAPTER
EIGHT
55
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 0 c < 12. The Even-Money Bets. These include Red, Black, Even, Odd, High, Low. Each number has three of the six possible even money Outcome s. 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 well probably want are handy names for the various kinds of odds. While can dene an Outcome as Outcome( "Number 1", 35 ) , this is a little opaque. A slightly nicer form is Outcome( "Number 1", RouletteGame.StraightBet ). These are specic to a game, not general features of all Outcomes. We havent designed the game yet, but we can provide a stub class with these these outcome odds denitions.
56
First Column Number. Set n 3r + 1. This will create values 1, 4, 7, ..., 34. Column 1-2 Split. Create a n, n +1 split Outcome with odds of 17:1. Assign to Bins. Associate this object with two Bins: n and n +1. Second Column Number. Set n 3r + 2. This will create values 2, 5, 8, ..., 35. Column 2-3 Split. Create a n , n +1 split Outcome. Assign to Bins. Associate this object to two Bins: n and n +1. A similar algorithm must be used for the numbers 1 through 33, to 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.
57
58
Low? If 1 n < 19, associate the low Outcome with Bin n. High? Otherwise, 19 n < 37, associate the high Outcome with Bin n. Even? If nmod2 = 0, associate the even Outcome with Bin n. Odd? Otherwise, nmod2 = 0, associate the odd Outcome with Bin n. 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.
8.3.1 Constructors
__init__(self ) Initializes the BinBuilder.
8.3.2 Methods
buildBins(self, wheel ) Creates the Outcome instances and uses the addOutcome() method to place each Outcome in the appropropriate Bin of wheel . Parameter wheel (Wheel) The Wheel with Bins that must be populated with Outcomes. There should be 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 Bin Builder Algorithms.
59
1. This constructs an instance of NullTranslations. This is then installed as a function named _(), which nds a translation for the default C-locale string. This class does nothing; but it can be replaced with the GNUTranslations class, which uses a message catalog to replace the default strings with localized strings. 2. This shows how to use the _() function to translate a default C-locale string to a localized string.
60
CHAPTER
NINE
One unfortunate feature of this is that we have to repeat the odds when creating an Outcome. This violates the DRY principle. The other option is to locate an existing Outcome object as part of creating a Bet instance. Heres what it might look like in Python.
61
Solution. We want to get a complete Outcome from just the name of the outcome. The will prevent repeating the odds information. Consequence. There are several consequences to this decision. We need to pick a collection. When we look at the collections (see Design Decision Choosing A Collection) we can see that a Map from name to complete Outcome instance is ideal. This helps us associate a Bet with an Outcome given just the name of the Outcome. Some object needs to build the collection of distinct Outcomes. Some object needs to maintain the collection of Outcomes for use by the Player in building Bets. If the builder and maintainer are the same object, then things would be somewhat simpler because all the responsibilities would fall into a single place.
62
Second, we would have to add specic Outcome getters to the BinBuilder. We could, for example, include a getOutcome() method that returns an Outcome based on its name. Heres what it might look like in Python.
class BinBuilder( object ): ... def apply( self, outcome, bin, wheel ): self.all_outcomes.add( outcome ) wheel.add( bin, outcome ) def getOutcome( self, name ): ...
Access the Wheel. A better choice is to get Outcome obects from the Wheel. To do this, wed have to do several things. First, we expand the Wheel to keep a simple Map of the various Outcomes created by the BinBuilder. This Map would be maintained by the Wheel.add(). Second, we would have to add specic Outcome getters to the Wheel. We could, for example, include a getOutcome() method that returns an Outcome based on its name. In Python, for example, we might a a method function like the following to Wheel.
class Wheel( object ): ... def add( self, bin, outcome ): self.all_outcomes.add( outcome ) ... def getOutcome( self, name ): return set( [ oc for oc in self.all_outcomes if oc.name.lower().contains( name.lower() ) ] )
Solution. The allocation of responsibility seems to be a toss-up. We can see that the amount of programming is almost identical. This means that the real question is one of clarity: which allocation more clearly states our intention? The Wheel is a rst-class part of the game of Roulette. It showed up in our initial noun analysis. The BinBuilder was an implementation convenience to separate the one-time construction of the Bins from the overall work of the Wheel. Since Wheel is a rst-class part of the problem, we should augment the Wheel to keep track of our individual Outcome objects by name.
63
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 specic duration, it has a nal 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.
9.5.1 Fields
amountBet The amount of the bet. outcome The Outcome on which the bet is placed.
9.5.2 Constructors
__init__(self, amount, outcome) Parameters amount (int) The amount of the bet. outcome (Outcome) The Outcome were betting on. Create a new Bet of a specic amount on a specic outcome. For these rst exercises, well omit the Player. Well come back to this class when necessary, and add that capability back in to this class.
9.5.3 Methods
winAmount(self ) Returns amount won Return type int Uses the Outcomes 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. loseAmount(self ) Returns amount lost Return type int
64
Returns the amount bet as the amount lost. This is the cost of placing the bet. __str__(self ) Returns string representation of this bet with the form "amount on outcome" Return type str Returns a string representation of this bet. Note that this method will delegate the much of the work to the __str__() method of the Outcome.
65
66
CHAPTER
TEN
67
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 the survey of collections in Design Decision Choosing A Collection 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. Bets dont have specic names. Since the number of bets varies, we cant 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 Bet s 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 Players Bets to be sure that the table minimum is met. Well 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 dont 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 specic Outcome. The Wheel selects a Bin, which has a collection of Outcomes. All bets with a winning outcome will be resolved as a winner. So well have multiple Outcomes in the winning Bin to be matched against multiple Bets active on the Table. Because some games are stateful, and the winning and losing bets depend on game state, we will defer the details of this collaboration design until we get to the Game class. For now, well 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 well return to bet deletion. Its import not to over-design this class at this time; we will often add features as we develop designs for additional use cases.
68
10.3.1 Fields
limit This is the table limit. The sum of a Player s bets must be less than or equal to this limit. bets This is a list of the Bets currently active. These will result in either wins or losses to the Player.
10.3.2 Constructors
Table() Creates an empty list of bets. Methods isValid(self, bet ) Parameter bet (Bet) A Bet instance to be validated. 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. placeBet(self, bet ) Parameter bet (Bet) A Bet instance to be validated. Raises 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. __iter__() Returns an iterator over the available list of Bet instances. This simply returns the iterator over the list of Bet objects. Note that we need to be able remove Bets from the table. Consequently, we have to update the list, which requires that we create a copy of the list. This is done with bets[:]. Returns iterator over all bets __str__() Return String representation of all current bets.
69
An InvalidBet exception class. This is a simple subclass of Exception. Since theres no unique programming here, theres no compelling need to construct a unit test. 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 Bet s are managed by the table correctly.
70
CHAPTER
ELEVEN
71
Driven by Bin. 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 Bets Outcome that matches the Bins Outcome is a winner and is paid o. The other bets are losers. This involves two nested loops: one to visit the winning Outcomes of a Bin and one to visit the Bet s of a Table. Driven by Table. The alternative is to visit each Bet contained by the Table. Since the winning Bin is has a Set of Outcomes, we can exploit set membership methods to test for presence or absence of an Bets Outcome in the winning Bins 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 Bet s 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 dierent 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. Well call this player Passenger57 because it always bets on Black. In several future exercises, well revisit this design to make more sophisticated players. For some additional design considerations, see Additional Roulette Design Considerations. This provides some more advanced game options that our current design can be made to support. Well leave this as an exercise for the more advanced student.
11.2.1 Fields
black This is the outcome on which this player focuses their betting. This Player will get this from the Wheel using a well-known bet name. table The Table that is used to place individual Bets.
11.2.2 Constructors
__init__(self, table) Parameter table (Table) A Table instance on which bets are placed. Constructs the Player with a specic table for placing bets. This also creates the black Outcome. This is saved in a variable named Passenger57.black for use in creating bets.
72
11.2.3 Methods
placeBets(self ) Updates the Table with the various bets. This version creates a Bet instance from the black Outcome. It uses Table placeBet() to place that bet. win(self, bet ) Parameter bet (Bet) The bet which won. Notication from the Game that the Bet was a winner. The amount of money won is available via a the winAmount() method of theBet. lose(self, bet ) Parameter bet (Bet) The bet which won. Notication from the Game that the Bet was a loser.
11.3.1 Fields
wheel The Wheel that returns a randomly selected Bin of Outcomes. table The Table which contains the Bets placed by the Player. player The Player which creates Bets at the Table.
11.3.2 Constructors
We based the Roulette Game constructor on a design that allows any of the elds to be replaced. This is the Strategy design pattern. Each of these collaborating objects is a replaceable strategy, and can be changed by the client that uses this game. Additionally, we specically 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. __init__(self, wheel, table) Parameters wheel (Wheel) The wheel which produces random events table (Table) The table which holds bets to be resolved. Constructs a new Game, using a given Wheel and Table.
73
11.3.3 Methods
cycle(self, player ) Parameter player (Player) the individual player that places bets, receives winnings and pays losses. This will execute a single cycle of play with a given Player. It will call thePlayers placeBets() method to get bets. It will call theWheels next() method to get the next winning Bin. It will then call theTables iterator to get an Iterator over the Bets. Stepping through this Iterator returns the individual Bet objects. If the winning Bin contains the Outcome, call the thePlayer win() method otherwise call the thePlayer lose() method.
74
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 diicult. Well address this testability issue in the next chapter.
75
76
CHAPTER
TWELVE
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 rst, and address testability as a feature to be added to a working class. Additionally, well address some issues in construction of class instances and an idealized structure for the main procedure.
77
On Random Numbers Random numbers arent actually random. Since they are generated by an algorithm, they are sometimes called pseudo-random. The distinction is important because pseudo-random numbers are generated in a xed sequence from a given seed value. Computing the next value in the sequence involves a simple calculation that is expected to overow the available number of bits of precision leaving apparently random bits as the next value. This leads to results which, while predictable, are arbitrary enough that they pass rigorous statistical tests and are indistinguishable from data created by random processes. We can make an application less predictable by choosing a very hard to predict seed value. One popular choice for the seed is the system clock. In some operating systems a special device is available for producing random seed values. In Linux this is typically /dev/random. As a consequence, we can make an application more predictable by setting a xed seed value and noting the sequence of numbers generated. We can write a short demonstration program to see the eect of setting a xed seed. This will also give us a set of predictable answers for unit testing.
Note: Python Subclass Design Because of the late binding in Python, and the lack of compile-time type checking, we dont have to use strict subclass inheritance to replace the Python random number generator with our own non-random number generator. We only have to provide a class with the minimal interface that our application requires. While legal, we prefer not to bypass the inheritance tree in Python. We nd that applications are much easier to maintain if reasonable care is taken to assure that the classes and interfaces are matched up as precisely as possible. In this case, well want to make our non-random generator a strict subclass of the Random class. 78 Chapter 12. Review of Testability
Note that the Mock Objects approach follows the principle of deferred binding or dependency injection. A class doesnt choose which implementation it will use; the decision is made somewhere else in the application. Ideally, by the user. Weve allowed the following options. Wheel can be built with an instance of Random initialized with a given seed. Wheel can be build with instance of a new subclass, NonRandom. Wheel can create its own random number generator if no other choice is given.
79
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 Roulette Solution Questions and Answers FAQ for more on this subject. Looking ahead, we will have additional notes on this topic as we add the SevenReds Player Class subclass of Player. If setting the seed works so well, why make a non-random subclass? 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 isnt always suiciently clear and convincing for non-mathematicians. Its 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.
80
81
82
CHAPTER
THIRTEEN
PLAYER CLASS
The variations on Player, all of which reect dierent betting strategies, is the heart of this application. In 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.
83
Well have to design an interface that will create Bet s, 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 dene 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 sta 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 reects 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 nished placing bets. There are three mechanisms for alerting the Game that the Player is nished 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 nished 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-dened condition, and doesnt deserve to be called exceptional . It is merely a terminating condition for the game.
84
We recommend adding a method to Player to indicate when Player is done playing. This gives the most exibility, 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: Design Evolution In this case, these were situations which we didnt 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 Outcome s, the Player can create the individual Bet instances based on their betting strategy.
13.2.1 Fields
stake The players current stake. Initialized to the players starting budget. 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 The Table used to place individual Bets. The Table contains the current Wheel from which the player can get Outcomes used to build Bets.
13.2.2 Constructors
__init__(self, table) Constructs the Player with a specic Table for placing Bets. Parameter table (Table) the table to use
13.2.3 Methods
playing(self )
85
Returns true while the player is still active. placeBets(self ) Updates the Table with the various Bet s. 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 Roulette Table Overview for more information. win(self, bet ) Parameter bet (Bet) The bet which won Notication from the Game that the Bet was a winner. The amount of money won is available via bet method winAmount(). lose(self, bet ) Parameter bet (Bet) The bet which won Notication from the Game that the Bet was a loser. Note that the amount was already deducted from the stake when the bet was created.
13.3.1 Fields
lossCount The number of losses. This is the number of times to double the bet. 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 .
13.3.2 Methods
placeBets(self ) Updates the Table with a bet on black. The amount bet is 2lossCount , which is the value of betMultiple. win(self, bet ) Parameter bet (Bet) The bet which won 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. lose(self, bet ) Parameter bet (Bet) The bet which won Uses the superclass lose() to do whatever bookkeeping the superclass already does. Increments lossCount by 1 and doubles betMultiple.
86
87
88
CHAPTER
FOURTEEN
89
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 payo. Craps has a game, which starts with the dice roll when the point is o, 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.
90
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 rst 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 a 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 Design Decision Choosing A Collection. Statistical Summary. The Simulator will interrogate the Player after each cycle and capture the current stake. We dont 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 rst round of design.
14.3.1 Fields
initDuration The duration value to use when initializing a Player for a session. A default value of 250 is a good
91
choice here. initStake The stake value to use when initializing a Player for a session. This is a count of the number of bets placed; i.e., 100 $10 bets is $1000 stake. A default value of 100 is sensible. samples The number of game cycles to simulate. A default value of 50 makes sense. 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. 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 The Player; essentially, the betting strategy we are simulating. game The casino game we are simulating. This is an instance of Game, which embodies the various rules, the Table and the Wheel.
14.3.2 Constructors
__init__(self, game, player ) Saves the Player and Game instances so we can gather statistics on the performance of the players betting strategy. Parameters game (Game) The Game were simulating. This includes the Table and Wheel. player (Player) The Player. This encapsulates the betting strategy.
14.3.3 Methods
session(self ) Returns list of stake values. Return type list 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(self ) 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. 92 Chapter 14. Overall Simulation Control
93
94
CHAPTER
FIFTEEN
95
Soapbox on Composition Generally, a solution is composed of a number of objects. However, the consequences of this are often misunderstood. Since the solution is a composition of objects, it falls on the main method to do just the composition and nothing more. Our ideal main program creates and composes the working set of objects. In this case, it should decode the command-line parameters, and use this information to create and initialize the simulation objects, then start the processing. For these simple exercises, however, were omitting the parsing of command-line parameters, and simply creating the necessary objects directly. Our main program should, therefore, look something like the following:
theRNG= random.Random() theWheel= Wheel( theRNG ) theTable= Table() theGame= Game( theWheel, theTable ) thePlayer= SevenReds( table ) sim= new Simulator( theGame, thePlayer ) sim.gather()
1. The theRNG variable can also be initialized to an instance of NonRandom. The idea is that we build up the functionality of Wheel by composition. We compose the wheel of the bins plus the number generator. 2. the thePlayer variable can also be Martingale or Passenger57. Again, we have assembled the nal simulation by composition of a Wheel (and its generator), the Table and the specic Player algorithm. 3. This is the actual work of the application, done by the Simulator, using the Game and Player objects built by main. In many instances, the construction of objects is not done directly by the main method. Instead, the main method will construct a base group of Abstract Factory and Builder objects. Given these objects and the command-line parameters, the application objects can be built. The application objects then collaborate to perform the required functions.
15.2.1 Fields
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 this class inherits betMultiple. This is initially 1, doubles with each loss and is reset to one on each win.
15.2.2 Methods
placeBets(self )
96
If redCount is zero, this places a bet on black, using the bet multiplier. winners(self, outcomes) Parameter outcomes (Set of Outcome) The Outcome set from a Bin. This is notication from the Game of all the winning outcomes. If this vector includes red, redCount is decremented. Otherwise, redCount is reset to 7.
97
98
CHAPTER
SIXTEEN
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.
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 eect, 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. Well 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 dont need any instance variables. Consequently, well 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.
f (i)
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) is shown. This is read as sum f ( i ) for i in the range 0 to n. One common denition of uses a closed range, including the end values of 0 and n . However, since this is not a helpful denition for software, we will dene 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. f (i)
0i<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 list, x. When computing the mean, as a special case, there is no function applied to the values in the list. When computing standard deviation, the function involves subtracting and multiplying. f (xi )
0i<n
We can transform this denition 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. 100 Chapter 16. Statistical Measures
This is the Python implementation of Sigma. This computes two values, the sum, s and the number of elements, n.
s= sum( theList ) n = len( theList )
Where the f(x) calculuation computes the measure of deviation from the average.
The denition of the mathematical operator leads us to the following method for computing the mean:
(xi x )2 n1
The denition of the mathematical operator leads us to the following method for computing the standard deviation:
101
16.4.1 Constructors
Since this class has entirely static methods, it doesnt really need a constructor.
16.4.2 Methods
mean(values) This requires the @staticmethod decorator. Parameter values (list) The list of values we are summarizing. Computes the mean of the List of Integer values. stdev(values) This requires the @staticmethod decorator. Parameter values (list) The list of values we are summarizing. Computes the standard deviation of the List of Integer values.
102
103
104
CHAPTER
SEVENTEEN
105
17.2.1 Fields
rng A Random Number Generator which will return the next random number.
17.2.2 Constructors
__init__(table, rng) 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. Parameters table (Table) The Table which will accept the bests. rng (random.Random) A :class:random.Random random number generator.
17.2.3 Methods
placeBets(self ) Updates the Table with a randomly placed bet.
106
CHAPTER
EIGHTEEN
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 On Win column, otherwise, we transition to the state named in the On Loss column. We always start in the No Wins state. Design Pattern. We can exploit the State design pattern to implemente 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 dierent bet amount, and dierent state transition rules. 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 dene the interface between the Player and the object that holds the Players current state.
107
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. A states 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. A states nextWon() method constructs a new state object based on the state transition table when the last bet was a winner. A states 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.
18.2 On Polymorphism
One very important note is that we never have to check the class of any object. This is so important, we will repeat it here. Important: We dont use isistance(). We use polymorphism and design all subclasses to have the same interface. Python relies on duck typing (if it walks like a duck and quacks like a duck, it is a duck). Additionally, Python relies on the principle that its better to seek forgiveness than ask permission. What this translates to is an approach where an objects methods are simply invoked. If the object implements the methods, then it walked like a duck and for all practical purposes actually was a duck. If the method is not implemented, the application has a serious design problem and needs to crash. We nd that isinstance() is sometimes used by beginning programmers who have failed to properly delegate processing to the subclass. Often, when a responsibility has been left out of the class hierarchy, it is allocated to the client object. The typical Pretty-Poor Polymorphism looks like the following:
class SomeClient( object def someMethod( self if isinstance(x, Special Case ): ): AClass): that should have been part of AClass
In all cases, uses of isinstance() must be examined critically. This will usually lead to refactoring the special case out of the collaborating class. There will be three changes as part of the refactoring. 1. We will move the special-case the functionality into the class being referenced by instanceof. In the above example, the special case is moved to AClass. 2. We will usually have to add default processing to the superclass of AClass so that all other sibling classes of AClass lack this special-case feature. 3. We simply call the refactored method from the client class. This refactoring leads to a class hierarchy that has the property of being polymorphic: all of the subclasses have the same interface: all objects of any class in the hierarchy are interchangable. Each object is, therefore, responsible for correct behavior. More important, a client object does not need to know which subclass the object is a member of: it simply invokes methods which are dened with a uniform interface across all subclasses. 108 Chapter 18. Player 1-3-2-6 Class
109
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 doesnt matter at all. In many cases, the anonymous uniformity of subclasses isnt 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 dierences between the various games and their level of interaction with the players.
18.4.1 Fields
player The Player1326 player who is currently in this state. This player will be used to provide the Outcome that will be used to create the Bet.
18.4.2 Constructors
__init__(self, player ) The constructor for this class saves the Player1326 which will be used to provide the Outcome on which we will bet.
18.4.3 Methods
currentBet(self ) Constructs a new Bet from the players preferred Outcome. Each subclass has a dierent multiplier used when creating this Bet. In Python, this method should return NotImplemented. 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. nextWon(self ) Constructs the new Player1326State instance to be used when the bet was a winner. In Python, this method should return NotImplemented. 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. nextLost(self ) 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. This dened in the superclass to assure that it is available for each subclass.
110
18.5.1 Methods
currentBet(self ) Constructs a new Bet from the players outcome information. The bet multiplier is 1. nextWon(self ) Constructs the new Player1326OneWin instance to be used when the bet was a winner.
18.6.1 Methods
currentBet(self ) Constructs a new Bet from the players outcome information. The bet multiplier is 3. nextWon(self ) Constructs the new Player1326TwoWins instance to be used when the bet was a winner.
18.7.1 Methods
currentBet(self ) Constructs a new Bet from the players outcome information. The bet multiplier is 2. nextWon(self ) Constructs the new Player1326ThreeWins instance to be used when the bet was a winner.
111
18.8.1 Methods
currentBet(self ) Constructs a new Bet from the players outcome information. The bet multiplier is 6. nextWon(self ) Constructs the new Player1326NoWins instance to be used when the bet was a winner. An alternative is to update the player to indicate that the player is nished playing.
18.9.1 Fields
outcome This is the players preferred Outcome. During construction, the Player must fetch this from the Wheel. state This is the current state of the 1-3-2-6 betting system. It will be an instance of a subclass of Player1326State. This will be one of the four states: No Wins, One Win, Two Wins or Three Wins.
18.9.2 Constructors
__init__(self ) Initializes the state and the outcome. The state is set to the initial state of an instance of Player1326NoWins. The outcome is set to some even money proposition, for example "Black".
18.9.3 Methods
placeBets(self ) Updates the Table with a bet created by the current state. This method delegates the bet creation to state objects currentBet() method. win(self, bet )
112
Parameter bet (Bet) The Bet which won 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 states objects nextWon() method and saving the new state in state lose(self, bet ) Parameter bet (Bet) The Bet which lost Uses the current state to determine what the next state will be. This method delegates the next state decision to state objects nextLost() method, saving the result in state.
113
1. Global objects for the distinct state objects. While truly global objects are usually a mistake, we can justify this by claiming that were only creating objects that are shared among a few objects. 2. The Singleton design pattern. With some cleverness, this can be made transparent in Python. However, its easiest to make this explicit, by dening a formal instance() method that fetches (or creates) the one-and-only instance of the class. 3. A Factory object that produces state objects. This Factory can retain a small pool of object instances, eliminating needless object construction. The deliverables for this exercise are revisions to the Player1326State class hierarchy to implement the Factory design pattern. This will not change any of the existing unit tests, demonstrations or application programs.
114
CHAPTER
NINETEEN
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.
19.2.1 Fields
sequence This List keeps the bet amounts; wins are removed from this list and losses are appended to this list. THe current bet is the rst value plus the last value. outcome This is the players preferred Outcome.
19.2.2 Constructors
__init__(self ) This uses the PlayerCancellation.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.
19.2.3 Methods
resetSequence(self ) Puts the initial sequence of six Integer instances into the sequence variable. These Integers are built from the values 1 through 6. placeBets(self ) Creates a bet from the sum of the rst and last values of sequence and the preferred outcome. win(self, bet ) Parameter bet (Bet) The bet which won Uses the superclass method to update the stake with an amount won. It then removes the st and last element from sequence. lose(self, bet ) Parameter bet (Bet) The bet which lost Uses the superclass method to update the stake with an amount lost. It then appends the sum of the rst and list elements of sequence to the end of sequence as a new Integer value.
CHAPTER
TWENTY
117
20.2.1 Fields
recent This is the most recent bet amount. Initially, this is 1. previous This is the bet amount previous to the most recent bet amount. Initially, this is zero.
20.2.2 Constructors
__init__(self ) Initialize the Fibonacci player.
20.2.3 Methods
win(self, bet ) Parameter bet (Bet) The bet which won Uses the superclass method to update the stake with an amount won. It resets recent and previous to their initial values of 1 and 0. lose(self, bet ) Parameter bet (Bet) The bet which lost 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. next recent + previous previous recent recent next param bet The Bet which lost. type bet Bet
119
120
CHAPTER
TWENTYONE
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 delity 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 applications 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 cant 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 nalized into deliverables with no opportunity for meaningful rework. In Review of Testability, we presented one common kind of rework to support more complete testing. In 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 nd 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.
121
122
Part III
Craps
123
This part describes parts of the more complex game of Craps. Craps is played with dice. A player throws the dice; sometimes its an immediate win (or loss). Other times, the number on the dice become the point, and you contine throwing dice until you make your point or crap out. Craps is a game with two states and a number of state-change rules. It has a variety betting alternatives, 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 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 reects 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 cant easily be located or explained in advance.
125
126
CHAPTER
TWENTYTWO
CRAPS DETAILS
In the rst section we will present elements of the game of Craps. Craps is a stateful game, and well describe the state changes in some detail. We will review some of the bets available on the Craps table in some depth. Craps oers some very obscure bets, and part of this obscurity stems from the opaque names for the various bets. Creating this wide variety of bets is an interesting programming exercise, and the rst four exercise chapters will focus on this. Finally, we will describe some common betting strategies that we will simulate. The betting strategies described in Roulette will be applied to Craps bets, forcing us to examine the Roulette solution for reuse opportunities.
127
The Craps game has two states: point o and point on . When a player begins a game, the point is o; the initial throw of the dice is called the come out roll . The number thrown on the dice determines the next state, and also the bets that win or lose. The rules are summarized in the following table. Table 22.1: Craps Game States State Point O; the Come Out Roll. Only Pass and Dont Pass bets allowed. Roll 2, 3, 12 Bet Resolution Craps: Pass bets lose, Dont Pass bets win. Winner: Pass bets win, Dont Pass bets lose. Next State Point O
7, 11 4, 5, 6, 8, 9, 10 Point On. Any additional bets may be placed. 2, 3, 12 11 7 Point, p Non-p number
Point O Point On the number rolled, p. Point still on Point still on Point O Point O Point still on
Nothing Nothing Loser: all bets lose. The table is cleared. Winner: point is made, Pass bets win, Dont Pass bets lose. Nothing; Come bets are activated
There is a symmetry to these rules. The most frequent number, 7, is either an immediate winner or an immediate loser. The next most frequent numbers are the six point numbers, 4, 5, 6, 8, 9, 10, which form three sets: 4 and 10 are high odds, 5 and 9 are middle odds, 6 and 8 are low odds. The relatively rare numbers (2, 3, 11 and 12) are handled specially on the rst roll only, otherwise they are ignored. Because of the small distinction made between the craps numbers (2, 3 and 12), and 11, there is an obscure C-E bet to cover both craps and eleven.
128
placed behind the line bet. There are actually two sets of game bets based on the pass line and the come line. The following illustration shows the left half of a typical craps table layout. On the left are the game bets. On the right (in the center of the table) are the single-roll propositions.
Figure 22.1: Craps Table Layout Game Bets. The Game bets have two states. There is a set of bets allowed for the rst roll, called the come out roll. If a point is established a number of additional bets become available. These are the pass line bets. In addition to the basic pass line bets, there are also come line bets, which are similar to game bets. Come Out Roll. When the point is o, the player will be making the come out roll, and only certain bets are available. Once the point is on, all bets are available. The bets which are allowed on the come out roll are the Pass Line and Dont Pass Line bets. These bets follow the action of the players game. There are several possible outcomes for these two bets. Player throws craps (2, 3, or 12). A Pass Line bet loses. A Dont Pass Line bet wins even money on 2 or 3; however, on 12, it returns the bet. This last case is called a push ; this rule is noted on the table by the bar 12 legend in the Dont Pass area. Player throws 7 or 11. A Pass Line bet wins even money. A Dont Pass Line bet loses. Player throws a point number. The point is now on. A large white token, marked on is placed in one of the six numbered boxes to announce the point. The player can now place an additional odds bet behind the line. Placing a bet behind the Pass Line is called buying odds on the point. Placing a bet behind the Dont Pass Line is called laying odds against the point. Point Rolls. Once the point is on, there are three possible outcomes. The player makes the point before rolling 7. A Pass Line bet wins even money. A Dont Pass Line bet loses. The point determines the odds used to pay the behind the Pass Line odds bet. The player throws 7 before making the point. A Pass Line bet loses. A Dont Pass Line bet pays even money. The point determines the odds used to pay the behind the Dont Pass Line odds bet. The player throws a number other than 7 and the point: the game continues. Come Line Bets. Once the point is on, the bets labeled Come and Dont Come are allowed. These bets eectively form a game parallel to the main game, using a come point instead of the main game point. Instead of dropping the large on token in a numbered square on the layout, the house will move the 22.3. Available Bets 129
individual Come and Dont Come bets from their starting line into the numbered boxes (4, 5, 6, 8, 9, 10) to show which bets are placed on each point number. There are several possible outcomes for these two bets. Player throws craps (2, 3, or 12). A Come Line bet loses. A Dont Come bet wins even money on 2 or 3; it is a push on 12. Established come point bets in the numbered squares remain. Player throws 11. A Come Line bet wins even money. A Dont Come bet loses. Established come point bets in the numbered squares remain. Player throws a point number (4, 5, 6, 8, 9, or 10). Any come bets in that numbered box are winners the come point was made and the point determines the odds used to pay the behind the line odds. (Note that if this is the point for the main game, there will be no bets in the box, instead the large on token will be sitting there.) New bets are moved from the Come Line and Dont Come Line to the numbered box, indicating that this bet is waiting on that point number to be made. Additional behind the line odds bets can now be placed. If the main games point was made, the on token will be removed (and ipped over to say o ), and the next roll will be a come out roll for the main game. These bets (with the exception of the free odds behind a Come Line bet) are still working bets. The player throws 7 before making the main point. A Come bet loses. A Dont Come bet pays even money. The bets in the numbered boxes are all losers. Propositions. There are also a number of one-roll propositions, including the individual numbers 2, 3, 7, 11, 12; also eld (2, 3, 4, 9, 10, 11 or 12), any craps (2, 3 or 12), the horn (2, 3, 11 or 12), and a hop bet. These bets have no minimum, allowing a player to cover a number of them inexpensively. Hardways. There are four hardways bets. These are bets that 4, 6, 8 or 10 will come up on two equal die (the hard way) before they come up on two unequal die (the easy way) or before a 7 is rolled. Hard 6, for example, is a bet that the pair (3,3) will be rolled before any of the other four combinations (1,5), (2,4), (5,1), (4,2) that total 6, or any of the 6 combinations that total 7. These bets can only be placed when the point is on, but they are neither single-roll propositions, nor are they tied to the game point. Unmarked Bets. There are additional unmarked bets. One is to make a place bet on a number; these bets have a dierent set of odds than the odds bets behind a line bet. Other unmarked bets are to buy a number or lay a number, both of which involve paying a commission on the bet. Typically, only the six and eight are bought, and they can only be bought when they are not the point. For programmers new to casino gambling, see Odds and Payouts for more information on odds payments for the various kinds of bets.
130
Odds and Payouts Not all of the Craps outcomes are equal probability. Some of the bets pay xed odds, dened as part of the bet. Other bets pay odds that depend on the point established. The basic Pass Line, Dont Pass, Come Line and Dont Come bets are even money bets. A $5 bet wins an additional $5. The odds of winning a Pass Line bet is 49.3%. The 1:1 payout does not reect the actual probability of winning. The points of 4 and 10 have odds of 3 in 36 to win and 6 in 36 to lose. The points of 5 and 9 have odds of 4 in 36 to win and 6 in 36 to lose. The points of 6 and 8 have odds of 5 in 36 to win and 6 in 36 to lose. The odds, therefore are 2:1, 3:2 and 6:5 for these behind-the-line odds bets, respectively. The combination of a Pass Line bet plus odds behind the line provides the narrowest house edge. For example, a bet on the Pass Line has an expected return of -1.414%. If we place double odds behind the line with an expected return of 0, the net expected return for the combination is -0.471%. The various proposition bets have odds stated on the table. Note that these rarely reect the actual odds of winning. For example, a bet on 12 is a 1/36 probability, but only pays 30:1. There are 1/36 ways to win a hard six or hard eight bet, 10/36 ways to loose and the remaining 25 outcomes are indeterminate. While a hard 6 or hard 8 should pay 10:1, the actual payment is 9:1. Similarly for a hard 4 or hard 10: they should pay 8:1, the actual payment is 7:1.
131
This distinction can be ignored by novice programmers. None of our simulated players are wrong bettors, since this involves putting a large amount at risk for a small payout, which violates one of the basic rules of gambling.
132
CHAPTER
TWENTYTHREE
133
1. Our preliminary note was that this class Runs the game. The responsibilities section has a summary of ve steps involved in running the game. 2. The rst step is gets bets from :class:Player. Find the Player card. 3. Does a Player collaborate with a Game to place bets? Note that the game state inuences the allowed bets. Does Game collaborate with Player to provide the state information? If not, add this information to one or both cards. 4. The Games second step is to throw the Dice. Is this collaboration on the Dice card? 5. The Games third step is to update the state of the game. While the state appears to be internal to the Game, requiring no collaboration, we note that the Player needs to know the state, and therefore should collaborate with Game. Be sure this collaboration is documented. 6. The Games fourth and fth steps are to pay winning bets and collect losing bets. Does the Game collaborate with the Table to get the working bets? If not, update the collaborations. Something well need to consider is the complex relationship between the dice, the number rolled on the dice, the game state and the various bets. In Roulette, the wheel picked a random Bin which had a simple list of winning bets; all other bets were losers. In Craps, however, we nd that we have game bets that are based on changes to the game state, not simply the number on the dice. The random outcome is used to resolve one-roll proposition bets, resolve hardways bets, change the game state, and resolve game bets. We also note that the house moves Come Line (and Dont Come) bets from the Come Line to the numbered spaces. In eect, the bet is changed from one Outcome to another Outcome . This means that a Bet has a kind of state change, in addition to the Game s state change and any possible Player state change. Important: Stateful Objects To continue this rant, we nd that most interesting IT applications involve stateful objects. Everything that has a state or status, or can be updated, is stateful. Often, designers overlook these state changes, describing them as simple updates. However, state changes are almost universally accompanied by rules that determine legal changes, events that precipitate changes, and actions that accompany a state change. Belittling stateful objects causes designers to overlook these additional details. The consequence of ignoring state is software that performs invalid or unexpected state transitions. These kinds of bugs are often insidious, and diicult to debug. A walkthrough will give you an overview 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.
135
The alternative is deeply nested if-statements. Multiple objects introduce some additional details in the form of class declarations, but objects have the advantage of clearly isolating responsibilities, making us more condent that our design will work properly. If-statements only conate all of the various conditions into a tangle that includes a huge risk of missing an important and rare condition. See the discussion under Design Decision Object Identity for more discussion on object identity and why each Outcome is a separate object. What is the dierence between Dice and Wheel? Dont they both represent simple collections with random selection? Perhaps. At the present time, the distinction appears to be in the initialization of the two collections of Bins of the Wheel or Throws of the Dice. Well revisit the question in some depth in Soapbox on Overengineering. Generally, we are slow to merge classes together without evidence that they are really the same thing. In this case, they appear very similar, so we will note the similarities and dierences as we work through the design details. There is a ne line between putting too many things together and splitting too many things apart. Generally, the mistake we see most often is putting too many things together, and resolving the dierences through complex if-statements and other hidden processing logic.
136
CHAPTER
TWENTYFOUR
OUTCOME CLASS
This chapter will examine the Outcome class, and its suitability for the game of Craps. Well present some additional code samples to show a way to handle the dierent kinds of constuctors that this class will need.
137
This leaves us with the Pass Line and Dont Pass Line odds bet, which also depend on the point rolled. In this case, the bets are eectively moved to a specic numbered box. In a casino, a large, white, on token in placed in the box. The eect is that same as if the house had moved all the Pass Line bets. The odds bet, while physically behind the Pass Line, is eectively in the box identied by the point. Again, the bet is moved from the 1:1 Pass Line to one of the six numbered boxes; any odds bet that is added will be on a single Outcome with xed odds. In this case, the existing Outcome class still serves many of our needs. Looking forward, we will have to rework Bet to provide a method that will change to a dierent Outcome. This will move a line bets to one of the six numbered point boxes. Additional Features. There are two additional responsibilities that we will need in the Outcome class: more complex odds and a house commission. In Roulette, all odds were stated as n :1, and our winAmount() depended on that. In craps, many of the odds have non-unit denominators. Example odds include 6:5, 3:2, 7:6, 9:5. In a casino, the bets are multiples of $5, $6 or $10 to accomodate the fractions. Complex Odds. In our simulation, we are faced with two choices for managing these more complex odds: exact fractions or approximate oating-point values. Since exact rational fractions are not part of the Java class library, well use simple double values instead of int values for the players stake and the amount won or lost. An interesting additional feature is to use rational numbers for the stakes and amounts. There are a number of rational number classes available on the Internet, any of which would be suitable for this problem. We will leave it to the interested student to replace the double values with Rational values for the players stake and the amounts won and lost with each bet. In Java, we will have to replace many instances of int with double. This is an unpleasant change, but a consequence of starting out with a built-in primitive type. The only eective escape from this kind of change is to use a class in all cases instead of a primitive type. This would allow us to use dierent subclasses as our design evolved. If, for example, we use java.lang.Number, we can switch to any subclass without a complex or sweeping change. The down-side of using a class in Python is that the built-in expression syntax only applies to primitive types. With primitive types we can write a + b . With the language classes like Integer, we have to write something like new Integer( a.intValue() + b.intValue() ). Withbclasses like BigInteger, we can write something like a.add( b ) . In Python, we dont have static compile-time type checking. We can easily replace an integer result with a double result with no impact throughout the application. Commission Payments. The second extension we have to consider is for the two bets which have a commission when they are created: buy bets and lay bets. The buy bet involves an extra 5% placed with the bet: the player puts down $21, a $20 bet and a $1 commission. A lay bet, which is a wrong bet, involves a risk of a large amount of money against a small win, and the commission is based on the potential winning. For a 2:3 wrong bet, the commission is 5% of the outcome; the player puts down $31 to win $20 if the point is not made. In both buy and lay cases, the Player sees a price to create a bet of a given amount. Indeed, this generalizes nicely to all other bets. Most bets are simple and the price is the amount of the bet. For buy bets, however, the price is 5% of the amount of the bet; for lay bets, the price is 5% of the possible payout. The open question is the proper allocation of responsibility for this price. Is the price related to the Outcome or the Bet? When we look at the buy and lay bets, we see that they are based on existing point number Outcome s and share the same odds. However, there are three very dierent ways create a Bet on one of these point number Outcome s: a bet on the Pass Line (or Dont Pass Line), a bet on the Come Line (or Dont Come Line), and a buy (or lay) bet on the number. When we bet via the Pass Line or Come Line, the Line bet was moved to the point number, and the odds bet follows the Line bet. For this reason, the price is a feature of creating the Bet. Therefore, well consider the commission as the price
138
of creating a bet and defer it to the Bet class design. We observe that the slight error in the Line bet odds is the houses edge on the Line bet. When we put an odds bet behind the line, the more correct odds dilutes this edge. When we buy a number, on the other hand, the odds are correct and the house takes the commission directly.
Note that Python mutable types (lists, sets, maps) should not be provided as default values for an initializer. This is because one initialization object is created when the class is dened, and all instances will share this singleton initializer object. To avoid this undesirable sharing of an instance of the default value, we have to do the following.
class SomeClass: def __init__( self, aList=None ): if aList: initialize using aList else: default initialization using a fresh []
24.3.1 Fields
name Holds the name of the Outcome. Examples include "1", "Red", "Pass Line". numerator Holds the numerator portion of odds for this Outcome. This is the multiplier in the odds fraction. For right bets in Craps, this is larger than the denominator. For wrong bets, it is smaller than the denominator. denominator Holds the denominator portion of odds for this Outcome. For Roulette, this is always 1. 24.2. Overloaded Methods 139
24.3.2 Constructors
__init__(name, numerator, denominator=1) Parameters numerator (integer) the payout odds numerator denominator (integer) the payout odds denominator Sets the name and odds from the parameters. Example 1: 6:5 is a right bet, the player will win 6 for each 5 that is bet. Example 2: 2:3 is a wrong bet, they player will win 2 for each 3 that is bet.
24.3.3 Methods
winAmount(self, amount ) Parameter amount (integer) amount of the bet Returns the product of this Outcomes odds numerator by the given amount, divided by the odds denominator. __str__(self ) 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.
140
CHAPTER
TWENTYFIVE
THROW CLASS
In Craps, a throw of the dice may change the state of the game. This close relationship between the Throw and CrapsGame leads to another chicken-and-egg design problem. Well design Throw in detail, but provide a rough stub for CrapsGame. Additionally, this chapter will introduce the subtle issue of over-engineering when doing design work. Well revist these issues a number of times in order to provde examples of good design and the issues that can lead to poor design.
141
Eleven. This is a throw of 11. On a come-out roll, this is an immediate win. On any other roll, this is ignored. There are 2 of these throws. Point. This is a throw of 4, 5, 6, 8, 9, or 10. On a come-out roll, this estanblishes the point, and changes the game state. On any other roll, this is is compared against the established point: if it matches, this is a win and a change of game state. Otherwise, it doesnt match and no game state change occurs. There are a total of 24 of these throws, with actual frequencies between three and ve. The state change can be implemented by dening methods in Game that match the varieties of Throw. We can imagine that the design for Game will have four methods: craps(), natural(), eleven(), and point(). Each kind of Throw will call the matching method of Game, leading to state changes, and possibly game bet resolution. The game state changes lead us to design a hierarchy of Throw classes to enumerate the four basic kinds of throws. We can then initialize a Dice object with 36 Throw objects, each of the appropriate subclass. When all of the subclasses have an identical interface, this embodies the principle of polymorphism. For additional information, see On Polymorphism. In looking around, we have a potential naming problem: both a wheels Bin and the dices Throw are somehow instances of a common abstraction. Looking forward, we may wind up wrestling with a deck of cards trying to invent a common nomenclature for all of these randomizers. They create random events, and this leads us to a possible superclass for Bin and Throw: RandomEvent. Currently, we cant identify any features that we can refactor up into the superclass. Rather than overengineer this, well hold o on complicating the design until we nd something else that is common between our sources of random events. See the Soapbox on Over-Engineering as a continuation of this rant. Soapbox on Over-Engineering Some of the horror stories of failed OO design projects seem to have over-engineering as the root cause. Over-engineering often includes the creation of superclasses and interfaces to embody potentially useful features. We suggest examining all engineering eorts by asking does it solve a real problem? Focus on potential value tends to dilute the creation of real value. We note that software is developed to solve a particular problem. If a design consideration does not help solve the problem as presented, we have to classify the design as potential over-engineering. The most common response to this classication is that we will tend to focus too narrowly and build a point solution; the presumption is that point solutions are not as adaptable as something more broadly focused. Our experience is that when the object design focuses on tangible real-world objects, and models those objects with a high degree of delity, these objects are very adaptable; they can be applied to solve a number of real-world problems. When the object design includes a number of irrelevant considerations, it takes too much eort to solve the initial problem, diluting the value of the software. Additionally, some over-engineering is the result of conating a number of features into a single class. We prefer to add features slowly so as to clearly separate the various concerns. As an example, it is very easy to conate Bet and Outcome into a single class, losing site of the fact that bet amounts can change, but the denition of the outcome itself is invariant.
142
25.2.1 Fields
outcomes A Set of one-roll Outcomes that win with this throw. d1 One of the two die values, from 1 to 6. d2 The other of the two die values, from 1 to 6.
25.2.2 Constructors
__init__(self, d1, d2, * outcomes) Creates this throw, and associates the given Set of Outcomes that are winning propositions. Parameters d1 The value of one die d2 The value of the other die outcomes The various outcomes for this Throw
25.2.3 Methods
hard(self ) Returns true if d1 is equal to d2. This helps determine if hardways bets have been won or lost. updateGame(self, game) Parameter game (CrapsGame) the Game to be updated based on this throw. Calls one of the Game state change methods: craps(), natural(), eleven(), point(). This may change the game state and resolve bets. __str__(self ) An easy-to-read String output method is also very handy. This should return a String representation of the dice. A form that looks like 1,2 works nicely.
25.3.1 Constructors
__init__(self, d1, d2 ) Parameters d1 The value of one die d2 The value of the other die
143
Creates this throw. The constraint is that d1 + d2 = 7. If the constraint is not satised, simply raise an exception. This uses the superclass constructor to add appropriate Outcomes for a throw of 7.
25.3.2 Methods
hard(self ) A natural 7 is odd, and can never be made the hard way. This method always returns false. updateGame(self, game) Parameter game (CrapsGame) the Game to be updated based on this throw. Calls the natural() method of a game Game. This may change the game state and resolve bets.
25.4.1 Constructors
__init__(self, d1, d2 ) Parameters d1 The value of one die d2 The value of the other die Creates this throw. The constraint is that d1 + d2 {2, 3, 12}. If the constraint is not satised, simply raise an exception. This uses the superclass constructor to add appropriate Outcomes for a throw of craps.
25.4.2 Methods
hard(self ) The craps numbers are never part of hardways bets. This method always returns false. updateGame(self, game) Parameter game (CrapsGame) the Game to be updated based on this throw. Calls the craps() method of a game Game. This may change the game state and resolve bets.
25.5.1 Constructors
__init__(self, d1, d2 ) Parameters d1 The value of one die d2 The value of the other die Creates this throw. The constraint is that d1 + d2 = 11. If the constraint is not satised, simply raise an exception. This uses the superclass constructor to add appropriate Outcomes for a throw of 11.
25.5.2 Methods
hard(self ) Eleven is odd and never part of hardways bets. This method always returns false. updateGame(self, game) Parameter game (CrapsGame) the Game to be updated based on this throw. Calls the eleven() method of a game Game. This may change the game state and resolve bets.
25.6.1 Constructors
__init__(self, d1, d2 ) Parameters d1 The value of one die d2 The value of the other die Creates this throw. The constraint is that d1 + d2 {4, 5, 6, 8, 9, 10}. If the constraint is not satised, simply raise an exception. This uses the superclass constructor to add appropriate Outcomes for a throw of craps.
25.6.2 Methods
hard(self ) Eleven is odd and never part of hardways bets. This method always returns false. Returns true if d1 is equal to d2. This helps determine if hardways bets have been won or lost. updateGame(self, game) Parameter game (CrapsGame) the Game to be updated based on this throw.
145
Calls the point() method of a game Game. This may change the game state and resolve bets.
25.7.1 Fields
point The current point. This will be replaced by a proper State design pattern.
25.7.2 Constructors
__init__(self ) Creates this Game. This will be replaced by a constructor that uses Dice and CrapsTable.
25.7.3 Methods
craps(self ) Resolves all current 1-roll bets. If the point is zero, this was a come out roll: Pass Line bets are an immediate loss, Dont Pass Line bets are an immediate win. If the point is non-zero, Come Line bets are an immediate loss; Dont Come Line bets are an immediate win. The state doesnt change. A future version will delegate responsibility to the craps() method of a current state object. natural(self ) Resolves all current 1-roll bets. If the point is zero, this was a come out roll: Pass Line bets are an immediate win; Dont Pass Line bets are an immediate loss. If the point is non-zero, Come Line bets are an immediate win; Dont Come bets are an immediate loss; the point is also reset to zero because the game is over. Also, hardways bets are all losses. A future version will delegate responsibility to the natural() method of a current state object. eleven(self ) Resolves all current 1-roll bets. If the point is zero, this is a come out roll: Pass Line bets are an immediate win; Dont Pass Line bets are an immediate loss.
146
If the point is non-zero, Come Line bets are an immediate win; Dont Come bets are an immediate loss. The game state doesnt change. A future version will delegate responsibility to the eleven() method of a current state object. point(self, point ) Parameter point (integer) The point value to set. Resolves all current 1-roll bets. If the point was zero, this is a come out roll, and the value of the dice establishes the point. If the point was non-zero and this throw matches the point the game is over: Pass Line bets and associated odds bets are winners; Dont Pass bets and associated odds bets are losers; the point is reset to zero. Finally, if the point is non-zero and this throw does not match the point, the state doesnt change; however, Come point and Dont come point bets may be resolved. Additionally, hardways bets may be resolved. A future version will delegate responsibility to the current states point() method to advance the game state. __str__(self ) An easy-to-read String output method is also very handy. This should return a String representation of the current state. The stub version of this class has no internal state object. This class can simply return a string representation of the point; and the string "Point Off" when point is zero.
Five classes which perform unit tests on the various classes of the Throw class hierarchy.
147
148
CHAPTER
TWENTYSIX
DICE CLASS
Unlike Roulette, where a single Bin could be identied by the number in the bin, dice use a pair of numbers. In this chapter, we design Dice, as well as designing an inner class that is used only to make a single key out of a composite object.
149
2. We can transform the two numeric dice values to a single index value for the sequence. This is a technique called Key Address Transformation; we transform the keys into the address (or index) of the data. We create the index, i, from two dice, d1 , d2 , via a simple linear equation: i = 6(d1 1) + (d2 1). We can reverse this calculation to determine the two dice values from an index. d1 = i 6 + 1; d2 = (i mod 6) + 1. Because of encapsulation, the choice of algorithm is completely hidden within the implementation of Dice. While the numeric calculation seems simple, it doesnt obviously scale to larger collections of dice very well. While Craps is a two-dice game, we can imagine simulating a game with larger number of dice, making this technique complex. Solution. Our recommendation is to encapsulate the pair of dice in a tuple instance. We can use this object as index into a dict that associates a tuple with a Throw. More advanced students can create a class hierarchy for Dice that includes all of the various implementations as alternative subclasses. Random Selection. The random number generator in random.Random helps us locate a Throw at random. First, we can get the list of keys from the dict that associates a tuple of dice numbers with a Throw. Second, we use Random.choice() to pick one of these tuples. We use this randomly selected tuple to return the selected Throw.
26.3.1 Fields
d1 Contains the face of one die.
150
d2 Contains the face of the other die. These attributes are built for us by the collections.namedtuple function.
26.3.2 Constructors
The constructor are built for us by the collections.namedtuple function.
26.4.1 Fields
throws This is a dict that associates a NumberPair with a Throw. rng An instance of random.Random Generates the next random number, used to select a Throw from the throws collection.
26.4.2 Constructors
__init__(self, rng=None) Build the dictionary of Throw instances. Parameter rng (random.Random) The random number generator to use. At the present time, this does not do the full initialization of all of the Throws. Were only building the features of Dice related to random selection. Well extend this class in a future exercise.
26.4.3 Methods
addThrow(self, throw ) Parameter throw (Throw) The Throw to add. Adds the given Throw to the mapping maintained by this instance of Dice. The key for this Throw is available from the Throw.getKey() method. next(self ) Returns the randomly selected Throw. First, get the list of keys from the throws. The random.Random.choice() method will select one of the available keys from the the list. This is used to get the corresponding Throw from the throws Map.
151
getThrow(self, d1, d2 ) Parameters d1 The value of one die d2 The other die While not needed by the application, unit tests may need a method to return a specic Throw rather than a randomly selected Throw. This method takes a particular combination of dice, locates (or creates) a NumberPair, and returns the appropriate Throw.
152
Removing this needless code is an exercise that should be considered, but not done at this time. In the Design Cleanup and Refactoring chapter, well clean up this design to remove several things which in retrospect will be poor design decisions.
153
154
CHAPTER
TWENTYSEVEN
The eld outcome, with two sets of odds. This belongs to throws for 2, 3, 4, 9, 10, 11 and 12. For 2 and 12, this pays 2:1, all others pay even money (1:1). We can use the following algorithm for building the Dice.
Building Dice
For All Faces Of Die 1. For all d1 , such that 1 d1 < 7: For All Faces Of A Die 2. For d2 , such that 1 d2 < 7: Sum the Dice. Compute the sum, s d1 + d2 . Craps? If s is in 2, 3, and 12, we create a CrapsThrow instance. This will include a reference to one of the 2, 3 or 12 Outcome s, plus references to the Any Craps, Horn and Field Outcomes. Point? For s in 4, 5, 6, 8, 9, and 10 we will create a PointThrow instance. Hard? When d1 = d2 , this is a hard 4, 6, 8 or 10. Easy? Otherwise, d1 = d2 , this is an easy 4, 6, 8 or 10. Field? For s in 4, 9 and 10 we include a refrence to the Field Outcome. Note that 2, 3, and 12 Field outcomes where handled above under Craps. Natural? For s of 7, we create a NaturalThrow instance. This will also include a reference to the 7 Outcome. Eleven? For s of 11, we create an ElevenThrow instance. This will include references to the 11, Horn and Field Outcomes.
complex, and dilutes the responsibility for creating a proper Bet. Once we put multiple Outcomes into a Bet, we need to assign responsibility for keeping the bundle of Field Outcomes together. Pursuing this further, we could expand Outcome to follow the Composite design pattern. We could introduce a subclass which was a bundle of multiple Outcomes. This would allow us to keep Bet very simple, but we still have to construct appropriate composite Outcome instances for the purpose of creating Bets. Rather than dive into allocating this responsibility, well look at other alternatives, and see if something turns up that doesnt add as much complexity. Another approach is to add an optional argument to Outcome that uses the current Throw to calculate the win amount. This allows us to have a single eld bet Outcome with dierent odds for the various numbers in the eld. This further allows us to create slightly dierent eld bet Outcome class denitions for the casino-specic variations on the rules. Solution. Our rst design decision, then, is to add an additional method to Outcome that calculates the win amount given the current Throw. Consequences. There are a number of consequences of this design decision. The next design problem we have to solve is where in the Outcome class hierarchy do we add this additional winAmount() method? After that, we will need to determine the signature of the revised winAmount() method. This leads us to two rounds of additional problem-solving.
157
To allow the games to evolve independently, we should not have any dependencies between games. This means that a general-purpose class like Outcome cant depend on a game-specic class like Throw. A generalpurpose class has to depend on some a superclass (or interface) that encompasses the Craps-specic Throw as well as the Roulette-specic Bin. Additional Classes. To break the dependency between a general-purposes class and a game-specic class, we need introduce a superclass that includes both Throw and Bin as subclasses. This will Outcome to work with either Craps and Roulette; keeping them independent of each other. We could call the parent class a RandomEvent. This new class would have an integer event identier: either the wheels bin number or the total of the two dice. Given this new superclass, we could then rearrange both Throw and Bin to be subclasses of RandomEvent. This would also force us to rework parts of Wheel that creates the Bins. A benet of creating a RandomEvent class hierarchy is that we can change the new winAmount() method to compute the win amount given a RandomEvent instead of a highly Craps-specic Throw. This makes the winAmount() method far more generally useful, and keeps Craps and Roulette separate from each other. This technique of reworking Throw and Bin to be subclasses of a common superclass is a fairly common kind of generalization refactoring. We observe that more experienced designers develop an ability to locate this kind of commonality. Less experienced designers tend to conate too many nearly-common features into a single class, leading to a brittle design that cannot easily be reworked. In our example, we considered lifting one common attribute to the superclass so that a related class (Outcome) could operate on instances of these two classes in a uniform manner. For more information on this rework, see Soapbox on Subclasses. Approaches. We will present two alternative designs paths: minimal rework, and a design that is at the fringe of over-engineering. The minimal design eort has one unpleasant consequence: Roulettes Outcome instances depend on the Craps-related Throw class. This leads to an entanglement between Roulette and Craps around a feature that is really a special case for Craps only. This kind of entanglement may limit our ability to successfully reuse these classes. See Soapbox on Architecture for a discussion on this issue. The possible over-engineering prevents this entanglement. We consider this separation very desirable, even in an application as small as this exercise.
158
of which are related because they have common attributes and behavior. The Outcome is fairly intangible, so the notion of commonality can be diicult to see. Contrast this with Dice and Wheel, which are tangible, and are obviously dierent things, however they have common behavior and a common relationship with a casino game. Design Aid. Sometimes it helps to visualize this by getting pads of dierent-colored sticky paper, and making a mockup of the object structure on whiteboard. Each class is represented by a dierent color of paper. Each individual object is an individual slip of sticky paper. To show the relationship of Dice, Throw and Outcome, we draw a large space on the board for an instance of Dice which has smaller spaces for 36 individual Throws. In one Throw instance, we put a sticky for Outcome s 2, Field, Horn, and Any Craps. We use three colors of stickies to show that 2 and Any Craps are ordinary Outcome s, Field is one subclass and Horn is another subclass. In another Throw instance, we put a sticky for Outcome 7, using the color of sticky for ordinary Outcomes. This can help to show what the nal game object will be examining to evaluate winning bets. The game object will have a list of winning Outcomes and bet Outcome s actually on the table. When a 2 is thrown, the game process will pick up each of the stickies, compare the winning Outcome s to the bets, and then use the method appropriate to the color of the sticky when computing the results of the bet.
easily add new applications or remove obsolete applications. In this case, each casino game is a separate application of our casino game simulator. At some point, we may want to isolate one of the games to reuse just the classes of that game in another application. By making the games proper siblings of each other, and children of an abstract parent, they can be more easily separated. General vs. Specic. Applying these layered design and application partitioning design patterns causes us to examine our casino game model more closely and further sub-divide the model into game-specic and game-independent elements. If necessary, we can further subdivide the general elements into those that are part of the problem domain (casino games) and those that are even more general application infrastructure (e.g., simulation and statistics). Our ideal is to have a tidy, short list of classes that provides a complete game simulation. We can cut our current design into three parts: Roulette, Craps and application infrastructure. This allows us to compose Roulette from the Roulette-specic classes and the general infrastructure classes, without including any of the Craps-specic classes. The following architecture diagram captures a way to structure the packages of these applications.
Our class denitions have implicitly followed this architecture, working from general to gameand player-specic classes. Note that our low-level classes evolved through several increments. We nd this to be superior to attempting to design the general classes from the outset: it avoids any over-engineering of the supporting infrastructure. Additionally, we we careful to assure that our top-level classes contain minimal processing, and are are compositions of lower-level object instances. Dependencies. A very good design could carefully formalize this aspect of the architecture by assuring that there are minimal references between layers and partitions, and all references are downward references from application-specic to general infrastructure packages. In our case, the Simulator should have access only to Player and Game layers. Two Game partitions should be separate with no references between these packages. Finally, we would like to assure that the Player and Game dont have invalid upward references to the Simulator. There are some tools that can enforce this architectural separation aspect of the design. Lacking tools, we need to exercise some discipline to honor the layers and partitions.
160
161
Architect. The structure is illogical: Craps isnt a special case of Roulette, theyre independent specializations of something more general. Manager. Illogical isnt a good enough justication. Our overall problem domain always contains illogical special cases and user-oriented considerations. Youll have to provide something more concrete. Architect. Okay, in addition to being illogical, it will become too complex: in the future, well probably have trouble implementing other games. Manager. How much trouble? Will it be more than X hours of eort? Architect. When we include maintenance, adaptation and debugging, the potential future cost is probably larger than 2X hours of eort. And its illogical. Manager. Probably larger? We cant justify rework based on probable costs. Youll need something tangible. Will it save us any lines of code? Architect. No, it will add lines of code. But it will reduce maintenance and adaptation costs because it will be more logical. Manager. I conclude that the change is unjustied. In many cases, we have a manager whos mind is made up and who wont be swayed by facts. This is generally a symptom of an organization that is thinking-impaired. Typically, it is impossible to engage project managers further than this. However, in some cases, conversation continues in the following vein. Architect. Unjustied? Okay, please dene what constitutes adequate justication for improvements? Manager. Real savings of eort is the only justication for disrupting the schedule. Architect. And future eort doesnt count? Manager. The probability of savings in the future isnt tangible. Architect. And more logical doesnt count? Manager. If course not; it doesnt result in real schedule savings. Architect. Real schedule savings? Thats absurd. The schedule is a notional projection of possible eort. A possible reduction in the possible eort is just as real as the schedule. Manager. Wouldnt it be simpler to...? For reasons we dont fully understand, a schedule becomes a kind of established fact. Any change to the schedule requires other established facts, not the conjecture of design. For some reason, the idea that the schedule is only a conjecture, based on a previously conjectured design doesnt seem to sway managers from clinging to the schedule. This makes it nearly impossible to justiy making a design change. The only way to accumulate enough evidence is to make the design change and then measure the impact of the change. In eect, no level of proof can ever override the precedence-setting fact of the schedule. To continue this rant, the same kind of inadequate evidence issue seems to surround all technology changes. We have had conversations like the one shown above regarding object-oriented design, the use of objects in relational databases, the use of object-oriented databases, the use of open-source software, and the use of the star-schema data model for reporting and analysis. For many people, it seems that change can only be considered based on established facts; we observe that these facts can only be established by making a change. While this chicken-and-egg problem is easily resolved by recognizing that the current architecture or schedule is actually not an established fact, this is a diicult mental step to take.
162
As a nal complaint, we note that wouldnt it be simpler is best described as a management trump card. The point is rarely an eort to reduce code complexity, but to promote management understanding of the design. While this is a noble eort, there is sometimes a communication gap between designers and managers. It is incumbent both on designers to communicate fully, and on managers to provide time, budget and positive reinforcement for detailed communication of design considerations and consequences.
163
The class RandomEvent is the superclass for the random events on which a player bets. This includes Bin of a Roulette wheel and Throw of Craps dice. Fields The most notable common feature between a Bin and a Throw is that both have a collection of Outcomes. This common set can be moved to this superclas from the subclasses. outcomes A frozenset that holds the collection of individual Outcomes.
164
Methods winAmount(self, throw=None) Returns the product this Outcomes odds numerator by the given amount, divided by the odds denominator. Parameter throw (Throw) An optional Throw that determines the actual odds to use. If not provided, this Outcomes odds are used. __str__(self ) This should return a String representation of the name and the odds. A form that looks like Field (1:1, 2 and 12 2:1) works nicely.
165
CHAPTER
TWENTYEIGHT
BET CLASS
This chapter will examine the Bet class, and its suitability for the game of Craps. Well expand the design to handle additional complications present in real casino games.
28.2.1 Methods
setOutcome(self, outcome) Parameter outcome (Outcome) The new outcome for this bet amount
167
Sets the Outcome for this bet. This has the eect of moving the bet to another Outcome. price(self ) Computes the price for this bet. For most bets, the price is the amount. Subclasses can override this to handle buy and lay bets where the price includes a 5% commission on the potential winnings. For Buy and Lay bets, a $20 bet has a price of $21.
28.3.1 Fields
vig Holds the amount of the vigorish. This is almost universally 5%.
28.3.2 Methods
price(self ) Computes the price for this bet. There are two variations: Buy bets and Lay bets. A Buy bet is a right bet; it has a numerator greater than or equal to the denominator (for example, 2:1 odds, which risks 1 to win 2), the price is 5% of the amount bet. A $20 Buy bet has a price of $21. A Lay bet is a wrong bet; it has a denominator greater than the numerator (for example, 2:3 odds, which risks 3 to win 2), the price is 5% of 2/3 of the amount. A $30 bet Layed at 2:3 odds has a price of $31, the $30 bet, plus the vig of 5% of $20 payout.
168
CHAPTER
TWENTYNINE
169
170
Allowed Bets. The rule for allowed and non-allowed bets is relatively simple. When the game state has no point (also known as the come out roll), only Pass Line and Dont Pass bets are allowed, all other bets are not allowed. When the point is on, all bets are allowed. Well have to add an isAllowed() to CrapsGame, which CrapsTable will use when the player attempts to place a bet. Working Bets. The rule for working and non-working bets is also relatively simple. On the come out roll, all odds bets placed behind any of the six Come Point numbers are not working. This rule only applies to odds behind Come Point bets; odds behind Dont Come bets are always working. Well have to add an isWorking() to CrapsGame, which CrapsTable will use when iterating through working bets. The sequence of events that can lead to this condition is as follows. First, the player places a Come Line bet, the dice roll is 4, 5, 6, 8, 9 or 10, and establishes a point; the bet is moved to one of the six come points. Second, the player creates an additional odds bet placed behind this come point bet. Third, the main game point is a winner, changing the game state so the next roll is a come out roll. In this state, any additional odds behind a come point bet will be non-working bets on the come-out roll. This kind of exceptional subtlety is one of the important reasons why object-oriented programming can be more successful than procedural programming. In this case, we can isolate this state-specic processing to the CrapsGame. We can also provide the interface to the CrapsTable that makes this responsibility explicit and easy to use.
29.4.1 Methods
isAllowed(self, outcome) Parameter outcome (Outcome) An Outcome that may be allowed or not allowed, depending on the game state. Determines if the Outcome is allowed in the current state of the game. When the point is zero, it is the come out roll, and only Pass, Dont Pass, Come and Dont Come bets are allowed. Otherwise, all bets are allowed. isWorking(self, outcome) Parameter outcome (Outcome) An Outcome that may be allowed or not allowed, depending on the game state. Determines if the Outcome is working in the current state of the game. When the point is zero, it is the come out roll, odds bets placed behind any of the six come point numbers are not working.
171
29.5.1 Fields
game The CrapsGame used to determine if a given bet is allowed or working in a particular game state.
29.5.2 Constructors
__init__(self, game) Parameter game (CrapsGame) The CrapsGame instance that controls the state of this table Uses the superclass for initialization of the empty LinkedList of bets.
29.5.3 Methods
isValid(self, bet ) Parameter bet (Bet) The bet to validate. Validates this bet by checking with the CrapsGame to see if the bet is valid; it returns true if the bet is valid, false otherwise. allValid(self ) This uses the superclass to see if the sum of all bets is less than or equal to the table limit. If the individual bet outcomes are also valid, return true. Otherwise, return false.
172
CHAPTER
THIRTY
CRAPSGAME CLASS
In Throw Class, we roughed out a stub version of CrapsGame that could be used to test Throw. We extended that stub in Craps Table Class. In this chapter, we will revise the game to provide the complete process for Craps. This involves a number of features, and we will have a state hierarchy as well as the Game class itself. In the process of completing the design for CrapsGame, we will uncover another subtlety of craps: winning bets and losing bets. Unlike Roulette, where a Bin contained winning Outcomes and all other Outcomes where losers, Craps includes winning Outcomes, losing Outcomes, and unresolved Outcomes. This will lead to some rework of previously created Craps classes. We can see three necessary features to the CrapsGame: Game State, Resolving Bets, Moveable Bets when a point is established. Also, we will discover some additional design features to add to other classes.
173
174
3. Resolve Proposition Bets. Resolve any one-roll proposition bets. This is the procedure described above for iterating through all one-roll propositions. See Resolve Proposition Bets. 4. Natural? If the throw was 7, the game is a loser. Resolve all bets; the game state will show that all bets are active. The game state will include Dont Pass and Dont Come bets as winners, as will any of the six point bets created from Dont Pass and Dont Come Line bets. All other bets will lose, including all hardways bets. This throw resolves the game, changing the game state. The point is o. 5. Point Made? If the throw was the main game point, the game is a winner. Resolve Pass Line and Dont Pass Line bets, as well as the point and any odds behind the point. Come Point and Dont Come Point bets (and their odds) remain for the next game. A Come Line or Dont Come Line bet will be moved to the appropriate Come Point. This throw ends the game; changing the game state. The point is o; odds placed behind Come Line bets are not working for the come out roll. 6. Other Point Number? If the throw was any of the come point numbers, come bets on that point are winners. Resolve the point come bet and any odds behind the point. Also, any buy or lay bets will be resolved as if they were odds bets behind the point; recall that the buy and lay bets involved a commission, which was paid when the bet was created. 7. Hardways? For 4, 6, 8 and 10, resolve hardways bets. If the throw was made the hard way (both dice equal), a hardways bet on the thrown number is a winner. If the throw was made the easy way, a hardways bet on the thrown number is a loser. If the throw was a 7, all hardways bets are losers. Otherwise, the hardways bets remain unresolved.
the game state. The interaction between CrapsGame, the four kinds of Throws and the two subclasses of CrapsGameStates works as follows: 1. There are 36 instances of Throw, one of which was selected at random to be the current throw of the dice. The Game object calls the Throw objects updateGame() method. Each of the subclasses of Throw have dierent implementations for this method. 2. The Throw object calls one of the Games methods to change the state. There are four methods available: craps(), natural(), eleven(), and point(). Dieren subclasses of Throw will call an appropriate method for the kind of throw. 3. The Game has a current state, embodied in a CrapsGameState object. The Game will delegate each of the four state change methods (craps(), natural(), eleven(), and point()) to the current CrapsGameState object. There are two subclasses, depending on the state of the point: point-on and point-o. 4. In parallel with Game, each CrapsGameState object has four state change methods (craps(), natural(), eleven(), and point()). Each state provides dierent implementations for these methods. In eect, the two states and four methods create a kind of table that enumerates all possible state change rules. Complex? At rst glance the indirection and delegation seems like a lot of overhead for a simple state change. When we consider the kinds of decision-making this embodies, however, we can see that this is an eective solution. When one of the 36 available Throws has been chosen, the CrapsGame calls a single method to update the game state. Because the various subclasses of Throw are polymorphic, they all respond with unique, correct behavior. Similarly, each of the subclasses of Throw simply uses one of four methods to update the CrapsGame, without having to discern the current state of the CrapsGame. We can consider CrapsGame as a kind of faade over the methods of the polymorphic CrapsGameState. Our objective is to do the decision-making once when the object is created; this makes all subsequent processing free of complex decision-making (i.e., simple) but indirect. Whats important about this design is that there are no if-statements required to make it work. Instead, objects simply invoke methods.
This unresolved outcome is fundamentally dierent from Roulette bet resolution and proposition bet resolution. Consider a Pass Line bet: in the point-o state, a roll of 7 or 11 makes this bet a winner, a roll of 2, 3 or 12 makes this bet a loser, all other numbers leave this bet unresolved. After a point is established, this Pass Line bet has the following resolutions: a roll of 7 makes this bet a loser, rolling the point makes this bet is a wiunner, all other numbers leave this bet unresolved. In addition to this three-way decision, we have the additional subtlety of Dont Pass Line bets that can lead to a fourth resolution: a push when the throw is 12 on a come out roll. We dont want to ignore this detail because it changes the odds by almost 3%. Hardways Bets. We have several choices for implementation of this multi-way decision. This is an overview, well dive into details below. We can keep separate collections of winning and losing Outcomes in each Throw. This will obligate the game to check a set winners and a set of losers for bet resolution. We can add a method to the Bet class that will return a code for the eect of a win, lose or wait for each Outcome. A win would add money to the Player; a lose would subtract money from the Player. This means that the game will have to decode this win-lose response as part of bet resolution. We can make each kind of resolution into a Command class. Each subclass of BetResolution would peform the pay a winner, collect a loser or leave unresolved procedure based on the Throw or class:CrapsGameState.
177
Resolving Bets on Hardways Outcomes. In addition to methods to resolve one roll and game bets, we have to resolve the hardways bets. Hardways bets are similar to game bets. For Throws of 4, 6, 8 or 10 there will be one of three outcomes: when the number is made the hard way, the matching hardways bet is a winner; when the number is made the easy way, the matching hardways bet is a loser; otherwise the hardways bet is unresolved; on a roll of seven, all hardways bets are losers. Since this parallels the game rules, but applies to a Throw, it leads us to consider the design of Throw to be parallel to the design of CrapsGame. We can use either a collection of losing Outcomes in addition to the collection of winning Outcomes, or create a multi-way discrimination method, or have the Throw call appropriate methods of CrapsTable to resolve the bet. Solution. A reasonably exible design for Bet resolution that works for all three kinds of bet resolutions is to have Throw and CrapsGameState call specic bet resolution methods in CrapsPlayer. This unies one-roll, game and hardways bets into a single mechanism. It requires us to provide methods for win, lose and push in the CrapsPlayer. We can slightly simplify this to treat a push as a kind of win that returns the bet amount. Consequences. The CrapsGame will iterate through the the active Bets. Each Bet and the Player will be provided to the current Throw for resolving one-roll and hardways bets. Each Bet and the Player will also be provided to the :class:`CrapsGameState to resolve the winning and losing game bets. We can further simplify this if each Bet carries a reference to the owning Player. In this way, the Bet has all the information necessary to notify the Player. In the long run, this reects the reality of craps table where the table operators assure that each bet has an owning player.
Hard way? When d1 = d2 , this is a hard 4, 6, 8 or 10. The appropriate hard number Outcome is a winner. Easy way? Otherwise, d1 = d2 , this is an easy 4, 6, 8 or 10. The appropriate hard number Outcome is a loser. Field? For s in 4, 9 and 10 we include the eld Outcome as a winner. Otherwise the eld Outcome is a loser. Note that 2, 3, and 12 Field outcomes where handled above under Craps. Losing Propositions. Other one-roll Outcomes, including 2, 3, 7, 11, 12, Horn and Any Craps are all losers for this Throw. Natural? If s is 7, we create a NaturalThrow instance. This will also include a 7 Outcome as a winner. It will have numbers 2, 3, 11, 12, Horn, Field and Any Craps Outcomes as losers for this Throw. Also, all four hardways are losers for this throw. Eleven? If s is 11, we create an ElevenThrow instance. This will include 11, Horn and Field Outcomes as winners. It will have numbers 2, 3, 7, 12 and Any Craps Outcomes as losers for this Throw. There is no hardways resolution. Craps Player Class Hierarchy. We have not designed the actual CrapsPlayer. This is really a complete tree of classes, each of which provides a dierent betting strategy. We will defer this design work until later. For the purposes of making the CrapsGame work, we can develop our unit tests with a kind of stub for CrapsPlayer which simply places a single Pass Line Bet. In several future exercises, well revisit this design to make more sophisticated players. See Some Betting Strategies for a further discussion on an additional player decision oered by some variant games. Our design can be expanded to cover this. Well leave this as an exercise for the more advanced student. This involves a level of collaboration between CrapsPlayer and CrapsGame that is over the top for this part. Well address this kind of very rich interaction in Blackjack.
180
30.8.1 Fields
win1Roll A set of of one-roll Outcomes that win with this throw. lose1Roll A set of one-roll Outcomes that lose with this throw. winHardway A set of hardways Outcomes that win with this throw. Not all throws resolve hardways bets, so this and the loseHardway Set may both be empty. loseHardway A set of hardways Outcomes that lose with this throw. Not all throws resolve hardways bets, so this and the winHardway Set may both be empty. d1 One of the two die values, from 1 to 6. d2 The other of the two die values, from 1 to 6.
30.8.2 Constructors
__init__(d1, d2, winners=None, losers=None) Parameters d1 (int) One die value. d2 (int) The other die value. winners (set of Outcomes) All the outcomes which will be paid as winners for this Throw. losers All the outcomes which will be collectsd as winners for this Throw. Creates this throw, and associates the two given Set s of Outcomes that are winning one-roll propositions and losing one roll propositions.
30.8.3 Methods
add1Roll(self, winners, losers) Parameters winners (set of Outcomes) All the outcomes which will be paid as winners for this Throw. losers All the outcomes which will be collectsd as winners for this Throw. Adds outcomes to the one-roll winners and one-roll losers Sets. addHardways(self, winners, losers) Parameters winners (set of Outcomes) All the outcomes which will be paid as winners for this Throw. losers All the outcomes which will be collectsd as winners for this Throw.
181
Adds outcomes to the hardways winners and hardways losers Sets. hard(self ) Returns true if d1 is equal to d2. This helps determine if hardways bets have been won or lost. updateGame(self, game) Parameter game (CrapsGame) CrapsGame instance to be updated with the results of this throw Calls one of the Game state change methods: craps(), natural(), eleven(), point(). This may change the game state and resolve bets. resolveOneRoll(self, bet ) Parameter bet The bet to to be resolved If this Bets Outcome is in the Set of one-roll winners, pay the Player that created the Bet. Return true so that this Bet is removed. If this Bets Outcome is in the Set of one-roll losers, return true so that this Bet is removed. Otherwise, return false to leave this Bet on the table. resolveHardways(self, bet ) Parameter bet The bet to to be resolved If this Bets Outcome is in the Set of hardways winners, pay the Player that created the Bet. Return true so that this Bet is removed. If this Bets Outcome is in the Set of hardways losers, return true so that this Bet is removed. Otherwise, return false to leave this Bet on the table. __str__(self ) This should return a String representation of the dice. A form that looks like 1,2 works nicely.
30.9.1 Methods
buildThrows(self, dice) Para dice The Dice to build Creates the 8 one-roll Outcome instances (2, 3, 7, 11, 12, Field, Horn, Any Craps), as well as the 8 hardways Outcome instances (easy 4, hard 4, easy 6, hard 6, easy 8, hard 8, easy 10, hard 10). It then creates each of the 36 Throws, each of which has the appropriate combination of Outcomes for one-roll and hardways. The various Throws are assigned to dice.
182
30.10.1 Constructors
__init__(self, amount, outcome, player=None) This replaces the existing constructor and adds an optional parameter. Parameters amount (int) The amount being wagered. outcome (Outcome) The specic outcome on which the wager is placed. player (CrapsPlayer) The player who will pay a losing bet or be paid by a winning bet. Initialize the instance variables of this bet. This works by saving the additional player information, then using the existing Bet.Bet() constructor.
30.11.1 Fields
passLine This is the Outcome on which this player focuses their betting. It will be an instance of the "Pass Line" Outcome, with 1:1 odds. workingBet This is the current Pass Line Bet. Initially this is None. Each time the bet is resolved, this is reset to None. This assures that only one bet is working at a time. table That Table which collects all bets.
30.11.2 Constructors
__init__(self, table) Parameter table (Table) The Table. Constructs the CrapsPlayer with a specic table for placing bets. The player creates a single "Pass Line" Outcome, which is saved in the passLine variable for use in creating Bets.
183
30.11.3 Methods
If workingBet is null, create a new Pass Line Bet, and use Table placeBet() to place that bet. If workingBet is not null, the bet is still working. Do not place any more bets. Notication from the Game that the Bet was a winner. The amount of money won is available via theBet winAmount(). Notication from the Game that the Bet was a loser.
30.12.1 Fields
game The overall CrapsGame for which this is a specic state. From this object, the various next state-change methods can get the CrapsTable and an Iterator over the active Bets.
30.12.2 Constructors
__init__(self, game) Parameter game (Game) The game to which this state applies Saves the overall CrapsGame object to which this state applies.
30.12.3 Methods
isValid(self, outcome) Parameter outcome (Outcome) The outcome to be tested for validity Returns true if this is a valid outcome for creating bets in the current game state. Each subclass provides a unique denition of valid bets for their game state. isWorking(self, outcome) Parameter outcome (Outcome) The outcome to be tested for if its working Returns true if this is a working outcome for existing bets in the current game state. Each subclass provides a unique denition of active bets for their game state. craps(self, throw ) Parameter throw (Throw) The throw that is associated with craps. Return an appropriate state when a 2, 3 or 12 is rolled. It then resolves any game bets. Each subclass provides a unique denition of what new state and what bet resolution happens. natural(self, throw )
184
Parameter throw (Throw) The throw that is associated with a natural seven. Returns an appropriate state when a 7 is rolled. It then resolves any game bets. Each subclass provides a unique denition of what new state and what bet resolution happens. eleven(self, throw ) Parameter throw (Throw) The throw that is associated an eleven. Returns an appropriate state when an 11 is rolled. It then resolves any game bets. Each subclass provides a unique denition of what new state and what bet resolution happens. point(self, throw ) Parameter throw (Throw) The throw that is associated with a point number. Returns an appropriate state when the given point number is rolled. It then resolves any game bets. Each subclass provides a unique denition of what new state and what bet resolution happens. pointOutcome(self ) Returns the Outcome based on the current point. This is used to create Pass Line Odds or Dont Pass Odds bets. This delegates the real work to the current CrapsGameState object. moveToThrow(self, bet, throw ) Parameters bet (Bet) The Bet to update based on the current Throw throw (Throw) The Throw to which the outcome is changed Moves a Come Line or Dont Come Line bet to a new Outcome based on the current throw. If the value of theThrow is 4, 5, 6, 8, 9 or 10, this delegates the move to the current CrapsGameState object. For values of 4 and 10, the odds are 2:1. For values of 5 and 9, the odds are 3:2. For values of 6 and 8, the odds are 6:5. For other values of theThrow, this method does nothing. __str__(self ) In the superclass, this doesnt do anything. Each subclass, however, should display something useful.
30.13.1 Constructors
__init__(self, game) Parameter game (CrapsGame) The game to which this state applies. Uses the superclass constructor to save the overall CrapsGame object.
185
30.13.2 Methods
isValid(self, outcome) Parameter outcome (Outcome) The outcome to be tested for validity There are two valid Outcomes: Pass Line, Dont Pass Line. All other Outcomes are invalid. isWorking(self, outcome) Parameter outcome (Outcome) The outcome to be tested to see if its working There are six non-working Outcomes: Come Odds 4, Come Odds 5, Come Odds 6, Come Odds 8, Come Odds 9 and Come Odds 10. All other Outcomes are working. craps(self, throw ) Parameter throw (Throw) The throw that is associated with craps. When the point is o, a roll of 2, 3 or 12 means the game is an immediate loser. The Pass Line Outcome is a loset. If the Throw value is 12, a Dont Pass Line Outcome is a push, otherwise the Dont Pass Line Outcome is a winner. The next state is the same as this state, and the method should return this. natural(self, throw ) Parameter throw (Throw) The throw that is associated with a natural seven. When the point is o, 7 means the game is an immediate winner. The Pass Line Outcome is a winner, the Dont Pass Line Outcome is a loser. The next state is the same as this state, and the method should return this. eleven(self, throw ) Parameter throw (Throw) The throw that is associated an eleven. When the point is o, 11 means the game is an immediate winner. The Pass Line Outcome is a winner, the Dont Pass Line Outcome is a loser. The next state is the same as this state, and the method should return this. point(self, throw ) Parameter throw (Throw) The throw that is associated with a point number. When the point is o, a new point is established. This method should return a new instance of CrapsGamePointOn created with the given Throws value. Note that any Come Point bets or Dont Come Point bets that may be on this point are pushed to player: they cant be legal bets in the next game state. pointOutcome(self ) Returns the Outcome based on the current point. This is used to create Pass Line Odds or Dont Pass Odds bets. This delegates the real work to the current CrapsGameState object. Since no point has been established, this returns null. __str__(self ) The point-o state should simply report that the point is o, or that this is the come out roll.
30.14.1 Fields
point The point value.
30.14.2 Constructors
__init__(self, point, game) Saves the given point value. Uses the superclass constructor to save the overall CrapsGame object.
30.14.3 Methods
isValid(self, outcome) Parameter outcome (Outcome) The outcome to be tested for validity It is invalid to Buy or Lay the Outcomes that match the point. If the point is 6, for example, it is invalid to buy the Come Point 6 Outcome. All other Outcomes are valid. isWorking(self, outcome) Parameter outcome (Outcome) The outcome to be tested to see if its working All Outcomes are working. craps(self, throw ) Parameter throw (Throw) The throw that is associated with craps. When the point is on, 2, 3 and 12 do not change the game state. The Come Line Outcome is a loser, the Dont Come Line Outcome is a winner. The next state is the same as this state, and the method should return this. natural(self, outcome) Parameter throw (Throw) The throw that is associated with a natural seven. When the point is on, 7 means the game is a loss. Pass Line Outcomes lose, as do the pass-line odds Outcome s based on the point. Dont Pass Line Outcomes win, as do all Dont Pass odds Outcome based on the point. The Come Line Outcome is a winner, the Dont Come Line Outcome is a loser. However, all Come Point number Outcomes and Come Point Number odds Outcome are all losers. All Dont Come Point number Outcomes and Dont Come Point odds Outcomes are all winners. The next state is a new instance of the CrapsGamePointOff state. Also note that the Throw of 7 also resolved all hardways bets. A consequence of this is that all Bets on the CrapsTable are resolved. eleven(self, throw ) Parameter throw (Throw) The throw that is associated an eleven. When the point is on, 11 does not change the game state. The Come Line Outcome is a winner, and the Dont Come Line Outcome is a loser. The next state is the same as this state, and the method should return this. point(self, throw ) Parameter throw (Throw) The throw that is associated with a point number.
187
When the point is on and the value of throw doesnt match point, then the various Come Line bets can be resolved. Come Point Outcome s for this number (and their odds) are winners. Dont Come Line Outcome s for this number (and their odds) are losers. Other Come Point number and Dont Come Point numbers remain, unresolved. Any Come Line bets are moved to the Come Point number Outcomes. For example, a throw of 6 moves the Outcome of the Come Line Bet to Come Point 6. Dont Come Line bets are moved to be Dont Come number Outcomes. The method should return this. When the point is on and the value of throw matches point, the game is a winner. Pass Line Outcomes are all winners, as are the behind the line odds Outcomes. Dont Pass line Outcomes are all losers, as are the Dont Pass Odds Outcomes. Come Line bets are moved to thee Come Point number Outcomes. Dont Come Line bets are moved to be Dont Come number Outcomes. The next state is a new instance of the CrapsGamePointOff state. pointOutcome(self ) Returns the Outcome based on the current point. This is used to create Pass Line Odds or Dont Pass Odds bets. This delegates the real work to the current CrapsGameState object. For points of 4 and 10, the Outcome odds are 2:1. For points of 5 and 9, the odds are 3:2. For points of 6 and 8, the odds are 6:5. __str__(self ) The point-o state should simply report that the point is o, or that this is the come out roll.
30.15.1 Fields
dice Contains the dice that returns a randomly selected Throw with winning and losing Outcomes. This is an instance of Dice. table The CrapsTable contains the bets placed by the player. player The CrapsPlayer who places bets on the CrapsTable.
30.15.2 Constructors
We based this constructor on an design that allows any of these objects 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 specically 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. __init__(self, dice, table) 188 Chapter 30. CrapsGame Class
Parameters dice (Dice) The dice to use table The table to use for collecting bets CrapsTable Constructs a new CrapsGame, using a given Dice and CrapsTable. The player is not dened at this time, since we may want to run several simulations with dierent players.
30.15.3 Methods
__init__(self, player ) Parameter player (CrapsPlayer) The player who will place bets on this game This will execute a single cycle of play with a given Player. 1. It will call thePlayer placeBets() to get bets. It will validate the bets, both individually, based on the game state, and collectively to see that the table limits are met. 2. It will call theDice next() to get the next winning Throw. 3. It will use the Throws updateGame() to advance the game state. 4. It will then call theTable bets() to get an Iterator ; stepping through this Iterator returns the individual Bet objects. It will use the Throws resolveOneRoll() method to check one-roll propositions. If the method returns true, the Bet is resolved and should be deleted. It will use the Throws resolveHardways() method to check the hardways bets. If the method returns true, the Bet is resolved and should be deleted. pointOutcome(self ) Returns the Outcome based on the current point. This is used to create Pass Line Odds or Dont Pass Odds bets. This delegates the real work to the current CrapsGameState object. moveToThrow(self, bet, throw ) Parameters bet (Bet) The Bet to move based on the current throw throw (Throw) The Throw to which to move the Bets Outcome Moves a Come Line or Dont Come Line bet to a new Outcome based on the current throw. This delegates the move to the current CrapsGameState object. This method should just as a precaution assert that the value of theThrow is 4, 5, 6, 8, 9 or 10. These point values indicate that a Line bet can be moved. For other values of theThrow, this method should raise an exception, since theres no reason for attempting to move a line bet on anything but a point throw. reset(self ) This will reset the game by setting the state to a new instance of GamePointOff. It will also tell the table to clear all bets.
189
190
CHAPTER
THIRTYONE
CRAPSPLAYER CLASS
The variations on Player, all of which reect dierent betting strategies, is the heart of this application. In Roulette Game Class, we roughed out a stub class for Player, and rened it in Player Class. We will further rene this denition of Player for use in Craps.
Beyond simply placing Pass Line and Pass Line Odds bets, we can use a Martingale or a Cancellation betting system to increase our bet on each loss, and decrease our betting amount on a win. Since we have two dierent bets in play a single bet created on the come out roll, a second odds bet if possible the simple Martingale system doesnt work well. In some casinos, the behind the line odds bet can be double the pass line bet, or even 10 times the pass line bet, giving us some complex betting strategies. For example, we could apply the Martingale system only to the odds bet, leaving the pass line bet at the table minimum. Well set this complexity aside for the moment, build a simple player rst.
31.2.1 Fields
stake The players current stake. Initialized to the players starting budget. 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 The CrapsTable used to place individual Bets.
31.2.2 Constructors
__init__(self, table) Parameter table (CrapsTable) The table Constructs the Player with a specic CrapsTable for placing Bets.
31.2.3 Methods
playing(self ) Returns true while the player is still active. A player with a stake of zero will be inactive. Because of the indenite duration of a craps game, a player will only become inactive after their roundsToGo is zero and they have no more active bets. This method, then, must check the CrapsTable to see when all the bets are fully resolved. Additionally, the players betting rules should stop placing new bets when the roundsToGo is zero. placeBets(self )
192
Updates the CrapsTable with the various Bet s. When designing the CrapsTable, we decided that we needed to deduct the price of the bet from the stake when the bet is created. See the Roulette Table Roulette Table Overview for more information on the timing of this deduction, and the Craps Bet Bet Overview for more information on the price of a bet. win(self, bet ) Parameter bet (Bet) that was a winner Notication from the CrapsGame that the Bet was a winner. The amount of money won is available via theBet winAmount(). lose(self, bet ) Parameter bet (Bet) that was a loser Notication from the CrapsGame that the Bet was a loser.
31.3.1 Methods
placeBets(self ) If no Pass Line bet is present, this will update the Table with a bet on the Pass Line at the base bet amount. Otherwise, this method does not place an additional bet.
31.4.1 Fields
lossCount The number of losses. This is the number of times to double the pass line odds bet. 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 set so that betM ultiple = 2lossCount .
31.4.2 Methods
placeBets(self )
193
If no Pass Line bet is present, this will update the Table with a bet on the Pass Line at the base bet amount. If no Pass Line Odds bet is present, this will update the Table with an Pass Line Odds bet. The amount is the base amount times the betMultiple. Otherwise, this method does not place an additional bet. win(self, bet ) Parameter bet (Bet) that was a winner 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. lose(self, bet ) Parameter bet (Bet) that was a loser Increments lossCount by 1 and doubles betMultiple.
194
CHAPTER
THIRTYTWO
195
Soapbox On Refactoring We feel very strongly that our :design by refactoring helps beginning designers produce a more functional design more quickly than the alternative approach, which is to attempt to dene the game abstraction and player abstraction rst and then specialize the various games. When dening an abstract superclass, some designers will build a quick and dirty design for some of the subclasses, and use this to establish the features that belong in the superclass. We nd that a more successful superclass design comes from have more than one working subclasses and a clear understanding of the kinds of extensions that are likely to emerge from the problem domain. While our approach of refactoring working code seems expensive, the total eort is often smaller. The largest impediment weve seen seems to stem from the project-management mythology that once something passes unit tests it is done for ever and can be checked o as completed. We feel that it is very important to recognized that passing a unit test is only one of many milestones. Passing an integration test, and passing the sanity test are better indicators of done-ness. The sanity test is the designers ability to explain the class structure to someone new to the project. We feel that class and package names must make obvious sense in the current project context. We nd that any explanation of a class name that involves the words historically, or originally means that there are more serious design deciencies that need to be repaired. We now know enough to factor out the common features of Game and CrapsGame to create three new classes from these two. However, to nd the common features of these two classes, well see that we have to unify Dice and Wheel, as well as Table and CrapsTable and Player and CrapsPlayer. Looking into Dice and Wheel, we see that well have to tackle Bin and Throw rst. Unifying Bin and Throw is covered in Design Heavy. If the necessary design for RandomEvent is already in place, skip to Unifying Dice and Wheel . After that, we can refactor the other classes.
the two dice and the Set of losing Outcome s. Note that we have made Throw and Bin siblings of a common superclass. See Soapbox on Architecture for more information on our preference for this kind of design.
197
RoulettePlayer. * RouletteMartingale. * RouletteRandom. * RouletteSevenReds. * Roulette1326. * RouletteCancellation. * RouletteFibonacci. CrapsPlayer. * CrapsMartingale. Looking forward to Blackjack, see see that there is much richer player interaction, because there are player decisions that are not related to betting. This class hierarchy seems to enable that kind of expansion. We note that there are two dimensions to this class hierarchy. One dimension is the game (Craps or Roulette), the other dimension is a betting system (Matingale, 1326, Cancellation, Fibonacci). For Blackjack, there is also a playing system in addition to a betting system. Sometimes this multi-dimensionsal aspect of a class hierarchy indicates that we could be using multiple inheritance. In the case of Python, we have multiple inheritance in the language, and we can pursue this directly. Java, however, demands the Strategy design pattern to add a exible betting strategy object to the basic interface for playing the game. In Roulette, where we are placing a single bet, there is almost no distinction between game interface and the betting system. However, in Craps, we made a distinction in our Martingale player by separating their Pass Line bet (where the payout doesnt match the actual odds very well) from their Pass Line Odds bet (where the payout does match the odds). This means that our Martingale Craps player really has two betting strategies objects: a at bet strategy for Pass Line and a Martingale Bet strategy for the Pass Line Odds. If we separate the player and the betting system, we could easily mix and match betting systems, playing systems and game rules. In the case of Craps, where we can have many working bets (Pass Line, Come Point Bets, Hardways Bets, plus Propostions), each player would have a mixture of betting strategies used for their unique mixture of working bets. This leads to an interesting issue in the composition of such a complex object. For the current exercise, however, we wont formally separate the player from the various betting strategies. Rather than fully separate the players game interface and betting system interface, we can simply adjust the class hierarchy and the class names to those shown above. We need to make the superclass, Player independent of any game. We can do this by extracting anything Roulette-specic from the original Player class and renaming our Roulette-focused Passenger57 to be RoulettePlayer, and x all the Roulette player subclasses to inherit from RoulettePlayer. We will encounter one design diiculty when doing this. That is the dependency from the various Player1326State classes on a eld of Player1326. Currently, we will simply be renaming Player1326 to Roulette1326. However, as we go forward, we will see how this small issue will become a larger problem. In Python, we can easily overlook this, as described in Python and Interface Design.
198
Python and Interface Design Because of the run-time binding done in Python, there is no potential problem in having the Player1326State classes depend on a eld of Player1326 . Since Java checks the validity of these references at compile time, we are forced to provide proper declarations for the outcome eld in Player1326 . In the event of change, this declaration may no longer be valid, alterting us to a dependency. In Python, however, the attributes of an object are kept in a dictionary, which is created dynamically during execution, making it diicult to assure that one class properly includes the attributes that will be required by a collaborating class. We dont discover problems in Python until the application crashes at run time because of a missing attribute. Some Python programmers will bypass the simple access methods and check an objects internal __dict__ for the presence of the expected outcome attribute. We dont condone this: the application should crash because of design changes, not work around them. While the dynamic nature of Python attributes has some benecial eects, we nd it can be easily abused. We consider references to the __dict__ object to raise serious quality concerns, and discourage its use by new OO designers.
32.2.1 Fields
rng The random number generator, a subclass of random.Random. Generates the next random number, used to select a RandomEvent from the bins collection. current The most recently returned RandomEvent.
199
32.2.2 Constructors
__init__(self, rng) Saves the given Random Number Generator. Calls the initialize() method to create the vector of results.
32.2.3 Methods
initialize(self ) Create a collection of RandomEvent objects with the pool of possible results. Each subclass must provide a unique implementation for this. next(self ) Return the next RandomEvent. Each subclass must provide a unique implementation for this.
32.3.1 Constructors
__init__(self, rng) Creates a new wheel. Create a sequence of the Wheel.events with with 38 empty Bins. Use the superclass to save the given random number generator instance and invoke initialize().
32.3.2 Methods
addOutcome(self, bin, outcome) Adds the given Outcome to the Bin with the given number. initialize(self ) Creates the events vector with the pool of possible events. This will create an instance of BinBuilder, bb, and delegate the construction to the buildBins() method of the bb object.
200
32.3.4 Constructors
__init__(self, rng) Create an empty set of Dice.events. Use the superclass to save the given random number generator instance and invoke initialize().
32.3.5 Methods
addOutcome(self, faces, outcome) Adds the given Outcome to the Throw with the given NumberPair. This allows us to create a collection of several one-roll Outcomes. For example, a throw of 3 includes four one-roll Outcomes: Field, 3, any Craps, and Horn. initialize(self ) Creates the 8 one-roll Outcome instances (2, 3, 7, 11, 12, Field, Horn, Any Craps). It then creates the 36 Throw s, each of which has the appropriate combination of Outcomes.
32.4.1 Fields
minimum This is the table lower limit. The sum of a Players bets must be greater than or equal to this limit. maximum This is the table upper limit. The sum of a Players bets must be less than or equal to this limit. bets This is a LinkedList of the Bets currently active. These will result in either wins or losses to the Player. game The Game used to determine if a given bet is allowed in a particular game state.
32.4.2 Constructors
__init__(self ) Creates an empty LinkedList of bets.
32.4.3 Methods
setGame(self, game) Saves the given Game to be used to validate bets. isValid(self, bet ) 32.4. Table Class 201
Validates this bet. The rst test checks the Game to see if the bet is valid. allValid(self, bet ) Validates the sum of all bets within the table limits. Returns false if the minimum is not met or the maximum is exceeded. placeBet(self, bet ) 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. __iter__(self ) Returns an Iterator 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, similarly, we can return an iterator over the available list of Bet instances. The more traditional Python approach is to return the list itself, rather than an iterator over the list. With the introduction of the generators in Python 2.3, however, it is slightly more exible to return an iterator rather than the collection itself. The iterator could be built, for example, using the yield statement instead of the iter() function. __str__(self ) Reports on all of the currently placed bets.
32.4.5 Fields
stake The players current stake. Initialized to the players starting budget. 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 The Table used to place individual Bets.
202
32.4.6 Constructors
__init__(self, table) Constructs the Player with a specic Table for placing Bets.
32.4.7 Methods
playing(self ) Returns true while the player is still active. There are two reasons why a player may be active. Generally, the player has a stake greater than the table minimum and has a roundsToGo greater than zero. Alternatively, the player has bets on the table; this will happen in craps when the game continues past the number of rounds budgeted. placeBets(self ) 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 Roulette Table Overview for more information. win(self, bet ) Notication from the Game that the Bet was a winner. The amount of money won is available via bet.winAmount(). lose(self, bet ) Notication from the Game that the Bet was a loser.
32.5.1 Fields
eventFactory Contains a Wheel or Dice or other subclass of RandomEventFactory that returns a randomly selected RandomEvent with specic Outcome s that win or lose. table Contains a CrapsTable or RouletteTable which holds all the Bets placed by the Player. player Holds the Player who places bets on the Table.
32.5.2 Constructors
We based this constructor on an design that allows any of these objects to be replaced. This is the Strategy (or Dependency Injection) design pattern. Each of these objects is a replaceable strategy, and can be changed by the client that uses this game.
203
Additionally, we specically 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. __init__(self, eventFactory, table) Constructs a new Game, using a given RandomEventFactory and Table.
32.5.3 Methods
cycle(self, player ) This will execute a single cycle of play with a given Player. For Roulette is is a single spin of the wheel. For Craps, it is a single throw of the dice, which is only one part of a complete game. This method will call player.placeBets() to get bets. It will call eventFactory.next() to get the next Set of Outcomes. It will then call table.bets() to get an Iterator over the Bets. Stepping through this Iterator returns the individual Bet objects. The bets are resolved, calling the thePlayer win(), otherwise call the thePlayer lose(). reset(self ) As a useful default for all games, this will tell the table to clear all bets. A subclass can override this to reset the game state, also.
32.6.1 Methods
cycle(self, player ) This will execute a single cycle of the Roulette with a given Player. It will call player.placeBets() to get bets. It will call wheel.next() to get the next winning Bin . It will then call table.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().
32.7.1 Methods
cycle(self, player ) This will execute a single cycle of play with a given Player. 1. It will call player.placeBets() to get bets. It will validate the bets, both individually, based on the game state, and collectively to see that the table limits are met. 204 Chapter 32. Design Cleanup and Refactoring
2. It will call dice.next() to get the next winning Throw. 3. It will use the throw.updateGame() to advance the game state. 4. It will then call table.bets() to get an Iterator; stepping through this Iterator returns the individual Bet objects. It will use the Throws resolveOneRoll() method to check one-roll propositions. If the method returns true, the Bet is resolved and should be deleted. It will use the Throws resolveHardways() method to check the hardways bets. If the method returns true, the Bet is resolved and should be deleted. pointOutcome(self ) Returns the Outcome based on the current point. This is used to create Pass Line Odds or Dont Pass Odds bets. This delegates the real work to the current CrapsGameState object. moveToThrow(self, bet, throw ) Moves a Come Line or Dont Come Line bet to a new Outcome based on the current throw. This delegates the move to the current CrapsGameState object. This method should just as a precaution assert that the value of theThrow is 4, 5, 6, 8, 9 or 10. These point values indicate that a Line bet can be moved. For other values of theThrow, this method should raise an exception, since theres no reason for attempting to move a line bet on anything but a point throw. reset(self ) This will reset the game by setting the state to a new instance of GamePointOff. It will also tell the table to clear all bets.
205
206
CHAPTER
THIRTYTHREE
207
Common Superclass. Refactoring the single instance variable up to the superclass makes a relatively minor change. However, it places a feature in the superclass which all but a few subclasses must ignore. This is another example of Swiss Army Knife design, where we will be subtracting a feature from a superclass. Delegate. If we change the Player1326State class to keep its own copy of the desired Outcome we cleanly remove any dependence on Player. The Player is still responsible for keeping track of the Outcome s, and has subcontracted or delegated this responsibility to an instance of Player1326State. The down side of this is that we must provide this Outcome to each state constructor as the state changes. The solution we embrace is changing the denition of Player1326State to include the Outcome. This delegates responsibility to the state, where it seems to belong. This will change all of the constructors, and all of the state change methods, but will cleanly separate the Player1326State class hierarchy from the Player class hierarchy. Python Duck-Typing In Python, the relationship between a Player1326State object and the Craps1326 is completely casual. We dont have to sweat the details of where precisely the Outcome is declared. In Java, however, we must be very careful to sort out the relationship between Craps1326, and Roulette1326. Pythons exibility is called duck typing: if it walks like a duck and quacks like a duck, it is a duck. In this case, any class with an outcome attribute is a candidate owner for a Player1326State object. Craps Cancellation. When can examine the Roulette Cancellation Cancellation Player Overview and see that this player will need to use a List of individual betting amounts. Each win for an odds bet will cancel from this List, and each loss of an odds bet will append to this List. As with the Craps Martingale player, we will be managing a base Pass Line bet, as well as an odds bet that uses the Cancellation strategy. The Cancellation algorithm can be easily transplanted from the original Roulette version to this new Craps version. Craps Fibonacci. When can examine the Roulette Fibonacci Fibonacci Player Overview and see that this player will need to compute new betting amounts based on wins and losses.
or Dont Pass Line), the odds bet is the corresponding odds bet (Pass Line Odds or Dont Pass Odds). This class implements the basic procedure for placing the line bet and the behind the line odds bet. However, the exact amount of the behind the line odds bet is left as an abstract method. This allows subclasses to use any of a variety of betting strategies, including Martingale, 1-3-2-6, Cancellation and Fibonacci.
33.3.1 Fields
lineOutcome Outcome for either Pass Line or Dont Pass Line. A right bettor will use a Pass Line bet; a wrong bettor will use the Dont Pass Line. oddsOutcome Outcome for the matching odds bet. This is either the Pass Line Odds or Dont Pass Line Odds bet. A right bettor will use a Pass Line Odds bet; a wrong bettor will use the Dont Pass Line Odds.
33.3.2 Constructors
__init__(self, table, line, odds) Parameters table (CrapsTable) The table on which bets are palced line (Outcome) The line bet outcome odds (Outcome) The odds bet outcome Constructs the CrapsSimplePlayer with a specic Table for placing Bets. Additionally a line bet (Pass Line or Dont Pass Line) and odds bet (Pass Line Odds or Dont Pass Odds) are provided to this constructor. This allows us to make either Pass Line or Dont Pass Line players.
33.3.3 Methods
placeBets(self ) Updates the Table with the various Bets. There are two basic betting rules. 1. If there is no line bet, create the line Bet from the line Outcome. 2. If there is no odds bet, create the behind the line odds Bet from the odds Outcome. Be sure to check the price of the Bet before placing it. Particularly, Dont Pass Odds bets may have a price that exceeds the players stake. This means that the Bet object must be constructed, then the price must be tested against the stake to see if the player can even aord it. If the stake is greater than or equal to the price, subtract the price and place the bet. Otherwise, simply ignore it.
209
33.4.1 Fields
lossCount The number of losses. This is the number of times to double the pass line odds bet. 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 betM ultiple = 2lossCount .
33.4.2 Methods
placeBets(self ) Extension to the superclass placeBets() method. This version sets the amount based on the value of CrapsMartingale.betMultiple. win(self, bet ) Parameter bet (Bet) The bet that was a winner 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. lose(self, bet ) Parameter bet (Bet) The bet that was a loser Increments lossCount by 1 and doubles betMultiple.
33.5.1 Fields
outcome The Outcome on which a Player will bet.
33.5.2 Constructors
__init__(self, outcome) Parameter outcome (Outcome) The outcome on which to bet The constructor for this class saves Outcome on which a Player will bet.
33.5.3 Methods
Much of the original design for this state hierarchy should remain in place. See Player 1-3-2-6 Class for more information on the original design. nextLost(self )
210
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. This method is dened in the superclass to assure that it is available for each subclass. This will use the outcome to be sure the new state has the Outcome on which the owning Player will be betting.
33.6.1 Fields
state 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.
33.6.2 Constructors
__init__(self, table, line, odds) Parameters table (CrapsTable) The table on which bets are palced line (Outcome) The line bet outcome odds (Outcome) The odds bet outcome Uses the superclass to initialize the Craps1326 instance with a specic Table for placing Bets, and set the line bet (Pass Line or Dont Pass Line) and odds bet (Pass Line Odds or Dont Pass Odds). Then the initial state of Player1326NoWins is constructed using the odds bet.
33.6.3 Methods
placeBets(self ) Updates the Table with a bet created by the current state. This method delegates the bet creation to state objects currentBet() method. win(self, bet ) Parameter bet (Bet) The bet that was a winner 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 states objects nextWon() method and saving the new state in state lose(self, bet ) Parameter bet (Bet) The bet that was a loser
211
Uses the current state to determine what the next state will be. This method delegates the next state decision to state objects nextLost() method, saving the result in state.
33.7.1 Fields
sequence This List keeps the bet amounts; wins are removed from this list and losses are appended to this list. THe current bet is the rst value plus the last value.
33.7.2 Constructors
__init__(self, table, line odds) Parameters table (CrapsTable) The table on which bets are palced line (Outcome) The line bet outcome odds (Outcome) The odds bet outcome Invokes the superclass constructor to initialize this instance of CrapsCancellation. Then calls resetSequence() to create the betting budget.
33.7.3 Methods
There are few real changes to the original implementation of CancellationPlayer. See Cancellation Player Class for more information. placeBets(self ) Creates a bet from the sum of the rst and last values of sequence and the preferred outcome. This uses the essential line bet and odds bet algorithm dened above. If no line bet, this is created. If theres s line bet and no odds bet, then the odds bet is created. If both bets are created, there is no more betting to do.
212
A revised CrapsMartingale class, that is a proper subclass of CrapsSimplePlayer . The existing unit test for CrapsMartingale should continue to work correctly after these changes. A revised Player1326State class hierarchy. Each subclass will use the outcome eld instead of getting this information from a Player1326 instance. The unit tests will have to be revised slightly to reect the changed constructors for this class. A revised Roulette1326 class, which reects the changed constructors for this Player1326State. The unit tests should indicate that this change has no adverse eect. The Craps1326 subclass of CrapsSimplePlayer. This will use the revised Player1326State. A unit test class for Craps1326. This test should synthesize a xed list of Outcomes, Throws, and calls a Craps1326 instance with various sequences of craps, naturals and points to assure that the bet changes appropriately. The CrapsCancellation subclass of CrapsSimplePlayer. A unit test class for CrapsCancellation. This test should synthesize a xed list of Outcomes, Throws, and calls a CrapsCancellation instance with various sequences of craps, naturals and points to assure that the bet changes appropriately.
213
214
CHAPTER
THIRTYFOUR
This cumulative chance of rolling a 7 means that the odds of the game ending with a loss because of throwing a 7 grow as the game progresses. We can compute the following sequence of odds of losing for larger numbers of rolls of the dice after the point is established: 17%, 28%, 35%, 40%, 43%, 46%, 47%, 48%, 49%. The idea is that the longer a game runs, the more likely you are to lose your initial Pass Line bet. Consequently, some players count the thows in the game, and eectively cancel their bet by betting against themselves on the Seven proposition. Note that the Seven proposition is a 1/6 probability that pays 5 for 1, (eectively 4:1). While the basic probability analysis of this bet is not encouraging, it does have an interesting design problem: the player now has multiple states. They have Pass Line bet, they can use a Martingale strategy for their Pass
215
Line Odds bet, they are counting throws, and they are using a Martingale strategy for a Seven proposition starting with the seventh throw of the game. We also note that this analysis doesnt work for wrong bettors using the Dont Pass Line bet. Their concern is the opposite: a short game may cause them to lose their Dont Pass bet, but a long game makes it more likely that they would win. One simulation for this is to place Dont Pass Odds bets after several throws, where the odds bet appears more likely to win. Decomposition. This leads us to consider the player as a composite object with a number of states and strategies. It also leads us to design a class just to handle Martingale betting. Note that when we were looking at the design for the various players in Design Cleanup and Refactoring, we glanced at the possibility of separating the individual betting strategies from the players, and opted not to. However, we did force each strategy to depend on a narrowly-dened interface of oddsBet(), won() and lost(). We can exploit this narrow interface in teasing apart the various strategies and rebuilding each variation of Player with a distinct betting strategy object. The separation of Player from BettingStrategy involves taking the betting-specic information out of each player class, and replacing the various methods and elds with one or more BettingStrategy objects. In the case of Roulette players, this is relatively simple. In the case of Craps players, we note that we have two bets, one with a trivial-case betting strategy where the bet never changes. If we add this NoChange strategy, we can redene all Craps players bets using BettingStrategy objects. The responsibilities of a BettingStrategy are to maintain a preferred Outcome, maintain a bet amount, and change that amount in response to wins and losses. The existing win() and lose() methods are a signicant portion of these reponsibilities. The oddsBet() method of the various CrapsSimplePlayer embodies other parts of this, however, the name is inappropriate and it has a poorly thought-out dependency on Player. The responsibilities of a Player are to keep one or more betting strategies, so as to place bets in a Game. All of the Roulette players will construct a single BettingStrategy object with their preferred Outcome. The various CrapsSimplePlayer classes will have two BettingStrategys: one for the line bet and one for the odds bet. The only dierence among the simple strategies is the actual BettingStrategy object, simplifying the Player class hierarchy to a single Roulette player and two kinds of Craps players: the stub player who makes only one bet and the other players who make more than one bet and use a betting strategy for their odds bet. Implementing SevenCounter. Once we have this design in place, our SevenCounter player can then be composed of a Pass Line bet, a Pass Line Odds bet, and a Seven proposition bet that will only be used after seven rolls have passed in a single game. The line bet uses the NoChange strategy. The other two bets can use any of the strategies we have built: Martingale, 1-3-2-6 or Cancellation. Currently, there is no notication to the CrapsPlayer of unresolved bets. The player is only told of winners and losers. The opportunity to place bets indicates that the dice are being rolled. Additionally, the ability to place a line bet indicates that a game is beginning. We can use these two conditions to count the throws in during a game, eectively counting unresolved bets.
216
34.2.1 Fields
outcome This is the Outcome that will be watched for wins and losses, as well as used to create new Bets.
34.2.2 Constructors
__init__(self, outcome) Parameter outcome (Outcome) The outcome on which this strategy will create bets Initializes this betting strategy with the given Outcome.
34.2.3 Methods
createBet(self ) Returns a new Bet using the outcome Outcome and any other internal state of this object. win(self, bet ) Parameter bet (Bet) The bet which was a winner Notication from the Player that the Bet was a winner. The Player has responsibility for handling money, this class has responsibility for tracking bet changes. lose(self, bet ) Parameter bet (Bet) The bet which was a loser Notication from the Player that the Bet was a loser. __str__(self ) Returns a string with the name of the class and appropriate current state information. For the superclass, it simply returns the name of the class. Subclasses will override this to provide subclass-specic information.
34.3.1 Fields
betAmount This is the amount that will be bet each time. A useful default value is 1.
217
34.3.2 Constructors
__init__(self, outcome) Parameter outcome (Outcome) The outcome on which this strategy will create bets Uses the superclass initializer with the given Outcome.
34.3.3 Methods
createBet(self ) Returns a new Bet using the outcome Outcome and betAmount. win(self, bet ) Parameter bet (Bet) The bet which was a winner Since the bet doesnt change, this does nothing. lose(self, bet ) Parameter bet (Bet) The bet which was a loser Since the bet doesnt change, this does nothing. __str__(self ) Returns a string with the name of the class, outcome and betAmount.
34.4.1 Fields
lossCount The number of losses. This is the number of times to double the pass line odds bet. 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 betM ultiple = 2lossCount .
34.4.2 Constructors
__init__(self, outcome) Parameter outcome (Outcome) The outcome on which this strategy will create bets Uses the superclass initializer with the given Outcome. Sets the initial lossCount and betMultiplier.
218
34.4.3 Methods
createBet(self ) Returns a new Bet using the outcome Outcome and the betMultiple. win(self, bet ) Parameter bet (Bet) The bet which was a winner Resets lossCount to zero, and resets betMultiple to 1. lose(self, bet ) Parameter bet (Bet) The bet which was a loser Increments lossCount by 1 and doubles betMultiple. __str__(self ) Returns a string with the name of the class, outcome, the current betAmount and betMultiple.
34.5.1 Fields
state This is the current state of the 1-3-2-6 betting system. It will be an instance of one of the four subclasses of Player1326State: No Wins, One Win, Two Wins or Three Wins.
34.5.2 Constructors
__init__(self, outcome) Parameter outcome (Outcome) The outcome on which this strategy will create bets Initializes this betting strategy with the given Outcome. Player1326NoWins using outcome. Creates an initial instance of
34.5.3 Methods
createBet(self ) Returns a new Bet using the currentBet() method from the state object. win(self, bet ) Parameter bet (Bet) The bet which was a winner Determines the next state when the bet is a winner. Uses states nextWon() method and saves the new state in state. lose(self, bet )
219
Parameter bet (Bet) The bet which was a loser Determines the next state when the bet is a loser. Uses states nextLost(), method saving the result in myState. __str__(self ) Returns a string with the name of the class, outcome and state.
34.6.1 Fields
lineStrategy An instance of BettingStrategy that applies to the line bet. Generally, this is an instance of NoChangeBetting because we want to make the minimum line bet and the maximum odds bet behind the line.
34.6.2 Constructors
__init__(self, table, lineStrategy) Constructs the CrapsOneBetPlayer with a specic Table for placing Bet s. This will save the given BettingStrategy in lineStrategy.
1. Get the basic Pass Line Outcome from the Dice. 2. Creates a Martingale betting strategy focused on the basic Pass Line outcome. 3. Creates a one-bet player, who will employ the Martingale betting strategy focused on the basic Pass Line outcome.
34.6.3 Methods
placeBets(self ) Updates the Table with the various Bet s. There is one basic betting rule. 1. If there is no line bet, create the line Bet from the lineStrategy.
220
Be sure to check the price of the Bet before placing it. Particularly, Dont Pass Odds bets may have a price that exceeds the players stake. This means that the Bet object must be constructed, then the price must be tested against the stake to see if the player can even aord it. If the stake is greater than or equal to the price, subtract the price and place the bet. Otherwise, simply ignore it. win(self, bet ) Parameter bet (Bet) The bet which was a winner Notication from the Game that the Bet was a winner. The amount of money won is available via theBet winAmount(). If the bets Outcome matches the lineStrategy s Outcome, notify the strategy, by calling the lineStrategy s win() method. lose(self, bet ) Parameter bet (Bet) The bet which was a loser Notication from the Game that the Bet was a loser. If the bets Outcome matches the lineStrategy s Outcome, notify the strategy, by calling the lineStrategy s lose() method.
34.7.1 Fields
oddsStrategy An instance of BettingStrategy that applies to the line bet.
221
oddsStrategys Outcome; if they match, it will notify the strategy, by calling the oddsStrategy s win() method. lose(self, bet ) Parameter bet (Bet) The bet which was a loser Notication from the Game that the Bet was a loser. The superclass handles the line bet notication. If the bets Outcome matches the oddsStrategys Outcome, notify the strategy, by calling the oddsStrategys lose() method.
34.8.1 Fields
sevenStrategy The BettingStrategy for the seven bet. Some argue that this should be a no-change strategy. The bet is rare, and if eect the player bets against themself with this. One could also argue that it should be a Martingale because each throw after the seventh are less and less likely to win. throwCount The number of throws in this game. This is set to zero when we place a line bet, and incremented each time we are allowed to place bets.
34.8.2 Constructors
__init__(self, table) This will create a NoChangeBetting strategy based on the Pass Line Outcome. It will also create a MartingaleBetting strategy based on the Pass Line Odds Outcome. These will be given to the superclass constructor to save the game, the line bet and the odds bet. Then this constructor creates a Bet1326Betting strategy for the Seven Proposition Outcome.
34.8.3 Methods
placeBets(self ) Updates the Table with the various Bet s. There are three basic betting rules. 1. If there is no line bet, create the line Bet from the lineStrategy. Set the throwCount to zero. 2. If there is no odds bet, create the odds Bet from the oddsStrategy. 3. If the game is over seven throws and there is no seven proposition bet, create the proposition Bet from the sevenStrategy. Each opportunity to place bets will also increment the throwCount by one.
222
win(self, bet ) Parameter bet (Bet) The bet which was a winner Notication from the Game that the Bet was a winner. The superclass handles the money won and the line and odds bet notication. lose(self, bet ) Parameter bet (Bet) The bet which was a loser Notication from the Game that the Bet was a loser. The superclass handles the line and odds bet notication.
223
A unit test for the CrapsSevenCountPlayer class. This will require a lengthy test procedure to assure that the player correctly places a Seven proposition bet when the game is over seven throws long.
224
CHAPTER
THIRTYFIVE
CONCLUSION
The game of Craps has given us an opportunity to extend and modify an application with a considerable number of classes and objects. It is large, but not overly complex, and produces interesting results. Further, as a maintenance and enhancement exercise, it gave us an opportunity to work from a base of software, extending and rening the quality of the design. We omitted exercises which would integrate this package with the Simulator and collect statistics. This step, while necessary, doesnt include many interesting design decisions. The nal deliverable should be a working application that parses command-line parameters, creates the required objects, and creates an instance of Simulator to collect data. Refactoring. We note that many design decisions required us to explore and refactor a great deal of the applications design. In writing this part, we found it very diicult to stick to our purpose of building up the design using realistic steps and realistic problem solving. A book with a description of an already-completed structure does not help new designers learn the process of design, and does not help people to identify design problems and correct them. Because of that philosophy, the complete refactoring of the design in the Design Cleanup and Refactoring chapter was an important activity because it is the kind of thing that distinguishes a good design from a haphazard design. Our observation is that this kind of design rework happens late in the life of a project, and project managers are uncomfortable evaluating the cost and benet of the change. Further, programmers are unable to express the cost of accumulating technical debt by making a series of less-than-optimal decisions. Simpler is Better. Perhaps, the most important lesson that we have is the constant search for something we call The Big Simple. We see the history of science as a search for simpler explanations of natural phenomena. The canonical example of this is the distinction between the geocentric model and the heliocentric model of the solar system. Both can be made to work: astronomers carefully built extremely elaborate models of the geocentric heavens and produced accurate predictions of planetary positions. However, the model of planetary motion around the sun describes real phenomena more accurately and has the added benet of being much simpler than competing models. To continue this rant, we nd that software designers and their managers do not feel the compulsion and do not budget the time to identify the grand simplication that is possible. In some cases, the result of simplifying the design on one axis will create more classes. Designers lack a handy metric for understandability; managers are able to count individual classes, no matter how transparently simple. Designers often face diiculties in defending a design with many simple classes; some people feel that a few complex classes is simpler because it has fewer classes. As our trump card, we reference the metrics for complexity. McCabes Cyclomatic Complexity penalizes ifstatements. By reducing the number of if-statements to just those required to create an object of the proper class, we reduce the complexity. The Halstead metrics penalize programs with lots of internal operators and operands when compared with the number of operands in the interface. Halstead measures simple, transparent classes as less complex. Neither measure penalizes overall size in lines of code, but rather they
225
penalize decision-making and hidden, internal state. A badly designed, complex class has hidden internal states, often buried in nested if-statements. We emphasize small, simple classes with no hidden features. Our concrete examples of this simplication process are contained in three large design exercises. In Throw Builder Class, we showed a kind of rework necessary to both generalize and isolate the special processing for Craps. In Design Cleanup and Refactoring, we reworked classes to create an easy-to-explain architecture with layers and partitions of responsibility. Finally, in Roll-Counting Player Class, we uncovered a clean separation between game rules and betting strategies.
226
Part IV
Blackjack
227
This part describes the more complex game of Blackjack. Both the player and the dealer are trying to build a hand that totals 21 points without going over. The hand closest to 21 wins. This game has a number of states and a number of complex state-change rules. It has very few dierent kinds of bets, but moderately complex rules for game play. However, it does have the most sophisticated playing strategy, since the player has a number of choices to make. The chapters of this part presents the details on the game, an overview of the solution, and a series of six relatively complex exercises to build a complete simulation of the game. In the case of Blackjack, we have to create a design that allows for considerable variation in the rules of the game as well as variation in the players betting strategies.
229
230
CHAPTER
THIRTYSIX
BLACKJACK DETAILS
In the rst section we will present elements of the game of Blackjack. Blackjack uses cards and has fairly complex rules for counting the number of points in a hard of cards. Blackjack oers relatively few bets, most of which are available based on the state of the game. Well cover these bets and the conditions under which they are allowed in the second sectionj. Finally, we will describe some common betting and playing strategies that we will simulate. In this case, we have playing strategies that are unique to Blackjack, combined with betting strategies initially dened in Roulette and reworked in Craps.
card up and one card down. If the dealers card is an ace, the player is oered insurance. The details will be described in a separate sceanario, below. Initially, the player has a number of choices. If the two cards are the same rank, the player can elect to split into two hands. This is a separate scenario, below. The player can double their bet and take just one more card. In some casinos this opportunity may be limited to certain totals on the cards, for instance, only 10 and 11; or it may be limited to a certain number of cards, for instance, two cards. The more typical scenario is for the player to take additional cards (a hit ) until either their hand totals more than 21 (they bust ), or their hand totals exactly 21, or they elect to stand. If the players hand is over 21, their bet is resolved immediately as a loss. Resolving these bets early is an important part of the houses edge in Blackjack. If the players hand is 21 or less, however, it will be compared to the dealers hand for resolution. The dealer then reveals the hole card and begins taking cards according to their xed rule. When their total is 16 or less, they take an additional card; if their total is 17 or more, they stand pat. This rule is summarized as hit on 16, stand on 17. In some casinos a dealer will hit a soft 17 (A-6), which improves the houses edge slightly. If the dealer busts, the player wins. If the dealer did not bust, then the hands are compared: if the players total is more than the dealer, the player wins; if the totals are equal, the bet is a push; otherwise the dealers total is more than the play and the player loses. If the players hand is an ace and a 10-point card (10, Jack, Queen or King), the hand is blackjack and the the ante is paid o at 3:2. Otherwise, winning hands that are not blackjack are paid o at 1:1. Dealer Shows An Ace. If the dealers up card is an ace, the player is oered an insurance bet. This is an additional proposition that pays 2:1 if the dealers hand is exactly 21 (a 4/13 probability). The amount of the bet is half the original ante. If this insurance bet wins, it will, in eect, cancel the loss of the ante. After oering insurance to the player, the dealer will check their hole card and resolve the insurance bets. If the hole card is 10-point card, the dealer has blackjack, the card is revealed, and insurance bets are paid. If the hole card is not a 10-point card, the insurance bets are lost, but the card is not revealed. In the unusual case that the dealer shows an ace and the player shows blackjack (21 in two cards), the player will be oered even money instead of the insurance bet. If the player accepts the even money oer, their hand is resolved at 1:1 immediately, without examining the dealers hole card or playing out the hand. If the player declines even money, they can still bet or decline insurance. Checking the odds carefully, there is a 4/13 (30.7%) chance of the dealer having 21, but insurance is paid as if the odds were 1/3 (33.3%). Since the player knows they have 21, there is a 4/13 probability of a push plus winning the insurance bet (both player and dealer have 21) and a 9/13 probability of winning at 3:2, but losing the insurance bet (eectively a push). Split Hands. When dealt two cards of the same rank, the player can split the cards to create two hands. This requires an additional bet on the new hand. The dealer will deal an additional card to each new hand, and the hands are played independently. Generally, the typical scenario described above applies to each of these hands. The general rule of thumb is to always split aces and eights. The ideal situation is to split aces, and get dealt a 10-point card on each ace. Both hands pay 3:2. A more common situation is to have a low card (from 2 to 7) paired up with the ace, leading to soft 13 through soft 18. Depending on the dealers up card, these are opportunities to double down, possibly increasing the bet to 4 times the original amount. Some casinos restrict doubling down on the split hands. In rare cases, one or more of the new cards will match the original pair, possibly allowing further splits. Some casinos restrict this, only allowing a single split. Other casinos prevent resplitting only in the case of aces.
232
Note that the players election to split hands is given after any oer and resolution of insurance bets.
Most players will decline the insurance oer, except when they hold a 21. In that rare case the even money oer should be declined, since the expected value analysis of the result shows a slightly better payout by competing against the dealer. The decision matrix has two parts: accepting or rejecting the split oer, and choosing among hit, stand or double down. You can buy cards in casino gift-shops that summarize a playing strategy in a single, colorful matrix with a letter code for split, hit, double and stand. Note that each decision to hit results in a new card, changing the situation used for decision-making. This makes the strategy an interesting, stateful algorithm. A player could easily add the betting strategies weve already dened to their Blackjack play strategies. A player could, for example, use the Martingale system to double their bets on each hand which is a loss, and reset their betting total on each hand which is a win. Indeed, our current design permits this, since we disentangled the betting strategies from the individual games in Roll-Counting Player Class.
234
CHAPTER
THIRTYSEVEN
235
37.3 A Walkthrough
The unique, new feature of Blackjack is the more sophisticated collaboration between the game and the player. This interaction involves a number of oers for various bets, and bet resolution. Additionally, it includes oers to double, hit or stand. Well examine parts of a typical sequence of play to assure ourselves that we have all of the necessary collaborations and responsibilities.
236
A good way to structure this task is to do a CRC walkthrough. For more information on this technique see A Walkthrough of Roulette. Well present the overall sequence of play, and leave it to the student to manage the CRC walkthrough.
37.3. A Walkthrough
237
7. Compare Hands. For each hand still valid, the Game compares the Players Hand point value against the Dealers Hand point value. Higher point value wins. In the case of a tie, it is a push and the bet is returned. When the Player wins, a winning hand with two cards totalling 21 (blackjack) is paid 3:2, any other winning hand is paid 1:1.
238
CHAPTER
THIRTYEIGHT
239
total for the hand as a whole. While this design does have some potential problems in dealing with multiple aces in a single hand, well let it stand until we have more design in place. Deck. A deck of cards has responsibility for shuing and dealing the various cards. Additionally, it should constuct the complete set of 52 cards. We note that shuing uses a random number generator, but a deck isnt the same kind of random event factory that a Wheel or pair of Dice is. In the case of Wheel and Dice, the random events were based on random selection with replacement : an individual event can be regenerated any number of times. In the case of a deck, however, once a card has been dealt, it will not show up until the deck is shued. In Roulette and Craps, the odds depended on a RandomEvent: either the Bin or Throw. In Blackjack, the antes win amount depends on the players entire hand. This means that being dealt an individual card isnt the same kind of thing that a throw of the dice is; rather it suggests that the dealers shoe is the random event factory, and the entire hand is the random event created by dealing cards. Continuing this line of thought, an Outcomes win amount could depend on a RandomEvent, if we consider the entire hand to be the random event. Considering an entire hand to be a single random event is skating on pretty thin ice, so we wont force-t a Deck into the random event factory part of our framework. Instead, we will let Deck stand alone. Well design a simple initialization for a deck that constructs the 52 cards. Beyond that, the notions of shuing and dealing can be assigned to the shoe. Since a Deck is a container, we have to examine the available collection classes to determine which concrete class we need. Interestingly, we only need two features of Collection: the add() method and the iterator(). These methods are implemented for all of the variations of Set and List. The only collection we can disregard is HashSet as that will use more storage than necessary. Shoe. The dealers shoe is where Cards are shued and dealt to create individual hands. A shoe will be built from some number of Decks. More properly, the shoe will contain the Cards from those Decks. The Deck objects arent really used for much more than constructing batches of individual Card objects. The Shoe responsibilities include proper initialization using a given number of decks, periodic shuing and proper dealing. In a casino, the shuing involves a ritual of spreading the cards on the table and stirring them around thouroughly, then stacking them back into the shoe. The top-most card is extracted, shown to the players, discarded, and a marker card is cut into the shoe at least two decks from the end, leaving about 100 cards unplayable after the marker. While most of the ritual does not require careful modeling, the presence of undealt cards at the end of the shoe is important as a way to defeat card-counting strategies. Since a Shoe is a container, we have to examine the available collection classes to determine which concrete class we need. Interestingly, we only need two features of Collection: the addAll() method to put another deck into the shoe and the iterator(). These methods are implemented for all of the variations of List. We will have to disregard the various Set implementations because they impose their own unique orders on the elements, dierent from our shued order. The simplest shuing algorithm iterates through all of the Cards in the Shoe s collection, and exchanges that Card with the card in a randomly-selection position. In order to move Card s around freely within the structure, an ArrayList or Vector could be used. See Card-Deck-Shoe Questions and Answers for more discussion on shuing.
First, and most important, if-statements add complexity. The question wouldnt it be simpler to use an if-statement is a kind of oxymoron. Second, and almost as important, if-statements dilute responsibility assignments. Combining all three subclasses into one puts three slightly dierent responsibilities into one place, making it more diicult to debug problems. Further, we could wind up repeating or other reusing the if-statement in inappropriate ways. If we create separate subclasses, the clear separation of responsibility becomes a matter of denition, not a matter of following a complex thread of programming logic. Third, if-statements limit growth, adaptation and change. If we have a modication to the rules, for example, making 1-eyed Jacks wild, we would prefer to simply introduce another subclass. We nd that chasing down one or more related if-statements to assure ourselves that we are correctly handling the new subtlety rapidly gets out of hand. Is that the best shuing algorithm Wont it sometimes move a card twice? Wont it sometimes put a card back into the original spot? Yes, it may move some cards twice and it may leave a card in position. This is part of random behavior. This algorithm touches every card, swapping it with a randomly selected card. We are assured that every card was put into a random position. Sometimes a card will have been moved more than once, but the minimum criteria is that every card has been moved. While a shuing algorithm that models the real world is tempting, this adds complexity for no actual improvement in the randomization. A popular technique in the real world is to cut the deck in half and then rie the cards into a single pile. If done with the kind of perfection that software provides (cutting the deck exactly in half and exactly alternating the cards) this shue leads to a perfectly predictable cycle of orders. What makes this shue work in the real world is the random inaccuracies in cutting and riing. We dont see any value in modeling these physical phenomenon. A similar analysis holds for the kind of shue done in the casino. In essence, they do a shallow copy if the original List object, and then rebuild the shoes List by picking cards at random from the copy. This produces a result that is statistically indistinguishable from our algorithm, which uses an element-by-element swap. One fruitless side-track is using the seemingly-random hash code values of the Card objects. This only puts the cards into a single xed, but arbitrary order. An apparently interesting alternative is to generate a random index for each Card and then sort by this index or assemble a SortedSet. We note that sorting is O(n log n), where our algorithm is O(n), running much faster than any sort.
241
Symbol
Theres one tiny problem with the Unicode characters: the suits dont sort into the order preferred by Bridge players. Since its irrelevant for this class of problems, well ignore this. For the truly fussy, we would need to assure that the suits have a symbol character and a preferred sort order.
38.3.1 Fields
In Python, symbolic names are often declared within the class, not within the initialization method function.
class Card( object ): Clubs, Diamonds, Hearts, Spades = u'\u2663', u'\u2666', u'\u2665', u'\u2660' Jack, Queen, King = 11, 12, 13 ... oneEyedJack= Card( Card.Jack, Card.Hearts )
rank The rank of the card. This is a number from 1 (Ace) to 13 (King). suit The suit of the card. This is a character code for Clubs, Diamonds, Hearts or Spades.
38.3.2 Constructors
__init___(self, rank, suit ) Parameters rank (integer) rank of this card. suit (character) Character code for this card. Initializes the attributes of this Card.
38.3.3 Methods
getRank(self ) Returns the rank of this card. softValue(self ) Returns the soft value of this card. The superclass simply returns the rank. Subclasses can override this. Face cards will return 10, Aces will return 11. hardValue(self ) Returns the hard value of this card. The superclass simply returns the rank. Subclasses can override this. Face cards will return 10, Aces will return 1.
242
__str__(self ) Ideally, wed like this function to return the rank and suit of this card. However, in Python 2, theres an issue with the denition of the str() function. In Python 2, this function must return an ASCII-encoded string. If were using Unicode suit names, this function isnt helpful, and we have to resort to an explicit Unicode formatting string. unicode(self ) This will return a proper unicode string. We have to use print someCard.unicode() instead of print someCard.
38.4.1 Methods
softValue(self ) Returns the soft value of this card, 10. hardValue(self ) Returns the hard value of this card, 10. __str__(self ) Returns a short String displaying the rank and suit of this card. The ranks should be translated to single letters: 11 to 'J', 12 to 'Q' and 13 to 'K'.
38.5.1 Methods
softValue(self ) Returns the soft value of this card, 11. hardValue(self ) Returns the hard value of this card, 1. __str__(self ) Returns a short String displaying the rank and suit of this card. The rank is always 'A'.
38.6.1 Fields
cards The collection of individual cards. The specic type of collection could be any of the Set or List implementation classes.
38.6.2 Constructors
__init__(self ) Creates the Collection, cards, and then creates the 52 cards. A simple nest pair of loops to iterate through the suits and ranks will work nicely for this.
38.6.3 Methods
getCards(self ) Returns the collection of cards in cards.
38.7.1 Fields
deal An Interator that is used to pick the next card from the shoe. stopDeal The approximate number of decks to be left undealt in the shoe.
244
38.7.2 Constructors
__init__(self, decks, stopDeal, rng=None) If rng is None, create a generator from random.Random. Parameters decks (integer) A number of decks to create stopDeal (integer) An approximate number of decks left undealt in the shoe rng (random.Random) A random number generator to use. Initializes the Shoe by creating the required number of decks and building the cards List. This saves the stopDeal value, which is the number of decks left in the shoe. Typically, this is two, and approximately 104 cards are left in the shoe. This also saves the random number generator used to shue. To facilitate testing, this initializes the dealing iterator to the unshued List of cards. This will produce cards in a xed order.
38.7.3 Methods
shuffle(self ) Shues the shoe by swapping every element in the Shoe.cards List with a random element. Creates an Iterator, Shoe.deal that can be used to deal cards. If the stopDeal is non-zero, do the following to exclude several decks from the deal. 1. Create a random number, v, such that 6 v 6. 2. Step through the deal iterator stop 52 + v times, removing approximately stop cards from being dealt as part of ordinary play. __iter__(self ) Return the Shoe.deal iterator so that the game can get cards from the Shoe.
245
246
CHAPTER
THIRTYNINE
3. Ante paying 1:1. This payout occurs when the players hand is less than or equal to 21 and the dealers hand goes over 21. This payout also occurs when the players hand is less than or equal to 21 and greater than the dealers hand. This outcome is a loser as soon as the players hand goes over 21. It is also a loser when the players hand is less than or equal to 21 and less than the dealers hand. 4. Ante paying 3:2. This payout occurs when the players hand is blackjack. The odds depend on the players hand. 5. Ante as a push, paying 0:1. This payout occurs when the players hand is less than or equal to 21 and equal to the dealers hand. The odds depend on both player and dealers hand. In Craps, we identied the Field and Horn Outcomes which had winning amounts that depended on the specic Throw. See Throw Builder Overview , for more information. Additionally, we compared Bets against the Outcomes as part of the transition from one GameState to another. See Resolving Bets, for more information. In order for this to work correctly, some bets were changed frm a generic Outcome (a Line Bet) to an Outcome with dierent odds. What Class is Hand? It appears that the hand, as a whole, is not simply associated with an Outcome. It appears that a Hand must produce an Outcome based on its content, the dealers content, and possibly the state of the game. This is a small change from the way Dice and Bin work. Those classes were associated with an Outcome. A Blackjack Hand, however, must do a bit of processing to determine which Outcome it represents. A two-card hand totalling soft 21 produces a blackjack Outcome that pays 3:2. All other hands produce an Outcome that pays 1:1 and could be resolved as a win, a loss or are a push. Also, changes to the state of the game depend on the values of both hands, as well as the visible up card in the dealers hand. This makes the state of the hand part of the evolving state of the game, unlike the simple RandomEvents we saw in Roulette and Craps. Choices. We have a few ways we can deal with Hand. We can make Hand a subclass of RandomEvent, even though its clearly more complex than other events. We can make Hand a unique kind of class, unrelated to other games. Hand is an Event? While a Hand appears to be a subclass of RandomEvent, it jars our sensibilities. A Hand is built up from a number of Cards. One could rationalize calling a Hand an event by claiming that the Shoe is the random event factor. The act of shuing is when the event is created. The complex event is then revealed one card at a time. It seems that we need to dene a Hand class that shares a common interface with a RandomEvent, but extends the basic concept because a hand has an evolving state. Hand is dierent. While we can object to calling a Hand a single event, its diicult to locate a compelling reason for making a Hand into something radically dierent from the other subclasses of RandomEvent: Bin and Dice. Hand Features. Our rst design decision, then is to dene Hand as a kind of RandomEvent. Well need to create several Outcomes: Insurance, Even Money, Ante and Blackjack. The Hand will produce an appropriate Outcome based on the hands structure, the game state, and the dealers hand. Generally, each Hand will produce a simple Ante outcome as a winner or loser. Sometimes a Hand will produce a Blackjack outcome. Sometimes the Player and Blackjack Game will collaborate to add an Insurance or Even Money outcome to the Hand.
248
249
These are resolved immediately if the dealer does not have 21, these bets are lost. If the dealer has 21, these bets win, but the Ante is a loss. The Double Down bet. This is generally oered at any time. It can be looked at as an an additional amount added to the ante bet and a modication to game play. Objects. It seems simplest to create a few Outcome instances: Ante, Insurance, Even Money and Double Down. The Table will have to merge the double-down bet amount into the Ante bet amount.
39.2.1 Constructor
__init__(self, hand ) Parameter hand (Hand) The hand for which to compute a total Creates a new HandTotal object associated with a given hand.
39.2.2 Methods
total(self, card=None) Computes a total of all the cards in the associated hand. If card is not None, omit the the indicated card from the total. This method is abstract, it should return NotImplemented. Each subclass will provide an implementation. Parameter card (Card) A card to exclude from the total
250
total(self, card=None) Computes the soft total of all the cards in the associated hand. If card is not None, omit the the indicated card from the total. Parameter card (Card) A card to exclude from the total
251
39.6.1 Fields
cards Holds the collection of individiual Cards of this hand. hard A instance of HandHardTotal. This means that hand.hard.total() will produce the hard total of the cards in this hand. This method is used as one of the point totals for the hand. soft A instance of HandSoftTotal. This means that hand.soft.total() will produce the soft total of the cards in this hand. altTotal This is a reference to either Hand.hardTotal or Hand.softTotal. This will produce a soft total when one is a appropriate; when a soft total isnt appropriate, it will produce a hard total. This is set by each Card. This method is used as one of the point totals for the hand.
39.6.2 Constructors
__init__(self, card=None) Parameter card (Card) A card to add Creates an empty hand. The Hard.cards variable is initialized to an empty sequene. The Hand.hard and Hand.soft objects are initialized to HandHardTotal and HandSoftTotal objects. Also, Hand.altTotal is set to Hand.hard. If card is provided, then use the add() method to add this card to the hand..
39.6.3 Methods
add(self, card ) Parameter card (Card) A card to hadd Add this card to the Hand.cards list. Evaluate Card.setAltTotal() to update the hard vs. soft total calculation for this hand. value(self ) Computes the alternate total of this hand using the Hand.altTotal object. If there are any aces, and the soft total is 21 or less, this will be the soft total. If there are no aces, or the soft total is over 21, this will be the hard total. size(self ) Returns the number of cards in the hand, the size of the List. blackjack(self ) Returns true if this hand has a size of two and a value of 21.
252
busted(self ) Returns true if this hand a value over 21. __iter__(self ) Returns an iterator over the cards of the List. __str__(self ) Displays the content of the hand as a String with all of the card names.
253
254
CHAPTER
FORTY
255
40.2.1 Constructors
__init__(self ) Uses the superclass constructor to create an empty Table.
40.2.2 Methods
placeBet(self, bet, hand ) Parameters bet (Bet) A bet for this hand; an ante bet is required. An insurance bet, even money bet or double down bet is optional. hand (Hand) A hand on which the player is creating a Bet. Updates the given hand to reference the given bet. Then uses the superclass placeBet() to add this bet to the list of working bets. __str__(self )
40.3.1 Fields
ante Holds a reference to the ante Bet for this hand. When this hand is resolved, the associated bet is paid and removed from the table.
40.3.2 Methods
setBet(self, ante) Parameter ante (Bet) The initial bet required to play Sets the ante Bet that will be resolved when this hand is nished. getBet(self ) Returns the ante Bet for this hand.
The BlackjackTable class. A class which performs a unit tests of the BlackjackTable class. The unit test should create several instances of Hand and Bet to create multiple Hands, each with unique Bets.
257
258
CHAPTER
FORTYONE
259
gets the initial hand; deal 2 cards to hand deal 2 cards to dealers hand gets up card from dealers hand; if this card requires insurance, do the insurance procedure iterate through all hands; is the given hand splittable? if the hand is splittable, oer a split bet
if splitting, move card and deal cards; loop back, looking for split oers iterate through all hands; if the hand is less than 21, do the ll-hand procedure while the dealers point value is 16 or less, deal another card
return true if two cards of the same rank get the players response; return it to the game take a card out; add a card
to split: create a split bet, and an empty hand; return the new hand
returns a list iterator return point value of the hand; add a card return point value of the hand return point value of the hand
if the dealer busts, iterate through all hands resolving the ante as a winner if the dealer does not bust, iterate through all hands comparing against the dealers points, determining win, loss or push
There are a few common variation in this procedure for play. Well set them aside for now, but will visit them in Variant Game Rules. 260 Chapter 41. Blackjack Game Class
Insurance. The insurance procedure involves additional interaction between Game and the the Players initial Hand. The following is done only if the dealer is showing an ace. Table 41.2: Blackjack Insurance Collaboration BlackjackGame if players hand is blackjack: oer even money Hand return true if 2 cards, soft 21 BlackjackPlayer to accept, return true BlackjackTable
if player accepted even money oer: change bet, resolve; end of game oer insurance to accept, create new bet; return true return point value
update bet; resolve and remove bet accept insurance bet resolve and remove bet
if player accepted insurance oer: check dealers hand; if blackjack, insurance wins, ante loses, game over; otherwise insurance loses
Filling the Hands. The procedure for lling each Hand involves additional interaction between Game and the the Players initial Hand. An Iterator used for perform the following procedure for each individual player Hand. Table 41.3: Blackjack Fill-Hand Collaboration BlackjackGame Hand BlackjackPlayer BlackjackTable resolve and remove bet update bet resolve the ante as a loss
while points less than 21, oer play options of double or hit; rejecting both oers is a stand. if over 21, the hand is a bust
to double, increase the bet for this hand and return true; to hit, return true
There is some variation in this procedure for lling Hands. The most common variation only allows a double-down when the Hand has two cards. Hand-specic Decisions. Some of the oers are directly to the BlackjackPlayer, while others require informing the BlackjackPlayer which of the players Hands is being referenced. How do we identify a specic hand? One choice is to have the BlackjackGame make the oer to the Hand. The Hand can pass the oer to the BlackjackPlayer; the Hand includes a reference to itself. An alternative is to have the BlackjackGame make the oer directly to the BlackjackPlayer, including a reference to the relevant Hand. While the dierence is minor, it seems slightly more sensible for the BlackjackGame to make oers directly to the BlackjackPlayer, including a reference to the relevant Hand.
261
41.4.1 Fields
hand Some kind of List which contains the initial Hand and any split hands that may be created.
41.4.2 Constructors
__init__(self, table) Parameter table (BlackjackTable) The table on which bets are placed Uses the superclass to construct a basic Player. Uses the newGame() to create an empty List fot the hands.
41.4.3 Methods
newGame(self )
262
Creates a new, empty list in which to keep Hands. placeBets(self ) Creates an empty Hand and adds it to the List of Hands. Creates a new ante Bet. Updates the Table with this Bet on the initial Hand. getFirstHand(self ) Returns the initial Hand. This is used by the pre-split parts of the Blackjack game, where the player only has a single Hand. __iter__(self ) Returns an iterator over the List of Hands this player is currently holding. evenMoney(self, hand ) Parameter hand (Hand) the hand which is oered even money Returns true if this Player accepts the even money oer. The superclass always rejects this oer. insurance(self, hand ) Parameter hand (Hand) the hand which is oered insurance Returns true if this Player accepts the insurance oer. In addition to returning true, the Player must also create the Insurance Bet and place it on the BlackjackTable. The superclass always rejects this oer. split(self, hand ) Parameter hand (Hand) the hand which is oered an opportunity to split If the hand has two cards of the same rank, it can be split. Dierent players will have dierent rules for determine if the hand should be split ot not. If the players rules determine that it wil accepting the split oer for the given Hand, hand, then the player will 1. Create a new Ante bet for this hand. 2. Create a new one-card Hand from the given hand and return that new hand. If the players rules determine that it will not accept the split oer, then None or null is returned. If the hand is split, adding cards to each of the resulting hands is the responsibility of the Game. Each hand will be played out independently. doubleDown(self, hand ) Parameter hand (Hand) the hand which is oered an opportunity to double down Returns true if this Player accepts the double oer for this Hand. The Player must also update the Bet associated with this Hand. This superclass always rejects this oer. hit(self, hand ) Parameter hand (Hand) the hand which is oered an opportunity to hit Returns true if this Player accepts the hit oer for this Hand. The superclass accepts this oer if the hand is 16 or less, and rejects this oer if the hand is 17 more more. This mimics the dealers rules. Failing to hit and failing to double down means the player is standing pat. __str__(self )
263
Displays the current state of the player, and the various hands.
41.6.1 Fields
player Holds a reference to the Player who owns this hand. Each of the various oers from the Game are delegated to the Player. splitDeclined Set to true if split was declined for a splittable hand. Also set to true if the hand is not splittable. The split procedure will be done when all hands return true for split declined.
41.6.2 Methods
splittable(self ) Returns true if this hand has a size of two and both Cards have the same rank. Also sets Hand.splitDeclined to true if the hand is not splittable. getUpCard(self ) Returns the rst Card from the list of cards, the up card.
264
41.7.1 Fields
shoe This is the dealers Shoe with the available pool of cards. dealer This is the dealers Hand.
41.7.2 Constructors
__init__(self, shoe, table) Parameters shoe (Shoe) The dealers shoe, populated with the proper number of decks table (BlackjackTable) The table on which bets are placed Constructs a new BlackjackGame, using a given Shoe for dealing Cards and a BlackjackTable for recording Bets that are associated with specic Hands.
41.7.3 Methods
cycle(self ) A single game of Blackjack. This steps through the following sequence of operations. 1. Call BlackjackPlayer.newGame() to reset the player. Call BlackjackPlayer.getFirstHand() to get the initial, empty Hand. Call Hand.add() to deal two cards into the players initial hand. 2. Reset the dealers hand and deal two cards. 3. Call BlackjackGame.hand.getUpCard() to get the dealers up card. If this card returns true for the Card.offerInsurance(), then use the insurance() method. Only an instance fo the subclass AceCard will return true for offerInstance(). All other Card classes will return false. 4. Iterate through all Hands, assuring that no hand it splittable, or split has been declined for all hands. If a hand is splittable and split has not previously been declined, call the Hands split() method. If the split() method returns a new hand, deal an additional Card to the original hand and the new split hand. 5. Iterate through all Hands calling the fillHand() method to check for blackjack, deal cards and check for a bust. This loop will nish with the hand either busted or standing pat. 6. While the dealers hand value is 16 or less, deal another card. This loop will nish with the dealer either busted or standing pat. 7. If the dealers hand value is bust, resolve all ante bets as winners. The OutcomeAnte should be able to do this evaluation for a given Hand compared against the dealers bust. 8. Iterate through all hands with unresolved bets, and compare the hand total against the dealers total. The OutcomeAnte should be able to handle comparing the players hand and dealers total to determine the correct odds. insurance(self )
265
Oers even money or insurance for a single game of blackjack. This steps through the following sequence of operations. 1. Get the players BlackjackPlayer.getFirstHand(). Is it blackjack? If the player holds blackjack, then call BlackjackPlayer.evenMoney(). If the even money oer is accepted, then move the ante bet to even money at 1:1. Resolve the bet as a winner. The bet will be removed, and the game will be over. 2. Call BlackjackPlayer.insurance(). If insurance declined, this method is done. 3. If insurance was accepted by the player, then check the dealers hand. Is it blackjack? If the dealer hold blackjack, the insurance bet is resolved as a winner, and the ante is a loser; the bets are removed and the game will be over. If the dealer does not have blackjack, the insurance bet is resolved as a loser, and the ante remains. If insurance was declined by the player, nothing is done. fillHand(self, hand ) Parameter hand (Hand) the hand which is being lled Fills one of the players hands in a single game of Blackjack. This steps through the following sequence of operations. 1. While points are less than 21, call BlackjackPlayer.doubleDown() to oer doubling down. If accepted, deal one card, lling is done. If double down is declined, call BlackjackPlayer.hit() to oer a hit. If accepted, deal one card. If both double down and hit are declined, lling is done, the player is standing pat. 2. If the points are over 21, the hand is bust, and is immediately resolved as a loser. The game is over. __str__(self ) Displays the current state of the game, including the player, and the various hands.
A class which performs a unit tests of the BlackjackGame class. The unit test will have to create a Shoe that produces cards in a known sequence, as well as BlackjackPlayer. The cycle() method, as described in the design, is too complex for unit testing, and needs to be decomposed into a number of simpler procedures.
267
268
CHAPTER
FORTYTWO
These rules will boil down to short sequences of if-statements in the split(), hit() and doubleDown() methods. In some contexts, complex if-statements are deplorable. Specically, complex if-statements are often a standin for proper allocation of responsibility. In this class, however, the complex if-statements implement a kind of index or lookup scheme. We have, for this exercise, 8 alternatives which depend on a two-dimensional index. One dimension contains four conditions that describe the players hand. The other dimension involves two conditions that describe the dealers hand. When we look at the various collections, we see that we can
269
index by primitive types or object instances. In this case, we are indexing by conditions; we would have to map each condition to either a numeric code or a distinct object in order to eliminate the if-statements. When we look at the conditions that describe the players hand, these are clearly state-like objects. Each card can be examined and a state transition can be made based on the the current state and the card. After accepting a card, we would check the total and locate the appropriate state object. We can then use this state object to index into a collection. When we look at the conditions that describe the dealers hand, there are only two state-like objects. The dealers op card can be examined, and we can locate the appropriate state object. We can use this state object to index into a collection. The nal strategy could be modeled as a collection with a two-part index. This can be nested collection objects, or a Map that uses a 2-valued tuple as an index.
42.2.1 Methods
evenMoney(self, hand ) Parameter hand (Hand) the hand which is being oered even money Returns true if this Player accepts the even money oer. This player always rejects this oer. insurance(self, hand ) Parameter hand (Hand) the hand which is being oered insurance Returns true if this Player accepts the insurance oer. This player always rejects this oer. split(self, hand ) Parameter hand (Hand) the hand which is being oered the opportunity to split Returns a new, empty Hand if this Player accepts the split oer for this Hand. The Player must create a new Hand, create an Ante Bet and place the on the new Hand on the BlackjackTable. If the oer is declined, both set Hand.splitDeclined to true and return null. This player splits when the hands cards ranks are aces or eights, and declines the split for all other ranks. doubleDown(self, hand ) Parameter hand (Hand) the hand which is being oered the opportunity to double down Returns true if this Player accepts the double oer for this Hand. The Player must also update the Bet associated with this Hand. This player tries to accept the oer when the hand points are 10 or 11, and the dealers up card is 7 to 10 or ace. Otherwise the oer is rejected.
270
Note that some games will restrict the conditions for a double down oer. For example, some games only allow double down on the rst two cards. Other games may not allow double down on hands that are the result of a split. hit(self, hand ) Parameter hand (Hand) the hand which is being oered the opportunity to hit Returns true if this Player accepts the hit oer for this Hand. If the dealer up card is from 2 to 6, there are four choices for the player. When the hand is 11 or less, hit. When the hand is a hard 12 to 16, stand. When the hand is a soft 12 to soft 16 (hard 2 to hard 6), hit. When the hand is 17 or more, stand. If the dealer up card is from 7 to 10 or an ace, there are four choices for the player. When the hand is 11 or less, double down. When the hand is a hard 12 to 16, hit. When the hand is a soft 12 to soft 16 (hard 2 to hard 6), hit. When the hand is 17 or more, stand. Otherwise, if the point total is 9 or less, accept the hit oer.
271
272
CHAPTER
FORTYTHREE
273
Its a 1:1 Outcome if the player does not have blackjack. Its a 3:2 bet if the player does have blackjack. Allocating this responsiblity to the Hand was a bad design decision. We should have allocated responsibility to the Game. We will need to add a method to the Game which compares a players Hand with the dealers Hand, sets the Outcome correctly, and resolves the bet.
43.2.1 Methods
adjustOdds(self, hand ) Parameter hand (Hand) The hand which has the ante bet odds corrected This method is used at the end of the game to resolve the players Ante bet. If the player went bust, their hand was already resolved. If the dealer went bust, all remaining bets are resolved as winners. The remaining situation (both player and dealer have non-bust hands) requires this BlackjackGame.adjustOdds() method. This method will set the hands outcomes odds to 3:2 if the player holds Blackjack.
274
CHAPTER
FORTYFOUR
CONCLUSION
The game of Blackjack has given us an opportunity to further extend and modify an application with a considerable number of classes and objects. These exercises gave us an opportunity to work from a base of software, extending and rening our design. We omitted concluding exercises which would integrate this package with the Simulator and collect statistics. This step, while necessary, doesnt include many interesting design decisions. The nal deliverable should be a working application that parses command-line parameters, creates the required objects, and creates an instance of Simulator to collect data. We have specically omitted delving into the glorious details of specic player strategies. We avoided these details for two reasons. Intellectual Property. We didnt create any strategies. Rather than get permission to quote existing blackjack strategies, we leave it to the interested student to either buy any of the available books on Blackjack or download a strategy description from the Internet. Irrelevant Complexity. We nd that all of the detailed programming required to implement a particular strategy doesnt have too much to do with object-oriented design. Rather, it is an exercise in construction of detailed if-statements. It appears to be the wrong focus for learning OO design. Next Steps. There are a number of logical next steps for the programmers looking to build skills in object-oriented design. Well split these along several broad fronts. Additional Technology. There are several technology directions that can be pursued for further design experience. This application is a command-line application, which makes it easy to build and test. Another area for building skills in design is the implementation of programs that make extensive use of a database. A graphical user interface (GUI) application will add the technology for graphical display, and responding to user input events. A web application will add the technology for HTTP web transactions, HTML, CSS, and potentially a database. Application Areas. We selected simulation because its part of the historical foundation for objectoriented programming. We selected casino games because they have a reasonable level of complexity. Clearly, numerous other application areas can be selected as the basis for problems. The number and variety of human endevors that can be automated is quite large. Moving beyond simulation or doing simulation on something more complex than a casino table game is a good next step.
275
Additional Depth in Design Patterns. Its possible to use additional and dierent design patterns to extend and rene the application that you have built. Any book or web site on OO design patterns will provide numerous examples of patterns. These can be used for add exibility to these casino game simulators.
276
Part V
277
A nished application includes more than just a working program. There are two additional questions. How do you know it works? That is, do you have any tests that demonstrate that it works correctly? We address this by creating unit tests. How is it designed? We address this by creating documentation within the programs source les. This documentation can be extracted to create a tidy, complete reference for all the classes and functions within our application.
279
280
CHAPTER
FORTYFIVE
281
45.1 Dependencies
Lets assume weve built two classes in some chapter; pretend that were building Card and Deck . One class denes a standard playing card and the other class deals individual card instances. We need unit tests for each class. Generally, unit tests are taken to mean that a class is tested in isolation. In our case, a unit test for Card is completely isolated because it has no dependencies. However, our Deck class depends on Card, leading us to make a choice. Either we have to create a Mock Card that can be used to test Deck in complete isolation, or our Deck test will depend on both Deck and Card. The choice depends on the relative complexity of Card, and whether or not Deck and Card will evolve independently. Some folks demand that all testing be done in complete isolation with a lot of mock classes. Other folks are less strict on this, recognizing that Deck and Card are very tightly coupled and Card is very simple. The Mock Card is almost as complex as Card.
45.2 Example
testCard.py
import unittest import card class TestCard( unittest.TestCase ): def setUp( self ): self.aceClubs= card.Card(1,card.Clubs) self.twoClubs= card.Card(2,card.Clubs) self.tenClubs= card.Card(10,card.Clubs) self.kingClubs= card.Card(13,card.Clubs) self.aceDiamonds= card.Card(1,card.Diamonds) def testString( self ): self.assertEquals( " AC", str(self.aceClubs) ) self.assertEquals( " 2C", str(self.twoClubs) ) self.assertEquals( "10C", str(self.tenClubs) ) def testOrder( self ): self.assertTrue( self.tenClubs < self.kingClubs ) self.assertFalse( self.tenClubs >= self.kingClubs ) self.assertTrue( self.kingClubs < self.aceClubs ) self.assertTrue( self.aceClubs == self.aceDiamonds ) if __name__ == "__main__": unittest.main()
282
1. Generally, we create a number of object instances in the setup method. In this case, we created ve distinct Card instances. These object constructors imply several things in our card module. (a) There will be a set of manifest constants for the suits: Clubs, Diamonds, Hearts and Spades. (b) The constructor (Card.__init__()) will accept a rank and a suit constant. Note that we didnt write tests to create all suits or all ranks. We can add these later. In some cases, where we are working in large teams, we may need to produce tests which exhaustively enumerate all possibe alternatives. For the purposes of learning OO design, we only need to sketch out our class by dening the tests it must pass. 2. In testString(), we exercise the __str__() method of the Card class to be sure that it formats cards correctly. These tests tell us what the formatting algorithm will look like. 3. In testOrder(), we exercise the __cmp__() method of the Card class to be sure that it compares card ranks correctly. Note that we have explicitly claimed that the equality test only checks the rank and ignores the suit; this is typical for Blackjack, but wont work well for Bridge or Solitaire. Note that we didnt exhaustively test all possible comparisons among the four cards we dened. We only need to execute the various paths within the __cmp__() method. When we initially create the test class, we may not have written the Card class yet. What we have to do is develop enough tests to get started, and add tests as we start writing our Card class. 4. This is the standard main program for unittest modules. Our initial Card class needs to have just enough of an API to allow the tests to run. Heres our skeleton Card class.
card.py
Diamonds= "D" Clubs= "C" Spades= "S" Hearts= "H" class Card( object ): def __init__( self, rank, suit ): pass def __str__( self ): return "" def __cmp__( self ): return 0
This class will minimally participate in the testing. It wont pass many tests, but it serves as a basis for developing the class implementation.
45.2. Example
283
284
CHAPTER
FORTYSIX
You can also add a test function to a module which runs doctest on the module. This test function should have a name which begins with _ to make it a name thats private to the module and not part fo the modules interface.
def _test(): import doctest doctest.testmod() if __name__ == "__main__": _test()
This _test() function is the main function of the module, so that when you run the module, it performs its internal doctests.
python roulette.py
Test-Driven Development. Note that the strict test-driven development (TDD) approach to unit testing is to build the tests rst, then write a class which at least doesnt crash, but may not pass all the tests. Once we have this in place, we can now debug the tests until everything looks right. This is called test-driven development, because the test cases come rst, driving the rest of the work.
285
The test-driven approach doesnt t well with doctest. Its very diicult to develop the test output without having the class available to exercise interactively. Also, we dont want to put comprehensive test scripts into our class docsting; they can easily get too long to be useful. Also, its diicult to fake the interactive log when designing the class. Its usually simpler to have a working class and then add test cases. Finally, we need to pick essential features for demonstration in the doctest comments. We cant easily write comprehensive tests that cover all features or all line of code in doctest comments. Limitations. Because of a few small gaps in Python 2s support for Unicode, its diicult to use doctest for testing the Card class. Specically, Python 2 source is assumed to be ASCII. The Unicode results of formatting a card cant easily be put into the Python source. Even if the explicit source encoding comment is used, the doctest module cant cope well with tests that involve Unicode. It can be challenging to do stand-alone unit tests with Mock classes to replace real classes. This can make for a rather long doctest string.
46.1 Example
Lets assume weve built two classes in some chapter; pretend that were building Card and Deck. One class denes a standard playing card and the other class deals individual card instances. Well dene some minimal doctests. The rst step is to develop our baseline class. Heres a module that seems like it works. This version uses ASCII suit names instead of Unicode suit names.
card.py - Initial
Clubs="C" Diamonds="D" Hearts="H" Spades="S" class Card( object ): """The card superclass, appropriate for number cards. .. todo:: Doctest goes here """ def __init__( self, rank, suit ): self.rank= rank self.suit= suit self.points= rank def hardValue( self ): return self.points def softValue( self ): return self.points def __eq__( self, other ): return self.rank == other.rank
286
def __lt__( self, other ): return self.rank < other.rank def __le__( self, other ): return self.rank <= other.rank def __gt__( self, other ): return self.rank > other.rank def __ge__( self, other ): return self.rank >= other.rank def __str__( self ): return self.unicode() def unicode (self ): return u"%d%s" % (self.rank, self.suit)
class AceCard( Card ): """The Ace class. .. todo:: Doctest goes here """ def __init__( self, suit ): self.rank= 14 self.suit= suit def hardValue( self ): return 1 def softValue( self ): return 11 def unicode (self ): return u"A%s" % (self.suit,)
Exercise the Class. Once we have the class, we need to exercise it using interactive Python. Heres what we saw.
MacBook-5:python slott$ python Python 2.6.3 (r263:75184, Oct 2 2009, 07:56:03) [GCC 4.0.1 (Apple Inc. build 5493)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> from card import * >>> c2d = Card( 2, Diamonds ) >>> str(c2d) '2D' >>> cas = AceCard( Spades ) >>> str(cas) 'AS' >>> cas.softValue() 11
During our session, small parts of the script show the preferred use cases for our class. We copy these from the interactive session and paste them into our class docstrings. Update the Docstrings. After we have some output that shows correct behavior of our class, we can put
46.1. Example
287
that output into the class docstrings. Heres our updated card.py module with doctest comments.
card.py - Revised
Clubs="C" Diamonds="D" Hearts="H" Spades="S" class Card( object ): """Create a new Ordinary (non-Ace, non-Face) Card. >>> c2d = Card( 2, Diamonds ) >>> str(c2d) '2D' """ etc. class AceCard( Card ): """The Ace class. >>> cas = AceCard( Spades ) >>> str(cas) 'AS' >>> cas.softValue() 11 """
288
testCard.py
import unittest import doctest import cards suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite(cards)) runner = unittest.TextTestRunner() runner.run(suite)
289
290
CHAPTER
FORTYSEVEN
PYTHON DOCUMENTATION
Application Program Interface (API) documentation is absolutely essential. The easiest and most reliable way to produce this documentation is by using a tool that examines the source itself and develops the document directly from the programs. By using cleverly formatted Python docstrings, we can augment that analysis with easy-to-understand descriptions. In the case of Python, there are several tools for extracting documentation from the source. Two populare ones are Epydoc http://epydoc.sourceforge.net/. Sphinx http://sphinx.pocoo.org/. Workow. Generally, the workow has the following outline. 1. Develop the skeleton class. 2. Develop unit tests for most features. 3. Rework the class until it passes the unit tests. This may involve adding or modifying tests as we understand the class better. This also involves writing docstrings for modules, classes and methods. 4. Revisit the modules, classes and methods, nishing each description using the ReStructured Text (RST) markup language. We wont cover all of this language, just enough to provide a sense of how the tool is used to make clean, professional API documentation. 5. Run the documentation tool (epydoc or sphinx) to create the documentation. Fix any errors in your docstring markup. Also, rework the documentation as necessary to be sure that youve capture important information about the class, the methods, the design decisions. Using the Epydoc. When you install Epydoc, it creates a script that you can use. To run Epydoc, youll use a command similar to the following.
epydoc -v --docformat restructuredtext --output apidoc card.py
The -v option provides detailed debugging information for incorrectly formatted docstrings. The --docformat option species that the various docstrings use RST markup. The --output option denes the directory to which the documentation is written. The arguments (in this case, card.py) is a list of package directories or module les to be documented. Note that RST is not the default markup language for Epydoc. You must either specify this on the command line or in the module source. To identify the formatting in each module you must provide the format as the value of a __docformat__ module global variable.
#!/usr/bin/env python """
291
Using Sphinx. When you install Sphinx, it creates a script that you can use. Generally, youll run the $ sphinx-quickstart script to create an empty documentation directory.
$ sphinx-quickstart
This interactive script will ask a number of questions, allowing you to pick sensible directories and le names. Youll need to include two extensions to enable Sphinx to create your API documentation. autodoc will create module documents based on the RST markup in the docstrings. autosummary will create summaries of packages and modules. Youll then add content to your index.rst le. This le is the root for the documentation. It can be very simple, or or it can do include other les to break a large document into manageable pieces. The most important commands are the following. To run Sphinx, youll use a command similar to the following.
$ sphinx-build -b html sourcedir builddir
ReStructured Text. Basic RST rules allow you to have a nice-looking document, with numbered and bulleted lists. You can include code examples and doctest results, also. With the inline markup, you can specify italic, bold or code samples. Additionally, RST oers eld markup to tie your documentation to specic pieces of Python syntax. This includes method parameters, return values, class instance variables, module variables, etc.
To include code code samples and doctest, youll use either the simple :: marker. Or the explicit .. code-block:: directive. Generally, the rst paragraph must be a pithy summary of the material to follow. This paragraph will often be used in overviews and index pages. Example testCard Module. For example, our module document might look like this.
#!/usr/bin/env python """The testCard module includes a number of unit tests for the Card and Deck classes in the card module. Overview ======== This module tests the following classes: - Card - Deck Card Tests ---------.. autoclass:: TestCards
Usage ===== This module uses the standard text runner, so it can be executed from the command line as follows:: python testcard.py """
1. We started with an overview paragraph. It summarizes the module in a single sentence. 2. We have an underlined section heading. In this document, the highest level of the outline is underlined with =s. The second level is underlined with -. RST is very exible with the underlining rules. 3. We have a bullet list. Simply indent, and begin the paragraph with -. Lists can be nested, the RST processor will work this out based on the indentation of the various sublists. 4. We used the .. autoclass:: directive to gather docstring documentation from the class docstring and include it in the overall module docstring. 5. We included a literal block of code after a paragraph which ends with ::. The following material is indented to show what should be formatted specially. Interpreted Text, or Inline Markup. In addition to basic document structure rules, RST includes a way to show that a span of characters should be interpreted specially. This is sometimes called Inline Markup because it is within the structural markup. *words* will render the words in italic. This can also be done with :emphasis:`words`. **words** will render the words in bold. This can also be done with :strong:`words`.
293
``words`` will render the words in a fixed font to indicate that it is a code sample. Literal programming text can also use :literal:`words`. Code samples can also be done with :samp:`word {replaceable}`. In this case, the material enclosed in {} is displayed in italics: word replaceable. Additionally, there are several cross-reference inline markup formats available for code elements. Heres an example of a paragraph using some inline markup to make some characters italic, other bold, and still others a xed-width nd that makes them looke like code.
This method computes the square root of a number, *n*. It returns a value, *r*, such that :samp:`r**2 == n`. **Note**. Square root of a negative number will raise a ``TypeError`` exception.
Classes and Modules. The following tags are used to dene specic variables in a module or class. :ivar v: description A description of the an instance variable of a class. Generally, this will be in the class level docstring, and will refer to one of the classs self. variables. :var v: description A global variable of a module. Generally, this will be in the module docstring, and will refer to one of the global variables created by the module. Heres an example of a class denition docstring that uses :ivar: tags.
class Card( object ): """A single playing card, suitable for Blackjack or Poker. While a suit is retained, it doesn't figure into the ordering of cards, as it would in Bridge.
294
**Reminder**. The internal rank of an Ace is 14. however, expects a value of 1. :ivar rank: the numeric rank of the card. :ivar suit: the string suit of the card. """
The constructor,
Directives. RST has a number of useful directives available for structuring the documentation. Sphinx uses all of the basic RST directives and adds a large number of additional directives. A directive is an RST markup command starts with a special .. line, and may include a number of lines indented within the directive. One of the common ones is the .. code-block:: directive. The :: is an essential part of the directive syntax. It looks like this.
Example. .. code-block:: python def fact(n): if n == 0: return 1 return n*fact(n-1) More Text.
The directive starts with the .. line and continues through the indented section. The directive content ends with the end of the indentation. Epydoc uses the :tag: syntax; it doesnt dene new RST directives. Because of this, there are two styles for some of the more advanced markup: an Epydoc tag style and a Sphinx directive style. Standard Paragraphs. There are several kinds of standard paragraphs that are part of any well-written document set. These include things like Related Topics. Epydoc usess the :seealso: tag to generate a Related Topics paragaph with references to other documents. Sphinx uses the .. seealso:: directive line to generate additional references. Admonitions (Notes, Warnings, etc). Epydoc uses tags like :note:, :attention:, :bug:, and :warning: tags to generate standard types of admonition paragraphs. Sphinx uses directives like .. note::, .. attention::, and .. warning:: directives to generate standard types of admonition paragraphs. Other adminitions include caution, danger, error, hint, important, tip. Status. You can track the development and deployment status of your programs. Tags like :version:, :todo:, :deprecated:, :since:, :status:, and change: are used by both Epydoc and Sphinx. Bibliographic Information. You can provide standard publication information. Tags like :author:, :organization:, :copyright:, :license: and :contact: can be used.
295
..
..
attribute:: suit The string suit of the card. This should be from the named constants (Clubs, Diamonds, Hearts, Spades).
""" def __init__( self, rank, suit ): """Build a card with a given rank and suit. :param rank: numeric rank, 2-10. Aces and FaceCards are separate. :type rank: integer in the range 2 to 10 inclusive. :param suit: String suit, use one of the module constants. """ self.rank= rank self.suit= suit self.points= rank def hardValue( self ):
296
"""For blackjack, the hard value of this card. :returns: int """ return self.points def softValue( self ): """For blackjack, the soft value of this card. :returns: int """ return self.points def __eq__( self, other ): """Compare cards, ignoring suit. >>> Card( 2, Diamonds ) == Card( 2, Spades ) True >>> Card( 2, Diamonds ) == Card( 10, Spades ) False """ return self.rank == other.rank def __lt__( self, other ): """Compare cards, ignoring suit. >>> Card( 2, Diamonds ) < Card( 3, Spades ) True >>> Card( 10, Diamonds ) < Card( 10, Spades ) False """ return self.rank < other.rank def __le__( self, other ): return self.rank <= other.rank def __gt__( self, other ): return self.rank > other.rank def __ge__( self, other ): return self.rank >= other.rank def __str__( self ): return "%d%s" % (self.rank, self.suit)
297
298
Part VI
Back Matter
299
CHAPTER
FORTYEIGHT
BIBLIOGRAPHY
48.1 Use Cases 48.2 Computer Science 48.3 Design Patterns 48.4 Statistics 48.5 Python 48.6 Java 48.7 Casino Games
301
302
CHAPTER
FORTYNINE
TOOLSET
The following toolset was used for production of this book. Python 2.6.3. Sphinx 0.63. Docutils 0.5. Komodo Edit 5.2.2. pyPDF 1.12. MacTeX-2008.
303
304
CHAPTER
FIFTY
305
306
BIBLIOGRAPHY
[Jacobson92] Ivar Jacobson, Magnus Christerson, Patrik Jonsson, Gunnar vergaard. Object-Oriented Software Engineering. A Use Case Driven Approach. 1992. Addison-Wesley. 0201544350. [Jacobson95] Ivar Jacobson, Maria Ericsson, Agenta Jacobson. The Object Advantage. Business Process Reengineering with Object Technology. 1995. Addison-Wesley. 0201422891. [Parnas72] Parnas D. On the Criteria to Be Used in Decomposing Systems into Modules. 1053-1058. 1972. Communications of the ACM. [Gamma95] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns. Elements of Object-Oriented Software. 1995. Addison-Wesley Professional. 0201633612. [Larman98] Craig Larman. Applying UML and Patterns. An Introduction to Object-Oriented Analysis and Design. 1998. Prentice-Hall. 0137488807. [Neter73] John Neter, William Wasserman, G. A. Whitmore. Fundamental Statistics for Business and Economics. 4. 1973. Allyn and Bacon, Inc.. 020503853. [vanRossum04] Guido van Rossum, Fred L. Drake. Python Documentation. 2004. Python Labs. [Silberstang05] Edwin Silberstang. The Winners Guide to Casino Gambling. 4th. 2005. Owl Books. 0805077650. [Skiena01] Steven Skiena. Calculated Bets. Computers, Gambling, and Mathematical Modeling to Win. 2001. Cambridge University Press. 0521009626. [Shackleford04] Michael Shackleford. The Wizard Of Odds. 2004.
307