Programming the World (v300)

Matthias Felleisen, PLT @ CCS, Northeastern, Boston

This documentation page presents a series of examples that illustrate the use of animations to introduce basic program design concepts. The exercises rely on the world.ss teachpack, which is described first. The remaining sections present a problem, an appropriate solution, a screen shot, and some icons for making the programs pretty.

Content
Basics
Dropping the Ball
Dropping a "Real" Ball: Giving Constants Names
Dropping the Ball: What is a World?
Landing the Ball: Conditionals
Lauching a Rocket: KeyEvents and more on Conditionals
Chasing a Satellite: Structures
Bouncing the Ball: Structures

Basics

The world.ss teachpack provides five functions for starting and dealing with (simple) animations:
big-bang : Number Number Number World -> true
(big-bang width height n w) creates and shows a width x height canvas, gets the clock ready, makes it tick every n seconds, and makes w the first world.

The collection of Worlds may consist of numbers, booleans, strings, symbols, structures, lists, and whatever else you want. The following sections present a series of examples.

on-tick-event : (World -> World) -> true
(on-tick-event tock) starts the clock according to the values given to big-bang; it also means that DrScheme must call tock on the current world every time the clock ticks; it uses the result as the next world.
on-key-event  : (World KeyEvent -> World) -> true

   ;; A KeyEvent is one of: 
   ;; -- Char (char?)
   ;; -- Symbol (symbol?)

(on-key-event change) means that DrScheme must call change on the current world and a (representation of the) keyevent for every keystroke the programmer (user of the computer) makes; it uses the result as the next world.

When the Keyevent is a char, the programmer (user of the computer) has hit an alphanumeric key. Symbols such as 'left, 'right, 'up, 'down, 'release denote arrow keys or the event of releasing a key on the keypad.

on-redraw : (World -> Image) -> true
(on-redraw world->image) means that DrScheme calls world->image whenever the canvas must be redrawn to obtain an image of the current world.
end-of-time : String u Symbol : -> World
When DrScheme evaluates (end-of-time s), it stops the clock and displays s; no further tick events, key events, or redraw events register until the world is created again.

In addition, the teachpack provides functions for creating empty scenes and for placing graphical images into scenes:


(place-image SomeImage 10 20 (empty-scene 100 100))  

The expression first creates an empty scene of 100 x 100 pixels. It then places SomeImage 10 pixels from the left and 20 pixels down from the top into this empty scene.

Finally, for the creation of images, the teachpack also provides all the functions that the image.ss teachpack introduces for creating images. Thus, you can create circles and rectangles like this: (circle 3 'solid 'red) and (rectangle 30 10 'solid 'green) Experiment with the functions from image.ss in the Interactions Window as you need them.

Dropping the Ball

The goal of the first exercise is to show how the teachpack can create a moving ball.

Design a program that simulates the fall of a ball from the top of the canvas to the bottom. The ball should drop one pixel per clock tick. To construct a ball, use (circle 3 'solid 'red) .


(define (next t)
  (+ t 1))

(define (image t)
  (place-image (circle 3 'solid 'red) 20 t (empty-scene 50 50)))

;; --- run program run 
(big-bang 50 50 .1 0)
(on-redraw image)
(on-tick-event next)

Dropping a "Real" Ball: Giving Constants Names

The goal of the second exercise is to motivate the notion of a variable definition. It also shows that plain old images (gifs, jpgs, pngs, etc) are values in DrScheme, too. Use the Special | Insert Image menu to insert images into the Definitions and/or Interactions Window.

Replace the small red circle with the image of a ball. The image is supplied on the right.

Copy the first program and insert the image of the ball in place of (circle 2 'solid 'red).


(define (next t)
  (+ t 1))

(define (image t)
  (place-image  20 t (empty-scene 50 50)))

;; --- run program run 
(big-bang 50 50 .1 0)
(on-redraw image)
(on-tick-event next)

Download the image for the ball from here:

