1 About This Teachpack
2 Installation
3 Functions from image.ss
4 New image functions
above
above-align-right
above-align-left
above-align-center
beside
beside-align-top
beside-align-bottom
beside-align-center
reflect-vert
reflect-horiz
reflect-main-diag
reflect-other-diag
rotate-cw
rotate-ccw
rotate-180
show-pinhole
crop-top
crop-bottom
crop-left
crop-right
place-image
scene+ line
empty-scene
5 Input and Output
with-input-from-string
with-output-to-string
with-input-from-file
with-output-to-file
with-io-strings
6 Animating a World
6.1 Worlds and Event Handlers
6.2 Simple Image Animations
6.3 Simple Numeric Simulations
animate
run-simulation
6.4 Interactions
big-bang
on-tick
key-event?
key=?
on-key
mouse-event?
mouse=?
on-mouse
on-draw
stop-when
check-with
record?
state
6.5 A First Sample World
6.5.1 Understanding a Door
6.5.2 Hints on Designing Worlds
7 Multiple Worlds
7.1 Messages
sexp?
7.1.1 Sending Messages
package?
make-package
7.1.2 Connecting with the Universe
LOCALHOST
register
name
7.1.3 Receiving Messages
on-receive
7.2 The Universe Server
7.2.1 Worlds and Messages
iworld?
iworld=?
iworld-name
iworld1
iworld2
iworld3
bundle?
make-bundle
mail?
make-mail
7.2.2 Universe Descriptions
universe
on-new
on-msg
on-disconnect
to-string
7.2.3 Exploring a Universe
launch-many-worlds
7.3 A First Sample Universe
7.3.1 Two Ball Tossing Worlds
7.3.2 Hints on Designing Universes
7.3.3 Designing the Ball Universe
7.3.4 Designing the Ball Server
7.3.5 Designing the Ball World
Version: 4.2.2

Picturing Programs Teachpack

Stephen Bloch

 (require (planet sbloch/picturing-programs/picturing-programs))

1 About This Teachpack

Provides a variety of functions for combining and manipulating images, as well as a slightly modified version of the "universe" teachpack. It is intended to be used with the book Picturing Programs.

2 Installation

If you’re reading this, you’ve probably already installed the teachpack successfully, but if you need to install it on a different machine, ...
  1. start DrScheme

  2. switch languages to Module and click "Run"

  3. in the Interactions pane, type
      (require (planet sbloch/picturing-programs))

  4. after a few seconds, you should see the message
      "Wrote file \"picturing-programs.ss\" to installed-teachpacks directory."

  5. switch languages back to one of the HtDP languages, like Beginning Student

  6. either
    • in the Definitions pane, type
        (require installed-teachpacks/picturing-programs)
      or

    • from the Language menu, choose "Add Teachpack..." and select "picturing-programs.ss"

  7. click "Run"

3 Functions from image.ss

This package includes all of the image teachpack, so if you’re using this teachpack, don’t also load the image teachpack. Some of the important functions:

4 New image functions

(above img1 img2 ...)  image?
  img1 : image?
  img2 : image?
Combines two or more images into one, putting the first above the second (above the third, ...).

(above-align-right img1 img2 ...)  image?
  img1 : image?
  img2 : image?
Just like above, but lines up the right-hand edges of all the images.

(above-align-left img1 img2 ...)  image?
  img1 : image?
  img2 : image?
Just like above, but lines up the left-hand edges of all the images.

(above-align-center img1 img2 ...)  image?
  img1 : image?
  img2 : image?
Just like above, but lines up the centers of all the images.

(beside img1 img2 ...)  image?
  img1 : image?
  img2 : image?
Combines two or more images into one, putting the first to the left of the second (to the left of the third, ...).

(beside-align-top img1 img2 ...)  image?
  img1 : image?
  img2 : image?
Just like beside, but lines up the top edges of all the images.

(beside-align-bottom img1 img2 ...)  image?
  img1 : image?
  img2 : image?
Just like beside, but lines up the bottom edges of all the images.

(beside-align-center img1 img2 ...)  image?
  img1 : image?
  img2 : image?
Just like beside, but lines up the centers of all the images.

(reflect-vert img)  image?
  img : image?
Flips an image top-to-botom.

(reflect-horiz img)  image?
  img : image?
Flips an image left-to-right.

(reflect-main-diag img)  image?
  img : image?
Flips an image from top-right to bottom-left, leaving the top-left and bottom-right unchanged.

(reflect-other-diag img)  image?
  img : image?
Flips an image from top-left to bottom-right, leaving the top-right and bottom-left unchanged.

(rotate-cw img)  image?
  img : image?
Rotates an image 90 degrees clockwise.

(rotate-ccw img)  image?
  img : image?
Rotates an image 90 degrees counterclockwise.

(rotate-180 img)  image?
  img : image?
Rotates an image 180 degrees around its center.

(show-pinhole img)  image?
  img : image?
Overlays a small black dot at the pinhole of the given image, so you can see where it is.

(crop-top img pixels)  image?
  img : image?
  pixels : natural-number/c
Chops off the specified number of pixels from the top of the image.

(crop-bottom img pixels)  image?
  img : image?
  pixels : natural-number/c
Chops off the specified number of pixels from the bottom of the image.

(crop-left img pixels)  image?
  img : image?
  pixels : natural-number/c
