Smalltalk Bowling

I’ve been meaning to learn Smalltalk for years now. Recently Dave Astels and I have a had a few discussion on the topic. They all seem to circulate around the theme of Smalltalk being a key OO language of the past, and again in the near future.

Now that Ruby has finally captures the attention it deserves, it’s not long before Smalltalk is back in the spotlight.

A few days ago, when he joined me for a train ride, I seized the opportunity and asked him to help me write some Smalltalk. He gave me a copy of Squeak and off we went to write the Bowling game.

Even though we only got through the first two test cases, Dave’s a good teacher and I was able to finish it on my own. The code is below.

My first impression of Smalltalk: “Damn, this requires a lot of clicking”. It requires two clicks just to run a test and another 5 clicks to see what failed. Dave gave me funny looks when I complained about this but I couldn’t believe people programmed this way.

Later my dad, remembering a demo by Kent Beck, reassured me that there are keyboard shortcuts for everything. Thank goodness!

In general it’s a sweet language. Experience with Ruby and Objective C exposed me to many of the concepts so there weren’t many surprises.

Perhaps the most peculiar thing I ran into was the Smalltalk equivalent to if/else statements. The ifTrue: ifFalse: structure makes perfect sense but it was unexpected. An example can be seen in the score method.

This is my first Smalltalk program and I’m sure I’ve made some newbie blunders. Feel free to rip my code to pieces in your comments. I’d love to hear advise and learn Smalltalk idiosyncrasies.

 1 TestCase subclass: #BowlingTest
 2     instanceVariableNames: 'game'
 3     classVariableNames: ''
 4     poolDictionaries: ''
 5     category: 'Bowling'!
 6 
 7 !BowlingTest methodsFor: 'testing' stamp: 'MDM 4/27/2006 18:11'!
 8 setUp
 9     game := Game new! !
10 
11 !BowlingTest methodsFor: 'testing' stamp: 'MDM 4/27/2006 18:19'!
12 testAllOnes
13     1 to: 20 do: [:i | game roll: 1].
14     self should: [game score = 20]! !
15 
16 !BowlingTest methodsFor: 'testing' stamp: 'MDM 4/30/2006 19:52'!
17 testAllSpares
18     1 to: 21 do: [ :i | game roll: 5 ].
19     self should: [game score = 150]! !
20 
21 !BowlingTest methodsFor: 'testing' stamp: 'MDM 4/27/2006 18:05'!
22 testGutterGame
23     1 to: 20 do: [:i | game roll: 0].
24     self should: [game score = 0]! !
25 
26 !BowlingTest methodsFor: 'testing' stamp: 'MDM 5/1/2006 12:26'!
27 testHeartBreaker
28     1 to: 11 do: [ :i | game roll: 10 ].
29     game roll: 9.
30     self should: [game score = 299]! !
31 
32 !BowlingTest methodsFor: 'testing' stamp: 'MDM 4/30/2006 19:20'!
33 testOneSpare
34     game roll: 5.
35     game roll: 5.
36     game roll: 1.
37     1 to: 17 do: [:i | game roll: 0].
38     self should: [game score = 12]! !
39 
40 !BowlingTest methodsFor: 'testing' stamp: 'MDM 4/30/2006 19:44'!
41 testOneStrike
42     game roll: 10.
43     game roll: 1.
44     game roll: 2.
45     1 to: 16 do: [:i | game roll: 0].
46     self should: [game score = 16]! !
47 
48 !BowlingTest methodsFor: 'testing' stamp: 'MDM 5/1/2006 12:26'!
49 testPerfectGame
50     1 to: 12 do: [ :i | game roll: 10 ].
51     self should: [ game score = 300 ]! !
52 Object subclass: #Game
53     instanceVariableNames: 'rolls'
54     classVariableNames: ''
55     poolDictionaries: ''
56     category: 'Bowling'!
57 
58 !Game methodsFor: 'scoring' stamp: 'MDM 5/3/2006 19:01'!
59 init
60     rolls := OrderedCollection new! !
61 
62 !Game methodsFor: 'scoring' stamp: 'MDM 5/3/2006 19:02'!
63 roll: pins  
64     rolls add: pins.! !
65 
66 !Game methodsFor: 'scoring' stamp: 'MDM 5/3/2006 19:03'!
67 score
68     | roll score |
69     roll := 1.
70     score := 0.
71     1 to: 10 do: [:i |
72         (rolls at: roll) = 10
73         ifTrue: [
74             score := score + 10 + (rolls at: roll + 1) + (rolls at: roll + 2).
75             roll := roll + 1
76             ]
77         ifFalse: [
78             (rolls at: roll) + (rolls at: roll + 1) = 10 
79             ifTrue: [ score := score + 10 + rolls at: roll + 2] 
80             ifFalse: [ score := score + (rolls at: roll) + (rolls at: roll + 1)].
81             roll := roll + 2
82             ].
83         ].
84     ^ score! !
85 
86 "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
87 
88 Game class
89     instanceVariableNames: ''!
90 
91 !Game class methodsFor: 'as yet unclassified' stamp: 'MDM 4/27/2006 18:23'!
92 new
93     ^ self basicNew init; yourself! !
Micah Martin, Chairman, Founder

Micah Martin is co-founder of 8th Light and is known for his open source work such as FitNesse, Limelight, Joodo, and Speclj.