What you see is that the ball is way too big for the image. So you change the 50 x 50 image into an image of, say, 200 x 200 pixels. But something goes wrong. You (or your students) forget to replace one of the 50s with 200. That's bad.

You can avoid this with definitions that give a name to a number and then using the name everywhere. If you need to change what the name stands for, change it in one place.


(define SIZE 300)

(define (next t)
  (+ t 1))

(define (image t)
  (place-image  200 t (empty-scene SIZE SIZE)))

;; --- run program run 
(big-bang SIZE SIZE .1 0)
(on-redraw image)
(on-tick-event next)

Now change the size of the canvas to 400.

Now change the size of the canvas to 200. What happened? Can you make sure that the image of the ball is always roughly in the middle of the canvas no matter what you choose for SIZE? (Well, it has to be large enough.)

Dropping the Ball: What is a World?

With this third section, we motivate the section 1 in HtDP, the idea that students need to understand "represent and interpret."

Say you want to change the speed at which the ball is dropping. Let's drop the ball again, but this time let's use the "world number" to represent how far the ball has dropped until now.


;; the World is a Number
;; interpretation: the number represents how far the ball has dropped

(define SIZE 300)
(define WORLD0 (empty-scene SIZE SIZE))

;; the clock ticked, the ball has dropped one more pixel 
(define (next y)
  (+ y 1))

;; place the ball into the middle of the canvas at y
(define (image y)
  (place-image  (quotient SIZE 2) y WORLD0))

;; --- run program run 
(big-bang SIZE SIZE .1 0)
(on-redraw image)
(on-tick-event next)

Add a comment that defines what kind of data you choose to represent things in the World (representation). Conversely, explain what a piece of data from World represents on the canvas (interpretation).

It is also a good idea to add comments that explain the purpose of the functions.

Now change the speed. Let the ball drop at the rate of 3 pixels per tick or 10 or 20. What do you need to change? What would you have needed to change in the first program?

Go back to the first program and add a sentence that explains what the world is and what it represents.

Landing the Ball: Conditionals

It is time to move on to conditional computations.

When the ball hits the ground, it should just stop and rest there.


;; World = Number 
;; interpretation: where is the ball (its current y coordinate)

(define SIZE 300)
(define WORLD0 (empty-scene SIZE SIZE))

;; drop the ball by 3 pixels, unless it has reached the bottom
;; then just stop the clock 
(define (next y)
  (cond
    [(= y SIZE) (end-of-time "The ball has landed.")]
    [else (+ y 3)]))

;; place the image of the ball into the scene at (100,y)
(define (image y)
  (place-image  100 y WORLD0))

;; --- run program run 
(big-bang SIZE SIZE .1 0)
(on-redraw image)
(on-tick-event next)

The conditional in next does the work.

The ball doesn't comes to rest on the ground, but drops into the ground, as if it were led. Can you stop the ball so that it rests exactly on the ground? (How far is the pinhole from the ground then? See image.ss and whether any of its functions may help.)

Lauching a Rocket: KeyEvents and more on Conditionals

With conditionals, we can also use keyevents in a reasonably interesting manner.

Design a program that simulates a rocket launch. At first the rocket just sits on the ground and doesn't move. When the player presses a key (any key) on the keyboard, the rocket begins its lift-off. The rocket should move one pixel per clock tick after it is launched.

Download the image for the rocket from here:


;; WORLD is one of: 
;; -- Number 
;; -- false 
;; interpretation: how long since the rocket has been launched 
;;  the clock start ticking when the player presses any key 

(define SIZE 300)
(define WORLD0 (empty-scene SIZE SIZE))

;; World -> World 

;; if the clock has started ticking, add 1 to the ticks 
(define (next t)
  (cond
    [(number? t) (+ t 1)]
    [(boolean? t) t]))

;; World -> World 
;; if the player presses any key, the clock has ticked 0 times 
(define (launch ke t)
  0)

;; World -> Image 
;; place the rocket into the picture 
(define (image t)
  (place-image  (/ SIZE 2) (y-coordinate t) WORLD0))