Chops off the specified number of pixels from the left side of the image.

(crop-right img pixels)  image?
  img : image?
  pixels : natural-number/c
Chops off the specified number of pixels from the right side of the image.

(place-image foreground x y background)  image?
  foreground : image?
  x : real?
  y : real?
  background : image?
Places the foreground image at the specified location relative to the top-left corner of the background image, clipping if necessary so the result is the same size as background. Differs from the bundled version of place-image in not requiring a "scene"; any image will do.

(scene+line background x0 y0 x1 y1 color)  image?
  background : image?
  x0 : real?
  y0 : real?
  x1 : real?
  y1 : real?
  color : color?
Adds a line from (x0, y0) to (x1, y1) in the specified color to the background image, clipping if necessary so the result is the same size as background. Differs from the bundled version of scene+line in not requiring a "scene"; any image will do. Differs from add-line in clipping the result to the background.

(empty-scene width height)  image?
  width : natural-number/c
  height : natural-number/c
creates a plain white, width x height image. It’s roughly equivalent to
  (rectangle width height "solid" "white")

5 Input and Output

This teachpack also provides five functions to help in testing I/O functions (in Advanced Student language; ignore this section if you’re in a Beginner or Intermediate language):

(with-input-from-string input thunk)  any/c
  input : string?
  thunk : (-> any/c)
Calls thunk, which presumably uses read, in such a way that read reads from input rather than from the keyboard.

(with-output-to-string thunk)  string?
  thunk : (-> any/c)
Calls thunk, which presumably uses display, print, write, and/or printf, in such a way that its output is accumlated into a string, which is then returned.

(with-input-from-file filename thunk)  any/c
  filename : string?
  thunk : (-> any/c)
Calls thunk, which presumably uses read, in such a way that read reads from the specified file rather than from the keyboard.

(with-output-to-file filename thunk)  any/c
  filename : string?
  thunk : (-> any/c)
Calls thunk, which presumably uses display, print, write, and/or printf, in such a way that its output is redirected into the specified file.

(with-io-strings input thunk)  string?
  input : string?
  thunk : (-> any/c)
Combines with-input-from-string and with-output-to-string: calls thunk with its input coming from input and accumulates its output into a string, which is returned. Especially useful for testing:
  (define (ask question)
    (begin (display question)
           (read)))
  (define (greet)
     (local [(define name (ask "What is your name?"))]
          (printf "Hello, ~a!" name)))
  (check-expect
     (with-io-strings "Steve" greet)
     "What is your name?Hello, Steve!")

6 Animating a World

Matthias Felleisen, with some modifications by Stephen Bloch

This package also includes a slightly modified version of the universe teachpack that comes with DrScheme, which allows you to easily write interactive, graphical programs. The main difference is that in a number of places, universe requires a special kind of images called "scenes", and this teachpack allows you to use any old image.

Interactive programs written using this teachpack are called world programs (because each such program is a cluster of functions working on "worlds"). In addition, several world programs can be combined, either on one computer or over a network, in what we call a universe.

The purpose of this documentation is to give experienced Schemers and HtDP teachers a concise overview for using the library. The first part of the documentation focuses on world programs. Section A First Sample World presents an illustration of how to design such programs for a simple domain; it is suited for a novice who knows how to design conditional functions for enumerations, intervals, and unions. The second half of the documentation focuses on "universe" programs: how it is managed via a server, how world programs register with the server, etc. The last two sections show how to design a simple universe of two communicating worlds.

Note: For a quick and educational introduction to just worlds, see How to Design Programs, Second Edition: Prologue. As of August 2008, we also have a series of projects available as a small booklet on How to Design Worlds.

The teachpack assumes working knowledge of the basic image manipulation primitives from the image teachpack.

6.1 Worlds and Event Handlers

GUI and animation programs (not just in this teachpack, and not just in Scheme) usually follow the principle of model/view separation. In English, that means that part of the program deals with an internal "model" or "world", and another part of the program deals with how that world is displayed on the screen (or some other output device). For example, in a chess game, the "model" or "world" would include the rules of chess, which pieces are currently at which squares of the board, whether each side has castled yet, how many moves it has been since the last piece was taken, etc. while the "view" part of the program would deal with the color of the screen, the size and shape of pieces as they appear on the screen, the arrangement of squares on the screen, etc.

The connection between model and view, in this teachpack, is a function called a draw handler, which takes in a world and returns an image. Every time the world changes for any reason, the draw handler will be automatically called, and the image it returns will appear on the screen. In most animations, you will write a function to be used as a draw handler, but if you don’t provide a draw handler, the teachpack will assume that the "model" is an image, and will simply show that image on the screen. (In other words, it will use the identity function as a draw handler.)

A draw handler is one of several kinds of event handlers, which are functions that the teachpack will call whenever a certain event happens. In this teachpack, there are several kinds of event handlers, each with its own contract (we’ll explain these as we get to them):
There are several other kinds of handlers related to network programming, which we’ll discuss in Multiple Worlds.

In this teachpack, a world can be of (almost) any data type, but we’ll start with two especially simple cases: when the world is an image, and when the world is a natural number.

To run an animation, you’ll call big-bang (discussed below) to "start the world", giving it as arguments the initial state of the world and whatever event handlers you want.

6.2 Simple Image Animations

