Sunday, October 18, 2015

Using traits to reuse code in Pharo

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.

No comments: