Lab 2 Exercises
Today's lab will involve constructing a simple model of a lift. (This will be topical in connection with a forthcoming lecture.) We will be working entirely in DASM, with the cloning and construction of objects as well as animation being the key focus. Some supplementary notes relating to the Lab exercises can be found here.
Your first step should be to set up a directory called (e.g.) lift in your home directory. Set up a konsole window as in Lab 1, and change into the lift directory using a 'cd'. Copy the files lift.png and floor.png from the ~empublic/teaching/cs405/lab2 directory into your lift directory. Begin by loading up a blank environment with a window to work in by invoking Cadence:
~empublic/bin/cadence
and loading the WGD library module from the DASM input window:
%include "wgd/wgd.dasm";
Objects, Prototypes and Cloning
The first thing you might wish to do when making a lift model is make a lift and place it on the screen. The best way to do this is to take some existing object that is similar to what we want and then change it a bit. For this purpose there are some prototype objects that are incomplete but provide a template. Prototypes already exist for image objects and we have provided an image called 'lift.png' so all we need to do is make a clone of that prototype and make a few changes.
@lift = (new union (@prototypes image)
filename = "lift.png"
x = 100
y = 100
width = 100
height = 100
);
What we have done here is make a new object, combine that with an existing image object and then change a few parameters. 'union' is what does the copy of everything in the object on the right-hand-side to the object on the left. You can chain several unions together to combine lots of objects. x and y give the position on the screen of the top left corner of the image in pixels (as measured from the top left corner of the screen). Before you display the image, it is a good idea to make a sketch of what you expect to see. To put the image on the screen:
@screen = (.widgets root children);
@screen mylift = (@lift);
At this point, you can change the focus of the browser, as in Lab 1. This can either be on a specific context:
@ui browser root = @screen;
or (the default setting) located at a node that gives direct access to all the contexts that can be directly addressed (such as @root, @lift, @ui, @screen etc):
@ui browser root is {@root notations dasm variables};
We have also provided an image called 'floor.png'. It is possible to clone the lift to make a floor. As before, it is a good idea to sketch what you expect to see before you clone and display the image in this way:
@screen floor1 = (new union (@lift)
filename = "floor.png"
x = 200
width = 400
);
Now clone this floor and change y to make 4 other floors. It would be a good idea to give y a definition such that each floor is always below the other. During this process of construction, you can check your understanding of the definitions by changing various parameters via the Browser.
Making the Lift Act Like A Lift: Animation
Since we are making a lift we would like it to be controlled and behave like a lift. So instead of having to worry about its pixel position we simply want to control which floor it is on. This can be done by giving y a definition that depends on some floor observable by converting a floor into pixels. There are a few ways to do this but try the following first and see if you can think of a better way:
@lift offsety = 100;
@lift floor = 0;
The observable '@lift floor' is intended to specify which floor the lift is currently at. The value of this observable needs to determine the position of the image of the lift accordingly. We need to work out the pixel location of each floor:
@lift floor_pix is { 400 - (.floor * 100) + (.offsety) };
To locate the lift image at the appropriate floor, use:
@lift y is { .floor_pix };
offsety is used to give the distance the top floor is from the top of the screen (you could give this a definition to actually link it with the top floor's position). Now try changing the value of floor to check that the lift jumps around correctly. Let's try and get this more realistic before adding buttons and doors. What we really need is for the lift to move smoothly at a certain speed between floors instead of teleporting.
So when floor is changed the lift will need to move up or down at a certain speed until it reaches that floor. Eventually this will involve modifying the definition of y but first we need to know things like desired speed, direction and whether it has arrived at the floor or not:
@lift speed = 20.0;
Has it arrived at the floor?:
@lift atfloor is {
.y < (.floor_pix + 2) and (.y > (.floor_pix -2))
};
Something else we need to know is the direction the lift should move in:
@lift direction is {
if (.y < (.floor_pix)) {1.0} else {-1.0}
};
Finally, we can now re-define y using the above to make it move smoothly between floors:
@lift y := {
if (.atfloor not) {
..y + (..direction * (..speed) * (@root itime))
} else {
..y
}
};
Try this out by changing the floor observable. Also try changing floor before it has reached the floor you last sent it to and see what happens. Each individual definition above has been kept small because of the way it has been split up into several smaller and meaningful observables. The similarity to spreadsheet modelling can be seen with this example. The most important definition to understand is the final one for y. This 'willbe' definition of y refers to y itself but is not actually circular. What it is actually doing is referring to the last known value of y, then using that in a formula to calculate its next value. Such a definition will update constantly as fast as the machine can do it because one of the things it depends on will always change (viz. y itself). For this reason we use itime which is the time in seconds between each update cycle so that we can control how fast it appears to move independent of how fast the machine is.
A more advanced exercise: Extend the Lift Model
Extend the model to include buttons on each floor and 5 buttons in the lift. Come up with definitions for floor which decide, based upon which buttons have been pressed, which floor to go to next. You may also want to add doors and people.
In preparation for this exercise, it is useful to inspect the code for the 'dial' button that features in the Stargate model you studied in Lab 1. To this end, close Cadence, then restart and load the Stargate model. Things you should seek out are the observables that correspond to the mouseover and mousedown conditions for the dial button, and the observables associated with the mouse. You can find these by looking in the context ' .widgets root chidren cam1 children' and the context '.input' respectively. If you open these contexts in the browser, you will be able to observe directly how these observables are changed when the mouse is used.
In extending the lift model, you can set up a button that can respond to mouse clicks by using the following definition:
@screen button_1 clicked is { .mouseover and (@mouse buttons left) };
This definition expresses the fact that the mouse button at floor 1 is being clicked. You will also need to consider how to create an observable to reflect the fact that the mouse button has been clicked. A 'willbe' definition is useful in this connection. (See the Supplementary Notes on Cadence accessible via the link in the right-hand panel for more information. Be aware also that the correspondence between the button as displayed and the sensitive region that you click on is not as precise as it should be in the implementation on the lab workstations. This is apparent from the fact that clicking off-centre on the 'dial' button in Stargate may cause the Stargate image to move!)
Experiment and ask questions.