One simple form of animation uses an image as the model, so you don’t have to provide your own draw handler. In fact, in the simplest case you can provide no event handlers at all. For example,
  (big-bang (triangle 50 "solid" "blue"))
opens an animation window with an equilateral blue triangle in it. Since there are no event handlers, the animation window doesn’t respond to any events, so it never changes until you close the window.

We can make things a little more interesting by adding event handlers, of which the simplest is a tick handler. The contract for a tick handler is always (-> world world), and since we’re not providing a draw handler yet, the world must be an image, so the tick handler must actually have contract (-> image? image?). A handy predefined function with this contract is rotate-cw:
  (big-bang (triangle 50 "solid" "blue") (on-tick rotate-cw))
opens an animation window with an equilateral blue triangle, but at every clock tick (which, by default, is 28 times per second), the triangle rotates clockwise.

If this spins too fast for you, we can give an optional argument to on-tick to specify the length of a clock tick in seconds:
  (big-bang (triangle 50 "solid" "blue") (on-tick rotate-cw 1/2))
opens an animation window with an equilateral blue triangle, rotating clockwise every half second.

One can also write image animations with mouse, key, and other kinds of event handlers, but we’ll come back to that in Interactions below.

6.3 Simple Numeric Simulations

Another common and simple form of animation uses a natural number as the world, starting at 0 and increasing by 1 at every clock tick. In other words, the "world" at any time is the number of clock ticks since the animation started. The programmer’s task is to supply a function that creates an image for each natural number, and hand this function to the teachpack.

(animate create-image)  natural-number
  create-image : (-> natural-number/c image?)
opens a canvas and starts a clock that ticks 28 times per second. Every time the clock ticks, DrScheme applies create-image to the number of ticks passed since this function call. The results of these function calls are displayed in the canvas. The simulation runs until you click the Stop button in DrScheme or close the window. At that point, animate returns the number of ticks that have passed.

Example:
  (define (create-UFO-image height)
    (place-image UFO 50 height (empty-scene 100 100)))
  
  (define UFO
    (overlay (circle 10 'solid 'green)
             (rectangle 40 4 'solid 'green)))
  
  (animate create-UFO-image)

By the way,
  (animate create-UFO-image)
is just short-hand for
  (big-bang 0 (on-tick add1) (on-draw create-UFO-image))
. We’ll discuss on-draw in the next section.

(run-simulation create-image)  natural-number/c
  create-image : (-> natural-number/c image?)
animate was originally called run-simulation, and this name is retained for backwards compatibility

6.4 Interactions

The step from numeric simulations to interactive programs is pretty easy. A numeric simulation designates one function, create-UFO-image in the above example, as a handler for one kind of event: re-drawing the screen. In addition to redrawing, world programs can also deal with other kinds of events: clock ticks, keyboard events and mouse events. A clock tick happens every second, every third of a second, etc. as the programmer chooses. A keyboard event is triggered when a computer user presses or releases a key on the keyboard. Similarly, a mouse event is the movement of the mouse, a click on a mouse button, the crossing of a boundary by a mouse movement, etc.

Your program may deal with such events by designating event handler functions. The teachpack provides keywords to install each of these kinds of event handlers: on-draw, on-tick, on-key, and on-mouse. Each of these kinds of handlers consumes the current state of the world, and in some cases additional information about the event. As already stated, a draw handler returns an image; the other three (tick handlers, key handlers, and mouse handlers) return a new state of the world.

In addition, a world program may specify (with stop-when) a Boolean-valued function to decide when to shut down the animation.

The following picture provides an intuitive overview of the workings of a world program in the form of a state transition diagram.

The big-bang form installs World_0 as the initial WorldState. The handlers tock, react, and click transform one world into another one; each time an event is handled, done is used to check whether the world is final, in which case the program is shut down; and every time the world changes, draw renders the new world as an image, which is then displayed on an external canvas.

WorldState : any/c

The design of a world program demands that you come up with a data definition of all possible states. We use WorldState to refer to this collection of data. In principle, there are no constraints on this data definition, except that it mustn’t be an instance of the Package structure (see Multiple Worlds). Optionally, you can specify (with check-with) a Boolean-valued function to check this data type: if any of the event handlers returns something that isn’t the right type, you’ll get an immediate and informative error message.

(big-bang state-expr clause ...)
 
clause = (on-tick tick-expr)
  | (on-tick tick-expr rate-expr)
  | (on-key key-expr)
  | (on-mouse key-expr)
  | (on-draw draw-expr)
  | (on-draw draw-expr width-expr height-expr)
  | (stop-when stop-expr)
  | (stop-when stop-expr last-image-expr)
  | (check-with world?-expr)
  | (record? boolean-expr)
  | (state boolean-expr)
  | (on-receive rec-expr)
  | (register IP-expr)
  | (name name-expr)
starts a world program in the initial state specified with state-expr, which must of course evaluate to an element of WorldState. Its behavior is specified via the handler functions designated in the optional spec clauses, especially how the world program deals with clock ticks, key events, mouse events, and eventually messages from the universe; how it renders itself as an image; when the program must shut down; where to register the world with a universe; and whether to record the stream of events. A world specification may not contain more than one on-tick, on-draw, or register clause. A big-bang expression returns the last world when the stop condition is satisfied (see below) or when the programmer clicks on the Stop button or closes the canvas.

