On this page:
8.1 Two Ball Tossing Worlds
8.2 Creating a Universe
8.3 Designing the Ball Universe
8.4 Code for the World
8.5 Code for the Universe
8.6 An Alternate Universe
Version: 4.1.2.4

8 A First Sample Universe

This section uses a simple example to explain how to design a universe of two cooperating worlds. The first subsection explains the example, the second introduces the general design plan for such universes. The third section presents the full-fledged solution.

8.1 Two Ball Tossing Worlds

Say we want to represent a universe that consists of two "ball exchanging" worlds. More precisely, at any point in time, one of the worlds displays a ball rising from the bottom to the top. When the ball reaches the top, the world "sends" the ball to the other world, and the ball rises there from bottom to top. The action goes back and forth like this until you stop the universe or one of the worlds ends.

The image suggests that the two worlds are on two distinct computers; of course, they could just be on one. A universe mediates between the two worlds, including the initial start-up.

8.2 Creating a Universe

The design of every universe starts with the design of a "message protocol." Such a "protocol" consists of two parts:

In addition, you should contemplate in what order these message exchanges occur and how they change the states of the world. Of course, the states of the world represent partial knowledge about the entire universe, and you must be keenly aware which participant should posses which knowledge.

The data definition must pick a subset of S-expressions. On many occasions, it suffices to pick symbols because the worlds’ behavior doesn’t depend on concrete data. On others, worlds must exchange real information, e.g., words, numbers, or entire lists.

Equipped with a protocol of messages, we can then proceed to design the state of the universe and the states of the worlds. That is, our next steps are

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. Put differently, we should sketch out all possible scenarios of behavior of our universe server and worlds.

8.3 Designing the Ball Universe

Each of the ball worlds just needs a "trigger" message that starts the ball’s travel or a "rest" message that puts it to sleep. In particular, once the two worlds have registered with the universe, one of them needs to become the active world and the other one needs to become the passive one. One obvious choice of messages is then

Both of these message are sent from the universe (server) to the participating worlds. Put differently, the universe acts as a "tie breaker" at the beginning and, from then on, everything works automatically.

