#lang scribble/doc @(require scribble/manual ; (only-in teachpack/htdp/scribblings/shared beginner-require) ; teachpack/2htdp/scribblings/shared (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 "world"]{Animating a World} @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)] This package also includes a slightly modified version of the @tt{universe} teachpack that comes with DrScheme, which allows you to easily write interactive, graphical programs. The main difference is that in a number of places, @tt{universe} requires a special kind of images called "scenes", and this teachpack allows you to use any old image. Interactive programs written using this teachpack are called @deftech{world} programs (because each such program is a cluster of functions working on "worlds"). In addition, several world programs can be combined, either on one computer or over a network, in what we call a @deftech{universe}. The purpose of this documentation is to give experienced Schemers and HtDP teachers a concise overview for using the library. The first part of the documentation focuses on @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[#:tag "basics"]{Basics} The teachpack assumes working knowledge of the basic image manipulation primitives from @seclink[#:doc '(lib "teachpack/teachpack.scrbl") "image" "the image teachpack"]. @; ----------------------------------------------------------------------------- @section[#:tag "handlers"]{Worlds and Event Handlers} GUI and animation programs (not just in this teachpack, and not just in Scheme) usually follow the principle of @defterm{model/view separation}. In English, that means that part of the program deals with an internal "model" or "world", and another part of the program deals with how that world is displayed on the screen (or some other output device). For example, in a chess game, the "model" or "world" would include the rules of chess, which pieces are currently at which squares of the board, whether each side has castled yet, how many moves it has been since the last piece was taken, @emph{etc.} while the "view" part of the program would deal with the color of the screen, the size and shape of pieces as they appear on the screen, the arrangement of squares on the screen, @emph{etc.} The connection between model and view, in this teachpack, is a function called a @tech{draw handler}, which takes in a world and returns an image. Every time the world changes for any reason, the draw handler will be automatically called, and the image it returns will appear on the screen. In most animations, you will write a function to be used as a draw handler, but if you don't provide a draw handler, the teachpack will assume that the "model" @emph{is} an image, and will simply show that image on the screen. (In other words, it will use the identity function as a draw handler.) A @tech{draw handler} is one of several kinds of @deftech{event handler}s, which are functions that the teachpack will call whenever a certain @deftech{event} happens. In this teachpack, there are several kinds of @tech{event handler}s, each with its own contract (we'll explain these as we get to them): @itemize{ @item{A @deftech{draw handler} has contract @scheme[(-> (unsyntax @tech{WorldState}) image?)], and is specified by @scheme[on-draw] (or, sort of, by @scheme[stop-when])} @item{A @deftech{world type checker} has contract @scheme[(-> any/c boolean?)], and is specified by @scheme[check-with]} @item{A @deftech{tick handler} has contract @scheme[(-> (unsyntax @tech{WorldState}) (unsyntax @tech{WorldState}))], and is specified by @scheme[on-tick]} @item{A @deftech{key handler} has contract @scheme[(-> (unsyntax @tech{WorldState}) key-event? (unsyntax @tech{WorldState}))], and is specified by @scheme[on-key]} @item{A @deftech{mouse handler} has contract @scheme[(-> (unsyntax @tech{WorldState}) natural-number/c natural-number/c mouse-event? (unsyntax @tech{WorldState}))], and is specified by @scheme[on-mouse]} @item{A @deftech{stop checker} has contract @scheme[(-> (unsyntax @tech{WorldState}) boolean?)], and is specified by @scheme[stop-when]} } There are several other kinds of handlers related to network programming, which we'll discuss in @secref["universe"]. In this teachpack, a @tech{world} can be of (almost) any data type, but we'll start with two especially simple cases: when the @tech{world} is an image, and when the @tech{world} is a natural number. To run an animation, you'll call @scheme[big-bang] (discussed below) to "start the world", giving it as arguments the initial state of the world and whatever event handlers you want. @; ----------------------------------------------------------------------------- @section[#:tag "image-animations"]{Simple Image Animations} One simple form of animation uses an image as the model, so you don't have to provide your own @tech{draw handler}. In fact, in the simplest case you can provide no event handlers at all. For example, @schemeblock[(big-bang (triangle 50 "solid" "blue"))] opens an animation window with an equilateral blue triangle in it. Since there are no event handlers, the animation window doesn't respond to any events, so it never changes until you close the window. We can make things a little more interesting by adding event handlers, of which the simplest is a @emph{tick handler}. The contract for a tick handler is always @scheme[(-> (unsyntax @tech{world}) (unsyntax @tech{world}))], and since we're not providing a @emph{draw handler} yet, the @tech{world} must be an image, so the tick handler must actually have contract @scheme[(-> image? image?)]. A handy predefined function with this contract is @scheme[rotate-cw]: @schemeblock[(big-bang (triangle 50 "solid" "blue") (on-tick rotate-cw))] opens an animation window with an equilateral blue triangle, but at every clock tick (which, by default, is 28 times per second), the triangle rotates clockwise. If this spins too fast for you, we can give an optional argument to @scheme[on-tick] to specify the length of a clock tick in seconds: @schemeblock[(big-bang (triangle 50 "solid" "blue") (on-tick rotate-cw 1/2))] opens an animation window with an equilateral blue triangle, rotating clockwise every half second. One can also write image animations with mouse, key, and other kinds of event handlers, but we'll come back to that in @secref["interactive"] below. @; ----------------------------------------------------------------------------- @section[#:tag "simulations"]{Simple Numeric Simulations} Another common and simple form of animation uses a natural number as the world, starting at 0 and increasing by 1 at every clock tick. In other words, the "world" at any time is the number of clock ticks since the animation started. The programmer's task is to supply a function that creates an image for each natural number, and hand this function to the teachpack. @defproc[(animate [create-image (-> natural-number/c image?)]) natural-number]{ opens a canvas and starts a clock that ticks 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-image height) (place-image UFO 50 height (empty-scene 100 100))) (define UFO (overlay (circle 10 'solid 'green) (rectangle 40 4 'solid 'green))) (animate create-UFO-image) ] By the way, @schemeblock[(animate create-UFO-image)] is just short-hand for @schemeblock[(big-bang 0 (on-tick add1) (on-draw create-UFO-image))]. We'll discuss @scheme[on-draw] in the next section. @defproc[(run-simulation [create-image (-> natural-number/c image?)]) natural-number/c]{ @scheme[animate] was originally called @scheme[run-simulation], and this name is retained for backwards compatibility} @;----------------------------------------------------------------------------- @section[#:tag "interactive"]{Interactions} The step from numeric simulations to interactive programs is pretty easy. A numeric simulation designates one function, @scheme[create-UFO-image] in the above example, as a handler for one kind of event: re-drawing the screen. In addition to redrawing, @tech{world} programs can also deal with other kinds of events: clock ticks, keyboard events and mouse events. A clock tick happens every second, every third of a second, @emph{etc.} as the programmer chooses. A keyboard event is triggered when a computer user presses or releases a key on the keyboard. Similarly, a mouse event is the movement of the mouse, a click on a mouse button, the crossing of a boundary by a mouse movement, etc. Your program may deal with such @tech{event}s by designating @tech{event handler} functions. The teachpack provides keywords to install each of these kinds of @tech{event handler}s: @scheme[on-draw], @scheme[on-tick], @scheme[on-key], and @scheme[on-mouse]. Each of these kinds of handlers consumes the current state of the @tech{world}, and in some cases additional information about the event. As already stated, a @tech{draw handler} returns an image; the other three (@tech{tick handler}s, @tech{key handler}s, and @tech{mouse handler}s) return a new state of the @tech{world}. In addition, a @tech{world} program may specify (with @scheme[stop-when]) a Boolean-valued function to decide when to shut down the animation. The following picture provides an intuitive overview of the workings of a @tech{world} program in the form of a state transition diagram. @;FIX THIS: the diagram still refers to "scenes". @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 every time the @tech{world} changes, @scheme[draw] renders the new world as an image, 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. In principle, there are no constraints on this data definition, except that it mustn't be an instance of the @tech{Package} structure (see @secref["universe"]). Optionally, you can specify (with @scheme[check-with]) a Boolean-valued function to check this data type: if any of the @tech{event handler}s returns something that isn't the right type, you'll get an immediate and informative error message. @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-image-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, key events, mouse events, and eventually messages from the universe; how it renders itself as an image; when the program must shut down; where to register the world with a universe; and whether to record the stream of events. A world specification may not contain more than one @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 number?])]{ 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]: for example, a @scheme[rate-expr] of 1/3 produces three ticks per second.}} @item{A @tech{key-event} represents keyboard events, e.g., keys pressed or released. @deftech{key-event} : @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? [thing any/c]) boolean?]{ determines whether @scheme[thing] is a @tech{key-event}} @defproc[(key=? [evt1 key-event?][evt2 key-event?]) boolean?]{ compares two @tech{key-event}s 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{key-event} 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])) ] } (This assumes that the programmer has written an auxiliary function @emph{world-go} that consumes a world and a number and produces a world.) } @item{ A @tech{mouse-event} represents mouse events, e.g., mouse movements or mouse clicks, by the computer's user. @deftech{mouse-event} : @scheme[(one-of/c "button-down" "button-up" "drag" "move" "enter" "leave")] All @tech{mouse-event}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? [thing any/c]) boolean?]{ determines whether @scheme[thing] is a @tech{mouse-event}} @defproc[(mouse=? [evt1 mouse-event?][evt2 mouse-event?]) boolean?]{ compares two @tech{mouse-event}s for equality} @defform[(on-mouse clack-expr) #:contracts ([clack-expr (-> (unsyntax @tech{WorldState}) natural-number/c natural-number/c (unsyntax @tech{mouse-event}) (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{mouse-event} 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}) image?)])]{ 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 image.} @defform/none[#:literals (on-draw) (on-draw render-expr width-expr height-expr) #:contracts ([render-expr (-> (unsyntax @tech{WorldState}) image?)] [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 image. }} @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{key-event}s, or @tech{mouse-event}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}) image?)])]{ 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 image rendered with @scheme[last-picture]. The clock is stopped; no more tick events, @tech{key-event}s, or @tech{mouse-event}s are forwarded to the respective handlers. The @scheme[big-bang] expression returns this last world. } } @item{ @defform[(check-with is-world?) #:contracts ([is-world? (-> any/c boolean?)])]{ tell DrScheme to call the @scheme[is-world?] 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 file per image 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. }} ] 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 lets 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 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{mouse-event} -> 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{key-event} -> 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.