Exercise: Add a condition for stopping the flight of the UFO when it reaches the bottom.

6.5 A First Sample World

This section uses a simple example to explain the design of worlds. The first subsection introduces the sample domain, a door that closes automatically. The second subsection is about the design of world programs in general, the remaining subsections implement a simulation of the door.

6.5.1 Understanding a Door

Say we wish to design a world program that simulates the working of a door with an automatic door closer. If this kind of door is locked, you can unlock it with a key. While this doesn’t open the door per se, it is now possible to do so. That is, an unlocked door is closed and pushing at the door opens it. Once you have passed through the door and you let go, the automatic door closer takes over and closes the door again. When a door is closed, you can lock it again.

Here is a diagram that translates our words into a graphical representation:

Like the picture of the general workings of a world program, this diagram displays a so-called “state machine.” The three circled words are the states that our informal description of the door identified: locked, closed (and unlocked), and open. The arrows specify how the door can go from one state into another. For example, when the door is open, the automatic door closer shuts the door as time passes. This transition is indicated by the arrow labeled “time passes.” The other arrows represent transitions in a similar manner:

6.5.2 Hints on Designing Worlds

Simulating any dynamic behavior via a world program demands two different activities. First, we must tease out those portions of our domain that change over time or in reaction to actions, and develop a data representation for this information. This is what we call WorldState. Keep in mind that a good data definition makes it easy for readers to map data to information in the real world and vice versa. For all others aspects of the world, we use global constants, including graphical or visual constants that are used in conjunction with the rendering operations.

Second, we must translate the actions in our domain – the arrows in the above diagram – into interactions with the computer that the universe teachpack can deal with. Once we have decided to use the passing of time for one aspect, key presses for another, and mouse movements for a third, we must develop functions that map the current state of the world – represented as data from WorldState – into the next state of the world. Put differently, we have just created a wish list with three handler functions that have the following general contract and purpose statements:

  ; tick : WorldState -> WorldState
  ; deal with the passing of time
  (define (tick w) ...)
  
  ; click : WorldState Number Number mouse-event -> WorldState
  ; deal with a mouse click at (x,y) of kind me
  ; in the current world w
  (define (click w x y me) ...)
  
  ; control : WorldState key-event -> WorldState
  ; deal with a key event ke
  ; in the current world w
  (define (control w ke) ...)

That is, the contracts of the various handler designations dictate what the contracts of our functions are, once we have defined how to represent the domain with data in our chosen language.

A typical program does not use all three of these functions. Furthermore, the design of these functions provides only the top-level, initial design goal. It often demands the design of many auxiliary functions. The collection of all these functions is your world program.

7 Multiple Worlds

Matthias Felleisen, with some modifications by Stephen Bloch

The library facilities covered so far are about designing individual programs with interactive graphical user interfaces (simulations, animations, games, etc.). In this section, we introduce capabilities for designing a distributed program, which is really a collection of programs that coordinate their actions in some fashion. Each of the individual programs may run on any computer, as long as it is on the internet and as long as the computer allows the program to send and receive messages (via TCP). We call this arrangement a universe (a bunch of worlds) and the program that coordinates it all a universe server or just server.

This section explains what messages are, how to send them from a world program, how to receive them, and how to connect a world program to a universe.

7.1 Messages

After a world program has become a part of a universe, it may send messages and receive them. In terms of data, a message is just an S-expression.

S-expression An S-expression is roughly a nested list of basic data; to be precise, an S-expression is one of:

  • a string,

  • a symbol,

  • a number,

  • a boolean,

  • a char, or

  • a list of S-expressions.

Note the last clause includes empty of course.

(sexp? x)  boolean?
  x : any/c
determines whether x is an S-expression.

7.1.1 Sending Messages

Each world-producing callback in a world program – those for handling clock tick events, keyboard events, and mouse events – may produce a Package instead of just a WorldState.

A Package is a pair consisting of a WorldState and a message from a world program to the server. Because programs only send messages via Package, the teachpack does not provide the selectors for the structure, only the constructor and a predicate.

(package? x)  boolean?
  x : any/c
determine whether x is a Package.

(make-package w m)  package?
  w : any/c
  m : sexp?
create a Package from a WorldState and an S-expression.

As mentioned above, those event handlers that were originally described as returning WorldState may actually return either WorldStates or Packages; here are the revised specifications:

(on-tick tick-expr)
 
  tick-expr : (-> WorldState (or/c WorldState package?))

(on-tick tick-expr rate-expr)
 
  tick-expr : (-> WorldState (or/c WorldState package?))
  rate-expr : natural-number/c

(on-key change-expr)
 
  change-expr : (-> WorldState key-event? (or/c WorldState package?))

(on-mouse clack-expr)
 
  clack-expr : 
(-> WorldState
    natural-number/c natural-number/c mouse-event
    (or/c WorldState package?))

If one of these event handlers produces a Package, the content of the world field becomes the next world and the message field specifies what the world sends to the universe. This distinction also explains why a WorldState may be almost any imaginable data type except a Package.

7.1.2 Connecting with the Universe

Messages are sent to the universe program, which runs on some computer in the world. The next section is about constructs for creating such a universe server. For now, we just need to know that it exists and that it is the recipient of messages.

IP string?

