Skip to main content
Engineering LibreTexts

11.8: A Complete Example

  • Page ID
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)

    Let’s design a morph to roll a die1. Clicking on it will display the values of all sides of the die in a quick loop, and another click will stop the animation.

    The die in Morphic.
    Figure \(\PageIndex{1}\): The die in Morphic.

    \(\bigstar\) Define the die as a subclass of BorderedMorph instead of Morph, because we will make use of the border.

    Code \(\PageIndex{1}\) (Squeak): Defining the Die Morph

    BorderedMorph subclass: #DieMorph
        instanceVariableNames: 'faces dieValue isStopped'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'SBE-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.

    Code \(\PageIndex{2}\) (Squeak): Creating a New Die with the Number of Faces We Like

    DieMorph class»faces: aNumber
        ↑ self new faces: aNumber

    The initialize method is defined on the instance side in the usual way; remember that new sends initialize to the newly-created instance.

    Code \(\PageIndex{3}\) (Squeak): Initializing Instances of DieMorph

        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:

    Code \(\PageIndex{4}\) (Squeak): Setting the Number of Faces of the Die

    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:

    1. The class method DieMorph class»faces: sends new to DieMorph class.
    2. The method for new (inherited by DieMorph class from Behavior) creates the new instance and sends it the initialize message.
    3. The initialize method in DieMorph sets faces to an initial value of 6.
    4. DieMorph class»new returns to the class method DieMorph class»faces:, which then sends the message faces: 9 to the new instance.
    5. The instance method DieMorph»faces: now executes, setting the faces instance variable to 9.

    Before defining drawOn:, we need a few methods to place the dots on the displayed face:

    Code \(\PageIndex{5}\) (Squeak): Nine Methods for Placing Points on the Faces of the Die

        ↑{0.25@0.25 . 0.75@0.75}
        ↑{0.25@0.25 . 0.75@0.75 . 0.5@0.5}
        ↑{0.25@0.25 . 0.75@0.25 . 0.75@0.75 . 0.25@0.75}
        ↑{0.25@0.25 . 0.75@0.25 . 0.75@0.75 . 0.25@0.75 . 0.5@0.5}
        ↑{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.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 1 \(\times\) 1; 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.

    Code \(\PageIndex{6}\) (Squeak): Drawing the Die Morph

    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 Smalltalk. 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, here ('face', dieValue asString) asSymbol. You will encounter this use of perform: quite regularly.

    Code \(\PageIndex{7}\) (Squeak): Drawing a Single Dot on a Face

    DieMorph»drawDotOn: aCanvas at: aPoint
            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.

    \(\bigstar\) We can already create a die instance from a workspace:

    (DieMorph faces: 6) openInWorld.

    To change the displayed face, we create an accessor that we can use as myDie dieValue: 4:

    Code \(\PageIndex{8}\) (Squeak): Setting the Current Value of the Die

    DieMorph»dieValue: aNumber
        (aNumber isInteger
            and: [aNumber > 0]
            and: [aNumber <= faces])
            [dieValue := aNumber.
            self changed]

    Now we will use the animation system to show quickly all the faces:

    Code \(\PageIndex{9}\) (Squeak): Animating the Die

    ↑ 100
        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:

    Code \(\PageIndex{10}\) (Squeak): Handling Mouse Clicks to Start and Stop the Animation

    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.

    1. NB: One die, two dice.

    This page titled 11.8: A Complete Example is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by Andrew P. Black, Stéphane Ducasse, Oscar Nierstrasz, Damien Pollet via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.