#lang scribble/doc @(require scribble/manual ; (only-in teachpack/htdp/scribblings/shared beginner-require) (for-label scheme (only-in lang/htdp-beginner check-expect) ; "sb-universe.ss" teachpack/htdp/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)))))) @; ----------------------------------------------------------------------------- @title[#:tag "universe"]{Multiple Worlds} @author{Matthias Felleisen, with some modifications by Stephen Bloch} @;@defmodule[#:require-form beginner-require 2htdp/universe #:use-sources (teachpack/htdp/image)] @;@defmodule[(planet sbloch/picturing-programs/picturing-programs)] 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 @emph{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 @tech{universe} (a bunch of worlds) 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}. @; ----------------------------------------------------------------------------- @section{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} instead of just a @tech{WorldState}. A @deftech{Package} is 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 above, those event handlers that were originally described as returning @tech{WorldState} may actually return either @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 natural-number/c])]{ } @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{mouse-event}) (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 a @tech{WorldState} may be almost any imaginable data type @emph{except} 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 testing whether the participating world programs collaborate correctly. This is called @emph{integration testing} and differs considerably from unit testing (testing whether each individual function, or each individual world, works correctly).} A @scheme[big-bang] description of a world program that wishes to communicate with other programs must contain a @scheme[register] clause of each 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. @; FIX THIS: the diagram still refers to "scenes". @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. @deftech{iworld} : a predefined struct to represent "internal worlds" from the server's perspective. @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 an @tech{iworld}. Because the universe server represents worlds via structures that collect essential information about the connections, the teachpack provides no constructor, and only one selector (@scheme[iworld-name]), for this type.} @defproc[(iworld=? [u iworld?][v iworld?]) boolean?]{ compares two @tech{iworld}s for equality.} @defproc[(iworld-name [w iworld?]) string?]{ extracts the name from a @tech{iworld} structure.} @defthing[iworld1 iworld?]{an @tech{iworld} for testing your programs} @defthing[iworld2 iworld?]{another @tech{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 @deftech{bundle}, which is a structure that contains the @tech{server}'s state, a list of mails to other worlds, and the list of @tech{iworld}s that are to be disconnected. @defproc[(bundle? [x any/c]) boolean?]{ determines whether @scheme[x] is a @tech{bundle}.} @defproc[(make-bundle [state any/c] [mails (listof mail?)] [low (listof iworld?)]) bundle?]{ creates a @tech{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 @deftech{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 @tech{mail}.} @defproc[(make-mail [to iworld?] [content sexp?]) mail?]{ creates a @tech{mail} from a @tech{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 is-universe?) ])]{ 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 @tech{bundle}. The state in the bundle is safe-guarded by the server until the next event, and the @tech{mail}s 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 natural-number/c])]{ 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 is-universe?) #:contracts ([is-universe? (-> any/c 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 @tech{iworld} is active and the remainder are the passive @tech{iworld}s. As for the two possible events, @itemize[ @item{it is natural to add new @tech{iworld}s to the end of the list; and} @item{it is natural to move an active @tech{iworld} that relinquishes its turn to the end of the list, too.} ] The server should send messages to the first @tech{iworld} of its list as long as it wishes this @tech{iworld} to remain active. In turn, it should expect to receive messages only from this one active @tech{iworld} and no other @tech{iworld}. The content of these two messages is nearly irrelevant because a message from the server to a @tech{iworld} means that it is the @tech{iworld}'s turn and a message from the @tech{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 @tech{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 @tech{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 -> image ;; render the world as a image (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 image;} @item{@scheme[(- HEIGHT 1)] when the ball is properly inside the image; and} @item{@scheme[0] when the ball has hit the top of the image.} ] 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 image 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 an image: @(begin #reader scribble/comment-reader (schemeblock ; 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: @(begin #reader scribble/comment-reader (schemeblock ; 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 @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?