Before a world program can send messages, it must register with the server. Registration must specify the internet address of the computer on which the server runs, also known as an IP address or a host. Here a IP address is a string of the right shape, e.g., "192.168.1.1" or "www.google.com".

LOCALHOST : string?
the IP of your computer. Use it while you are developing a distributed program, especially while you are testing whether the participating world programs collaborate correctly. This is called integration testing and differs considerably from unit testing (testing whether each individual function, or each individual world, works correctly).

A big-bang description of a world program that wishes to communicate with other programs must contain a register clause of each of the following shapes:

When a world program registers with a universe program and the universe program stops working, the world program stops working, too.

7.1.3 Receiving Messages

Finally, the receipt of a message from the server is an event, just like tick events, keyboard events, and mouse events. Dealing with the receipt of a message works exactly like dealing with any other event. DrScheme applies the event handler that the world program specifies; if there is no clause, the message is discarded.

The on-receive clause of a big-bang specifies the event handler for message receipts.

(on-receive receive-expr)
 
  receive-expr : (-> WorldState sexp? (or/c WorldState package?))
tell DrScheme to call receive-expr for every message receipt, on the current WorldState and the received message. The result of the call becomes the current WorldState.

Because receive-expr is (or evaluates to) a world-transforming function, it too can produce a Package instead of just a WorldState. If the result is a Package, its message content is sent to the server.

The diagram below summarizes the extensions of this section in graphical form.

A registered world program may send a message to the universe server at any time by returning a Package from an event handler. The message is transmitted to the server, which may forward it to some other world program as given or in some massaged form. The arrival of a message is just another event that a world program must deal with. Like all other event handlers receive accepts a WorldState and some auxiliary arguments (a message in this case) and produces a WorldState or a Package.

When messages are sent from any of the worlds to the universe or vice versa, there is no need for the sender and receiver to synchronize. Indeed, a sender may dispatch as many messages as needed without regard to whether the receiver has processed them yet. The messages simply wait in queue until the receiving server or world program take care of them.

7.2 The Universe Server

A server is the central control program of a universe and deals with receiving and sending of messages between the world programs that participate in the universe. Like a world program, a server is a program that reacts to events, though to different events than worlds. The two primary kinds of events are the appearance of a new world program in the universe and the receipt of a message from a world program.

The teachpack provides a mechanism for designating event handlers for servers that is quite similar to the mechanism for describing world programs. Depending on the designated event handlers, the server takes on distinct roles:

As a matter of fact, a pass-through server can become basically invisible, making it appear as if all communication goes from peer world to peer in a universe.

This section first introduces some basic forms of data that the server uses to represent worlds and other matters. Second, it explains how to describe a server program.

7.2.1 Worlds and Messages

Understanding the server’s event handling functions demands several data representations: that of (a connection to) a world program and that of a response of a handler to an event.

iworld : a predefined struct to represent "internal worlds" from the server’s perspective.

7.2.2 Universe Descriptions

A server keeps track of information about the universe that it manages. One kind of tracked information is obviously the collection of participating world programs, but in general the kind of information that a server tracks and how the information is represented depends on the situation and the programmer, just as with world programs.

UniverseState any/c represents the server’s state. For running universes, the teachpack demands that you come up with a data definition for (your state of the) server. Any piece of data can represent the state. We just assume that you introduce a data definition for the possible states and that your event handlers are designed according to the design recipe for this data definition.

The server itself is created with a description that includes the first state and a number of clauses that specify functions for dealing with universe events.

(universe state-expr clause ...)
 
clause = (on-new new-expr)
  | (on-msg msg-expr)
  | (on-tick tick-expr)
  | (on-tick tick-expr rate-expr)
  | (on-disconnect dis-expr)
  | (state boolean-expr)
  | (to-string render-expr)
  | (check-with is-universe?)
creates a server with a given state, state-expr. The behavior is specified via handler functions through mandatory and optional clauses. These functions govern how the server deals with the registration of new worlds, how it disconnects worlds, how it sends messages from one world to the rest of the registered worlds, and how it renders its current state as a string.

Evaluating a universe expression starts a server. Visually it opens a console window on which you can see that worlds join, which messages are received from which world, and which messages are sent to which world. For convenience, the console also has two buttons: one for shutting down a universe and another one for re-starting it. The latter functionality is especially useful during the integration of the various pieces of a distributed program.

The mandatory clauses of a universe server description are on-new and on-msg:

  • (on-new new-expr)
     
      new-expr : (-> UniverseState iworld? bundle?)
    tell DrScheme to call the function new-expr every time another world joins the universe. The event handler is called with the current state and the joining iworld, which isn’t on the list yet. In particular, the handler may reject a world program from participating in a universe, by simply including it in the resulting bundle structure (third field).

  • (on-msg msg-expr)
     
      msg-expr : (-> UniverseState iworld? sexp? bundle?)
    tell DrScheme to apply msg-expr to the current state of the universe, the world w that sent the message, and the message itself. Note that w is guaranteed to be on the list low.

All proper event handlers produce a bundle. The state in the bundle is safe-guarded by the server until the next event, and the mails are broadcast as specified. The list of iworlds in the third field of the bundle is removed from the list of participants from which to expect messages.

The following picture provides a graphical overview of the server’s workings.

In addition to the mandatory handlers, a program may wish to add some optional handlers:

