1644 lines
63 KiB
Racket
1644 lines
63 KiB
Racket
#lang scribble/doc
|
|
|
|
@(require scribble/manual "shared.ss"
|
|
(for-label scheme
|
|
(only-in lang/htdp-beginner check-expect)
|
|
teachpack/2htdp/universe
|
|
2htdp/image))
|
|
@(require scribble/struct)
|
|
|
|
@(define (table* . stuff)
|
|
;; (list paragraph paragraph) *-> Table
|
|
(define (flow* x) (make-flow (list x)))
|
|
(make-blockquote #f
|
|
(list
|
|
(make-table (make-with-attributes 'boxed
|
|
'((cellspacing . "6")))
|
|
;list
|
|
(map (lambda (x) (map flow* x)) stuff)
|
|
#;(map flow* (map car stuff))
|
|
#;(map flow* (map cadr stuff))))))
|
|
|
|
@; -----------------------------------------------------------------------------
|
|
|
|
@teachpack["universe"]{Worlds and the Universe}
|
|
|
|
@author{Matthias Felleisen}
|
|
|
|
@defmodule[#:require-form beginner-require 2htdp/universe #:use-sources (teachpack/htdp/image)]
|
|
|
|
@;{FIXME: the following paragraph uses `defterm' instead of `deftech',
|
|
because the words "world" and "universe" are used as datatypes, and
|
|
datatypes are currently linked as technical terms --- which is a hack.
|
|
Fix the paragraph when we have a better way to link datatype names.}
|
|
|
|
This @tt{universe.ss} teachpack implements and provides the functionality
|
|
for creating interactive, graphical programs that consist of plain
|
|
mathematical functions. We refer to such programs as @deftech{world}
|
|
programs. In addition, world programs can also become a part of a
|
|
@deftech{universe}, a collection of worlds that can exchange messages.
|
|
|
|
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 @tech{world} programs. Section
|
|
@secref["world-example"] 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 @tech{world} programs register with the server,
|
|
etc. The last two sections show how to design a simple universe of two
|
|
communicating worlds.
|
|
|
|
@emph{Note}: For a quick and educational introduction to just worlds, see
|
|
@link["http://www.ccs.neu.edu/home/matthias/HtDP2e/prologue.html"]{How
|
|
to Design Programs, Second Edition: Prologue}. As of August 2008, we also
|
|
have a series of projects available as a small booklet on
|
|
@link["http://world.cs.brown.edu/"]{How to Design Worlds}.
|
|
|
|
@; -----------------------------------------------------------------------------
|
|
@section{Background}
|
|
|
|
The universe teachpack assumes working knowledge of the basic image manipulation primitives,
|
|
either @schememodname[htdp/image] or @schememodname[2htdp/image]. Its operations
|
|
sometimes require scenes which for @scheme[htdp/image] images means an image whose
|
|
pinhole is at (0,0). For @schememodname[2htdp/image], every image is a scene.
|
|
|
|
The example programs in this document are all written using @schememodname[2htdp/image]
|
|
primitives.
|
|
|
|
@; -----------------------------------------------------------------------------
|
|
@section[#:tag "simulations"]{Simple Simulations}
|
|
|
|
The simplest kind of animated @tech{world} program is a time-based
|
|
simulation, which is a series of scenes. The programmer's task is to
|
|
supply a function that creates a scene for each natural number. By handing
|
|
this function to the teachpack displays the simulation.
|
|
|
|
@defproc[(animate [create-image (-> natural-number/c scene?)])
|
|
true]{
|
|
|
|
opens a canvas and starts a clock that tick 28 times per second. Every
|
|
time the clock ticks, DrScheme applies @scheme[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 @tt{Stop} button in DrScheme or close the window. At that
|
|
point, @scheme[animate] returns the number of ticks that have
|
|
passed.
|
|
}
|
|
|
|
Example:
|
|
@schemeblock[
|
|
(define (create-UFO-scene height)
|
|
(underlay/xy (rectangle 100 100 "solid" "white") 50 height UFO))
|
|
|
|
(define UFO
|
|
(underlay/align "center"
|
|
"center"
|
|
(circle 10 "solid" "green")
|
|
(rectangle 40 4 "solid" "green")))
|
|
|
|
(animate create-UFO-scene)
|
|
]
|
|
|
|
@defproc[(run-simulation [create-image (-> natural-number/c scene?)])
|
|
true]{
|
|
|
|
@scheme[animate] was originally called @scheme[run-simulation], and this
|
|
binding is retained for backwards compatibility}
|
|
|
|
@;-----------------------------------------------------------------------------
|
|
@section[#:tag "interactive"]{Interactions}
|
|
|
|
The step from simulations to interactive programs is relatively
|
|
small. Roughly speaking, a simulation designates one function,
|
|
@scheme[_create-image], as a handler for one kind of event: clock ticks. In
|
|
addition to clock ticks, @tech{world} programs can also deal with two
|
|
other kinds of events: keyboard events and mouse events. 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 via the @emph{designation} of
|
|
@emph{handler} functions. Specifically, the teachpack provides for the
|
|
installation of three event handlers: @scheme[on-tick], @scheme[on-key],
|
|
and @scheme[on-mouse]. In addition, a @tech{world} program may specify a
|
|
@scheme[draw] function, which is called every time your program should
|
|
visualize the current world, and a @scheme[done] predicate, which is used
|
|
to determine when the @tech{world} program should shut down.
|
|
|
|
Each handler function consumes the current state of the @tech{world} and
|
|
optionally a data representation of the event. It produces a new state of
|
|
the @tech{world}.
|
|
|
|
The following picture provides an intuitive overview of the workings of a
|
|
@tech{world} program in the form of a state transition diagram.
|
|
|
|
@image["nuworld.png"]
|
|
|
|
The @scheme[big-bang] form installs @scheme[World_0] as the initial @tech{WorldState}.
|
|
The handlers @scheme[tock], @scheme[react], and @scheme[click] transform
|
|
one world into another one; each time an event is handled, @scheme[done] is
|
|
used to check whether the world is final, in which case the program is
|
|
shut down; and finally, @scheme[draw] renders each world as a scene, which
|
|
is then displayed on an external canvas.
|
|
|
|
@deftech{WorldState} : @scheme[any/c]
|
|
|
|
The design of a world program demands that you come up with a data
|
|
definition of all possible states. We use @tech{WorldState} to refer to
|
|
this collection of data, using a capital W to distinguish it from the
|
|
program. In principle, there are no constraints on this data
|
|
definition though it mustn't be an instance of the @tech{Package}
|
|
structure (see below). You can even keep it implicit, even if this
|
|
violates the Design Recipe.
|
|
|
|
@defform/subs[#:id big-bang
|
|
#:literals
|
|
(on-tick on-draw on-key on-mouse on-receive stop-when
|
|
check-with register record? state name)
|
|
(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-scene-expr)
|
|
(check-with world?-expr)
|
|
(record? boolean-expr)
|
|
(state boolean-expr)
|
|
(on-receive rec-expr)
|
|
(register IP-expr)
|
|
(name name-expr)
|
|
])]{
|
|
|
|
starts a @tech{world} program in the initial state specified with
|
|
@scheme[state-expr], which must of course evaluate to an element of
|
|
@tech{WorldState}. Its behavior is specified via the handler functions
|
|
designated in the optional @scheme[spec] clauses, especially how the
|
|
@tech{world} program deals with clock ticks, with key events, with mouse
|
|
events, and eventually with messages from the universe; how it renders
|
|
itself as a scene; 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 @scheme[on-tick],
|
|
@scheme[on-draw], or @scheme[register] clause. A @scheme[big-bang]
|
|
expression returns the last world when the stop condition is satisfied
|
|
(see below) or when the programmer clicks on the @tt{Stop} button or
|
|
closes the canvas.
|
|
}
|
|
|
|
@itemize[
|
|
|
|
@item{
|
|
@defform[(on-tick tick-expr)
|
|
#:contracts
|
|
([tick-expr (-> (unsyntax @tech{WorldState}) (unsyntax @tech{WorldState}))])]{
|
|
|
|
tell DrScheme to call the @scheme[tick-expr] function on the current
|
|
world every time the clock ticks. The result of the call becomes the
|
|
current world. The clock ticks at the rate of 28 times per second.}}
|
|
|
|
@item{
|
|
@defform/none[#:literals(on-tick)
|
|
(on-tick tick-expr rate-expr)
|
|
#:contracts
|
|
([tick-expr (-> (unsyntax @tech{WorldState}) (unsyntax @tech{WorldState}))]
|
|
[rate-expr (and/c real? positive?)])]{
|
|
tell DrScheme to call the @scheme[tick-expr] function on the current
|
|
world every time the clock ticks. The result of the call becomes the
|
|
current world. The clock ticks at the rate of @scheme[rate-expr].}}
|
|
|
|
@item{A @tech{KeyEvent} represents key board events, e.g., keys pressed or
|
|
released.
|
|
|
|
@deftech{KeyEvent} : @scheme[string?]
|
|
|
|
For simplicity, we represent key events with strings, but not all strings
|
|
are key events. The representation of key events comes in distinct
|
|
classes. First, a single-character string is used to signal that the user
|
|
has hit a "regular" key. Some of these one-character strings may look
|
|
unusual:
|
|
@itemize[
|
|
|
|
@item{@scheme[" "] stands for the space bar (@scheme[#\space]);}
|
|
@item{@scheme["\r"] stands for the return key (@scheme[#\return]);}
|
|
@item{@scheme["\t"] stands for the tab key (@scheme[#\tab]); and}
|
|
@item{@scheme["\b"] stands for the backspace key (@scheme[#\backspace]).}
|
|
|
|
]
|
|
On rare occasions you may also encounter @scheme["\u007F"], which is the
|
|
string representing the delete key (aka rubout).
|
|
|
|
Second, some keys have multiple-character string representations. Strings
|
|
with more than one character denotes arrow keys or other special events,
|
|
starting with the most important:
|
|
@itemize[
|
|
@item{@scheme["left"] is the left arrow;}
|
|
@item{@scheme["right"] is the right arrow;}
|
|
@item{@scheme["up"] is the up arrow;}
|
|
@item{@scheme["down"] is the down arrow;}
|
|
@item{@scheme["release"] is the event of releasing a key;}
|
|
@item{@scheme["start"]}
|
|
@item{@scheme["cancel"]}
|
|
@item{@scheme["clear"]}
|
|
@item{@scheme["shift"]}
|
|
@item{@scheme["control"]}
|
|
@item{@scheme["menu"]}
|
|
@item{@scheme["pause"]}
|
|
@item{@scheme["capital"]}
|
|
@item{@scheme["prior"]}
|
|
@item{@scheme["next"]}
|
|
@item{@scheme["end"]}
|
|
@item{@scheme["home"]}
|
|
@item{@scheme["escape"]}
|
|
@item{@scheme["select"]}
|
|
@item{@scheme["print"]}
|
|
@item{@scheme["execute"]}
|
|
@item{@scheme["snapshot"]}
|
|
@item{@scheme["insert"]}
|
|
@item{@scheme["help"]}
|
|
@item{@scheme["numpad0"],
|
|
@scheme["numpad1"],
|
|
@scheme["numpad2"],
|
|
@scheme["numpad3"],
|
|
@scheme["numpad4"],
|
|
@scheme["numpad5"],
|
|
@scheme["numpad6"],
|
|
@scheme["numpad7"],
|
|
@scheme["numpad8"],
|
|
@scheme["numpad9"],
|
|
@scheme["numpad-enter"],
|
|
@scheme["multiply"],
|
|
@scheme["add"],
|
|
@scheme["separator"],
|
|
@scheme["subtract"],
|
|
@scheme["decimal"],
|
|
@scheme["divide"]}
|
|
@item{@scheme["f1"],
|
|
@scheme["f2"],
|
|
@scheme["f3"],
|
|
@scheme["f4"],
|
|
@scheme["f5"],
|
|
@scheme["f6"],
|
|
@scheme["f7"],
|
|
@scheme["f8"],
|
|
@scheme["f9"],
|
|
@scheme["f10"],
|
|
@scheme["f11"],
|
|
@scheme["f12"],
|
|
@scheme["f13"],
|
|
@scheme["f14"],
|
|
@scheme["f15"],
|
|
@scheme["f16"],
|
|
@scheme["f17"],
|
|
@scheme["f18"],
|
|
@scheme["f19"],
|
|
@scheme["f20"],
|
|
@scheme["f21"],
|
|
@scheme["f22"],
|
|
@scheme["f23"],
|
|
@scheme["f24"]}
|
|
@item{@scheme["numlock"]}
|
|
@item{@scheme["scroll"]}
|
|
@item{@scheme["wheel-up"]}
|
|
@item{@scheme["wheel-down"]}
|
|
]
|
|
|
|
@defproc[(key-event? [x any]) boolean?]{
|
|
determines whether @scheme[x] is a @tech{KeyEvent}}
|
|
|
|
@defproc[(key=? [x key-event?][y key-event?]) boolean?]{
|
|
compares two @tech{KeyEvent} for equality}
|
|
|
|
@defform[(on-key change-expr)
|
|
#:contracts
|
|
([change-expr (-> (unsyntax @tech{WorldState}) key-event? (unsyntax @tech{WorldState}))])]{
|
|
tell DrScheme to call @scheme[change-expr] function on the current world and a
|
|
@tech{KeyEvent} for every keystroke the user of the computer makes. The result
|
|
of the call becomes the current world.
|
|
|
|
Here is a typical key-event handler:
|
|
@schemeblock[
|
|
(define (change w a-key)
|
|
(cond
|
|
[(key=? a-key "left") (world-go w -DELTA)]
|
|
[(key=? a-key "right") (world-go w +DELTA)]
|
|
[(= (string-length a-key) 1) w] (code:comment "order-free checking")
|
|
[(key=? a-key "up") (world-go w -DELTA)]
|
|
[(key=? a-key "down") (world-go w +DELTA)]
|
|
[else w]))
|
|
]
|
|
}
|
|
The omitted, auxiliary function @emph{world-go} is supposed to consume a
|
|
world and a number and produces a world.
|
|
}
|
|
|
|
@item{ A @tech{MouseEvent} represents mouse events, e.g., mouse movements
|
|
or mouse clicks, by the computer's user.
|
|
|
|
@deftech{MouseEvent} : @scheme[(one-of/c "button-down" "button-up" "drag" "move" "enter" "leave")]
|
|
|
|
All @tech{MouseEvent}s are represented via strings:
|
|
@itemize[
|
|
|
|
@item{@scheme["button-down"]
|
|
signals that the computer user has pushed a mouse button down;}
|
|
@item{@scheme["button-up"]
|
|
signals that the computer user has let go of a mouse button;}
|
|
@item{@scheme["drag"]
|
|
signals that the computer user is dragging the mouse;}
|
|
@item{@scheme["move"]
|
|
signals that the computer user has moved the mouse;}
|
|
@item{@scheme["enter"]
|
|
signals that the computer user has moved the mouse into the canvas area; and}
|
|
@item{@scheme["leave"]
|
|
signals that the computer user has moved the mouse out of the canvas area.}
|
|
]
|
|
|
|
@defproc[(mouse-event? [x any]) boolean?]{
|
|
determines whether @scheme[x] is a @tech{MouseEvent}}
|
|
|
|
@defproc[(mouse=? [x mouse-event?][y mouse-event?]) boolean?]{
|
|
compares two @tech{MouseEvent}s for equality}
|
|
|
|
@defform[(on-mouse clack-expr)
|
|
#:contracts
|
|
([clack-expr
|
|
(-> (unsyntax @tech{WorldState})
|
|
natural-number/c natural-number/c (unsyntax @tech{MouseEvent})
|
|
(unsyntax @tech{WorldState}))])]{
|
|
tell DrScheme to call @scheme[clack-expr] on the current world, the current
|
|
@scheme[x] and @scheme[y] coordinates of the mouse, and and a
|
|
@tech{MouseEvent} for every (noticeable) action of the mouse by the
|
|
computer user. The result of the call becomes the current world.
|
|
|
|
Note: the computer's software doesn't really notice every single movement
|
|
of the mouse (across the mouse pad). Instead it samples the movements and
|
|
signals most of them.}
|
|
}
|
|
|
|
@item{
|
|
|
|
@defform[(on-draw render-expr)
|
|
#:contracts
|
|
([render-expr (-> (unsyntax @tech{WorldState}) scene?)])]{
|
|
|
|
tell DrScheme to call the function @scheme[render-expr] whenever the
|
|
canvas must be drawn. The external canvas is usually re-drawn after DrScheme has
|
|
dealt with an event. Its size is determined by the size of the first
|
|
generated @tech{scene}.}
|
|
|
|
@defform/none[#:literals (on-draw)
|
|
(on-draw render-expr width-expr height-expr)
|
|
#:contracts
|
|
([render-expr (-> (unsyntax @tech{WorldState}) scene?)]
|
|
[width-expr natural-number/c]
|
|
[height-expr natural-number/c])]{
|
|
|
|
tell DrScheme to use a @scheme[width-expr] by @scheme[height-expr]
|
|
canvas instead of one determine by the first generated @tech{scene}.
|
|
}}
|
|
|
|
@item{
|
|
|
|
@defform[(stop-when last-world?)
|
|
#:contracts
|
|
([last-world? (-> (unsyntax @tech{WorldState}) boolean?)])]{
|
|
tell DrScheme to call the @scheme[last-world?] function whenever the canvas is
|
|
drawn. If this call produces @scheme[true], the world program is shut
|
|
down. Specifically, the clock is stopped; no more
|
|
tick events, @tech{KeyEvent}s, or @tech{MouseEvent}s are forwarded to
|
|
the respective handlers. The @scheme[big-bang] expression returns this
|
|
last world.
|
|
}
|
|
|
|
@defform/none[#:literals (stop-when)
|
|
(stop-when last-world? last-picture)
|
|
#:contracts
|
|
([last-world? (-> (unsyntax @tech{WorldState}) boolean?)]
|
|
[last-picture (-> (unsyntax @tech{WorldState}) scene?)])]{
|
|
tell DrScheme to call the @scheme[last-world?] function whenever the canvas is
|
|
drawn. If this call produces @scheme[true], the world program is shut
|
|
down after displaying the world one last time, this time using the scene
|
|
rendered with @scheme[last-picture]. Specifically, the clock is stopped; no more
|
|
tick events, @tech{KeyEvent}s, or @tech{MouseEvent}s are forwarded to
|
|
the respective handlers. The @scheme[big-bang] expression returns this
|
|
last world.
|
|
}
|
|
}
|
|
|
|
@item{
|
|
|
|
@defstruct[stop-with ([w (unsyntax @tech{WorldState})])]{signals to
|
|
DrScheme that the world program should shut down. That is, any
|
|
handler may return @scheme[(stop-with w)] provided @scheme[w] is a
|
|
@tech{WorldState}. If it does, the state of the world becomes @scheme[w]
|
|
and @scheme[big-bang] will close down all event handling.}
|
|
|
|
}
|
|
|
|
@item{
|
|
|
|
@defform[(check-with world-expr?)
|
|
#:contracts
|
|
([world-expr? (-> Any boolean?)])]{
|
|
tell DrScheme to call the @scheme[world-expr?] function on the result of
|
|
every world handler call. If this call produces @scheme[true], the result
|
|
is considered a world; otherwise the world program signals an error.
|
|
}}
|
|
|
|
@item{
|
|
|
|
@defform[(record? boolean-expr)
|
|
#:contracts
|
|
([boolean-expr boolean?])]{
|
|
tell DrScheme to record all events and to enable a replay of the entire
|
|
interaction. The replay action also generates one png image per scene and
|
|
an animated gif for the entire sequence.
|
|
}}
|
|
|
|
@item{
|
|
|
|
@defform[(state boolean-expr)
|
|
#:contracts
|
|
([boolean-expr boolean?])]{
|
|
tell DrScheme to display a separate window in which the current
|
|
state is rendered each time it is updated. This is useful for beginners
|
|
who wish to see how their world evolves---without having to design a
|
|
rendering function---plus for the debugging of world programs.
|
|
}}
|
|
]
|
|
|
|
The following example shows that @scheme[(run-simulation create-UFO-scene)] is
|
|
a short-hand for three lines of code:
|
|
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
@schemeblock[
|
|
(define (create-UFO-scene height)
|
|
(underlay/xy (rectangle 100 100 "solid" "white") 50 height UFO))
|
|
|
|
(define UFO
|
|
(underlay/align "center"
|
|
"center"
|
|
(circle 10 "solid" "green")
|
|
(rectangle 40 4 "solid" "green")))
|
|
|
|
;; (run-simulation create-UFO-scene) is short for:
|
|
(big-bang 0
|
|
(on-tick add1)
|
|
(on-draw create-UFO-scene))
|
|
])
|
|
|
|
Exercise: Add a condition for stopping the flight of the UFO when it
|
|
reaches the bottom.
|
|
|
|
@; -----------------------------------------------------------------------------
|
|
@section[#:tag "world-example"]{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 @tech{world}
|
|
programs in general, the remaining subsections implement a simulation of
|
|
the door.
|
|
|
|
@subsection{Understanding a Door}
|
|
|
|
Say we wish to design a @tech{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:
|
|
|
|
@image["door-real.png"]
|
|
|
|
Like the picture of the general workings of a @tech{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:
|
|
|
|
@itemize[
|
|
|
|
@item{``push'' means a person pushes the door open (and let's go);}
|
|
|
|
@item{``lock'' refers to the act of inserting a key into the lock and turning
|
|
it to the locked position; and}
|
|
|
|
@item{``unlock'' is the opposite of ``lock.''}
|
|
|
|
]
|
|
|
|
@; -----------------------------------------------------------------------------
|
|
@subsection{Hints on Designing Worlds}
|
|
|
|
Simulating any dynamic behavior via a @tech{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 we must
|
|
develop a data representation for this information. This is what we call
|
|
@tech{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 @tech{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:
|
|
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
(schemeblock
|
|
;; tick : WorldState -> WorldState
|
|
;; deal with the passing of time
|
|
(define (tick w) ...)
|
|
|
|
;; click : WorldState @emph{Number} @emph{Number} @tech{MouseEvent} -> WorldState
|
|
;; deal with a mouse click at @emph{(x,y)} of kind @emph{me}
|
|
;; in the current world @emph{w}
|
|
(define (click w x y me) ...)
|
|
|
|
;; control : WorldState @tech{KeyEvent} -> WorldState
|
|
;; deal with a key event @emph{ke}
|
|
;; in the current world @emph{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 @tech{world} program.
|
|
|
|
@centerline{An extended example is available in
|
|
@link["http://www.ccs.neu.edu/home/matthias/HtDP2e/"]{How to Design Worlds/2e}.}
|
|
|
|
|
|
@; -----------------------------------------------------------------------------
|
|
@section[#:tag "world2"]{The World is not Enough}
|
|
|
|
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 number of programs that
|
|
coordinate their actions in some fashion. Each of the individual programs
|
|
may run on any computer in the world (as in our planet and the spacecrafts
|
|
that we sent out), 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 @tech{universe} and the program that coordinates
|
|
it all a @emph{universe server} or just @tech{server}.
|
|
|
|
This section explains what messages are, how to send them from a
|
|
@tech{world} program, how to receive them, and how to connect a
|
|
@tech{world} program to a @tech{universe}.
|
|
|
|
@; -----------------------------------------------------------------------------
|
|
|
|
@subsection{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
|
|
@tech{S-expression}.
|
|
|
|
@deftech{S-expression} An S-expression is roughly a nested list of basic
|
|
data; to be precise, an S-expression is one of:
|
|
|
|
@itemize[
|
|
@item{a string,}
|
|
@item{a symbol,}
|
|
@item{a number,}
|
|
@item{a boolean,}
|
|
@item{a char, or}
|
|
@item{a list of S-expressions.}
|
|
]
|
|
Note the last clause includes @scheme[empty] of course.
|
|
|
|
@defproc[(sexp? [x any/c]) boolean?]{
|
|
determines whether @scheme[x] is an @tech{S-expression}.}
|
|
|
|
@subsection{Sending Messages}
|
|
|
|
Each world-producing callback in a world program---those for handling clock
|
|
tick events, keyboard events, and mouse events---may produce a
|
|
@tech{Package} in addition to just a @tech{WorldState}.
|
|
|
|
@deftech{Package} represents a pair consisting of a @tech{WorldState}
|
|
and a message from a @tech{world} program to the @tech{server}. Because
|
|
programs only send messages via @tech{Package}, the teachpack does not
|
|
provide the selectors for the structure, only the constructor and a
|
|
predicate.
|
|
|
|
@defproc[(package? [x any/c]) boolean?]{
|
|
determine whether @scheme[x] is a @tech{Package}.}
|
|
|
|
@defproc[(make-package [w any/c][m sexp?]) package?]{
|
|
create a @tech{Package} from a @tech{WorldState} and an @tech{S-expression}.}
|
|
|
|
As mentioned, all event handlers may return @tech{WorldState}s or
|
|
@tech{Package}s; here are the revised specifications:
|
|
|
|
@defform/none[#:literals (on-tick)
|
|
(on-tick tick-expr)
|
|
#:contracts
|
|
([tick-expr (-> (unsyntax @tech{WorldState}) (or/c (unsyntax @tech{WorldState}) package?))])]{
|
|
}
|
|
|
|
@defform/none[#:literals (on-tick)
|
|
(on-tick tick-expr rate-expr)
|
|
#:contracts
|
|
([tick-expr (-> (unsyntax @tech{WorldState}) (or/c (unsyntax @tech{WorldState}) package?))]
|
|
[rate-expr (and/c real? positive?)])]{
|
|
}
|
|
|
|
@defform/none[#:literals (on-key)
|
|
(on-key change-expr)
|
|
#:contracts
|
|
([change-expr (-> (unsyntax @tech{WorldState}) key-event? (or/c (unsyntax @tech{WorldState}) package?))])]{
|
|
}
|
|
|
|
@defform/none[#:literals (on-mouse)
|
|
(on-mouse clack-expr)
|
|
#:contracts
|
|
([clack-expr
|
|
(-> (unsyntax @tech{WorldState})
|
|
natural-number/c natural-number/c (unsyntax @tech{MouseEvent})
|
|
(or/c (unsyntax @tech{WorldState}) package?))])]{
|
|
}
|
|
|
|
If one of these event handlers produces a @tech{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 the data
|
|
definition for @tech{WorldState} may not include a @tech{Package}.
|
|
|
|
@subsection{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.
|
|
|
|
@deftech{IP} @scheme[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 @tech{IP} address or a host. Here a
|
|
@tech{IP} address is a string of the right shape, e.g., @scheme["192.168.1.1"]
|
|
or @scheme["www.google.com"].
|
|
|
|
@defthing[LOCALHOST string?]{the @tech{IP} of your computer. Use it while you
|
|
are developing a distributed program, especially while you are
|
|
investigating whether the participating world programs collaborate in an
|
|
appropriate manner. This is called @emph{integration testing} and differs
|
|
from unit testing quite a bit.}
|
|
|
|
A @scheme[big-bang] description of a world program that wishes to communicate
|
|
with other programs must contain a @scheme[register] clause of one of the
|
|
following shapes:
|
|
|
|
@itemize[
|
|
|
|
@item{
|
|
@defform[(register ip-expr) #:contracts ([ip-expr string?])]{
|
|
connect this world to a universe server at the specified @scheme[ip-expr]
|
|
address and set up capabilities for sending and receiving messages.}
|
|
}
|
|
|
|
@item{
|
|
@defform[(name name-expr)
|
|
#:contracts
|
|
([name-expr (or/c symbol? string?)])]{
|
|
provide a name (@scheme[namer-expr]) to this world, which is used as the
|
|
title of the canvas and the name sent to the server.}
|
|
}
|
|
|
|
]
|
|
|
|
When a world program registers with a universe program and the universe program
|
|
stops working, the world program stops working, too.
|
|
|
|
@subsection{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 @scheme[on-receive] clause of a @scheme[big-bang] specifies the event handler
|
|
for message receipts.
|
|
|
|
@defform[(on-receive receive-expr)
|
|
#:contracts
|
|
([receive-expr (-> (unsyntax @tech{WorldState}) sexp? (or/c (unsyntax @tech{WorldState}) package?))])]{
|
|
tell DrScheme to call @scheme[receive-expr] for every message receipt, on the current
|
|
@tech{WorldState} and the received message. The result of the call becomes the current
|
|
@tech{WorldState}.
|
|
|
|
Because @scheme[receive-expr] is (or evaluates to) a world-transforming
|
|
function, it too can produce a @tech{Package} instead of just a
|
|
@tech{WorldState}. If the result is a @tech{Package}, its message content is
|
|
sent to the @tech{server}.}
|
|
|
|
The diagram below summarizes the extensions of this section in graphical form.
|
|
|
|
@image["universe.png"]
|
|
|
|
A registered world program may send a message to the universe server
|
|
at any time by returning a @tech{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 @emph{receive} accepts a @tech{WorldState} and some
|
|
auxiliary arguments (a message in this case) and produces a
|
|
@tech{WorldState} or a @tech{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 @tech{server} or @tech{world} program take care of them.
|
|
|
|
@; -----------------------------------------------------------------------------
|
|
@section[#:tag "universe-server"]{The Universe Server}
|
|
|
|
A @deftech{server} is the central control program of a @tech{universe} and
|
|
deals with receiving and sending of messages between the world
|
|
programs that participate in the @tech{universe}. Like a @tech{world}
|
|
program, a server is a program that reacts to events, though to different
|
|
events than @tech{world}s. The two primary kinds of events are the
|
|
appearance of a new @tech{world} program in the @tech{universe}
|
|
and the receipt of a message from a @tech{world} program.
|
|
|
|
The teachpack provides a mechanism for designating event handlers for
|
|
servers that is quite similar to the mechanism for describing @tech{world}
|
|
programs. Depending on the designated event handlers, the server takes on
|
|
distinct roles:
|
|
|
|
@itemize[
|
|
|
|
@item{A server may be a ``pass through'' channel between two worlds, in which case
|
|
it has no other function than to communicate whatever message it receives
|
|
from one world to the other, without any interference.}
|
|
|
|
@item{A server may enforce a ``back and forth'' protocol, i.e., it may force two
|
|
(or more) worlds to engage in a civilized tit-for-tat exchange. Each
|
|
world is given a chance to send a message and must then wait
|
|
to get a reply before it sends anything again.}
|
|
|
|
@item{A server may play the role of a special-purpose arbiter, e.g., the referee
|
|
or administrator of a game. It may check that each world ``plays'' by the rules,
|
|
and it administrate the resources of the game.}
|
|
|
|
]
|
|
|
|
As a matter of fact, a pass-through @tech{server} can become basically
|
|
invisible, making it appear as if all communication goes from peer
|
|
@tech{world} to peer in a @tech{universe}.
|
|
|
|
This section first introduces some basic forms of data that the
|
|
@tech{server} uses to represent @tech{world}s and other matters. Second,
|
|
it explains how to describe a server program.
|
|
|
|
@; -----------------------------------------------------------------------------
|
|
@subsection{Worlds and Messages}
|
|
|
|
Understanding the server's event handling functions demands several data
|
|
representations: that of (a connection to) a @tech{world} program and that
|
|
of a response of a handler to an event.
|
|
|
|
@itemize[
|
|
|
|
@item{The @tech{server} and its event handlers must agree on a
|
|
data representation of the @tech{world}s that participate in the
|
|
universe.
|
|
|
|
@defproc[(iworld? [x any/c]) boolean?]{
|
|
determines whether @scheme[x] is a @emph{iworld}. Because the universe server
|
|
represents worlds via structures that collect essential information about
|
|
the connections, the teachpack does not export any constructor or selector
|
|
functions on worlds.}
|
|
|
|
@defproc[(iworld=? [u iworld?][v iworld?]) boolean?]{
|
|
compares two @emph{iworld}s for equality.}
|
|
|
|
@defproc[(iworld-name [w iworld?]) string?]{
|
|
extracts the name from a @emph{iworld} structure.}
|
|
|
|
@defthing[iworld1 iworld?]{an @emph{iworld} for testing your programs}
|
|
@defthing[iworld2 iworld?]{another iworld for testing your programs}
|
|
@defthing[iworld3 iworld?]{and a third one}
|
|
|
|
The three sample iworlds are provided so that you can test your functions
|
|
for universe programs. For example:
|
|
|
|
@schemeblock[
|
|
(check-expect (iworld=? iworld1 iworld2) false)
|
|
(check-expect (iworld=? iworld2 iworld2) true)
|
|
]
|
|
}
|
|
|
|
@item{Each event handler produces a @emph{bundle}, which is a structure
|
|
that contains the @tech{server}'s state, a list of mails to other worlds,
|
|
and the list of @emph{iworld}s that are to be disconnected.
|
|
|
|
@defproc[(bundle? [x any/c]) boolean?]{
|
|
determines whether @scheme[x] is a @emph{bundle}.}
|
|
|
|
@defproc[(make-bundle [state any/c] [mails (listof mail?)] [low (listof iworld?)]) bundle?]{
|
|
creates a @emph{bundle} from a piece of data that
|
|
represents a server state, a list of mails, and a list of iworlds.}
|
|
|
|
If disconnecting from these worlds results in an empty list of
|
|
participants, the universe server is restarted in the initial state.
|
|
|
|
A @emph{mail} represents a message from an event handler to a world. The
|
|
teachpack provides only a predicate and a constructor for these structures:
|
|
|
|
@defproc[(mail? [x any/c]) boolean?]{
|
|
determines whether @scheme[x] is a @emph{mail}.}
|
|
|
|
@defproc[(make-mail [to iworld?] [content sexp?]) mail?]{
|
|
creates a @emph{mail} from a @emph{iworld} and an @tech{S-expression}.}
|
|
}
|
|
]
|
|
|
|
@; -----------------------------------------------------------------------------
|
|
@subsection{Universe Descriptions}
|
|
|
|
A @tech{server} keeps track of information about the @tech{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 @tech{world} programs.
|
|
|
|
@deftech{UniverseState} @scheme[any/c] represents the server's state. For running
|
|
@tech{universe}s, the teachpack demands that you come up with a data
|
|
definition for (your state of the) @tech{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 @tech{server} itself is created with a description that includes the
|
|
first state and a number of clauses that specify functions for dealing
|
|
with @tech{universe} events.
|
|
|
|
@defform/subs[#:id universe
|
|
#:literals
|
|
(on-new on-msg on-tick on-disconnect to-string check-with state)
|
|
(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 universe?-expr)
|
|
])]{
|
|
|
|
creates a server with a given state, @scheme[state-expr]. The
|
|
behavior is specified via handler functions through mandatory and optional
|
|
@emph{clause}s. 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 @scheme[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 @scheme[universe] server description are
|
|
@scheme[on-new] and @scheme[on-msg]:
|
|
|
|
@itemize[
|
|
|
|
@item{
|
|
@defform[(on-new new-expr)
|
|
#:contracts
|
|
([new-expr (-> (unsyntax @tech{UniverseState}) iworld? bundle?)])]{
|
|
tell DrScheme to call the function @scheme[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 @tech{world} program from participating in a @tech{universe},
|
|
by simply including it in the resulting @scheme[bundle] structure (third field).}
|
|
}
|
|
|
|
@item{
|
|
@defform[(on-msg msg-expr)
|
|
#:contracts
|
|
([msg-expr (-> (unsyntax @tech{UniverseState}) iworld? sexp? bundle?)])]{
|
|
tell DrScheme to apply @scheme[msg-expr] to the current state of the
|
|
universe, the world
|
|
@scheme[w] that sent the message, and the message itself.
|
|
}
|
|
}]
|
|
All proper event handlers produce a @emph{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.
|
|
|
|
@; -----------------------------------------------------------------------------
|
|
@;; THE PICTURE IS WRONG
|
|
@; -----------------------------------------------------------------------------
|
|
|
|
@image["server.png"]
|
|
|
|
In addition to the mandatory handlers, a program may wish to add some
|
|
optional handlers:
|
|
|
|
@itemize[
|
|
|
|
@item{
|
|
@defform/none[#:literals (on-tick)
|
|
(on-tick tick-expr)
|
|
#:contracts
|
|
([tick-expr (-> (unsyntax @tech{UniverseState}) bundle?)])]{
|
|
tell DrScheme to apply @scheme[tick-expr] to the current list of
|
|
participating worlds and the current state of the
|
|
universe.
|
|
}
|
|
|
|
@defform/none[#:literals (on-tick)
|
|
(on-tick tick-expr rate-expr)
|
|
#:contracts
|
|
([tick-expr (-> (unsyntax @tech{UniverseState}) bundle?)]
|
|
[rate-expr (and/c real? positive?)])]{
|
|
tell DrScheme to apply @scheme[tick-expr] as above but use the specified
|
|
clock tick rate instead of the default.
|
|
}
|
|
}
|
|
|
|
@item{
|
|
@defform[(on-disconnect dis-expr)
|
|
#:contracts
|
|
([dis-expr (-> (unsyntax @tech{UniverseState}) iworld? bundle?)])]{
|
|
tell DrScheme to invoke @scheme[dis-expr] every time a participating
|
|
@tech{world} drops its connection to the server. The first argument
|
|
is the current state of the universe server, while the second argument is
|
|
the (representation of the) world that got disconnected. The resulting
|
|
bundle usually includes this second argument in the third field, telling
|
|
drscheme not to wait for messages from this world anymore.}
|
|
}
|
|
|
|
@item{
|
|
@defform[(to-string render-expr)
|
|
#:contracts
|
|
([render-expr (-> (unsyntax @tech{UniverseState}) string?)])]{
|
|
tell DrScheme to render the state of the universe after each event and to
|
|
display this string in the universe console.
|
|
}
|
|
}
|
|
|
|
@item{
|
|
@defform/none[#:literals (check-with)
|
|
(check-with universe?-expr)
|
|
#:contracts
|
|
([universe?-expr (-> Any boolean?)])]{
|
|
ensure that what the event handlers produce is really an element of
|
|
@tech{UniverseState}.}
|
|
}
|
|
|
|
@item{
|
|
@defform/none[(state boolean-expr)
|
|
#:contracts
|
|
([boolean-expr boolean?])]{
|
|
tell DrScheme to display a separate window in which the current
|
|
state is rendered each time it is updated. This is mostly useful for
|
|
debugging server programs.
|
|
}}
|
|
|
|
]
|
|
|
|
@subsection{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.
|
|
|
|
@defform[(launch-many-worlds expression ...)]{
|
|
evaluates all sub-expressions in parallel. Typically each sub-expression
|
|
is an application of a function that evaluates a @scheme[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 @scheme[big-bang] to the end of the tab:
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
(schemeblock
|
|
;; String -> World
|
|
(define (main n)
|
|
(big-bang ... (name n) ...))
|
|
))
|
|
Then in DrScheme's Interactions area, use @scheme[launch-many-worlds]
|
|
to create several distinctively named worlds:
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
(schemeblock
|
|
> (launch-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 @scheme[10], @scheme[25], and
|
|
@scheme[33].
|
|
|
|
@; -----------------------------------------------------------------------------
|
|
@section[#:tag "universe-sample"]{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.
|
|
|
|
@subsection{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:
|
|
|
|
@image["balls" #:suffixes '(".gif" ".png")]
|
|
|
|
The two @tech{world} programs could be located on two distinct computers
|
|
or on just one. A @tech{server} mediates between the two worlds, including
|
|
the initial start-up.
|
|
|
|
@; -----------------------------------------------------------------------------
|
|
@subsection{Hints on Designing Universes}
|
|
|
|
The first step in designing a @tech{universe} is to understand the
|
|
coordination of the @tech{world}s from a global perspective. To some
|
|
extent, it is all about knowledge and the distribution of knowledge
|
|
throughout a system. We know that the @tech{universe} doesn't exist until
|
|
the server starts and the @tech{world}s are joining. Because of the nature
|
|
of computers and networks, however, we may assume little else. Our network
|
|
connections ensure that if some @tech{world} or the @tech{server} sends
|
|
two messages to the @emph{same} place in some order, they arrive in the
|
|
same order (if they arrive at all). In contrast, if two distinct
|
|
@tech{world} programs send one message each, the network does not
|
|
guarantee the order of arrival at the server; similarly, if the
|
|
@tech{server} is asked to send some messages to several distinct
|
|
@tech{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 @tech{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 @emph{protocol design}.
|
|
|
|
From the perspective of the @tech{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
|
|
@tech{S-expression}s, but usually @tech{world} programs don't send all
|
|
kinds of @tech{S-expression}s. The data definitions for messages must
|
|
therefore select a subset of suitable @tech{S-expression}s. 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:
|
|
|
|
@itemize[
|
|
|
|
@item{a data definition for the information about the universe that the
|
|
server tracks, call it @tech{UniverseState};}
|
|
|
|
@item{a data definition for the world(s) about their current relationship
|
|
to the universe;}
|
|
|
|
@item{data definitions for the messages that are sent from the server to
|
|
the worlds and vice versa. Let's call them @deftech{S2W} for messages
|
|
from the server to the worlds and @deftech{W2S} for the other direction;
|
|
in the most general case you may need one pair per world.}
|
|
]
|
|
|
|
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.
|
|
|
|
|
|
@verbatim{
|
|
|
|
Server World1 World2
|
|
| | |
|
|
| 'go | |
|
|
|<------------------| |
|
|
| 'go | |
|
|
|------------------------------------------>|
|
|
| | |
|
|
| | |
|
|
}
|
|
|
|
Each vertical line is the life line of a @tech{world} program or the
|
|
@tech{server}. Each horizontal arrow denotes a message sent from one
|
|
@tech{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,
|
|
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
(schemeblock
|
|
;; Bundle is
|
|
;; (make-bundle UniverseState [Listof mail?] [Listof iworld?])
|
|
|
|
;; UniverseState iworld? -> Bundle
|
|
;; next list of worlds when world @scheme[iw] is joining
|
|
;; the universe in state @scheme[s]
|
|
(define (add-world s iw) ...)
|
|
|
|
;; UniverseState iworld? W2U -> Bundle
|
|
;; next list of worlds when world @scheme[iw] is sending message @scheme[m] to
|
|
;; the universe in state @scheme[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.
|
|
|
|
@; -----------------------------------------------------------------------------
|
|
@subsection{Designing the Ball Universe}
|
|
|
|
Running the ball @tech{universe} has a simple overall goal: to ensure that at any
|
|
point in time, one @tech{world} is active and all others are passive. The active
|
|
@tech{world} displays a moving ball, and the passive @tech{world}s should display
|
|
something, anything that indicates that it is some other @tech{world}'s turn.
|
|
|
|
As for the server's state, it must obviously keep track of all @tech{world}s that
|
|
joined the @tech{universe}, and it must know which one is active and which ones
|
|
are passive. Of course, initially the @tech{universe} is empty, i.e., there are
|
|
no @tech{world}s and, at that point, the server has nothing to track.
|
|
|
|
While there are many different useful ways of representing such a
|
|
@tech{universe}, we just use the list of @emph{iworlds} that is handed to
|
|
each handler and that handlers return via their bundles. The
|
|
@tech{UniverseState} itself is useless for this trivial example. We
|
|
interpret non-empty lists as those where the first @emph{iworld} is active
|
|
and the remainder are the passive @emph{iworld}s. As for the two possible
|
|
events,
|
|
|
|
@itemize[
|
|
|
|
@item{it is natural to add new @emph{iworld}s to the end of the list; and}
|
|
|
|
@item{it is natural to move an active @emph{iworld} that relinquishes its turn to
|
|
the end of the list, too.}
|
|
]
|
|
|
|
The server should send messages to the first @emph{iworld} of its list as
|
|
long as it wishes this @emph{iworld} to remain active. In turn, it should
|
|
expect to receive messages only from this one active @emph{iworld} and no
|
|
other @emph{iworld}. The content of these two messages is nearly irrelevant
|
|
because a message from the server to a @emph{iworld} means that it is the
|
|
@emph{iworld}'s turn and a message from the @emph{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:
|
|
@itemize[
|
|
@item{A @defterm{GoMessage} is @scheme['it-is-your-turn].}
|
|
@item{A @defterm{StopMessage} is @scheme['done].}
|
|
]
|
|
|
|
From the @tech{universe}'s perspective, each @tech{world} is in one of two states:
|
|
@itemize[
|
|
@item{A passive @tech{world} is @emph{resting}. We use @scheme['resting] for this state.}
|
|
@item{An active @tech{world} is not resting. We delay choosing a representation
|
|
for this part of a @tech{world}'s state until we design its ``local'' behavior.}
|
|
]
|
|
It is also clear that an active @tech{world} may receive additional messages,
|
|
which it may ignore. When it is done with its turn, it will send a
|
|
message.
|
|
|
|
@verbatim{
|
|
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 @tech{server}
|
|
decides to make the first registered world the active one and to enlist
|
|
all others as they join.
|
|
|
|
|
|
@; -----------------------------------------------------------------------------
|
|
@subsection{Designing the Ball Server}
|
|
|
|
The preceding subsection dictates that our server program starts like this:
|
|
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
[schemeblock
|
|
;; 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:
|
|
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
[schemeblock
|
|
;; Result is
|
|
;; (make-bundle [Listof iworld?]
|
|
;; (list (make-mail iworld? GoMessage))
|
|
;; '())
|
|
|
|
;; [Listof iworld?] iworld? -> Result
|
|
;; add world @scheme[iw] to the universe, when server is in state @scheme[u]
|
|
(define (add-world u iw) ...)
|
|
|
|
;; [Listof iworld?] iworld? StopMessage -> Result
|
|
;; world @scheme[iw] sent message @scheme[m] when server is in state @scheme[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:
|
|
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
[schemeblock
|
|
;; 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 @scheme[world1], @scheme[world2], and
|
|
@scheme[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 @emph{add-world} just adds the given
|
|
@emph{world} structure---recall that this a data representation of the
|
|
actual @tech{world} program---to the given list of worlds. It then sends a
|
|
message to the first world on this list to get things going:
|
|
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
[schemeblock
|
|
(define (add-world univ wrld)
|
|
(local ((define univ* (append univ (list wrld))))
|
|
(make-bundle univ*
|
|
(list (make-mail (first univ*) 'it-is-your-turn))
|
|
'())))
|
|
])
|
|
|
|
Because @emph{univ*} contains at least @emph{wrld}, it is acceptable to
|
|
create a mail to @scheme[(first univ*)]. Of course, this same reasoning
|
|
also implies that if @emph{univ} isn't empty, its first element is an
|
|
active world and is about to receive a second @scheme['it-is-your-turn] message.
|
|
|
|
Similarly, the protocol says that when @emph{switch} is invoked because a
|
|
@tech{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:
|
|
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
[schemeblock
|
|
(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.
|
|
|
|
@schemeblock[(universe '() (on-new add-world) (on-msg switch))]
|
|
|
|
Exercise: The function definition simply assumes that @emph{wrld} is
|
|
@scheme[world=?] to @scheme[(first univ)] and that the received message
|
|
@emph{m} is @scheme['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 @tech{universe} it is quite
|
|
possible that a program registers with a @tech{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 @tech{universe} at this point by
|
|
returning an empty list of worlds. Consider alternative solutions, too.)
|
|
|
|
Exercise: An alternative state representation would equate
|
|
@tech{UniverseState} with @emph{world} structures, keeping track of the
|
|
active world. The list of world in the server would track the passive
|
|
worlds only. Design appropriate @scheme[add-world] and @scheme[switch]
|
|
functions.
|
|
|
|
@; -----------------------------------------------------------------------------
|
|
@subsection{Designing the Ball World}
|
|
|
|
The final step is to design the ball @tech{world}. Recall that each world
|
|
is in one of two possible states: active or passive. The second kind of
|
|
@tech{world} moves a ball upwards, decreasing the ball's @emph{y}
|
|
coordinate; the first kind of @tech{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:
|
|
|
|
@(begin #reader scribble/comment-reader
|
|
(schemeblock
|
|
;; teachpack: universe.ss
|
|
|
|
;; WorldState is one of:
|
|
;; -- Number %% representing the @emph{y} coordinate
|
|
;; -- @scheme['resting]
|
|
|
|
(define WORLD0 'resting)
|
|
|
|
;; A WorldResult is one of:
|
|
;; -- WorldState
|
|
;; -- (make-package WorldState StopMessage)
|
|
))
|
|
The definition says that initially a @tech{world} is passive.
|
|
|
|
The communication protocol and the refined data definition of @tech{WorldState}
|
|
imply a number of contract and purpose statements:
|
|
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
(schemeblock
|
|
|
|
;; 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 @scheme['resting]
|
|
(define (move w) ...)
|
|
|
|
;; WorldState -> Scene
|
|
;; render the world as a scene
|
|
(define (render w) ...)
|
|
))
|
|
|
|
Let's design one function at a time, starting with @emph{receive}. Since
|
|
the protocol doesn't spell out what @emph{receive} is to compute, let's
|
|
create a good set of functional examples, exploiting the structure of the
|
|
data organization of @tech{WorldState}:
|
|
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
(schemeblock
|
|
(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 @scheme['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 @tech{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 @emph{receive}:
|
|
|
|
@itemize[
|
|
@item{@scheme[HEIGHT] when the ball is at the bottom of the scene;}
|
|
@item{@scheme[(- HEIGHT 1)] when the ball is properly inside the scene; and}
|
|
@item{@scheme[0] when the ball has hit the top of the scene.}
|
|
]
|
|
|
|
In the third case the function could produce three distinct results:
|
|
@scheme[0], @scheme['resting], or @scheme[(make-package 'resting
|
|
'done)]. The first leaves things alone; the second turns the active @tech{world}
|
|
into a resting one; the third does so, too, and tells the universe about
|
|
this switch.
|
|
|
|
We choose to design @emph{receive} so that it ignores the message and
|
|
returns the current state of an active @tech{world}. This ensures that the ball
|
|
moves in a continuous fashion and that the @tech{world} remains active.
|
|
|
|
Exercise: One alternative design is to move the ball back to the bottom of
|
|
the scene every time @scheme['it-is-your-turn] is received. Design this function, too.
|
|
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
(schemeblock
|
|
|
|
(define (receive w m)
|
|
(cond
|
|
[(symbol? w) HEIGHT] ;; meaning: @scheme[(symbol=? w 'resting)]
|
|
[else w]))
|
|
))
|
|
|
|
Our second function to design is @emph{move}, the function that computes
|
|
the ball movement. We have the contract and the second step in the design
|
|
recipe calls for examples:
|
|
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
(schemeblock
|
|
; WorldState -> WorldState or @scheme[(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:
|
|
@scheme['resting], two end points of the specified numeric interval, and
|
|
one interior point. They tell us that @emph{move} leaves a passive @tech{world}
|
|
alone and that it otherwise moves the ball until the @emph{y} coordinate
|
|
becomes @scheme[0]. In the latter case, the result is a package that
|
|
renders the @tech{world} passive and tells the server about it.
|
|
|
|
Turning these thoughts into a complete definition is straightforward now:
|
|
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
(schemeblock
|
|
(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 @emph{receive} so that it
|
|
produces @scheme['resting] when the state of the world is @scheme[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 a scene:
|
|
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
(schemeblock
|
|
; WorldState -> Scene
|
|
; render the state of the world as a scene
|
|
|
|
(check-expect (render HEIGHT) (underlay/xy MT 50 HEIGHT BALL))
|
|
(check-expect (render 'resting)
|
|
(underlay/xy MT 10 10 (text "resting" 11 "red")))
|
|
|
|
(define (render w)
|
|
(underlay/xy
|
|
(cond
|
|
[(symbol? w) (underlay/xy MT 10 10 (text "resting" 11 "red"))]
|
|
[(number? w) (underlay/xy MT 50 w BALL)])
|
|
5 85
|
|
(text name 11 "black")))
|
|
|
|
))
|
|
|
|
Here is an improvement that adds a name to the scene and abstracts over
|
|
the name at the same time:
|
|
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
(schemeblock
|
|
; String -> (WorldState -> Scene)
|
|
; render the state of the world as a scene
|
|
|
|
(check-expect
|
|
((draw "Carl") 100)
|
|
(underlay/xy (underlay/xy MT 50 100 BALL)
|
|
5 85
|
|
(text "Carl" 11 "black")))
|
|
|
|
(define (draw name)
|
|
(lambda (w)
|
|
(overlay/xy
|
|
(cond
|
|
[(symbol? w) (underlay/xy MT 10 10 (text "resting" 11 "red"))]
|
|
[(number? w) (underlay/xy MT 50 w BALL)])
|
|
5 85
|
|
(text name 11 'black))))
|
|
|
|
))
|
|
|
|
By doing so, we can use the same program to create many different
|
|
@tech{world}s that register with a @tech{server} on your computer:
|
|
@(begin
|
|
#reader scribble/comment-reader
|
|
(schemeblock
|
|
|
|
; String -> WorldState
|
|
; create and hook up a world with the @scheme[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 @scheme[(create-world 'carl)] and @scheme[(create-world 'same)],
|
|
respectively, to run two different worlds, after launching a @tech{server}
|
|
first.
|
|
|
|
Exercise: Design a function that takes care of a world to which the
|
|
universe has lost its connection. Is @emph{Result} the proper contract for
|
|
the result of this function?
|
|
|