This worksheet is less of a step-by-step "cook book" that the previous ones. Instead, we suggest two projects, and give you some hints as to how to program them in Squeak using Morphic. You probably won't have time to do both, so pick the one that looks more interesting to you. Of course, if you and your partner wish, you could tackle some completely different project.
I'm not going to talk about design at all. This is not because design is unimportantquite the contrary! But I'm assuming that you are proficient in OO design and OO programming in another language.
The goal in this project is to implement a clock, the kind with a face and two (or three) hands. You can make it look pretty much as you like, but it should, of course, keep time! Here are some suggestions to make the project more interesting.
OK, how might you get started? It is always good to "shop" for functionality that others have already built, which you can reuse, In fact, the normal way of figuring out how to use some piece of a Smalltalk system is to do a senders of ... and see how others are already using it.
In this case, if you do a find ... "clock" in a system browser, you will find that there is already a ClockMorph. It's a digital clock, but worth examining nonetheless. ClockMorph uses a number of techniques that you have already seen, for example, a step method that is called once every second. Notice the use of Time now to obtain the time of day from the system clock. Why is this preferable to simply adding one second to the previous time?
ClockMorph is a subclass of StringMorph, which already handles the updating of the display. So, when the contents of the Morph is changed (by sending the message self contents: time in the method for step), the display will change immediately.
Incidentally, you can make a new instance of any of the Morph classes using the world>>new morph... menu. Some of the more common Morphs are also available from the supplies flap at the bottom of the screen
It turns out that there is also a WatchMorph in Squeak, which already implements an analogue clock. But try not to look at it just yet! See how far you can get figuring things out on your own.
The best bet for a clock face would seem to be an ellipse; you can drag an EllipseMorph out of the supplies flap. (When you blue-click on a Morph, the name of its class appears on the screen below it, without the Morph suffix.)
Think back to the bouncing colored ball in worksheet 2. In that worksheet, we changed the appearance of our TestMorph in arbitrary ways by overriding its drawOn: method. Could you use the same idea to draw the hands on the face of your ellipse? Previously, we used the fillOval:color: method to do the painting. Use the method finder to open a system browser on that methodyou will see that it is in class Canvas. The browser will show you similar methods in other categories that draw rectangles and polygons.
What about the face of the clock? Remember that you can embed one Morph inside another. String Morphs can be used to place numerals in the right places. Or, perhaps you just want to draw tick marks every 30° around the face.
You know that positions on the display are represented by Points, like 100@50. But points also understand polar coordinates, which are sometimes more convenient. For example, Point r: 2 sqrt degrees: 45 prints as 1.0@1.0.
I think that this should be enough to get you started ...
The reflex game is like one of those machines that you might have seen in a science museum or an arcade; it measures the speed of your reflexes.
how quickly you respond. Then it turns out the green light, and shows your reaction time on an LED display.
What if the user never clicks the mouse at all? Eventually, the game should time out, and reset itself back to the initial state. What if the user cheats, and clicks the mouse before the light turns green? The game should detect this.
This very simple game can be embellished in a number of ways.
The display for this game shown here is quite simplejust an EllipseMorph and a StringMorph embedded in a RectangleMorph. I built this first by direct manipulation. Once I had a layout that I liked, I copied the coordinates out of an explorer into my initialize method. Morphic supports quite complex dynamic 2D tabular layouts, but I won't discuss them here.
Most of the complexity of the reflex game is in the testing and timing. For this reason I separated those aspects from the user interface part. I used Morphic to provide the "view" and the "controller", and a separate model object to implement the timing part. Of course, there are many other valid architectures for this game.
My model was a state machine. Messages to the model caused it to change state. One might use the state pattern to implement this, or just a simple instance variable whose value (a Symbol) represents the state. In the latter case, the methods implementing the state transitions will have bodies that do a case selection on the state. (Yes, Squeak does have a case statement; it is described on the Squeak Language Reference sheet.)
Squeak has a class Random whose instances are random number generators. As with many classes, there is an example method in Random class (that's on the class side) that tells you how to use Randoms.
The class Time has many useful methods, including Time millisecondClockValue, and Time milliseconds: since:, which computes the difference between two times. Another useful class is Delay. A Delay is an object on which the current process can wait. Once again, there is an example method on Delay class that shows how a Delay can be used.
Along with delays, you will need to create processes. This could hardly be easier: just send a block the message fork. The Delay class example also illustrated the use of fork, or rather forkAt:, which allows you to specify a priority.
I found it convenient to create a new class, Event, which has a single method on the class side:
Event class>>afterMilliseconds: anInteger then: aBlock"evaluate aBlock (in a new process) after waiting for anInteger milliseconds. This method returns immediately, answering the new process."
| d |
d := self forMilliseconds: anInteger.
^ [d wait. aBlock value] fork
Then an object could cause its timeOut method to be invoked asynchronously at a time 3 seconds in the future by executing
Event afterMilliseconds: 3000 then: [self timeOut]
Those of you who are familiar with the Model-View-Controller paradigm also know that it is traditional to separate the model from the view. In this example, such a separation makes it hard to do accurate timing in the model, because the model will only affect the view (e.g., turn on the green light) indirectly. For this reason, I felt that it was appropriate for the model and the view to be closely coupled. My model has direct access to the display and the light of the reflex game morph.
If you have sound on your Squeak platform, you might try speaking the word 'Go'. Look at the examples in the class Speaker, or just try Speaker default say: 'Go!' There is a problem here, though, because the text-to-speech system takes a few moments to convert the string into sound. So, you will have to probe around a bit to find out where when to start measuring the reaction time.
More simply, try Smalltalk beep. Look at Implementors of beep to see other ways to make a sound.