7.2.3 Exploring a Universe

In order to explore the workings of a universe, it is necessary to launch a server and several world programs on one and the same computer. We recommend launching one server out of one DrScheme tab and as many worlds as necessary out of second lab. For the latter, the teachpack provides a special form.

(launch-many-worlds expression ...)
evaluates all sub-expressions in parallel. Typically each sub-expression is an application of a function that evaluates a big-bang expression. When all worlds have stopped, the expression returns all final worlds in order.

Once you have designed a world program, add a function definition concerning big-bang to the end of the tab:
  ; String -> World
  (define (main n)
    (big-bang ... (name n) ...))
Then in DrScheme’s Interactions area, use launch-with-many-worlds to create several distinctively named worlds:
  > (launch-with-many-worlds (main "matthew")
                             (main "kathi")
                             (main "h3"))
  10
  25
  33
The three worlds can then interact via a server. When all of them have stopped, they produce the final states, here 10, 25, and 33.

7.3 A First Sample Universe

This section uses a simple example to explain the design of a universe, especially its server and some participating worlds. The first subsection explains the example, the second introduces the general design plan for such universes. The remaining sections present the full-fledged solution.

7.3.1 Two Ball Tossing Worlds

Say we want to represent a universe that consists of a number of worlds and that gives each world a “turn” in a round-robin fashion. If a world is given its turn, it displays a ball that ascends from the bottom of a canvas to the top. It relinquishes its turn at that point and the server gives the next world a turn.

Here is an image that illustrates how this universe would work if two worlds participated:

The two world programs could be located on two distinct computers or on just one. A server mediates between the two worlds, including the initial start-up.

7.3.2 Hints on Designing Universes

The first step in designing a universe is to understand the coordination of the worlds from a global perspective. To some extent, it is all about knowledge and the distribution of knowledge throughout a system. We know that the universe doesn’t exist until the server starts and the worlds are joining. Because of the nature of computers and networks, however, we may assume little else. Our network connections ensure that if some world or the server sends two messages to the same place in some order, they arrive in the same order (if they arrive at all). In contrast, if two distinct world programs send one message each, the network does not guarantee the order of arrival at the server; similarly, if the server is asked to send some messages to several distinct world programs, they may arrive at those worlds in the order sent or in the some other order. In the same vein, it is impossible to ensure that one world joins before another. Worst, when someone removes the connection (cable, wireless) between a computer that runs a world program and the rest of the network or if some network cable is cut, messages don’t go anywhere. Due to this vagaries, it is therefore the designer’s task to establish a protocol that enforces a certain order onto a universe and this activity is called protocol design.

From the perspective of the universe, the design of a protocol is about the design of data representations for tracking universe information in the server and the participating worlds and the design of a data representation for messages. As for the latter, we know that they must be S-expressions, but usually world programs don’t send all kinds of S-expressions. The data definitions for messages must therefore select a subset of suitable S-expressions. As for the state of the server and the worlds, they must reflect how they currently relate to the universe. Later, when we design their “local” behavior, we may add more components to their state space.

In summary, the first step of a protocol design is to introduce:

If all the worlds exhibit the same behavior over time, a single data definition suffices for step 2. If they play different roles, we may need one data definition per world.

Of course, as you define these collections of data always keep in mind what the pieces of data mean, what they represent from the universe’s perspective.

The second step of a protocol design is to figure out which major events – the addition of a world to the universe, the arrival of a message at the server or at a world – to deal with and what they imply for the exchange of messages. Conversely, when a server sends a message to a world, this may have implications for both the state of the server and the state of the world. A good tool for writing down these agreements is an interaction diagram.

 

Server              World1                  World2

  |                   |                       |

  |   'go             |                       |

  |<------------------|                       |

  |    'go            |                       |

  |------------------------------------------>|

  |                   |                       |

  |                   |                       |

Each vertical line is the life line of a world program or the server. Each horizontal arrow denotes a message sent from one universe participant to another.

The design of the protocol, especially the data definitions, have direct implications for the design of event handling functions. For example, in the server we may wish to deal with two kinds of events: the joining of a new world and the receipt of a message from one of the worlds. This translates into the design of two functions with the following headers,

  ; Bundle is
  ;   (make-bundle UniverseState [Listof mail?] [Listof iworld?])
  
  ; UniverseState iworld? -> Bundle
  ; next list of worlds when world iw is joining
  ; the universe in state s
  (define (add-world s iw) ...)
  
  ; UniverseState iworld? W2U -> Bundle
  ; next list of worlds when world iw is sending message m to
  ; the universe in state s
  (define (process s iw m) ...)

Finally, we must also decide how the messages affect the states of the worlds; which of their callback may send messages and when; and what to do with the messages a world receives. Because this step is difficult to explain in the abstract, we move on to the protocol design for the universe of ball worlds.

7.3.3 Designing the Ball Universe

Running the ball universe has a simple overall goal: to ensure that at any point in time, one world is active and all others are passive. The active world displays a moving ball, and the passive worlds should display something, anything that indicates that it is some other world’s turn.

As for the server’s state, it must obviously keep track of all worlds that joined the universe, and it must know which one is active and which ones are passive. Of course, initially the universe is empty, i.e., there are no worlds and, at that point, the server has nothing to track.

