While working on a small Tetris-like clone in Pharo, I found an opportunity to reuse some code.
It's common for Tetris implementations to show a small preview of the tetrimino that comes next. Here's how it looks:
I wanted to create a separate Morphic component to show this preview. But I didn't want to duplicate the code required to paint the tetriminos. Sharing this code will allow me to have just one place to change the way tetriminos look. Also I didn't want to create a base class on top of both the game and the preview Morphs.
While reading about Pharo and Squeak I found that it supports Traits. The Pharo collaborActive book provides a nice explanation of how to use traits. Here's a quick definition from this document:
... traits are just groups of methods that can be reused in different classes.
This is exactly what I needed for reusing the tetrimino matrix drawing code. The following code shows the code defined in the game matrix Morph component.
drawOn: canvas
"Draws the current game state"
|rows columns currentValue rectangle currentColor cellWidth cellHeight|
rows := gameState size x.
columns := gameState size y.
super drawOn: canvas.
cellWidth := ((self width) / columns) asFloat truncated.
cellHeight := ((self height) / rows) asFloat truncated.
1 to: rows do: [ :row |
1 to: columns do: [ :column|
currentValue := gameState at: row at: column .
currentValue ~= 0 ifTrue: [
currentColor := (colors at: currentValue).
rectangle := Rectangle left: (self bounds left)
canvas frameAndFillRectangle: rectangle
fillColor: currentColor
borderWidth: 1
borderColor: (Color white).
]
]
].
Moving this code to a trait implies that I have to pass all instance state variables as an argument of the draw method.
The definition of the trait looks like this:
Trait named: #TTetriminoDrawing
uses: {}
category: 'TryTrix'
And the definition of the method inside this trait looks like this:
drawGameStateOn: canvas
width: areaWidth height: areaHeight
columns: columns rows: rows
morphBounds: morphBounds
matrix: contentMatrix
colors: colorPalette
"Draw the contents of the specified matrix on the given canvas"
|cellWidth cellHeight currentValue currentColor rectangle|
cellWidth := (areaWidth / columns) asFloat truncated.
cellHeight := (areaHeight / rows) asFloat truncated.
1 to: rows do: [ :row |
1 to: columns do: [ :column|
currentValue := contentMatrix at: row at: column .
currentValue ~= 0 ifTrue: [
currentColor := (colorPalette at: currentValue).
rectangle := Rectangle left: (morphBounds left) + ((column - 1)*cellWidth)
right: (morphBounds left) + ((column - 1)*cellWidth) + cellWidth
top: (morphBounds top) + ((row - 1)*cellHeight )
bottom: (morphBounds top) + ((row - 1)*cellHeight ) + cellHeight.
canvas frameAndFillRectangle: rectangle
fillColor: currentColor
borderWidth: 1
borderColor: (Color white).
]
]
].
Now I can use this trait inside each morph definition:
Morph subclass: #TryTrixMorph
uses: TTetriminoDrawing
instanceVariableNames: 'gameState colors eventHandlers'
classVariableNames: ''
category: 'TryTrix'
Morph subclass: #TetriminoPreview
uses: TTetriminoDrawing
instanceVariableNames: 'matrix'
classVariableNames: ''
category: 'TryTrix'
This code can be found in here.