;; World -> Number 
;; what is the y coordinate of the ROCKET image at this time
(define (y-coordinate t)
  (cond
    [(number? t) (- SIZE 10 t)]
    [else (- SIZE 10)]))

;; --- RUN PROGRAM RUN

(big-bang SIZE SIZE .1 false)
(on-key-event launch)
(on-tick-event next)
(on-redraw image)

It is time now to use contracts.

Explain the definition of image.

The 10 in image is a guess to make the rocket sit atop the ground. As you can tell from the image on the right, it doesn't quite work out. Can you do better?


(define ROCKET )

;; World -> Image 
;; place the rocket into the picture 
(define (image t)
  (place-image ROCKET (/ SIZE 2) (y-coordinate t) WORLD0))

;; World -> Number 
;; what is the y coordinate of the ROCKET image at this time 
;; ASSUME: the rocket moves one pixel per clock tick 
(define (y-coordinate t)
  (cond
    [(number? t) (- SIZE (/ (image-height ROCKET) 2) t)]
    [else (- SIZE (/ (image-height ROCKET) 2))])))    

How often did you have to change the function? Twice, because the 10 appears twice. If you decide to lift the rocket to a full image height, you would still have to change the definition twice.

Good programmers avoid duplicating work. In this case we can:


(define (y-coordinate t)
  (- SIZE (/ (image-height ROCKET) 2)
    (cond
      [(number? t) t]
      [else 0])))

While this may seem trivial, repeated work piles up. And before you know it, it consumes your day.

Now add flames to the flying rocket.

Chasing a Satellite: Structures

Sometimes we want more than one moving object in a world. For example, the we might wish to add a rotating satellite to the previous program. Although it is in principle possible to encode all of this information in a single number, it is a really bad idea. Instead you want to use structures that represent the moving objects in the world.

Design a program that simulates the launch a rocket and catches up with a satellite for repair purposes.



;; DATA ;; ;; A World is one of: ;; -- Number ;; -- (make-world Number Number) ;; Intepretation: ;; -- a plain Number: the x-coordinate of the satellite ;; -- a world struct: the x-coordinate of the satellite ;; and the y-coordinate of the rocket (define-struct world (sat rock)) ;; dimensions: (define WIDTH 200) (define HEIGHT 600) (define WORLD0 (empty-scene WIDTH HEIGHT)) (define SATELLITE ) (define ROCKET ) ;; where the satellite starts, where the rocket starts (define X0 (quotient WIDTH 2)) (define Y0 (- HEIGHT (image-height ROCKET)))
Download the image for the satellite from here:

If you don't like icons, how is this code?


(define SBODY
  (overlay/xy 
    (circle 10
      'solid 'red)
    -10 0 
    (rectangle 30 10
      'solid 'red)))