The agreement on this part of the protocol already has implications for the state of each world and for the function that processes messages from the universe. Each of them must have a receive function for processing messages from the universe, and these functions must be able to cope with the chosen symbols:

  ; World Message -> World
  ; place the ball at lower end or stop
  
  (check-expect (receive 'AnyState 'stop) ...) ; a resting state
  (check-expect (receive 'AnyState 'go)   ...) ; ball at bottom
  
  (define (receive w m)
    (cond
      [(symbol=? m 'go) ...]
      [(symbol=? m 'stop) ...]))

We must also design messages from the worlds to the universe. In this particular case, the message is of course from one world to the other; the universe is just a conduit for sending these messages after it has started up the worlds. Thus imagine that the ball in the first active world reaches the top of the canvas. At that point, we would like the world to come to rest and send a message to the other world so that it starts up its ball.

One obvious choice of message is 'go. Upon receipt of this message, the universe just passes this message on to the other world. Because it knows which world is active, it always knows from which world it is receiving a message. Conversely, the universe server is thus able to forward this message to the other world. Of course, all this greatly simplifies the design of the process function.

8.4 Code for the World

We start with the design of the "ball universe". Recall that each world is in one of two possible states: moving or resting. The first kind of world moves a ball upwards, decreasing the ball’s y coordinate; the second kind of world is displayed as "resting." Assuming the ball always moves a vertical line and that the vertical line is fixed, the state of the world is an enumeration of two cases:

  (require "universe2.ss")
  
  ; World is one of
  ; -- Number
  ; -- 'resting

Initially both worlds are resting:

  (define WORLD0 'resting)

As for communications, two messages suffice:

  ; Message is one of:
  ; -- 'go
  ; -- 'stop

The first one is sent and received; the second one is only received.

The two data definitions also determine the signature and the purpose of several functions: receive, the function that deals with messages from the universe; draw, the function that renders a world as a scene; and move, the function that is run every tick of the clock.

  ; World Message -> World
  ; place the ball at lower end or set the world to 'resting
  
  (check-expect (receive 'ANY 'stop) 'resting)
  (check-expect (receive 'ANY 'go) SIZE)
  
  (define (receive w n)
    (cond
      [(symbol=? n 'go) SIZE]
      [(symbol=? n 'stop) 'resting]))
  ; This definition assumes a SIZE by SIZE canvas.
  
  ; String -> (World -> Scene)
  ; render the world
  
  (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)]))))
  
  ; World -> World or (make-package 'resting 'go)
  ; move the ball if it is flying
  
  (check-expect (move 'resting) 'resting)
  (check-expect (move SIZE) (- SIZE 1))
  (check-expect (move 0) (make-package 'resting 'go))
  
  (define (move x)
    (cond
      [(symbol? x) x]
      [(number? x) (if (<= x 0) (make-package 'resting 'go) (sub1 x))]))

Finally, we want two of these worlds so we abstract over the launching of the world with a single function:

  ; String -> true
  ; create and hook up a player with the LOCALHOST server
  (define (make-player name)
    (and (big-bang SIZE SIZE 1/30 WORLD0)
         (on-receive-event receive)
         (on-redraw (draw name))
         (on-tick-event move)
         (register LOCALHOST name)))

The two worlds just calls (make-player 'carl) and (make-player 'same), respectively.

8.5 Code for the Universe

When the ball reaches the top of the world, i.e., the y coordinate is 0, the tick function produces a package. The teachpack then knows to send the specified message to the universe, which takes care of it.

Like worlds, the universe is in a state and each message may transform this state into a new one. So let’s start with a data definition for universe states.

  (require "universe2.ss")
  
  (define-struct unistate (active passive))
  
  ; Universe = (make-unistate Player Player)
  ; interpretation: (make-unistate p q) means p is the currently
  ; active player, and q} is the one waiting for the ball
  
  ; Mail = [Listof (list Player Message)]
  ; Message is one of:
  ; -- 'go
  ; -- 'stop

As for the design of the worlds, these data definitions determine the contracts and purpose statements of the two functions we must design to create the universe:

  ; Player Player -> (cons Universe Mail)
  ; create initial universe and tell p2 to start playing
  (define (ball-universe p1 p2) ...)
  
  ; Universe Player Sexp -> (cons Universe Mail)
  ; p sent message m in universe u,
  ; forward message to passive player and switch roles
  (define (switch-players u p m) ...)

Indeed, a moment’s thought shows that the data definitions and our understanding of the problem also determine the exact purpose of each function.

The rest is easy. The create-universe function pairs up the two registered players; making one of them active and the other one the passive player; and creates appropriate messages to the two players. Upon receipt of any messages, the universe invokes the function switch-players on the current state of the universe, the player that sent the message, and the message. This function then switches the roles of the players and forwards the 'go message to the newly activated player.

  ; Player Player -> (cons Universe Mail)
  ; create initial universe and tell p2 to start playing
  (define (ball-universe p1 p2)
    (cons (make-unistate p2 p1)
          (list (list p2 'go)
                (list p1 'stop))))
  
  ; Universe Player Sexp -> (cons Universe Mail)
  ; p sent message m in universe u
  (define (switch-players u p m)
    (cons (make-unistate (unistate-passive u) (unistate-active u))
          (list (list (unistate-passive u) 'go))))
  
  (universe ball-universe switch-players)

Use the posted code to test the players on one machine, watching the interplay between the two worlds. Watch the interplay between the worlds and the messages that go back and forth.

Note how all this code requires nothing more than an understanding of Parts I and II of HtDP.

Exercise: Design a switch-player function that rejects messages from the passive player if it should ever send any. Could this ever happen? Why should we take such precautions?

8.6 An Alternate Universe

The design of distributed systems is all about understanding how the state and the actions of individual pieces (worlds and universe) change the overall state. In the case of our simple ball game, we have two critical pieces of knowledge:

A consequence of these two bullets is that after start-up, one of the worlds moves the ball and the other one rests.

We establish this state of the overall universe with appropriate pieces of code. For the first point, we set the initial state of the worlds to 'resting. For the second one, search for make-package in the world program and notice that only one handler returns packages. Specifically, when the ball in the active world reaches the top of the canvas, the world comes to a rest and sends a message to the universe.

Now imagine a person who finds this ball universe boring (well, who doesn’t?) because the two worlds are both resting when the universe is booted. One way to change this is to use SIZE as the initial world, meaning we make both worlds active while they are registering with the universe. Doing so guarantees that there is always something happening, but of course when both worlds are active, both of them can send messages to he universe. Since there are absolutely no guarantees about the order of messages or the speed at which messages are delivered across a network connection, it is impossible to predict reliable what can happen. Some experimentation with this revised universe should demonstrate, however, that we may never reach a state of the universe in which one world is actively moving the world and the other one is resting.

Exercise: How can you fix this revised universe so that it is guaranteed to reach the desired state of one active and one passive world?