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!!'.].
].