While there are many different useful ways of representing such a universe, we just use the list of iworlds that is handed to each handler and that handlers return via their bundles. The UniverseState itself is useless for this trivial example. We interpret non-empty lists as those where the first iworld is active and the remainder are the passive iworlds. As for the two possible events,

The server should send messages to the first iworld of its list as long as it wishes this iworld to remain active. In turn, it should expect to receive messages only from this one active iworld and no other iworld. The content of these two messages is nearly irrelevant because a message from the server to a iworld means that it is the iworld’s turn and a message from the iworld to the server means that the turn is over. Just so that we don’t confuse ourselves, we use two distinct symbols for these two messages:
  • A GoMessage is 'it-is-your-turn.

  • A StopMessage is 'done.

From the universe’s perspective, each world is in one of two states:
  • A passive world is resting. We use 'resting for this state.

  • An active world is not resting. We delay choosing a representation for this part of a world’s state until we design its “local” behavior.

It is also clear that an active world may receive additional messages, which it may ignore. When it is done with its turn, it will send a message.

Server

  |                 World1

  |<==================|

  |  'it-is-your-turn |

  |------------------>|

  |                   |                    World2

  |<==========================================|

  |  'done            |                       |

  |<------------------|                       |

  |  'it-is-your-turn |                       |

  |------------------------------------------>|

  |                   |                       |

  |                   |                       |

  |  'done            |                       |

  |<------------------------------------------|

  |  'it-is-your-turn |                       |

  |------------------>|                       |

  |                   |                       |

  |                   |                       |

Here the double-lines (horizontal) denote the registration step, the others are message exchanges. The diagram thus shows how the server decides to make the first registered world the active one and to enlist all others as they join.

7.3.4 Designing the Ball Server

The preceding subsection dictates that our server program starts like this:

  ; teachpack: universe.ss
  
  ; UniverseState is '*
  ; StopMessage is 'done.
  ; GoMessage is 'it-is-your-turn.

