13.8: A Complete Example
- Page ID
- 43178
Let’s design a morph to roll a die. Clicking on it will display the values of all sides of the die in a quick loop, and another click will stop the animation.
Define the die as a subclass of BorderedMorph
instead of Morph
, because we will make use of the border.
BorderedMorph subclass: #DieMorph instanceVariableNames: 'faces dieValue isStopped' classVariableNames: '' package: 'PBE-Morphic'
The instance variable faces
records the number of faces on the die; we allow dice with up to 9 faces! dieValue
records the value of the face that is currently displayed, and isStopped
is true if the die animation has stopped running. To create a die instance, we define the faces: n
method on the class side of DieMorph
to create a new die with n
faces.
DieMorph class >> faces: aNumber ^ self new faces: aNumber
The initialize
method is defined on the instance side in the usual way; remember that new automatically sends initialize
to the newly-created instance.
DieMorph >> initialize super initialize. self extent: 50 @ 50. self useGradientFill; borderWidth: 2; useRoundedCorners. self setBorderStyle: #complexRaised. self fillStyle direction: self extent. self color: Color green. dieValue := 1. faces := 6. isStopped := false
We use a few methods of BorderedMorph
to give a nice appearance to the die: a thick border with a raised effect, rounded corners, and a color gradient on the visible face. We define the instance method faces:
to check for a valid parameter as follows:
DieMorph >> faces: aNumber "Set the number of faces" ((aNumber isInteger and: [ aNumber > 0 ]) and: [ aNumber <= 9 ]) ifTrue: [ faces := aNumber ]
It may be good to review the order in which the messages are sent when a die is created. For instance, if we start by evaluating DieMorph faces: 9
:
- The class method
DieMorph class >> faces:
sends new toDieMorph class
. - The method for new (inherited by
DieMorph class
fromBehavior
) creates the new instance and sends it theinitialize
message. - The
initialize
method inDieMorph
setsfaces
to an initial value of 6. DieMorph class >> new
returns to the class methodDieMorph class >> faces:
, which then sends the messagefaces: 9
to the new instance.- The instance method
DieMorph >> faces:
now executes, setting thefaces
instance variable to 9.
Before defining drawOn:
, we need a few methods to place the dots on the displayed face:
DieMorph >> face1 ^ {(0.5 @ 0.5)}
DieMorph >> face2 ^{0.25@0.25 . 0.75@0.75}
DieMorph >> face3 ^{0.25@0.25 . 0.75@0.75 . 0.5@0.5}
DieMorph >> face4 ^{0.25@0.25 . 0.75@0.25 . 0.75@0.75 . 0.25@0.75}
DieMorph >> face5 ^{0.25@0.25 . 0.75@0.25 . 0.75@0.75 . 0.25@0.75 . 0.5@0.5}
DieMorph >> face6 ^{0.25@0.25 . 0.75@0.25 . 0.75@0.75 . 0.25@0.75 . 0.25@0.5 . 0.75@0.5}
DieMorph >> face7 ^{0.25@0.25 . 0.75@0.25 . 0.75@0.75 . 0.25@0.75 . 0.25@0.5 . 0.75@0.5 . 0.5@0.5}
DieMorph >> face8 ^{0.25@0.25 . 0.75@0.25 . 0.75@0.75 . 0.25@0.75 . 0.25@0.5 . 0.75@0.5 . 0.5@0.5 . 0.5@0.25}
DieMorph >> face9 ^{0.25@0.25 . 0.75@0.25 . 0.75@0.75 . 0.25@0.75 . 0.25@0.5 . 0.75@0.5 . 0.5@0.5 . 0.5@0.25 . 0.5@0.75}
These methods define collections of the coordinates of dots for each face. The coordinates are in a square of size 1x1; we will simply need to scale them to place the actual dots.
The drawOn:
method does two things: it draws the die background with the super-send, and then draws the dots.
DieMorph >> drawOn: aCanvas super drawOn: aCanvas. (self perform: ('face', dieValue asString) asSymbol) do: [:aPoint | self drawDotOn: aCanvas at: aPoint]
The second part of this method uses the reflective capacities of Pharo. Drawing the dots of a face is a simple matter of iterating over the collection given by the faceX
method for that face, sending the drawDotOn:at:
message
for each coordinate. To call the correct faceX
method, we use the perform:
method which sends a message built from a string, ('face', dieValue asString) asSymbol
. You will encounter this use of perform:
quite regularly.
DieMorph >> drawDotOn: aCanvas at: aPoint aCanvas fillOval: (Rectangle center: self position + (self extent * aPoint) extent: self extent / 6) color: Color black
Since the coordinates are normalized to the [0:1]
interval, we scale them to the dimensions of our die: self extent * aPoint
.
We can already create a die instance from a playground (see result on Figure \(\PageIndex{2}\)):
(DieMorph faces: 6) openInWorld.
To change the displayed face, we create an accessor that we can use as myDie dieValue: 5
:
DieMorph >> dieValue: aNumber ((aNumber isInteger and: [ aNumber > 0 ]) and: [ aNumber <= faces ]) ifTrue: [ dieValue := aNumber. self changed ]
Now we will use the animation system to show quickly all the faces:
DieMorph >> stepTime ^ 100
DieMorph >> step isStopped ifFalse: [self dieValue: (1 to: faces) atRandom]
Now the die is rolling!
To start or stop the animation by clicking, we will use what we learned previously about mouse events. First, activate the reception of mouse events:
DieMorph >> handlesMouseDown: anEvent ^ true
DieMorph >> mouseDown: anEvent anEvent redButtonPressed ifTrue: [isStopped := isStopped not]
Now the die will roll or stop rolling when we click on it.