ArticleS. MicahMartin.
SmalltalkBowling [add child]

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.


TestCase subclass: #BowlingTest
instanceVariableNames: 'game'
classVariableNames: ''
poolDictionaries: ''
category: 'Bowling'!

!BowlingTest methodsFor: 'testing' stamp: 'MDM 4/27/2006 18:11'!
setUp
game := Game new! !

!BowlingTest methodsFor: 'testing' stamp: 'MDM 4/27/2006 18:19'!
testAllOnes
1 to: 20 do: [:i | game roll: 1].
self should: [game score = 20]! !

!BowlingTest methodsFor: 'testing' stamp: 'MDM 4/30/2006 19:52'!
testAllSpares
1 to: 21 do: [ :i | game roll: 5 ].
self should: [game score = 150]! !

!BowlingTest methodsFor: 'testing' stamp: 'MDM 4/27/2006 18:05'!
testGutterGame
1 to: 20 do: [:i | game roll: 0].
self should: [game score = 0]! !

!BowlingTest methodsFor: 'testing' stamp: 'MDM 5/1/2006 12:26'!
testHeartBreaker
1 to: 11 do: [ :i | game roll: 10 ].
game roll: 9.
self should: [game score = 299]! !

!BowlingTest methodsFor: 'testing' stamp: 'MDM 4/30/2006 19:20'!
testOneSpare
game roll: 5.
game roll: 5.
game roll: 1.
1 to: 17 do: [:i | game roll: 0].
self should: [game score = 12]! !

!BowlingTest methodsFor: 'testing' stamp: 'MDM 4/30/2006 19:44'!
testOneStrike
game roll: 10.
game roll: 1.
game roll: 2.
1 to: 16 do: [:i | game roll: 0].
self should: [game score = 16]! !

!BowlingTest methodsFor: 'testing' stamp: 'MDM 5/1/2006 12:26'!
testPerfectGame
1 to: 12 do: [ :i | game roll: 10 ].
self should: [ game score = 300 ]! !


Object subclass: #Game
instanceVariableNames: 'rolls'
classVariableNames: ''
poolDictionaries: ''
category: 'Bowling'!

!Game methodsFor: 'scoring' stamp: 'MDM 5/3/2006 19:01'!
init
rolls := OrderedCollection new! !

!Game methodsFor: 'scoring' stamp: 'MDM 5/3/2006 19:02'!
roll: pins
rolls add: pins.! !

!Game methodsFor: 'scoring' stamp: 'MDM 5/3/2006 19:03'!
score
| roll score |
roll := 1.
score := 0.
1 to: 10 do: [:i |
(rolls at: roll) = 10
ifTrue: [
score := score + 10 + (rolls at: roll + 1) + (rolls at: roll + 2).
roll := roll + 1
]
ifFalse: [
(rolls at: roll) + (rolls at: roll + 1) = 10
ifTrue: [ score := score + 10 + rolls at: roll + 2]
ifFalse: [ score := score + (rolls at: roll) + (rolls at: roll + 1)].
roll := roll + 2
].
].
^ score! !

"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!

Game class
instanceVariableNames: ''!

!Game class methodsFor: 'as yet unclassified' stamp: 'MDM 4/27/2006 18:23'!
new
^ self basicNew init; yourself! !



!commentForm -r
 Tue, 29 Aug 2006 02:03:35, Jamesl, Yay, Smalltalk ...
Very interested to see someone go 'back' to use some Smalltalk.

Other than the 'click' frustration that you experienced, what else did you 'feel' about the
language ?

I'm a smalltalk zealot but get paid to do java, that's just how it is.

Rgs,James.
 Thu, 15 Jun 2006 08:40:37, snoobab, Good to see some smalltalk!
Hi

Great to see Smalltalk code! I have been Java zealot for 6 years now and made a decision 2 months ago to learn Smalltalk. And I absolutley love Smalltalk's simplicity and ideas and am hoping to apply these ideas in my paid software development. I'd love to find a job in South Africa using some Smalltalk.

Must say that you quickly learned alot of Smalltalk on a train ride!
BTW you don't have to override the 'new' method. You could simply implement the 'initialize' method on the instance side. This is called via the normal object creation. (Which can be found in the 'new' method on the instance side of class Behavior)
 Fri, 12 May 2006 22:49:35, Dave Astels, Bowling in ruby
David Chelimsky posted one on his blog here
 Fri, 12 May 2006 22:48:39, ,
 Tue, 9 May 2006 21:01:15, Francesco Rizzi, see what happens ?
..I'm downloading Ruby now.
:)
I wonder if I'll find a Ruby version of the bowling kata anywhere...

but this was about Smalltalk originally.. I got to admit that languages where you don't need { and } around blocks that include more than one line scare me !
 Tue, 9 May 2006 18:03:14, Dave Astels, sSpec on the way
I'm currently working on a new version of sSpec (which is aligned with the latest rSpec). The first release will be for VW7.4, with a squeak version to follow.
 Sun, 7 May 2006 19:33:09, Francesco Rizzi, rSpec ?
cool...have you had a chance to look at rSpec (and the whole BDD vs TDD philosophy) ?

Yes! Dave assures me that I was the first user of the rSpec in it's latest form. -Micah Martin
 Thu, 4 May 2006 00:48:13, Randy Coulman, Minor suggestions
Overall, it doesn't look too bad for a first effort. A couple of comments:

1. I haven't used Squeak, so maybe it doesn't have this method, but I'd be surprised. When you write a loop that doesn't need to use the index variable, you can do n timesRepeat: [... whatever ...] instead of 1 to: n do: [:i | ... whatever ...].

2. SUnit also has assert: and deny: methods that expect Booleans in addition to should: and shouldnt: that expect blocks. So instead of self should: [ game score = 300 ], you could put self assert: game score = 300. It's a matter of taste, really, so either way is fine.

3. In VisualWorks[?] Smalltalk, there are some add-on tools that make it much easier to run tests and find failures, so not as many clicks are required. I'd be surprised if someone hasn't done the same for Squeak.

Great tips! Thanks Randy -Micah Martin