Ubuntu insights, Programming in groovy, java, et als!

Sunday, March 18, 2012

TicTacToe Game in Pharo Smalltalk

For an absolute pharo/smalltalk beginner, I think this would be a good place to start with. This is a simple TicTacToe implementation with just three classes : TicTacToe, TicTacToeCell and TicTacToeModel using Morphs. Works on MVC based architecture with view and model separate (no specific class for controller though).

Object subclass: #TicTacToe
instanceVariableNames: 'container model'
classVariableNames: ''
poolDictionaries: ''
category: 'VK-Games'

initialize 
container := Morph new 

              layoutPolicy: TableLayout new; 
              color: Color transparent.
model := TicTacToeModel new:3.
self addRows.
self addControls.
^self.

addRows
| rowMorph aCell rowCol |
1 to:3 do:[ :row |
rowMorph := Morph new layoutPolicy: RowLayout new.
1 to: 3 do: [ :col |
aCell := TicTacToeCell new.
aCell setModel: (model) row: row col: col.
rowMorph addMorph: aCell.
].
container addMorph: rowMorph.
]

addControls
| rowMorph newGameButton exitGameButton |
rowMorph := Morph new 

             layoutPolicy: RowLayout new; 
             color: Color transparent.
newGameButton := self createCtrlLabelled: 'New'      onClickExecutes: [self restart].
exitGameButton := self createCtrlLabelled: 'Exit'  onClickExecutes: [container delete].
rowMorph addMorph: exitGameButton.
rowMorph addMorph: newGameButton.
container addMorph: rowMorph.

createCtrlLabelled: aString onClickExecutes: aBlock
| aCtrlButton |
aCtrlButton := SimpleButtonMorph new label: aString.
aCtrlButton color: (Color black alpha: 0.2).
aCtrlButton extent: 60@30.
aCtrlButton on: #click send: #value to: aBlock.
^aCtrlButton.

open 
container openInWorld.

restart
container delete.
Smalltalk garbageCollect.
TicTacToe new open.

*****************************************

SimpleButtonMorph subclass: #TicTacToeCell
instanceVariableNames: 'parentModel rowNum colNum'
classVariableNames: ''
poolDictionaries: ''
category: 'VK-Games'



initialize 
super initialize.
self label: ''.
self extent: 40@40.
self on: #click send: #value to: (self onClickExecutionBlock).
^self.



setModel: ticTacToeModel row: aRow col: aCol
parentModel := ticTacToeModel.
rowNum := aRow.
colNum := aCol.



onClickExecutionBlock
^[
(self label size) == 0
ifTrue:[
self label: (parentModel updateAtRow: rowNum 
                Col: colNum).
parentModel checkWinCondition.
self extent: 40@40.
].
 ]


***************************************** 

Matrix subclass: #TicTacToeModel
instanceVariableNames: 'filledCellCount currentFill winner'
classVariableNames: ''
poolDictionaries: ''
category: 'VK-Games'

initialize 
super initialize.
filledCellCount := 0.
currentFill := nil.
winner := nil.

updateAtRow: r Col: c
currentFill == nil
ifTrue:[ currentFill := 'X'. ]
ifFalse:[
currentFill == 'X'
ifTrue: [ currentFill := 'O'. ]
ifFalse: [ currentFill := 'X'. ]
].
self at: r at: c put: currentFill.
filledCellCount := filledCellCount + 1.
^currentFill.

checkWinCondition
filledCellCount >= 5 "for optimization. Win can occur minimum at 5th turn"
ifTrue: [
Transcript show: 'Yes'.
1 to: 3 do: [:idx |
self checkWinConditionInRow: idx.
self checkWinConditionInColumn: idx.
].
self checkWinConditionInDiagonals.
].
checkWinConditionInRow: rowNum
|set|
winner isNil
ifTrue: [
set := (self atRow: rowNum) asSet.
self checkWinConditionInSet: set
].
^winner.

checkWinConditionInColumn: colNum
|set|
winner isNil
ifTrue: [
set := (self atColumn: colNum) asSet.
self checkWinConditionInSet: set.
].
^winner.

checkWinConditionInDiagonals
|set1 set2 |
winner isNil
ifTrue: [
set1 := (self diagonal) asSet.
set2 := Set newFrom: {(self at: 1 at: 3). (self at: 2 at: 2). (self at: 3 at: 1)} asOrderedCollection.
self checkWinConditionInSet: set1.
self checkWinConditionInSet: set2.
].
^winner.

checkWinConditionInSet: aSet
aSet size == 1
ifTrue: [
(aSet includes: 'X')
ifTrue: [winner := 'P1'. Transcript open. Transcript show: 'Player 1 is the winner!!'.].
(aSet includes: 'O')
ifTrue: [winner := 'P2'.  Transcript open. Transcript show: 'Player 2 is the winner!!'.].
].



9 comments:

mozillanerd said...

I find that RawLayout is undefined. Where can I find this?

Vamsi Emani said...

Hi. It is not RawLayout. It is RowLayout which comes with the default Pharo base.

Unknown said...

On Pharo 1.3, I have a bug due to the fact that Set does not accept nil element.
In checkWinConditionInRow: , atRow return sometime nil.
And asSet try to add those nil element => Error
Peharps, nill could be replaced by #empty element, or ' ' char ?

Anonymous said...

i am completely new to smalltalk.
I copy pasted all the code in and i get an error when i try to run it and i cannot figure out what to do about it. something like

MessageNotUnderstood:AnObsoleteTicTacToeModel>>updateAtRow:Col:

i then click on

AnObsoleteTicTacToeModel(Object) doesNotUnderstand: #undateAtRow:Col:

and at the bottome it displays

doesNotUnderstand: t1
|t2 t3|
(t2 := MessageNotUnderstood new) message: t1;
receiver:self.
t3 := t2 signal.
^ t2 reachedDefaultHandler
ifTrue: [t1 sentTo: self]
ifFalse: [t3]

:=t2 signal is highlighted in blue

tic tac toe games said...

Tic Tac Toe, the best preference of children for their playing time. It just sharpen the minds and a good fun for leisure time.

Alex moner said...

I beyond doubt appreciate your articles and blogs.quality wordpress themes

Alex moner said...

I absolutely respect and appreciate your point on each and every object.quality wordpress themes

jek said...
This comment has been removed by the author.
jek said...

Are you finding the watch for women? Check out these Best Mechanical Watch For Women

Post a Comment