(define SANTENNA
  (line 20 0 'red))
(define SATELLITE
  (overlay/xy
   (overlay/xy 
    SBODY
    5 5 
    SANTENNA)
   5 -5 
   SANTENNA))

(define RBODY
  (put-pinhole 
    (rectangle 10 30
      'solid 'blue) 
    5 30))
(define RTOP
  (triangle
    10 'solid 'blue))
(define ROCKET
  (overlay 
   (put-pinhole 
    (overlay RBODY RTOP)
    5 -5)
   (triangle 10
     'solid 'blue)))



;; FUNCTIONS ;; World -> World ;; compute the next world (define (next w) (cond [(number? w) (satellite-next w)] [else (make-world (satellite-next (world-sat w)) (rocket-next (world-rock w)))])) ;; World -> Image ;; create an image that represents the world (define (image w) (cond [(number? w) (satellite-add w (rocket-add Y0 WORLD0))] [else (satellite-add (world-sat w) (rocket-add (world-rock w) WORLD0))])) ;; Number -> Number ;; move the rocket for this tick (define (rocket-next w) (- w 2)) ;; Number Image -> Image ;; add the satellite to the image (define (rocket-add w scene) (place-image ROCKET (/ WIDTH 2) w scene)) ;; Number -> Number ;; where is the satellite now? (define (satellite-next w) (modulo (+ 1 w) WIDTH)) ;; Number Image -> Image ;; add the satellite to the image (define (satellite-add w scene) (place-image SATELLITE w 10 scene)) ;; World KeyEvent -> World ;; react to a keyevent (define (launch w ke) (cond [(symbol? ke) w] ;; ignore all symbolic events [(char? ke) (cond [(world? w) w] [else (make-world w Y0)])]))
;; RUN PROGRAM RUN (big-bang WIDTH HEIGHT .1 0) (on-tick-event next) (on-redraw image) (on-key-event launch)
Have students develop tests, using the expected and computed value.


;; TESTS 'testing-satellite (equal? (satellite-next 10) 11) (equal? (satellite-add 0 WORLD0) (place-image SATELLITE 0 10 WORLD0)) 'testing-rocket (equal? (rocket-next 5) 3) (equal? (rocket-add Y0 WORLD0) (place-image ROCKET (/ WIDTH 2) Y0 WORLD0)) 'testing-world (equal? (next 0) 1) (equal? (next (make-world 10 100)) (make-world 11 98)) (equal? (image 1) (rocket-add Y0 (satellite-add 1 WORLD0))) (equal? (image (make-world 10 1)) (rocket-add 1 (satellite-add 10 WORLD0))) (equal? (launch 10 #\space) (make-world 10 Y0)) (equal? (launch (make-world 10 3) #\space) (make-world 10 3)) (equal? (launch 0 'release) 0)
These tests are a bit simpler than in real-life but so is the program. Get them started on good habits!

Bouncing the Ball: Structures

One last program that needs it all again: structures, conditionals, and so on.

Design a program that simulates a bouncing ball. The ball should drop from the top and, when it hits the bottom, reverse its direction and go back to the top. Similarly, when it hits the top, it should change direction and fall back to the bottom.

Supply the purpose statement, contract, tests.



;; DATA: ;; World = (make-world Number UpDown) ;; UpDown is one of: ;; -- 'up ;; -- 'down ;; interpretation: the number represents the y-coordinate of the ball, ;; the dir field says in which dir it is moving (define-struct world (y dir)) (define SIZE 100) (define WORLD0 (empty-scene SIZE SIZE)) ;; data example (define FIRST-WORLD (make-world 0 'down))
;; FUNCTIONS ;; World -> World ;; create the next world, assuming the ball moves ;; 3 pixels up or down per clock tick (define (next w) (cond [(< (world-y w) 0) (make-world 0 (dir-reverse (world-dir w)))] [(<= 0 (world-y w) SIZE) (make-world (ball-move (world-y w) (world-dir w)) (world-dir w))] [(< SIZE (world-y w)) (make-world SIZE (dir-reverse (world-dir w)))])) ;; UpDown -> UpDown ;; what is the opposite of the given direction (define (dir-reverse d) (cond [(symbol=? d 'up) 'down] [(symbol=? d 'down) 'up])) ;; Number UpDown -> Number ;; move the ball in the appropriate direction (define (ball-move y dir) (+ y (cond [(symbol=? dir 'up) -3] [(symbol=? dir 'down) +3]))) ;; World -> Image ;; create an image from the given world (define (image w) (place-image (circle 3 'solid 'red) (/ SIZE 2) (world-y w) WORLD0))
;; TESTS 'dir-reverse (equal? (dir-reverse 'up) 'down) 'ball-move (equal? (ball-move 10 'up) 7) (equal? (ball-move 10 'down) 13) 'next (equal? (next (make-world -1 'up)) (make-world 0 'down)) (equal? (next (make-world (+ SIZE 2) 'down)) (make-world SIZE 'up)) (equal? (next (make-world 10 'up)) (make-world 7 'up)) 'image (equal? (image (make-world 10 'up)) (place-image (circle 3 'solid 'red) (/ SIZE 2) 10 WORLD0))
;; RUN PROGRAM RUN (big-bang SIZE SIZE .1 FIRST-WORLD) (on-redraw image) (on-tick-event next)