The design of a protocol has immediate implications for the design of the event handling functions of the server. Here we wish to deal with two events: the appearance of a new world and the receipt of a message. Based on our data definitions and based on the general contracts of the event handling functions spelled out in this documentation, we get two functions for our wish list:

  ; Result is
  ;   (make-bundle [Listof iworld?]
  ;                (list (make-mail iworld? GoMessage))
  ;                '())
  
  ; [Listof iworld?] iworld? -> Result
  ; add world iw to the universe, when server is in state u
  (define (add-world u (scheme iw)) ...)
  
  ; [Listof iworld?] iworld? StopMessage -> Result
  ; world iw sent message m when server is in state u
  (define (switch u iw m) ...)

Although we could have re-used the generic contracts from this documentation, we also know from our protocol that our server sends a message to exactly one world. Note how these contracts are just refinements of the generic ones. (A type-oriented programmer would say that the contracts here are subtypes of the generic ones.)

The second step of the design recipe calls for functional examples:

  ; an obvious example for adding a world:
  (check-expect
    (add-world '() world1)
    (make-bundle (list world1)
                 (list (make-mail world1 'it-is-your-turn))
                 '()))
  
  ; an example for receiving a message from the active world:
  (check-expect
   (switch (list world1 world2) world1 'done)
   (make-bundle (list world2 world1)
                (list (make-mail world2 'it-is-your-turn))
                '()))

Note that our protocol analysis dictates this behavior for the two functions. Also note how we use world1, world2, and world3 because the teachpack applies these event handlers to real worlds.

Exercise: Create additional examples for the two functions based on our protocol.

The protocol tells us that add-world just adds the given world structure – recall that this a data representation of the actual world program – to the given list of worlds. It then sends a message to the first world on this list to get things going:

  (define (add-world univ state wrld)
    (local ((define univ* (append univ (list wrld))))
      (make-bundle univ*
                   (list (make-mail (first univ*) 'it-is-your-turn))
                   '())))

Because univ* contains at least wrld, it is acceptable to create a mail to (first univ*). Of course, this same reasoning also implies that if univ isn’t empty, its first element is an active world and is about to receive a second 'it-is-your-turn message.

Similarly, the protocol says that when switch is invoked because a world program sends a message, the data representation of the corresponding world is moved to the end of the list and the next world on the (resulting) list is sent a message:

  (define (switch univ wrld m)
    (local ((define univ* (append (rest univ) (list (first univ)))))
      (make-bundle univ*
                   (list (make-mail (first univ*) 'it-is-your-turn))
                   '())))

As before, appending the first world to the end of the list guarantees that there is at least this one world on this list. It is therefore acceptable to create a mail for this world.

Start the server now.

  (universe '() (on-new add-world) (on-msg switch))

Exercise: The function definition simply assumes that wrld is world=? to (first univ) and that the received message m is 'done. Modify the function definition so that it checks these assumptions and raises an error signal if either of them is wrong. Start with functional examples. If stuck, re-read the section on checked functions from HtDP. (Note: in a universe it is quite possible that a program registers with a server but fails to stick to the agreed-upon protocol. How to deal with such situations properly depends on the context. For now, stop the universe at this point by returning an empty list of worlds. Consider alternative solutions, too.)

Exercise: An alternative state representation would equate UniverseState with world structures, keeping track of the active world. The list of world in the server would track the passive worlds only. Design appropriate add-world and switch functions.

7.3.5 Designing the Ball World

The final step is to design the ball world. Recall that each world is in one of two possible states: active or passive. The second kind of world moves a ball upwards, decreasing the ball’s y coordinate; the first kind of world displays something that says it’s someone else’s turn. Assuming the ball always moves along a vertical line and that the vertical line is fixed, the state of the world is an enumeration of two cases:

  ; teachpack: universe.ss
  
  ; WorldState is one of:
  ;  Number          %% representing the y coordinate
  ;  'resting
  
  (define WORLD0 'resting)
  
  ; A WorldResult is one of:
  ;  WorldState
  ;  (make-package WorldState StopMessage)
The definition says that initially a world is passive.

The communication protocol and the refined data definition of WorldState imply a number of contract and purpose statements:

  ; WorldState GoMessage -> WorldResult
  ; make sure the ball is moving
  (define (receive w n) ...)
  
  ; WorldState -> WorldResult
  ; move this ball upwards for each clock tick
  ; or stay 'resting
  (define (move w) ...)
  
  ; WorldState -> image
  ; render the world as a image
  (define (render w) ...)

Let’s design one function at a time, starting with receive. Since the protocol doesn’t spell out what receive is to compute, let’s create a good set of functional examples, exploiting the structure of the data organization of WorldState:

  (check-expect (receive 'resting 'it-is-your-turn) HEIGHT)
  (check-expect (receive (- HEIGHT 1) 'it-is-your-turn) ...)

Since there are two kinds of states, we make up at least two kinds of examples: one for a 'resting state and another one for a numeric state. The dots in the result part of the second unit test reveal the first ambiguity; specifically it isn’t clear what the result should be when an active world receives another message to activate itself. The second ambiguity shows up when we study additional examples, which are suggested by our approach to designing functions on numeric intervals (HtDP, section 3). That is we should consider the following three inputs to receive:

In the third case the function could produce three distinct results: 0, 'resting, or (make-package 'resting 'done). The first leaves things alone; the second turns the active world into a resting one; the third does so, too, and tells the universe about this switch.

We choose to design receive so that it ignores the message and returns the current state of an active world. This ensures that the ball moves in a continuous fashion and that the world remains active.

Exercise: One alternative design is to move the ball back to the bottom of the image every time 'it-is-your-turn is received. Design this function, too.

  (define (receive w m)
    (cond
      [(symbol? w) HEIGHT] ; meaning: (symbol=? w 'resting)
      [else w]))

Our second function to design is move, the function that computes the ball movement. We have the contract and the second step in the design recipe calls for examples:

  ; WorldState -> WorldState or (make-package 'resting 'done)
  ; move the ball if it is flying
  
  (check-expect (move 'resting) 'resting)
  (check-expect (move HEIGHT) (- HEIGHT 1))
  (check-expect (move (- HEIGHT 1)) (- HEIGHT 2))
  (check-expect (move 0) (make-package 'resting 'done))
  
  (define (move x) ...)

Following HtDP again, the examples cover four typical situations: 'resting, two end points of the specified numeric interval, and one interior point. They tell us that move leaves a passive world alone and that it otherwise moves the ball until the y coordinate becomes 0. In the latter case, the result is a package that renders the world passive and tells the server about it.

Turning these thoughts into a complete definition is straightforward now:

  (define (move x)
    (cond
      [(symbol? x) x]
      [(number? x) (if (<= x 0)
                       (make-package 'resting 'done)
                       (sub1 x))]))

Exercise: what could happen if we had designed receive so that it produces 'resting when the state of the world is 0? Use your answer to explain why you think it is better to leave this kind of state change to the tick event handler instead of the message receipt handler?

Finally, here is the third function, which renders the state as an image:

  ; WorldState -> image
  ; render the state of the world as a image
  
  (check-expect (render HEIGHT) (place-image BALL 50 HEIGHT MT))
  (check-expect (render 'resting)
                (place-image  (text "resting" 11 'red) 10 10 MT))
  
  (define (render w)
    (place-image
      (text name 11 'black) 5 85
      (cond
        [(symbol? w) (place-image (text "resting" 11 'red) 10 10 MT)]
        [(number? w) (place-image BALL 50 w MT)])))

Here is an improvement that adds a name to the image and abstracts over the name at the same time:

  ; String -> (WorldState -> image)
  ; render the state of the world as a image
  
  (check-expect
   ((draw "Carl") 100)
   (place-image (text "Carl" 11 'black)
                5 85
                (place-image BALL 50 100 MT)))
  
  (define (draw name)
    (lambda (w)
      (place-image
       (text name 11 'black) 5 85
       (cond
         [(symbol? w) (place-image (text "resting" 11 'red) 10 10 MT)]
         [(number? w) (place-image BALL 50 w MT)]))))

By doing so, we can use the same program to create many different worlds that register with a server on your computer:
  ; String -> WorldState
  ; create and hook up a world with the LOCALHOST server
  (define (create-world n)
    (big-bang WORLD0
             (on-receive receive)
             (on-draw (draw n))
             (on-tick move)
             (name n)
             (register LOCALHOST)))

Now you can use (create-world 'carl) and (create-world 'same), respectively, to run two different worlds, after launching a server first.

Exercise: Design a function that takes care of a world to which the universe has lost its connection. Is Result the proper contract for the result of this function?