From f5714c2086ff9916e36bb439adb59fa6a34abbdf Mon Sep 17 00:00:00 2001 From: Matthias Felleisen Date: Sat, 3 Jan 2009 02:38:09 +0000 Subject: [PATCH] added universe via a 2htdp teachpack svn: r12980 --- collects/2htdp/private/check-aux.ss | 174 ++ collects/2htdp/private/design.txt | 17 + collects/2htdp/private/image.ss | 195 +++ collects/2htdp/private/last.ss | 30 + collects/2htdp/private/syn-aux-aux.ss | 60 + collects/2htdp/private/syn-aux.ss | 43 + collects/2htdp/private/timer.ss | 38 + collects/2htdp/private/universe.ss | 363 ++++ collects/2htdp/private/world.ss | 382 ++++ collects/2htdp/universe.ss | 311 ++++ collects/lang/htdp-langs.ss | 27 +- collects/teachpack/2htdp/scribblings/balls.ss | 59 + collects/teachpack/2htdp/scribblings/fsa.ss | 59 + .../teachpack/2htdp/scribblings/nuworld.ss | 119 ++ .../teachpack/2htdp/scribblings/server2.ss | 181 ++ .../teachpack/2htdp/scribblings/shared.ss | 10 + .../2htdp/scribblings/universe.scrbl | 1536 +++++++++++++++++ .../teachpack/2htdp/scribblings/universe.ss | 200 +++ collects/teachpack/2htdp/universe.ss | 3 + collects/teachpack/balls.gif | Bin 0 -> 215721 bytes collects/teachpack/nuworld.png | Bin 0 -> 15606 bytes collects/teachpack/server2.png | Bin 0 -> 17544 bytes collects/teachpack/universe.png | Bin 0 -> 28263 bytes collects/teachpack/universe2.png | Bin 0 -> 19411 bytes 24 files changed, 3799 insertions(+), 8 deletions(-) create mode 100644 collects/2htdp/private/check-aux.ss create mode 100644 collects/2htdp/private/design.txt create mode 100644 collects/2htdp/private/image.ss create mode 100644 collects/2htdp/private/last.ss create mode 100644 collects/2htdp/private/syn-aux-aux.ss create mode 100644 collects/2htdp/private/syn-aux.ss create mode 100644 collects/2htdp/private/timer.ss create mode 100644 collects/2htdp/private/universe.ss create mode 100644 collects/2htdp/private/world.ss create mode 100755 collects/2htdp/universe.ss create mode 100644 collects/teachpack/2htdp/scribblings/balls.ss create mode 100644 collects/teachpack/2htdp/scribblings/fsa.ss create mode 100644 collects/teachpack/2htdp/scribblings/nuworld.ss create mode 100644 collects/teachpack/2htdp/scribblings/server2.ss create mode 100644 collects/teachpack/2htdp/scribblings/shared.ss create mode 100644 collects/teachpack/2htdp/scribblings/universe.scrbl create mode 100644 collects/teachpack/2htdp/scribblings/universe.ss create mode 100644 collects/teachpack/2htdp/universe.ss create mode 100644 collects/teachpack/balls.gif create mode 100644 collects/teachpack/nuworld.png create mode 100644 collects/teachpack/server2.png create mode 100644 collects/teachpack/universe.png create mode 100644 collects/teachpack/universe2.png diff --git a/collects/2htdp/private/check-aux.ss b/collects/2htdp/private/check-aux.ss new file mode 100644 index 0000000000..b0a404c4d7 --- /dev/null +++ b/collects/2htdp/private/check-aux.ss @@ -0,0 +1,174 @@ +#lang scheme + +(require htdp/image + htdp/error + (only-in lang/htdp-beginner image?)) + +(provide (all-defined-out)) + +(define INSET 5) ;; the space around the image in the canvas +(define RATE 1/30) ;; the clock tick rate +(define TRIES 3) ;; how many times should register try to connect to the server +(define PAUSE 1/2) ;; # secs to wait between attempts to connect to server +(define SQPORT 4567) ;; the port on which universe traffic flows + +(define (K w . r) w) +(define (False w) #f) + +; +; +; +; ;;; ;;; +; ; ; ; ; +; ; ; ; ; +; ; ;;; ;;;;; ;;;;; ;;; ;;;; ; ; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;;;; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ;; +; ;;; ;;; ; ; ; ; ; ; ;;; ; ; ; ; ;; ; ; ; ;; +; +; +; + +;; ----------------------------------------------------------------------------- + +;; Any -> Boolean +(define (scene? i) + (and (image? i) (internal-scene? i))) + +;; Image -> Boolean +(define (internal-scene? i) + (and (= 0 (pinhole-x i)) (= 0 (pinhole-y i)))) + +;; Number -> Integer +(define (number->integer x) + (inexact->exact (floor x))) + +;; ----------------------------------------------------------------------------- +;; Nat Nat ->String +;; converts i to a string, adding leading zeros, make it at least as long as L +(define (zero-fill i L) + (let ([n (number->string i)]) + (string-append (make-string (max (- L (string-length n)) 0) #\0) n))) + +;; ----------------------------------------------------------------------------- + +;; MouseEvent -> [List Nat Nat MouseEventType] +;; turn a mouse event into its pieces +(define (mouse-event->parts e) + (define x (- (send e get-x) INSET)) + (define y (- (send e get-y) INSET)) + (list x y (cond [(send e button-down?) 'button-down] + [(send e button-up?) 'button-up] + [(send e dragging?) 'drag] + [(send e moving?) 'move] + [(send e entering?) 'enter] + [(send e leaving?) 'leave] + [else ; (send e get-event-type) + (error 'on-mouse-event + (format + "Unknown event type: ~a" + (send e get-event-type)))]))) + +;; ----------------------------------------------------------------------------- +;; Any -> Symbol +(define (name-of draw tag) + (define fname (object-name draw)) + (if fname fname tag)) + +;; ----------------------------------------------------------------------------- +;; Any -> Boolean +(define (sexp? x) + (cond + [(empty? x) true] + [(string? x) true] + [(symbol? x) true] + [(number? x) true] + [(char? x) true] + [(pair? x) (and (list? x) (andmap sexp? x))] + [else false])) + +(define (no-newline? x) + (not (member #\newline (string->list x)))) + +;; ----------------------------------------------------------------------------- +;; exchange one-line messages between worlds and the server + +(define tcp-eof (gensym 'tcp-eof)) + +;; Any -> Boolean +(define (tcp-eof? a) (eq? tcp-eof a)) + +;; OutPort Sexp -> Void +(define (tcp-send out msg) + (write msg out) + (newline out) + (flush-output out)) + +;; InPort -> Sexp +(define (tcp-receive in) + (with-handlers ((exn? (lambda (x) (raise tcp-eof)))) + (define x (read in)) + (if (eof-object? x) + (raise tcp-eof) + (begin + (read-line in) ;; read the newline + x)))) + +; +; +; +; ;;; ;;; ; ; +; ; ; ; ; ; ; +; ; ; ; ; ; ; +; ; ; ; ;; ;;;; ; ;;;; ; ; +; ;;;;; ;; ; ; ; ; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ;; +; ; ; ; ; ; ; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ; ; +; ; ; ; ;;;; ;;; ; ; ; ; +; ; +; ; ; +; ;;; + +;; Symbol Any String -> Void +(define (check-pos t c r) + (check-arg + t (and (number? c) (> (number->integer c) 0)) "positive integer" r c)) + +;; Symbol Any String String *-> Void +(define (check-image tag i rank . other-message) + (if (and (pair? other-message) (string? (car other-message))) + (check-arg tag (image? i) (car other-message) rank i) + (check-arg tag (image? i) "image" rank i))) + +;; Symbol Any String -> Void +(define (check-scene tag i rank) + (define error "image with pinhole at (~s,~s)") + (if (image? i) + (check-arg tag (internal-scene? i) "scene" rank (image-pins i)) + (check-arg tag #f "scene" rank i))) + +;; Symbol Any -> Void +(define (check-scene-result tname i) + (if (image? i) + (check-result tname internal-scene? "scene" i (image-pins i)) + (check-result tname (lambda (x) (image? x)) "scene" i))) + +(define (image-pins i) + (format "image with pinhole at (~s,~s)" (pinhole-x i) (pinhole-y i))) + + +;; Symbol Any String -> Void +(define (check-color tag width rank) + (check-arg tag (or (symbol? width) (string? width)) + "color symbol or string" rank width)) + +;; Symbol (union Symbol String) Nat -> Void +(define (check-mode tag s rank) + (check-arg tag (or (eq? s 'solid) + (eq? s 'outline) + (string=? "solid" s) + (string=? "outline" s)) "mode (solid or outline)" rank s)) + diff --git a/collects/2htdp/private/design.txt b/collects/2htdp/private/design.txt new file mode 100644 index 0000000000..df68373fc0 --- /dev/null +++ b/collects/2htdp/private/design.txt @@ -0,0 +1,17 @@ + +Files for constructing universe.ss: + + world.ss the old world + world% = (clock-mixin ...) -- the basic world + aworld% = (class world% ...) -- the world with recording + + universe.ss the universe server + universe% = (clock-mixin ...) -- the basic universe + + timer.ss the clock-mixin + + check-aux.ss common primitives + image.ss the world image functions + syn-aux.ss syntactic auxiliaries + syn-aux-aux.ss auxiliaries to the syntactic auxiliaries + diff --git a/collects/2htdp/private/image.ss b/collects/2htdp/private/image.ss new file mode 100644 index 0000000000..de4c4b39ce --- /dev/null +++ b/collects/2htdp/private/image.ss @@ -0,0 +1,195 @@ +#lang scheme + +(require htdp/image + htdp/error + "check-aux.ss") + +; +; +; ;;;;; ;;;;; +; ; ; +; ; ; +; ; ;;; ; ;;;; ;;;; ;;; ; ; ; ; ;; ;;; +; ; ; ;;; ; ; ; ; ; ; ;;;;; ; ; ;; ; ; ; +; ; ; ; ; ; ; ; ; ;;;;; ; ; ; ; ; ;; +; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +; ; ; ; ; ; ;; ; ;; ; ; ; ;; ; ; ; ; +; ;;;;; ; ; ; ;; ; ;; ; ;;;; ; ;; ; ; ; ;;; +; ; +; ;;;; +; + +(provide (all-from-out htdp/image)) + +(provide + ;; Scene is Image with pinhole in origin + nw:rectangle ;; Number Number Mode Color -> Image + place-image ;; Image Number Number Scene -> Scene + empty-scene ;; Number Number -> Scene + scene+line ;; Scene Number Number Number Number Color -> Scene + ;; cut all pieces that are outside the given rectangle + ) + +(define (nw:rectangle width height mode color) + (check-pos 'rectangle width "first") + (check-pos 'rectangle height "second") + (check-mode 'rectangle mode "third") + (check-color 'rectangle color "fourth") + (put-pinhole (rectangle width height mode color) 0 0)) + +(define (place-image image x y scene) + (check-image 'place-image image "first") + (check-arg 'place-image (number? x) 'integer "second" x) + (check-arg 'place-image (number? y) 'integer "third" y) + (check-scene 'place-image scene "fourth") + (let ([x (number->integer x)] + [y (number->integer y)]) + (place-image0 image x y scene))) + +(define (empty-scene width height) + (check-pos 'empty-scene width "first") + (check-pos 'empty-scene height "second") + (put-pinhole + (overlay (rectangle width height 'solid 'white) + (rectangle width height 'outline 'black)) + 0 0)) + +(define (scene+line img x0 y0 x1 y1 c) + ;; img and c are checked via calls to add-line from image.ss + (check-arg 'scene+line (scene? img) "scene" "first" "plain image") + (check-arg 'scene+line (number? x0) "number" "second" x0) + (check-arg 'scene+line (number? y0) "number" "third" y0) + (check-arg 'scene+line (number? x1) "number" "fourth" x1) + (check-arg 'scene+line (number? y1) "number" "fifth" y1) + (let ([x0 (number->integer x0)] + [x1 (number->integer x1)] + [y0 (number->integer y0)] + [y1 (number->integer y1)]) + (add-line-to-scene0 img x0 y0 x1 y1 c))) + +;; Image Number Number Image -> Image +(define (place-image0 image x y scene) + (define sw (image-width scene)) + (define sh (image-height scene)) + (define ns (overlay/xy scene x y image)) + (define nw (image-width ns)) + (define nh (image-height ns)) + (if (and (= sw nw) (= sh nh)) ns (shrink ns 0 0 (- sw 1) (- sh 1)))) + +;; Image Number Number Number Number Color -> Image +(define (add-line-to-scene0 img x0 y0 x1 y1 c) + (define w (image-width img)) + (define h (image-height img)) + (cond + [(and (<= 0 x0) (< x0 w) (<= 0 x1) (< x1 w) (<= 0 y0) (< y0 w) (<= 0 y1) (< y1 w)) + (add-line img x0 y0 x1 y1 c)] + [(= x0 x1) ;; vertical + (if (<= 0 x0 w) (add-line img x0 (app y0 h) x0 (app y1 h) c) img)] + [(= y0 y1) ;; horizontal + (if (<= 0 y0 h) (add-line img (app x0 w) y0 (app x1 w) y0 c) img)] + [else + (local ((define lin (points->line x0 y0 x1 y1)) + (define dir (direction x0 y0 x1 y1)) + (define-values (upp low lft rgt) (intersections lin w h)) + (define (add x y) (add-line img x0 y0 x y c))) + (cond + [(and (< 0 x0 w) (< 0 y0 h)) ;; (x0,y0) is in the interior + (case dir + [(upper-left) (if (number? upp) (add upp 0) (add 0 lft))] + [(lower-left) (if (number? low) (add low h) (add 0 lft))] + [(upper-right) (if (number? upp) (add upp 0) (add h rgt))] + [(lower-right) (if (number? low) (add low h) (add w rgt))] + [else (error 'dir "contract violation: ~e" dir)])] + [(and (< 0 x1 w) (< 0 y1 h)) ;; (x1,y1) in interior; symmetry! + (add-line-to-scene0 img x1 y1 x0 y0 c)] + [else + (cond + [(and (number? upp) (number? low)) (add-line img upp 0 low h c)] + [(and (number? upp) (number? lft)) (add-line img upp 0 0 lft c)] + [(and (number? upp) (number? rgt)) (add-line img upp 0 w rgt c)] + [(and (number? low) (number? lft)) (add-line img low h 0 lft c)] + [(and (number? low) (number? rgt)) (add-line img low h w rgt c)] + [(and (number? lft) (number? rgt)) (add-line img 0 lft w rgt c)] + [else img])]))])) + +;; Nat Nat -> Nat +;; y if in [0,h], otherwise the closest boundary +(define (app y h) + (cond + [(and (<= 0 y) (< y h)) y] + [(< y 0) 0] + [else (- h 1)])) + +;; Nat Nat Nat Nat -> (union 'upper-left 'upper-right 'lower-left 'lower-right) +;; how to get to (x1,y1) from (x0,y0) +(define (direction x0 y0 x1 y1) + (string->symbol + (string-append + (if (<= y0 y1) "lower" "upper") "-" (if (<= x0 x1) "right" "left")))) + +#| TESTS +'direction +(equal? (direction 10 10 0 0) 'upper-left) +(equal? (direction 10 10 20 20) 'lower-right) +(equal? (direction 10 10 0 20) 'lower-left) +(equal? (direction 10 10 20 0) 'upper-right) +|# + +;; ----------------------------------------------------------------------------- +;; LINEs + +;; Number Number -> LINE +;; create a line from a slope and the intersection with the y-axis +(define-struct lyne (slope y0)) + +;; Nat Nat Nat Nat -> LINE +;; determine the line function from the four points (or the attributes) +;; ASSUME: (not (= x0 x1)) +(define (points->line x0 y0 x1 y1) + (local ((define slope (/ (- y1 y0) (- x1 x0)))) + (make-lyne slope (- y0 (* slope x0))))) + +;; LINE Number -> Number +(define (of ln x) (+ (* (lyne-slope ln) x) (lyne-y0 ln))) + +;; LINE Nat Nat -> [Opt Number] [Opt Number] [Opt Number] [Opt Number] +;; where does the line intersect the rectangle [0,w] x [0,h] +;; (values UP LW LF RT) means the line intersects with +;; the rectangle [0,w] x [0,h] at (UP,0) or (LW,h) or (0,LF) or (w,RT) +;; when a field is false, the line doesn't interesect with that side +(define (intersections l w h) + (values + (opt (X l 0) w) (opt (X l h) w) (opt (lyne-y0 l) h) (opt (of l w) h))) + +;; Number Number -> [Opt Number] +(define (opt z lft) (if (<= 0 z lft) z false)) + +;; LINE Number -> Number +;; the x0 where LINE crosses y(x) = h +;; assume: LINE is not a horizontal +(define (X ln h) (/ (- h (lyne-y0 ln)) (lyne-slope ln))) + +;; --- TESTS --- +#| +(define line1 (points->line 0 0 100 100)) +(= (of line1 0) 0) +(= (of line1 100) 100) +(= (of line1 50) 50) + +(= (X (make-lyne 1 0) 0) 0) +(= (X (make-lyne 1 0) 100) 100) + +(equal? (call-with-values + (lambda () (intersections (points->line -10 -10 110 110) 100 100)) + list) + (list 0 100 0 100)) +(equal? (call-with-values + (lambda () (intersections (points->line 0 10 100 80) 100 100)) + list) + (list false false 10 80)) +|# + +;; ----------------------------------------------------------------------------- + +; +; diff --git a/collects/2htdp/private/last.ss b/collects/2htdp/private/last.ss new file mode 100644 index 0000000000..8faf32bcd2 --- /dev/null +++ b/collects/2htdp/private/last.ss @@ -0,0 +1,30 @@ +#lang scheme/gui + +(require "timer.ss") + +(provide last-mixin) + +(define last-mixin + (mixin (start-stop<%>) () + (field [end:ch (make-channel)]) + ;; X -> Void + (define/override (stop! w) + (send-to-last w) + (super stop! w)) + + ;; -> World + (define/public (last) + (define result (yield end:ch)) + (if (exn? result) (raise result) result)) + + (field [dr:cust (current-custodian)]) + + ;; X -> Void + ;; send x to last method + (define/private (send-to-last x) + (parameterize ((current-custodian dr:cust)) + (thread (lambda () (channel-put end:ch x))))) + + (super-new))) + + diff --git a/collects/2htdp/private/syn-aux-aux.ss b/collects/2htdp/private/syn-aux-aux.ss new file mode 100644 index 0000000000..538164f672 --- /dev/null +++ b/collects/2htdp/private/syn-aux-aux.ss @@ -0,0 +1,60 @@ +#lang scheme + +(require htdp/error) + +; +; +; +; ;;; ;;; +; ; ; ; ; +; ; ; ; +; ; ; ; ;;;; ; ; ; ; ; ; +; ;;; ; ; ; ; ;;;;; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ;; ; ; +; ;;; ;;;; ; ; ; ; ;; ; ; ; +; ; +; ; ; +; ;;; + +(provide nat> nat? proc> bool> num> ip> string> symbol>) + +;; Any -> Boolean +(define (nat? x) + (and (number? x) (integer? x) (>= x 0))) + +;; Symbol X -> X +(define (bool> tag x) + (check-arg tag (boolean? x) "boolean" "first" x) + x) + +;; Symbol X -> X +(define (string> tag x) + (check-arg tag (string? x) "string" "first" x) + x) + +(define ip> string>) + +;; Symbol X -> X +(define (symbol> tag x) + (check-arg tag (symbol? x) "symbol" "second" x) + x) + +;; Symbol X Nat -> X +(define (proc> tag f ar) + (check-proc tag f ar "first" + (if (> ar 1) + (format "~a arguments" ar) + "one argument")) + f) + +;; Symbol X (Number -> Boolean) String String -> X +(define (num> tag x pred? spec which) + (check-arg tag (and (number? x) (pred? x)) spec which x) + x) + +;; Symbol X String -> X +(define (nat> tag x spec) + (check-arg tag (nat? x) spec "natural number" x) + x) diff --git a/collects/2htdp/private/syn-aux.ss b/collects/2htdp/private/syn-aux.ss new file mode 100644 index 0000000000..a44a2af08d --- /dev/null +++ b/collects/2htdp/private/syn-aux.ss @@ -0,0 +1,43 @@ +#lang scheme + +(provide define-keywords function-with-arity except err check-flat-spec + (all-from-out "syn-aux-aux.ss")) + +(require "syn-aux-aux.ss" + (for-template "syn-aux-aux.ss" + scheme + (rename-in lang/prim (first-order->higher-order f2h)))) + +(define-syntax-rule (define-keywords the-list (kw coerce) ...) + (begin + (provide kw ...) + (define-syntax (kw x) + (raise-syntax-error 'kw "used out of context" x)) + ... + (define-for-syntax the-list (list (list 'kw (coerce ''kw)) ...)))) + +(define-syntax function-with-arity + (syntax-rules (except) + [(_ arity) + (lambda (tag) + (lambda (p) + (syntax-case p () + [(x) #`(proc> #,tag (f2h x) arity)] + [_ (err tag p)])))] + [(_ arity except extra) + (lambda (tag) + (lambda (p) + (syntax-case p () + [(x) #`(proc> #,tag (f2h x) arity)] + extra + [_ (err tag p)])))])) + +(define (err spec p) + (raise-syntax-error #f "illegal specification" #`(#,spec . #,p) p)) + +;; Symbol (Symbol X -> X) -> (X -> X) +(define (check-flat-spec tag coerce>) + (lambda (p) + (syntax-case p () + [(b) #'(coerce> tag b)] + [_ (err tag p)]))) \ No newline at end of file diff --git a/collects/2htdp/private/timer.ss b/collects/2htdp/private/timer.ss new file mode 100644 index 0000000000..1beaa18c30 --- /dev/null +++ b/collects/2htdp/private/timer.ss @@ -0,0 +1,38 @@ +#lang scheme/gui + +;; The module provides a timer mixing for world and universe. + +;; The interface ensures that super class provides start and stop method, +;; plus a call back for clock ticks. The super-init call provides the +;; on-tick parameter, which the super-class uses to define the callback. + + +(require "check-aux.ss") + +(provide clock-mixin start-stop<%>) + +(define start-stop<%> (interface () start! ptock stop!)) + +;; T = (U (World -> World) (list (World -> World) Nat)) +;; X [(list (World -> World) Nat) -> X] [(World -> World) -> X] -> [T -> X] +(define (selector default lchoice pchoice) + (lambda (on-tick) + (cond + [(cons? on-tick) (lchoice on-tick)] + [(procedure? on-tick) (pchoice on-tick)] + [else default]))) + +(define clock-mixin + (mixin (start-stop<%>) () + (inherit ptock) + (init-field [on-tick #f]) + (field [rate ((selector 0 second (lambda _ RATE)) on-tick)] + [timer (new timer% [notify-callback (lambda () (ptock))])]) + (define/override (start!) + (unless (<= rate 0) + (send timer start (number->integer (* 1000 rate)))) + (super start!)) + (define/override (stop! w) + (send timer stop) + (super stop! w)) + (super-new [tick ((selector void first (lambda (x) x)) on-tick)]))) diff --git a/collects/2htdp/private/universe.ss b/collects/2htdp/private/universe.ss new file mode 100644 index 0000000000..17b5263732 --- /dev/null +++ b/collects/2htdp/private/universe.ss @@ -0,0 +1,363 @@ +#lang scheme/gui + +(require (for-syntax "syn-aux.ss") + "check-aux.ss" + "timer.ss" + "last.ss" + scheme/match + htdp/error + (only-in mzlib/etc evcase) + string-constants) + +(provide universe%) + +; +; +; +; ; ; ; +; ; ; ; +; ; ; +; ; ; ;;;; ; ; ; ;;; ; ;; ;;; ;;; +; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; +; ; ; ; ; ; ; ; ;;;;; ; ; ;;; ;;;;; +; ; ; ; ; ; ; ; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ; ; ; ; +; ;;; ; ; ; ; ;;; ; ;;; ;;; +; +; +; + +(define universe% + (last-mixin + (clock-mixin + (class* object% (start-stop<%>) (inspect #f) (super-new) + (init-field ;; type Result = (make-bundle Universe [Listof Mail]) + universe0 ;; the initial state of the universe + on-new ;; Universe World -> Result + on-msg ;; Universe World Message -> Result + tick ;; Universe -> Result + (on-disconnect ;; Universe World -> Result + (lambda (u w) (list u))) + (to-string #f) ;; Universe -> String + ) + + (field [universe universe0]) + + ;; ----------------------------------------------------------------------- + ;; dealing with events + (define-syntax-rule + ;; A B ... -> Void + (def/cback pub (pname a ...) + ;; Universe A B ... -> (cons Universe Mail) + ;; effect: change server state, broadcast mails + name) + (begin + (pub pname) + (define (pname a ...) + (define (handler e) (stop! e)) + (with-handlers ([exn? handler]) + (define r (check-state-x-mail 'name (name universe a ...))) + (define u (bundle-state r)) + (set! universe u) + (unless (boolean? to-string) (send gui add (to-string u))) + (broadcast (bundle-mails r)))))) + + (def/cback private (pmsg world received) on-msg) + + (def/cback private (pdisconnect world) on-disconnect) + + (def/cback private (pnew world) ppnew) + + (define/private (ppnew uni p) + (world-send p 'okay) + (on-new uni p)) + + (def/cback public (ptock) tick) + + ;; Symbol Any -> Result + ;; check that r is Result + ;; effect: stop the server if the callbacks perform badly + (define/private (check-state-x-mail tag r) + (with-handlers ((exn? (lambda (x) (stop! x)))) + (define s (format "expected from ~a, given: " tag)) + (unless (bundle? r) + (error tag (format "(make-bundle Universe [Listof Mail]) ~a~e" s r))) + r)) + + ;; ----------------------------------------------------------------------- + ;; start and stop server, start and stop the universe + + (field [worlds '()] ;; [Listof World] + [gui (new gui% + [stop-server (lambda () (stop! universe))] + [stop-and-restart (lambda () (restart))])] + [dr:custodian (current-custodian)] + [the-custodian (make-custodian)]) + + ;; start the universe, enable registrations and message exchanges + (define/public (start!) + (set! the-custodian (make-custodian)) + (parameterize ([current-custodian the-custodian]) + (define (loop) + (apply sync + (handle-evt (tcp-accept-evt tcp-listener) add-world) + (map world-wait-for-msg worlds))) + (define (add-world in-out) + (with-handlers ((tcp-eof? (lambda _ (loop)))) + (define in (first in-out)) + (define next (tcp-receive in)) + (match next + [(cons 'REGISTER info) + (let* ([w (create-world in (second in-out) info)]) + (set! worlds (cons w worlds)) + (pnew w) + (send gui add (format "~a signed up" info)) + (loop))] + [else (loop)]))) + (define (world-wait-for-msg p) + (handle-evt (world-in p) + (lambda (in) + (with-handlers + ((tcp-eof? + (lambda (e) + (handler p e + (lambda () + (if (null? worlds) + (restart) + (loop))))))) + (define r (tcp-receive in)) + (send gui add (format "~a ->: ~a" (world-name p) r)) + (pmsg p r) + (loop))))) + (define tcp-listener + (with-handlers ((exn:fail:network? (lambda (x) (stop! x)))) + (tcp-listen SQPORT 4 #t))) + ;; --- go universe go --- + (set! worlds '()) + (set! universe universe0) + (send gui add "a new universe is up and running") + (thread loop))) + + ;; World Exn (-> X) -> X + (define/private (handler p e cont) + (close-output-port (world-out p)) + (close-input-port (world-in p)) + (send gui add (format "~a !! closed port" (world-name p))) + (set! worlds (remq p worlds)) + (pdisconnect p) + (cont)) + + ;; [Listof Mail] -> Void + ;; send payload of messages to designated worlds + (define/private (broadcast lm) + ;;; --- why the heck is there no exception handler ------------- + (for-each (lambda (p+m) + ;; what exception should I catch + ;; remove the world from the list + ;; factor out from elsewhere + ;; can this mean I perform a callback during a callback? + ;; collect 'bad' worlds instead and disconnect them later? + ;; (handler + (with-handlers ((exn? (lambda (e) (printf "\n\n*** to be done ***\n\n")))) + (define w (mail-to p+m)) + (define n (world-name w)) + (define p (mail-content p+m)) + (unless (memq w worlds) + (send gui add (format "~s not on list" n))) + (when (memq w worlds) + (world-send w p) + (send gui add (format "-> ~a: ~a" n p))))) + lm)) + + (define/private (restart) + ;; I am running in a custodian that is about to be killed, + ;; so let's switch to one up in the hierarchy + (let ([old-t (current-thread)] + [go (make-semaphore)]) + (parameterize ([current-custodian dr:custodian]) + (thread (lambda () + (sync old-t go) + (start!)))) + (send gui add "stopping the universe") + (send gui add "----------------------------------") + (for-each (lambda (w) + (close-input-port (world-in w)) + (close-output-port (world-out w))) + worlds) + (custodian-shutdown-all the-custodian) + (semaphore-post go))) + + (define/public (stop! msg) + (send gui show #f) + (custodian-shutdown-all the-custodian)) + + ;; ----------------------------------------------------------------------- + ;; initialize the universe and run + (send gui show #t) + (start!))))) + + +; +; +; +; ; ; ; ; +; ; ; ; ; +; ; ; ; ; +; ; ; ;;; ; ;; ; ;;;; ;;; +; ; ; ; ; ;; ; ; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ; ;;; +; ;; ;; ; ; ; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ; +; ; ; ;;; ; ;; ;;;; ;;; +; +; +; + +(provide + world? ;; Any -> Boolean + world=? ;; World World -> Boolean + world1 ;; sample worlds + world2 + world3) + +;; --- the server representation of a world --- +(define-struct world (in out name info) #:transparent) +;; World = (make-world IPort OPort Symbol [Listof Sexp]) + +(define world1 (make-world (current-input-port) (current-output-port) 'sk '())) +(define world2 (make-world (current-input-port) (current-output-port) 'mf '())) +(define world3 (make-world (current-input-port) (current-output-port) 'rf '())) + +(define (world=? u v) + (check-arg 'world=? (world? u) 'world "first" u) + (check-arg 'world=? (world? v) 'world "second" v) + (eq? u v)) + +;; IPort OPort Sexp -> Player +(define (create-world i o info) + (if (and (pair? info) (symbol? (car info))) + (make-world i o (car info) (cdr info)) + (make-world i o (gensym 'world) info))) + +;; Player S-exp -> Void +(define (world-send p sexp) + (tcp-send (world-out p) sexp)) + +; +; +; +; ;;; ; ; ; +; ; ; ; ; ; +; ; ; ; ; +; ; ; ; ; +; ; ;; ; ; ; +; ; ; ; ; ; +; ; ; ; ; ; +; ; ; ; ; ; +; ;;; ;;; ; +; +; +; + +;; effect: create and show a gui with two buttons and an editor for logging +(define gui% + (class frame% + (init stop-server stop-and-restart) + (inherit show) + (define/augment (on-close) (end)) + (super-new [label "Universe"][width 500][height 300][style '(metal)]) + (field + [end (lambda _ (show #f) (stop-server))] + [panel (new horizontal-panel% [parent this] [stretchable-height #f] + [alignment '(center center)])] + [stop (new button% [parent panel] [label "stop"] [callback end])] + [s&re (new button% [parent panel] [label "stop and restart"] + [callback (lambda (but evt) (stop-and-restart))])] + [text (new text%)] + [edit (new editor-canvas% [parent this] [editor text] + [style '(no-border combo no-hscroll auto-vscroll)])]) + + ;; add lines to the end of the text + (define/public (add str) + (queue-callback + (lambda () + (send text lock #f) + (send text insert (format "~a\n" str) (send text last-position)) + (send text lock #t)))) + + ;; ------------------------------------------------------------------------- + ;; add menu, lock, and show + (copy-and-paste this) + (send text lock #t))) + +;; ----------------------------------------------------------------------------- +;; Frame Text -> Void +;; add menu bar to frame for copying all of the text +(require string-constants) + +(define (copy-and-paste frame) + (define mb (new menu-bar% [parent frame])) + (define edit (new menu% + [label (string-constant edit-menu-label)] + [parent mb])) + (new menu-item% + [label (string-constant copy-menu-item)] + [parent edit] + [shortcut #\c] + [callback (lambda (m e) + (define t (send frame get-focus-object)) + (when (is-a? t editor<%>) + (send t copy)))]) + (new menu-item% + [label (string-constant select-all-menu-item)] + [parent edit] + [shortcut #\a] + [callback (lambda (m e) + (define t (send frame get-focus-object)) + (when (is-a? t text%) + (send t set-position 0 (send t last-position))))]) + (void)) + +; +; +; ;;; ;;; ; ;; +; ;; ;; ; +; ;; ;; ;;; ;;; ; +; ; ; ; ; ; ; ; +; ; ; ; ;;;; ; ; +; ; ; ; ; ; ; +; ; ; ; ; ; ; +; ;;; ;;; ;;;;; ;;;;; ;;;;; +; +; +; +; + +(provide + ;; type Bundle = (make-bundle Universe [Listof Mail]) + ;; type Mail = (make-mail World S-expression) + make-bundle ;; Universe [Listof Mail] -> Bundle + bundle? ;; is this a bundle? + make-mail ;; World S-expression -> Mail + mail? ;; is this a real mail? + ) + +(define-struct bundle (state mails) #:transparent) + +(set! make-bundle + (let ([make-bundle make-bundle]) + (lambda (state mails) + (check-arg 'make-bundle (list? mails) "list [of mails]" "second" mails) + (for-each (lambda (c) + (check-arg 'make-bundle (mail? c) "mail" "(elements of) second" c)) + mails) + (make-bundle state mails)))) + +(define-struct mail (to content) #:transparent) + +(set! make-mail + (let ([make-mail make-mail]) + (lambda (to content) + (check-arg 'make-mail (world? to) 'world "first" to) + (check-arg 'make-mail (sexp? content) 'S-expression "second" content) + (make-mail to content)))) diff --git a/collects/2htdp/private/world.ss b/collects/2htdp/private/world.ss new file mode 100644 index 0000000000..2270aafeb1 --- /dev/null +++ b/collects/2htdp/private/world.ss @@ -0,0 +1,382 @@ +#lang scheme/gui + +(require "check-aux.ss" + "timer.ss" + "last.ss" + htdp/image + htdp/error + mzlib/runtime-path + mrlib/bitmap-label + string-constants + mrlib/gif) + +(provide world% aworld%) + +; +; +; +; ; ; ; ; +; ; ; ; ; +; ; ; ; ; +; ; ; ;;; ; ;; ; ;;;; +; ; ; ; ; ;; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ; +; ;; ;; ; ; ; ; ; ; +; ; ; ; ; ; ; ; ; +; ; ; ;;; ; ;; ;;;; +; +; +; + +;; ----------------------------------------------------------------------------- +;; packages for broadcasting information to the universe + +(define-struct package (world message) #:transparent) + +;; World Sexp -> Package +(define (create-package w m) + (check-arg 'make-package (sexp? m) 'sexp "second" m) + (make-package w m)) + +(provide + (rename-out (create-package make-package)) ;; World S-expression -> Package + package? ;; Any -> Package + ) + +(define world% + (last-mixin + (clock-mixin + (class* object% (start-stop<%>) + (inspect #f) + (init-field + world0 ;; World + (tick K)) ;; (U (World -> World) (list (World -> World) Nat)) + + (init + (on-key K) ;; World KeyEvent -> World + (on-mouse K) ;; World Nat Nat MouseEvent -> World + (on-receive #f) ;; (U #f (World S-expression -> World)) + (on-draw #f) ;; (U #f (World -> Scene) (list (World -> Scene) Nat Nat)) + (stop-when False) ;; World -> Boolean + (record? #f) ;; Boolean + (register #f)) ;; (U #f String (list String Symbol)) + + ;; ----------------------------------------------------------------------- + (field (world world0)) + + ;; (U World Package) -> Boolean + ;; does the new world differ from the old? + ;; effect: if so, set world + (define/private (set-world new-world) + (when (package? new-world) + (broadcast (package-message new-world)) + (set! new-world (package-world new-world))) + (if (equal? world new-world) + #t + (begin + (set! world new-world) + #f))) + + ;; ----------------------------------------------------------------------- + (field [*out* #f] ;; (U #f OutputPort), where to send messages to + [*rec* (make-custodian)] ;; Custodian, monitor traffic + [host (cond + [(string? register) register] + [(pair? register) (car register)] + [else register])] + [name (cond + [(string? register) (gensym 'world)] + [(pair? register) (second register)] + [else register])]) + + (define/private (register-with-host) + (define FMTtry "unable to register with ~a after ~s tries") + (define FMTcom "unable to register with ~a due to protocol problems") + ;; try to register with the server n times + (define (register n) + (printf "trying to register with ~a ...\n" host) + (with-handlers ((tcp-eof? + (lambda (x) + (error 'register FMTcom host))) + (exn:fail:network? + (lambda (x) + (if (= n 1) + (error 'register FMTtry host TRIES) + (begin (sleep PAUSE) + (register (- n 1))))))) + (define-values (in out) (tcp-connect host SQPORT)) + (tcp-send out `(REGISTER ,(if name name (gensym 'world)))) + (if (eq? (tcp-receive in) 'okay) + (values in out) + (raise tcp-eof)))) + ;; --- now register, obtain connection, and spawn a thread for receiving + (parameterize ([current-custodian *rec*]) + (define-values (in out) (register TRIES)) + (define dis (text "the universe disappeared" 11 'red)) + (define (RECEIVE) + (sync + (handle-evt + in + (lambda (in) + (with-handlers ((tcp-eof? (compose (handler #f) + (lambda (e) + (set! draw (lambda (w) dis)) + (pdraw) + e)))) + ;; --- "the universe disconnected" should come from here --- + (define msg (tcp-receive in)) + (cond + [(sexp? msg) (prec msg) (RECEIVE)] ;; break loop if EOF + [#t (error 'RECEIVE "sexp expected, received: ~e" msg)])))))) + (printf "... successful registered and ready to receive\n") + (set! *out* out) + (thread RECEIVE))) + + (define/private (broadcast msg) + (when *out* + (check-result 'send sexp? "Sexp expected; given ~e\n" msg) + (tcp-send *out* msg))) + + ;; ----------------------------------------------------------------------- + (field + (draw (cond + [(procedure? on-draw) on-draw] + [(pair? on-draw) (first on-draw)] + [else on-draw])) + (live (not (boolean? draw))) + (width (if (pair? on-draw) (second on-draw) #f)) + (height (if (pair? on-draw) (third on-draw) #f))) + + ;; the visible world + (field [enable-images-button void] ;; used if stop-when call produces #t + [disable-images-button void] + [visible (new pasteboard%)]) + + (define (show-canvas) + (send visible set-cursor (make-object cursor% 'arrow)) + (let ([fst-scene (ppdraw)]) + (set! width (if width width (image-width fst-scene))) + (set! height (if height height (image-height fst-scene))) + (create-frame) + (show fst-scene))) + + ;; effect: create, show and set the-frame + (define/pubment (create-frame) + (define play-back:cust (make-custodian)) + (define frame (new (class frame% + (super-new) + (define/augment (on-close) + (callback-stop! 'frame-stop) + (custodian-shutdown-all play-back:cust))) + (label (if name (format "~a's World" name) "World")) + (stretchable-width #f) + (stretchable-height #f) + (style '(no-resize-border metal)))) + (define editor-canvas + (new (class editor-canvas% + (super-new) + ;; deal with keyboard events + (define/override (on-char e) + (when live (pkey (send e get-key-code)))) + ;; deal with mouse events if live and within range + (define/override (on-event e) + (define l (mouse-event->parts e)) + (when live + (when (and (<= 0 (first l) width) (<= 0 (second l) height)) + (pmouse . l))))) + (parent frame) + (editor visible) + (style '(no-hscroll no-vscroll)) + (horizontal-inset INSET) + (vertical-inset INSET))) + (send editor-canvas min-client-width (+ width INSET INSET)) + (send editor-canvas min-client-height (+ height INSET INSET)) + (set!-values (enable-images-button disable-images-button) + (inner (values void void) create-frame frame play-back:cust)) + (send editor-canvas focus) + (send frame show #t)) + + ;; Image -> Void + ;; show the image in the visible world + (define/public (show pict) + (send visible begin-edit-sequence) + (send visible lock #f) + (let ([s (send visible find-first-snip)] + [c (send visible get-canvas)]) + (when s (send visible delete s)) + (send visible insert (send pict copy) 0 0)) + (send visible lock #t) + (send visible end-edit-sequence)) + + ;; ----------------------------------------------------------------------- + ;; callbacks + (field + (key on-key) + (mouse on-mouse) + (rec on-receive)) + + (define-syntax-rule (def/pub-cback (name arg ...) transform) + ;; Any ... -> Boolean + (define/public (name arg ...) + (queue-callback + (lambda () + (with-handlers ([exn:break? (handler #f)][exn? (handler #t)]) + (define changed-world? (set-world (transform world arg ...))) + (unless changed-world? + (when draw (pdraw)) + (when (pstop) + (callback-stop! 'name) + (enable-images-button))) + changed-world?))))) + + ;; tick, tock : deal with a tick event for this world + (def/pub-cback (ptock) tick) + + ;; key events + (def/pub-cback (pkey ke) key) + + ;; mouse events + (def/pub-cback (pmouse x y me) mouse) + + ;; receive revents + (def/pub-cback (prec msg) rec) + + ;; ----------------------------------------------------------------------- + ;; draw : render this world + (define/private (pdraw) (show (ppdraw))) + + (define/private (ppdraw) + (check-scene-result (name-of draw 'your-draw) (draw world))) + + ;; ----------------------------------------------------------------------- + ;; stop-when + (field [stop stop-when]) + + (define/private (pstop) + (define result (stop world)) + (check-result (name-of stop 'your-stop-when) boolean? "boolean" result) + result) + + ;; ----------------------------------------------------------------------- + ;; start & stop + (define/public (callback-stop! msg) + (stop! world)) + + (define (handler re-raise) + (lambda (e) + (disable-images-button) + (stop! (if re-raise e world)))) + + (define/public (start!) + (when draw (show-canvas)) + (when host (register-with-host))) + + (define/public (stop! w) + (set! live #f) + (custodian-shutdown-all *rec*)) + + ;; ------------------------------------------------------------------------- + ;; initialize the world and run + (super-new) + (start!))))) + +;; ----------------------------------------------------------------------------- +(define-runtime-path break-btn:path '(lib "icons/break.png")) +(define break-button:label + ((bitmap-label-maker (string-constant break-button-label) break-btn:path) '_)) + +(define-runtime-path image-button:path '(lib "icons/file.gif")) +(define image-button:label ((bitmap-label-maker "Images" image-button:path) '_)) + +(define aworld% + (class world% (super-new) + (inherit-field world0 tick key mouse rec draw rate width height) + (inherit show callback-stop!) + + ;; Frame Custodian -> (-> Void) + ;; adds the stop animation and image creation button, + ;; whose callbacks runs as a thread in the custodian + ;; provide a function for switching button enabling + (define/augment (create-frame frm play-back-custodian) + (define p (new horizontal-pane% [parent frm][alignment '(center center)])) + (define (switch) + (send stop-button enable #f) + (send image-button enable #t)) + (define (stop) (send stop-button enable #f)) + (define-syntax-rule (btn l a y ...) + (new button% [parent p] [label l] [style '(border)] + [callback (lambda a y ...)])) + (define stop-button + (btn break-button:label (b e) (callback-stop! 'stop-images) (switch))) + (define image-button + (btn image-button:label (b e) + (parameterize ([current-custodian play-back-custodian]) + (thread (lambda () (play-back))) + (stop)))) + (send image-button enable #f) + (values switch stop)) + + (field [event-history '()]) ;; [Listof Evt] + ;; Symbol Any *-> Void + (define/private (add-event type . stuff) + (set! event-history (cons (cons type stuff) event-history))) + + ;; --- new callbacks --- + (define-syntax-rule (def/over-cb (pname name arg ...)) + (define/override (pname arg ...) + (when (super pname arg ...) (add-event name arg ...)))) + + (def/over-cb (ptock tick)) + (def/over-cb (pkey key e)) + (def/over-cb (pmouse mouse x y me)) + (def/over-cb (prec rec m)) + + ;; --> Void + ;; re-play the history of events; create a png per step; create animated gif + ;; effect: write to user-chosen directory + (define/private (play-back) + ;; World EventRecord -> World + (define (world-transition world fst) (apply (car fst) world (cdr fst))) + ;; --- creating images + (define total (+ (length event-history) 1)) + (define digt# (string-length (number->string total))) + (define imag# 0) + (define bmps '()) + ;; Image -> Void + (define (save-image img) + (define bm (make-object bitmap% width height)) + (define dc (make-object bitmap-dc% bm)) + (send dc clear) + (send img draw dc 0 0 0 0 width height 0 0 #f) + (set! imag# (+ imag# 1)) + (send bm save-file (format "i~a.png" (zero-fill imag# digt#)) 'png) + (set! bmps (cons bm bmps))) + ;; --- choose place + (define img:dir (get-directory "image directory:" #f (current-directory))) + (when img:dir + (parameterize ([current-directory img:dir]) + (define last + (foldr (lambda (event world) + (save-image (draw world)) + (show (text (format "~a/~a created" imag# total) 18 'red)) + (world-transition world event)) + world0 + event-history)) + (show (text (format "creating ~a" ANIMATED-GIF-FILE) 18 'red)) + (create-animated-gif rate (reverse bmps)) + (show (draw last))))))) + +;; Number [Listof (-> bitmap)] -> Void +;; turn the list of thunks into animated gifs +;; effect: overwrite the ANIMATED-GIF-FILE (in current directory) +;; [Listof (-> bitmap)] -> Void +;; turn the list of thunks into animated gifs +;; effect: overwrite the ANIMATED-GIF-FILE (in current directory) +(define (create-animated-gif R bitmap-list) + (when (file-exists? ANIMATED-GIF-FILE) (delete-file ANIMATED-GIF-FILE)) + (write-animated-gif bitmap-list (if (> +inf.0 R 0) (number->integer R) 5) + ANIMATED-GIF-FILE + #:one-at-a-time? #t + #:loop? #f)) + +(define ANIMATED-GIF-FILE "i-animated.gif") diff --git a/collects/2htdp/universe.ss b/collects/2htdp/universe.ss new file mode 100755 index 0000000000..33801da892 --- /dev/null +++ b/collects/2htdp/universe.ss @@ -0,0 +1,311 @@ +#lang scheme/gui + +#| TODO: + -- make window resizable :: why? +|# + +(require (for-syntax "private/syn-aux.ss") + "private/syn-aux-aux.ss" + "private/syn-aux.ss" + "private/check-aux.ss" + "private/image.ss" + "private/world.ss" + "private/universe.ss" + htdp/error + (rename-in lang/prim (first-order->higher-order f2h)) + (only-in mzlib/etc evcase)) + +(provide (all-from-out "private/image.ss")) + +(provide + sexp? ;; Any -> Boolean + scene? ;; Any -> Boolean + ) + +;; Spec = (on-tick Expr) +;; | (on-tick Expr Expr) +;; -- on-tick must specify a tick handler; it may specify a clock-tick rate + +(define-keywords AllSpec + [on-tick (function-with-arity + 1 + except + [(x rate) + #'(list (proc> 'on-tick (f2h x) 1) + (num> 'on-tick rate positive? "pos. number" "rate"))])]) + +; +; +; +; ; ; ; ; +; ; ; ; ; +; ; ; ; ; +; ; ; ;;; ; ;; ; ;;;; +; ; ; ; ; ;; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ; +; ;; ;; ; ; ; ; ; ; +; ; ; ; ; ; ; ; ; +; ; ; ;;; ; ;; ;;;; +; +; +; + +(provide big-bang ;; : see below + make-package ;; World Sexp -> Package + package? ;; Any -> Boolean + run-movie ;; [Listof Image] -> true + + ;; A MouseEventType is one of: + ;; - 'button-down + ;; - 'button-up + ;; - 'drag + ;; - 'move + ;; - 'enter + ;; - 'leave + + mouse-event? ;; Any -> Boolean + mouse=? ;; MouseEventType MouseEventType -> Boolean + + ;; KeyEvent is one of: + ;; -- Char + ;; -- Symbol + + key-event? ;; Any -> Boolean + key=? ;; KeyEvent KeyEvent -> Boolean + + ;; IP : a string that points to a machine on the net + LOCALHOST ;; IP + ) + +(provide-higher-order-primitive + run-simulation (create-scene) ; (Number Number Number (Nat -> Scene) -> true) + ) + +;; Expr = (big-bang Expr WorldSpec ...) +;; WorldSpec = AllSpec +;; | (on-draw Expr) +;; | (on-draw Expr Expr Expr) +;; -- on-draw must specify a rendering function; it may specify canvas dimensions +;; | (on-key Expr) +;; -- on-key must specify a key event handler +;; | (on-mouse Expr) +;; -- on-mouse must specify a mouse event handler +;; | (stop-when Expr) +;; -- stop-when must specify a boolean-valued function +;; | (register Expr) +;; | (register Expr Expr) +;; -- register must specify the internet address of a host (including LOCALHOST) +;; -- it may specify a world's name +;; | (record? Expr) +;; -- should the session be recorded and turned into PNGs and an animated GIF +;; | (on-receive Expr) +;; -- on-receive must specify a receive handler + +(define-keywords WldSpec + [on-draw (function-with-arity + 1 + except + [(f width height) + #'(list (proc> 'on-draw (f2h f) 1) + (nat> 'on-draw width "width") + (nat> 'on-draw height "height"))])] + [on-mouse (function-with-arity 4)] + [on-key (function-with-arity 2)] + [on-receive (function-with-arity 2)] + [stop-when (function-with-arity 1)] + [register (lambda (tag) + (lambda (p) + (syntax-case p () + [(host) #`(ip> #,tag host)] + [(ip name) #`(list (ip> #,tag ip) (symbol> #,tag name))] + [_ (err tag p)])))] + [record? (lambda (tag) + (lambda (p) + (syntax-case p () + [(b) #`(bool> #,tag b)] + [_ (err tag p)])))]) + +(define-syntax (big-bang stx) + (syntax-case stx () + [(big-bang) (raise-syntax-error #f "bad world description" stx)] + [(big-bang w s ...) + (let* ([Spec (append AllSpec WldSpec)] + [kwds (map (lambda (x) (datum->syntax #'here x)) (map car Spec))] + [rec? #'#f] + [spec (map (lambda (stx) + (syntax-case stx () + [(kw . E) + (and (identifier? #'kw) + (for/or ([n kwds]) (free-identifier=? #'kw n))) + (begin + (when (free-identifier=? #'kw #'record?) + (syntax-case #'E () + [(V) (set! rec? #'V)] + [_ (err 'record? stx)])) + (cons (syntax-e #'kw) (syntax E)))] + [_ (raise-syntax-error + 'big-bang "not a legal big-bang clause" stx)])) + (syntax->list (syntax (s ...))))] + ;; assert: all bind = (kw . E) and kw is constrained via Bind + [args (map (lambda (x) + (define kw (car x)) + (define co (assq kw Spec)) + (list kw ((cadr co) (cdr x)))) + spec)]) + #`(send (new (if #,rec? aworld% world%) [world0 w] #,@args) last))])) + + +; +; +; +; ; ; ; ; ;;; +; ; ; ; ; ; ; +; ; ; ; ; ; ; +; ; ; ;;; ; ;; ; ;;;; ; ; ; ; ; ; +; ; ; ; ; ;; ; ; ; ; ;;;;; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; +; ;; ;; ; ; ; ; ; ; ; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; +; ; ; ;;; ; ;; ;;;; ; ; ;; ; ; ; +; +; +; + +(define (run-simulation f) + (check-proc 'run-simulation f 1 "first" "one argument") + (big-bang 1 (on-tick add1) (on-draw f))) + +(define (run-movie r m*) + (check-arg 'run-movie (positive? r) "positive number" "first" r) + (check-arg 'run-movie (list? m*) "list (of images)" "second" m*) + (for-each (lambda (m) (check-image 'run-movie m "first" "list of images")) m*) + (let* ([fst (car m*)] + [wdt (image-width fst)] + [hgt (image-height fst)]) + (big-bang + m* + (on-tick rest r) + (on-draw (lambda (m) (if (empty? m) (text "The End" 22 'red) (first m)))) + (stop-when empty?)))) + +(define (mouse-event? a) + (pair? (member a '(button-down button-up drag move enter leave)))) + +(define (mouse=? k m) + (check-arg 'mouse=? (mouse-event? k) 'MouseEvent "first" k) + (check-arg 'mouse=? (mouse-event? m) 'MouseEvent "second" m) + (eq? k m)) + +(define (key-event? k) + (or (char? k) (symbol? k))) + +(define (key=? k m) + (check-arg 'key=? (key-event? k) 'KeyEvent "first" k) + (check-arg 'key=? (key-event? m) 'KeyEvent "second" m) + (eqv? k m)) + +(define LOCALHOST "127.0.0.1") + +;; ----------------------------------------------------------------------------- + +; +; +; +; ; ; ; +; ; ; ; +; ; ; +; ; ; ;;;; ; ; ; ;;; ; ;; ;;; ;;; +; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; +; ; ; ; ; ; ; ; ;;;;; ; ; ;;; ;;;;; +; ; ; ; ; ; ; ; ; ; ; ; +; ; ; ; ; ; ; ; ; ; ; ; ; ; +; ;;; ; ; ; ; ;;; ; ;;; ;;; +; +; +; + +(provide + ;; type World + world? ;; Any -> Boolean + world=? ;; World World -> Boolean + world1 ;; sample worlds + world2 + world3 + ;; type Bundle = (make-bundle Universe [Listof Mail]) + ;; type Mail = (make-mail World S-expression) + make-bundle ;; Universe [Listof Mail] -> Bundle + bundle? ;; is this a bundle? + make-mail ;; World S-expression -> Mail + mail? ;; is this a real mail? + universe ;; : see below + universe2 ;; (World World -> U) (U World Message) -> U + ) + +;; Expr = (universe Expr UniSpec) +;; UniSpec = AllSepc +;; | (on-new Expr) +;; -- on-new must specify a 'new world" handler; what happens when a world joins +;; | (on-msg Expr) +;; -- on-msg must specify a 'message' handler +;; | (on-disconnect Expr) +;; -- on-disconnect may specify a handler for the event that a world is leaving +;; | (to-string Expr) +;; -- to-string specifies how to render the universe as a string for display +;; in the console + +(define-keywords UniSpec + [on-new (function-with-arity 2)] + [on-msg (function-with-arity 3)] + [on-disconnect (function-with-arity 2)] + [to-string (function-with-arity 1)]) + +(define-syntax (universe stx) + (syntax-case stx () + [(universe) (raise-syntax-error #f "not a legal universe description" stx)] + [(universe u) (raise-syntax-error #f "not a legal universe description" stx)] + [(universe u bind ...) + (let* ([Spec (append AllSpec UniSpec)] + [kwds (map (lambda (x) (datum->syntax #'here x)) (map car Spec))] + [spec (map (lambda (stx) + (syntax-case stx () + [(kw . E) + (and (identifier? #'kw) + (for/or ([n kwds]) (free-identifier=? #'kw n))) + (cons (syntax-e #'kw) (syntax E))] + [(kw E) + (and (identifier? #'kw) + (for/or ([n kwds]) (free-identifier=? #'kw n))) + (list (syntax-e #'kw) (syntax E))] + [_ (raise-syntax-error + 'universe "not a legal universe clause" stx)])) + (syntax->list (syntax (bind ...))))] + ;; assert: all bind = (kw . E) and kw is constrained via Bind + [args (map (lambda (x) + (define kw (car x)) + (define co (assq kw Spec)) + (list kw ((cadr co) (cdr x)))) + spec)] + [domain (map car args)]) + (cond + [(not (memq 'on-new domain)) + (raise-syntax-error #f "missing on-new clause" stx)] + [(not (memq 'on-msg domain)) + (raise-syntax-error #f "missing on-msg clause" stx)] + [else ; (and (memq #'on-new domain) (memq #'on-msg domain)) + #`(send (new universe% [universe0 u] #,@args) last)]))])) + +;; (World World -> U) (U World Msg) -> U +(define (universe2 create process) + ;; UniState = '() | (list World) | Universe + ;; UniState World -> (cons UniState [Listof (list World S-expression)]) + (define (nu s p) + (cond + [(null? s) (make-bundle (list p) '())] + [(not (pair? s)) (make-bundle s '())] + [(null? (rest s)) (create (first s) p)] + [else (error 'create "a third world is signing up!")])) + (universe '() + (on-new nu) + (on-msg process) + #; + (on-tick (lambda (u) (printf "hello!\n") (list u)) 1))) \ No newline at end of file diff --git a/collects/lang/htdp-langs.ss b/collects/lang/htdp-langs.ss index 486d2f4218..de17f770ea 100644 --- a/collects/lang/htdp-langs.ss +++ b/collects/lang/htdp-langs.ss @@ -662,11 +662,16 @@ data-class-names))))))))) (define (get-teachpack-from-user parent) - (define tp-dir (collection-path "teachpack" "htdp")) + (define tp-dirs (list (collection-path "teachpack" "htdp") + (collection-path "teachpack" "2htdp"))) (define columns 2) - (define tps (filter - (λ (x) (file-exists? (build-path tp-dir x))) - (directory-list tp-dir))) + (define tps (apply + append + (map (λ (tp-dir) + (filter + (λ (x) (file-exists? (build-path tp-dir x))) + (directory-list tp-dir))) + tp-dirs))) (define sort-order (λ (x y) (string<=? (path->string x) (path->string y)))) (define pre-installed-tps (sort tps sort-order)) (define dlg (new dialog% [parent parent] [label (string-constant drscheme)])) @@ -826,7 +831,7 @@ (define compiling-message (new message% [parent button-panel] [label ""] [stretchable-width #t])) (define-values (ok-button cancel-button) (gui-utils:ok/cancel-buttons button-panel - (λ (b e) + (λ (b e) (set! answer (figure-out-answer)) (send dlg show #f)) (λ (b e) @@ -837,9 +842,15 @@ (cond [(send pre-installed-lb get-selection) => - (λ (i) `(lib ,(send pre-installed-lb get-string i) - "teachpack" - "htdp"))] + (λ (i) + (define f (send pre-installed-lb get-string i)) + (cond + [(file-exists? (build-path (collection-path "teachpack" "htdp") f)) + `(lib ,f "teachpack" "htdp")] + [(file-exists? (build-path (collection-path "teachpack" "2htdp") f)) + `(lib ,f "teachpack" "2htdp")] + [else (error 'figuer-out-answer "argh: ~a ~a" + (collection-path "teachpack" "htdp") f)]))] [(send user-installed-lb get-selection) => (λ (i) `(lib ,(send user-installed-lb get-string i) diff --git a/collects/teachpack/2htdp/scribblings/balls.ss b/collects/teachpack/2htdp/scribblings/balls.ss new file mode 100644 index 0000000000..a0e3bda180 --- /dev/null +++ b/collects/teachpack/2htdp/scribblings/balls.ss @@ -0,0 +1,59 @@ +;; The first three lines of this file were inserted by DrScheme. They record metadata +;; about the language level of this file in a form that our tools can easily process. +#reader(lib "htdp-intermediate-lambda-reader.ss" "lang")((modname balls) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f ()))) +(require (lib "world.ss" "htdp")) + +;; constants +(define height 50) +(define delta 80) +(define width (+ delta (* 2 height))) + +(define left (quotient height 2)) +(define right (+ height delta left)) + +;; World = (make-posn left Number) | (make-posn right Number) + +(define server (text "server" 11 'black)) +(define server* (overlay server (nw:rectangle (image-width server) (image-height server) 'outline 'black))) + +;; visual constants +(define bg + (place-image + (text "universe" 11 'green) + 60 0 + (place-image + server* + (+ height 15) 20 + (place-image + (text "left" 11 'blue) + 10 10 + (place-image + (text "right" 11 'red) + (+ height delta 10) 10 + (place-image + (nw:rectangle delta height 'solid 'white) + height 0 + (place-image + (nw:rectangle width height 'solid 'gray) + 0 0 + (empty-scene width height)))))))) + +(define ball (circle 3 'solid 'red)) + +;; World -> Scene +(define (draw w) + (place-image ball (posn-x w) (posn-y w) bg)) + + +;; World -> World +(define (tick w) + (local ((define y (posn-y w)) + (define x (posn-x w))) + (cond + [(> y 0) (make-posn x (- y 1))] + [(= x left) (make-posn right height)] + [(= x right) (make-posn left height)]))) + +(big-bang width height 1/66 (make-posn left height) true) +(on-redraw draw) +(on-tick-event tick) diff --git a/collects/teachpack/2htdp/scribblings/fsa.ss b/collects/teachpack/2htdp/scribblings/fsa.ss new file mode 100644 index 0000000000..6fd029e6a5 --- /dev/null +++ b/collects/teachpack/2htdp/scribblings/fsa.ss @@ -0,0 +1,59 @@ +#lang slideshow + +(require slideshow/pict) + +(define DELTA 40) +(define FT 12) + +; (fsa "unlock" "lock" "push" "tick") +(define (fsa L C O unlock lock push tick) + (define (make-state txt) + (define t (text txt '() FT)) + (define e (rounded-rectangle (+ 10 (pict-width t)) (+ 10 (pict-height t)))) + (cc-superimpose t e)) + + (define locked (make-state L)) + (define closed (make-state C)) + (define open (make-state O)) + + (define bg (rectangle (+ (pict-width locked) (* 2 DELTA)) + (+ (pict-height locked) + (pict-height closed) + (pict-height open) + (* 3 DELTA)))) + + (define width (pict-width bg)) + + (define (center base state y) + (define w (pict-width state)) + (define d (quotient (- width w) 2)) + (pin-over base d y state)) + + (define nx + (center + (center + (center + bg locked (/ DELTA 2)) + closed + (+ (/ DELTA 2) (pict-height locked) DELTA)) + open + (+ (/ DELTA 2) DELTA (pict-height locked) DELTA (pict-height closed)))) + + (define (add-labeled-arrow nx locked lb-find closed lt-find txt) + (define-values (x0 y0) (lb-find nx locked)) + (define-values (x1 y1) (lt-find nx closed)) + (define lbl (text txt '() (- FT 2))) + (define wlbl (pict-width lbl)) + (define hlbl (pict-height lbl)) + (define x (- x0 (/ wlbl 2))) + (define y (+ y0 (/ ( - y1 y0 hlbl) 2))) + (pin-over (pin-arrow-line 4.0 nx locked lb-find closed lt-find) x y lbl)) + + (define l1 (add-labeled-arrow nx locked lb-find closed lt-find unlock)) + (define l2 (add-labeled-arrow l1 closed lb-find open lt-find push)) + (define l3 (add-labeled-arrow l2 open rt-find closed rb-find tick)) + (define l4 (add-labeled-arrow l3 closed rt-find locked rb-find lock)) + l4) + +(fsa "locked" "closed" "open" "unlock" "lock" "push" "time") +(fsa "'locked" "'closed" "'open" "#\\u" "#\\l" "#\\space" "tick") \ No newline at end of file diff --git a/collects/teachpack/2htdp/scribblings/nuworld.ss b/collects/teachpack/2htdp/scribblings/nuworld.ss new file mode 100644 index 0000000000..56a536a350 --- /dev/null +++ b/collects/teachpack/2htdp/scribblings/nuworld.ss @@ -0,0 +1,119 @@ +#lang slideshow + +(require slideshow/pict mred/mred) + +(define DELTA 80) +(define FT 12) + +(define txt + '("(big-bang World_0" + " (on-draw render WIDTH HEIGHT)" + " (on-tick tock RATE)" + " (on-mouse click)" + " (on-key react))" + )) + +(define program + (apply vl-append (map (lambda (t) (text t '() (- FT 2))) txt))) + +(define Program + (cc-superimpose + (rectangle (+ 5 (pict-width program)) (+ 5 (pict-height program))) + program)) + +(define (make-state txt) + (define t (text txt '() FT)) + (define e (rounded-rectangle (+ 10 (pict-width t)) (+ DELTA (pict-height t)))) + (cc-superimpose t e)) + +(define False (text "FALSE" '() FT)) +(define True (text "TRUE" '() FT)) +(define BOOL (rectangle (+ 5 (pict-width False)) (+ 5 (pict-height False)))) + +;; String Boolean -> Pict +(define (make-state0 txt b) + ;; create the basic state + (define t (text txt '() FT)) + (define s (if b + (cc-superimpose + (rounded-rectangle (+ 5 (pict-width t)) (+ (- DELTA 5) (pict-height t))) + t) + t)) + (define w + (cc-superimpose + s + (rounded-rectangle (+ 10 (pict-width t)) (+ DELTA (pict-height t))))) + ;; add the boolean + (define bb (cc-superimpose (if b True False) BOOL)) + (define ar (add-labeled-arrow (vc-append DELTA bb w) w ct-find bb cb-find "done")) + (define scene (text "Scene" '() FT)) + (define sc (cc-superimpose scene (rectangle (+ 20 (pict-width scene)) (+ 30 (pict-height scene))))) + (define br (add-labeled-arrow (vc-append DELTA ar sc) ar cb-find sc ct-find "render")) + br) + +(define (add-labeled-arrow nx locked lb-find closed lt-find txt) + (define-values (x0 y0) (lb-find nx locked)) + (define-values (x1 y1) (lt-find nx closed)) + (define lbl (text txt '() (- FT 2))) + (define wlbl (pict-width lbl)) + (define hlbl (pict-height lbl)) + (define x (- x0 (/ wlbl 2))) + (define y (+ y0 (/ ( - y1 y0 hlbl) 2))) + (pin-over (pin-arrow-line 4.0 nx locked lb-find closed lt-find) x y lbl)) + +(define (h-labeled-arrow t) + (define tock (text t '() (- FT 2))) + (define blk (blank (+ DELTA 4) 2)) + (vc-append tock (pin-arrow-line 4.0 blk blk lc-find blk rc-find))) + +(define arrows + (vc-append (h-labeled-arrow "tock") + (h-labeled-arrow "click") + (h-labeled-arrow "react"))) + +(define state0 (make-state0 "World_0" #f)) +(define state1 (make-state0 "World_1" #f)) +(define dots (cc-superimpose (blank (pict-width state1) (pict-height state1)) (text "..." '() FT))) +(define state2 (make-state0 "World_N-1" #f)) +(define stateN (make-state0 "World_N" #t)) +(define states (list state0 arrows state1 arrows dots arrows state2 arrows stateN)) + +(define bg (blank (+ (apply + (map pict-width states)) + DELTA #;(* (length states) DELTA)) + (+ (pict-height state0) DELTA))) + +(define (center base state x) + (define w (pict-height state)) + (define d (quotient (- width w) 2)) + (pin-over base x d state)) + +(define width (pict-height bg)) + +(define x (* 1/2 DELTA)) +(define xx + (foldl (lambda (f ls s) + (define y (center s f x)) + (set! x (+ x ls)) + y) + bg + states + (map (lambda (x) (+ (pict-width x) #;(* 1/1 DELTA))) states))) + +(define the-image (ct-superimpose xx Program)) + +(define image-bm + (make-object bitmap% + (inexact->exact (round (pict-width the-image))) + (inexact->exact (round (pict-height the-image))))) + +(send image-bm ok?) + +(define image-dc + (new bitmap-dc% [bitmap image-bm])) +(send image-dc clear) + +(draw-pict the-image image-dc 0.0 0.0) + +(send image-bm save-file "nuworld.png" 'png) + +the-image diff --git a/collects/teachpack/2htdp/scribblings/server2.ss b/collects/teachpack/2htdp/scribblings/server2.ss new file mode 100644 index 0000000000..427a7c22ed --- /dev/null +++ b/collects/teachpack/2htdp/scribblings/server2.ss @@ -0,0 +1,181 @@ +#lang slideshow + +(require slideshow/pict) + +(define DELTA 80) +(define FT 12) + +(define initialize "register") +(define proc-msg "process") + +(define program + (apply vl-append (map (lambda (t) (text t '() (- FT 2))) + (list (format "(universe ~a ~a)" initialize proc-msg))))) + +(define Program + (cc-superimpose + (rectangle (+ 5 (pict-width program)) (+ 5 (pict-height program))) + program)) + +;; String Boolean -> Pict +(define (make-state0 txt b) + ;; create the basic state + (define t (text txt '() FT)) + (cc-superimpose t (rounded-rectangle (+ 10 (pict-width t)) (+ DELTA (pict-height t))))) + +(define (add-labeled-arrow nx locked lb-find closed lt-find txt) + (define-values (x0 y0) (lb-find nx locked)) + (define-values (x1 y1) (lt-find nx closed)) + (define lbl (text txt '() (- FT 2))) + (define wlbl (pict-width lbl)) + (define hlbl (pict-height lbl)) + (define x (- x0 (/ wlbl 2))) + (define y (+ y0 (/ ( - y1 y0 hlbl) 2))) + (pin-over (pin-arrow-line 4.0 nx locked lb-find closed lt-find) x y lbl)) + +(define (h-labeled-arrow t) + (define tock (text t '() (- FT 2))) + (define blk (blank (+ DELTA 4) 2)) + (vc-append tock (pin-arrow-line 4.0 blk blk lc-find blk rc-find))) + +(define message (text "Message" '() FT)) +(define (make-Message) + (cc-superimpose message (rectangle (+ 20 (pict-width message)) (+ 30 (pict-height message))))) + +(define Message (vc-append (make-Message) (arrowhead 4 (* 1/2 pi)))) +(define MessageK (vc-append (arrowhead 4 (* 3/2 pi)) (make-Message))) +(define MessageI (vc-append (arrowhead 4 (* 3/2 pi)) (make-Message))) + +(define M (rb-superimpose Message (blank DELTA DELTA))) +(define K (rb-superimpose MessageK (blank DELTA DELTA))) +(define I (rb-superimpose MessageI (blank DELTA DELTA))) + +(define (make-arrows M lbl) + (define Tock (h-labeled-arrow lbl)) + (values Tock (vc-append (blank DELTA (/ DELTA 2)) Tock M))) + +(define-values (TockM arrowsR) (make-arrows M proc-msg)) +(define-values (TockK arrowsL) (make-arrows K proc-msg)) +(define-values (init arrows) (make-arrows I initialize)) + +(define state0 (make-state0 "Server_0" #f)) +(define state2 (make-state0 "Server_N-1" #f)) +(define Univrs (hc-append (arrowhead 4 0) (cc-superimpose (cloud 160 80) (text "Universe" '() FT )))) +(define dots (vc-append + (blank (pict-width state2) (quotient (pict-height state2) 1)) + (text "..." '() FT) + (blank (pict-width state2) (* (pict-height state2))) + Univrs)) + +(define states (list arrows + state0 + arrowsL + dots + arrowsR + state2 + (h-labeled-arrow proc-msg))) + +(define bg (blank (+ (apply + (map pict-width states)) DELTA) (pict-height dots))) + +(define (center base state x) + (define w (pict-height state)) + (define d (quotient (- (pict-height bg) w) 2)) + (pin-over base x d state)) + +(define x (* 1/2 DELTA)) +(define xx + (foldl (lambda (f ls s) + (define y (center s f x)) + (set! x (+ x ls)) + y) + bg + states + (map pict-width states))) + +(define zz (ct-superimpose xx Program)) + +(require mred/mred) + +(define the-image + (lt-superimpose + (dc (lambda (dc x y) + (define-values (mx my) (cb-find zz MessageK)) + (define-values (tx ty) (ct-find zz MessageK)) + (define-values (ix iy) (ct-find zz MessageI)) + (define-values (jx jy) (cb-find zz MessageI)) + (define-values (sx sy) (lc-find zz Univrs)) + (define-values (tockx tocky) (lb-find zz TockK)) + (define-values (initx inity) (lb-find zz init)) + (define (add-curve rx ry) + (set! dcp (make-object dc-path%)) + (set! cx (max rx tx)) + (set! cy (min ry ty)) + (send dcp move-to tx ty) + (send dcp curve-to tx ty cx cy rx ry) + (send dc draw-path dcp)) + (define dcp (make-object dc-path%)) + ;; --- draw arc from Message to Server + (define cx (min sx mx)) + (define cy (max sy my)) + (send dc set-smoothing 'aligned) + (send dcp move-to mx my) + (send dcp curve-to mx my cx cy sx sy) + (send dc draw-path dcp) + (set! dcp (make-object dc-path%)) + (set! cx (min sx jx)) + (set! cy (max sy jy)) + (send dc set-smoothing 'aligned) + (send dcp move-to jx jy) + (send dcp curve-to jx jy cx cy sx sy) + (send dc draw-path dcp) +;; --- draw arc from Message to Receiver + (add-curve tockx tocky) + (set! tx ix) (set! ty iy) + (add-curve initx inity) + ;; --- + dc) + (pict-width zz) (pict-height zz)) + (lt-superimpose + zz + (dc (lambda (dc x y) + (define-values (mx my) (cb-find zz Message)) + (define-values (tx ty) (ct-find zz Message)) + (define-values (sx sy) (rc-find zz Univrs)) + (define-values (tockx tocky) (rb-find zz TockM)) + (define (add-curve rx ry) + (set! dcp (make-object dc-path%)) + (set! cx (min rx tx)) + (set! cy (min ry ty)) + (send dcp move-to tx ty) + (send dcp curve-to tx ty cx cy rx ry) + (send dc draw-path dcp)) + (define dcp (make-object dc-path%)) + ;; --- draw arc from Message to Server + (define cx (max sx mx)) + (define cy (max sy my)) + (send dc set-smoothing 'aligned) + (send dcp move-to mx my) + (send dcp curve-to mx my cx cy sx sy) + (send dc draw-path dcp) + ;; --- draw arc from Message to Receiver + (add-curve tockx tocky) + ;; --- + dc) + (pict-width zz) (pict-height zz))))) + +(define image-bm + (make-object bitmap% + (inexact->exact (round (pict-width the-image))) + (inexact->exact (round (pict-height the-image))))) + +(send image-bm ok?) + +(define image-dc + (new bitmap-dc% [bitmap image-bm])) +(send image-dc clear) + +(draw-pict the-image image-dc 0.0 0.0) + +(send image-bm save-file "server2.png" 'png) + +the-image \ No newline at end of file diff --git a/collects/teachpack/2htdp/scribblings/shared.ss b/collects/teachpack/2htdp/scribblings/shared.ss new file mode 100644 index 0000000000..8bdc62c027 --- /dev/null +++ b/collects/teachpack/2htdp/scribblings/shared.ss @@ -0,0 +1,10 @@ +#lang scheme/base + +(require scribble/manual) + +(provide teachpack) + +(define (teachpack tp . name) + (apply title #:tag tp + `(,@name ": " ,(filepath (format "~a.ss" tp)) + ,(index (format "~a teachpack" tp))))) diff --git a/collects/teachpack/2htdp/scribblings/universe.scrbl b/collects/teachpack/2htdp/scribblings/universe.scrbl new file mode 100644 index 0000000000..c95ddeabc8 --- /dev/null +++ b/collects/teachpack/2htdp/scribblings/universe.scrbl @@ -0,0 +1,1536 @@ +#lang scribble/doc + +@(require scribble/manual "shared.ss" + (for-label scheme ; lang/htdp-beginner + (only-in lang/htdp-beginner check-expect) + "../universe.ss" + teachpack/htdp/image)) +@(require scribble/struct) + +@(define (table* . stuff) + ;; (list paragraph paragraph) *-> Table + (define (flow* x) (make-flow (list x))) + (make-blockquote 'blockquote + (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{Worlds and the Universe} + +@author{Matthias Felleisen} + +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 symbols. The second half of the documentation focuses on + @tech{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/HtDP/Prologue/book.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}. + +@declare-exporting["../universe.ss" #:use-sources (teachpack/htdp/image)] + +@; ----------------------------------------------------------------------------- + +@section[#:tag "basics"]{Basics} + +The teachpack assumes working knowledge of the basic image manipulation + primitives and supports several functions that require a special kind of + image, called a @deftech{scene}, , which are images whose pinholes are at + position @scheme[(0,0)]. For example, the teachpack displays only + @tech{scene}s in its canvas. + +@defproc[(scene? [x any/c]) boolean?]{ + determines whether @scheme[x] is a @tech{scene}.} + +@defproc[(empty-scene [width natural-number/c] + [height natural-number/c]) + scene?]{ + creates a plain white, @scheme[width] x @scheme[height] @tech{scene}.} + +@defproc[(place-image [img image?] [x number?] [y number?] + [s scene?]) + scene?]{ + creates a scene by placing @scheme[img] at @scheme[(x,y)] into @scheme[s]; + @scheme[(x,y)] are computer graphics coordinates, i.e., they count right and + down from the upper-left corner.} + +@; ----------------------------------------------------------------------------- +@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[(run-simulation [create-image (-> natural-number/c scene)]) + true]{ + + opens a canvas and starts a clock that tick 28 times per second + seconds. Every time the clock ticks, drscheme applies + @scheme[create-image] to the number of ticks passed since this function + call. The results of these applications are displayed in the canvas. +} + +Example: +@schemeblock[ +(define (create-UFO-scene height) + (place-image UFO 50 height (empty-scene 100 100))) + +(define UFO + (overlay (circle 10 'solid 'green) + (rectangle 40 4 'solid 'green))) + +(run-simulation create-UFO-scene) +] + +@;----------------------------------------------------------------------------- +@section[#:tag "interactive"]{Interactions} + +The step from simulations to interactive programs is relatively + small. Roughly speaking, a simulation designates one function, + @emph{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 + @emph{draw} function, which is called every time your program should + visualize the current world, and a @emph{stop?} 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 @emph{World_0} as the initial + world. The handlers @emph{tock}, @emph{react}, and @emph{click} transform + one world into another one; each time an event is handled, @emph{done} is + used to check whether the world is final, in which case the program is + shut down; and finally, @emph{draw} renders each world as a scene, which + is then displayed on an external canvas. + +@deftech{World} : @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{World} 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 register record?) + (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) + (record? boolean-expr) + (on-receive rec-expr) + (register IP-expr) + (register IP-expr 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{World}. 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.} + +@itemize[ + +@item{ +@defform[(on-tick + [tick-expr (-> (unsyntax @tech{World}) (unsyntax @tech{World}))])]{ + +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[(on-tick + [tick-expr (-> (unsyntax @tech{World}) (unsyntax @tech{World}))] + [rate-expr natural-number/c])]{ +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{An @tech{KeyEvent} represents key board events, e.g., keys pressed or + released. + +@deftech{KeyEvent} : @scheme[(or/c char? symbol?)] + +A @tech{Char} is used to signal that the user has hit an alphanumeric + key. A @tech{Symbol} denotes arrow keys or special events: + +@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, and} + +@item{@scheme['release] is the event of releasing a key.} +] + +@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 (-> (unsyntax @tech{World}) key-event? (unsyntax @tech{World}))])]{ + 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: +@(begin +#reader scribble/comment-reader +(schemeblock +(define (change w a-key) + (cond + [(key=? a-key 'left) (world-go w -DELTA)] + [(key=? a-key 'right) (world-go w +DELTA)] + [(char? a-key) w] ;; to demonstrate 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 symbols: +@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{KeyEvent}} + +@defproc[(key=? [x mouse-event?][y mouse-event?]) boolean?]{ + compares two @tech{KeyEvent} for equality} + +@defform[(on-mouse + [clack-expr + (-> (unsyntax @tech{World}) natural-number/c natural-number/c (unsyntax @tech{MouseEvent}) (unsyntax @tech{World}))])]{ + 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 (-> (unsyntax @tech{World}) 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[(on-draw + [render-expr (-> (unsyntax @tech{World}) 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? (-> (unsyntax @tech{World}) 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. +}} + +@item{ + +@defform[(record? + [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. +}} +] + +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) + (place-image UFO 50 height (empty-scene 100 100))) + +(define UFO + (overlay (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 "scenes-and-images"]{Scenes and Images} + +For the creation of scenes from the world, use the functions from +@secref["image"]. The teachpack adds the following two functions, which +are highly useful for creating scenes. + +@defproc[(nw:rectangle [width natural-number/c] [height natural-number/c] [solid-or-filled Mode] [c Color]) image?]{ + creates a @scheme[width] by @scheme[height] rectangle, solid or outlined as specified by + @scheme[solid-or-filled] and colored according to @scheme[c], with a pinhole at the upper left + corner.} + +@defproc[(scene+line [s scene?][x0 number?][y0 number?][x1 number?][y1 number?][c Color]) scene?]{ + creates a scene by placing a line of color @scheme[c] from @scheme[(x0,y0)] to + @scheme[(x1,y1)] into @scheme[scene]; + @scheme[(x,y)] are computer graphics coordinates. + In contrast to the @scheme[add-line] function, @scheme[scene+line] cuts + off those portions of the line that go beyond the boundaries of + the given @scheme[s].} + +@; ----------------------------------------------------------------------------- +@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 @deftech{D} for this information. 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{D}---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 : @tech{D} -> @tech{D} +;; deal with the passing of time +(define (tick w) ...) + +;; click : @tech{D} @emph{Number} @emph{Number} @tech{MouseEvent} -> @tech{D} +;; 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 : @tech{D} @tech{KeyEvent} -> @tech{D} +;; deal with a key event (symbol, char) @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. + +@; ----------------------------------------------------------------------------- +@subsection{Simulating a Door: Data} + +Our first and immediate goal is to represent the world as data. In this + specific example, the world consists of our door and what changes about + the door is whether it is locked, unlocked but closed, or open. We use + three symbols to represent the three states: + +@deftech{SD} : state of door + +@(begin +#reader scribble/comment-reader +(schemeblock +;; The state of the door (SD) is one of: +;; -- @scheme['locked] +;; -- @scheme['closed] +;; -- @scheme['open] +)) + +Symbols are particularly well-suited here because they directly express + the state of the door. + +Now that we have a data definition, we must also decide which computer + interactions should model the various actions on the door. Our pictorial + representation of the door's states and transitions, specifically the + arrow from @tt{open} to @tt{closed} suggests the use of a function that + simulates time. For the other three arrows, we could use either keyboard + events or mouse clicks or both. Our solution uses three keystrokes: + @scheme[#\u] for unlocking the door, @scheme[#\l] for locking it, and + @scheme[#\space] for pushing it open. We can express these choices + graphically by translating the above state-machine diagram from the world + of information into the world of data. + +@table*[ @list[ @t{@image["door-sim.png"]} @t{@image["door-real.png"]}] ] + +For completeness, we have repeated the original diagram on the right so +that you can see which computer interaction corresponds to which domain +action. + +@; ----------------------------------------------------------------------------- +@subsection{Simulating a Door: Functions} + +Our analysis and data definition leaves us with three functions to design: + +@itemize[ + +@item{@emph{automatic-closer}, which closes the time during one tick;} + +@item{@emph{door-actions}, which manipulates the time in response to +pressing a key; and} + +@item{@emph{render}, which translates the current state of the door into +a visible scene.} + +] + +Let's start with @emph{automatic-closer}. Substituting @tech{SD} for +@tech{D} and @emph{automatic-closer} for @emph{tick}, we get its contract, +and it is easy to refine the purpose statement, too: + +@(begin +#reader scribble/comment-reader +(schemeblock +;; automatic-closer : @tech{SD} -> @tech{SD} +;; closes an open door over the period of one tick +(define (automatic-closer state-of-door) ...) +)) + + Making up examples is trivial when the world can only be in one of three + states: + +@table*[ + @list[@t{ given state } @t{ desired state }] + @list[@t{ @scheme['locked] } @t{ @scheme['locked] }] + @list[@t{ @scheme['closed] } @t{ @scheme['closed] }] + @list[@t{ @scheme['open] } @t{ @scheme['closed] }] +] + +@(begin +#reader scribble/comment-reader +(schemeblock +;; automatic-closer : @tech{SD} -> @tech{SD} +;; closes an open door over the period of one tick + +(check-expect (automatic-closer 'locked) 'locked) +(check-expect (automatic-closer 'closed) 'closed) +(check-expect (automatic-closer 'open) 'closed) + +(define (automatic-closer state-of-door) ...) +)) + + The template step demands a conditional with three clauses: + +@(begin +#reader scribble/comment-reader +(schemeblock +(define (automatic-closer state-of-door) + (cond + [(symbol=? 'locked state-of-door) ...] + [(symbol=? 'closed state-of-door) ...] + [(symbol=? 'open state-of-door) ...])) +)) + + The examples basically dictate what the outcomes of the three cases must + be: + +@(begin +#reader scribble/comment-reader +(schemeblock +(define (automatic-closer state-of-door) + (cond + [(symbol=? 'locked state-of-door) 'locked] + [(symbol=? 'closed state-of-door) 'closed] + [(symbol=? 'open state-of-door) 'closed])) +)) + + Don't forget to run the example-tests. + +For the remaining three arrows of the diagram, we design a function that + reacts to the three chosen keyboard events. As mentioned, functions that + deal with keyboard events consume both a world and a keyevent: + +@(begin +#reader scribble/comment-reader +(schemeblock +;; door-actions : @tech{SD} @tech{KeyEvent} -> @tech{SD} +;; key events simulate actions on the door +(define (door-actions s k) ...) +)) + +@table*[ + @list[@t{ given state } @t{ given keyevent } @t{ desired state }] + +@list[ @t{ @scheme['locked] } @t{ @scheme[#\u]} @t{@scheme['closed]}] +@list[ @t{ @scheme['closed] } @t{ @scheme[#\l]} @t{@scheme['locked]}] +@list[ @t{ @scheme['closed] } @t{ @scheme[#\space]} @t{@scheme['open] }] +@list[ @t{ @scheme['open] } @t{ --- } @t{@scheme['open] }]] + + The examples combine what the above picture shows and the choices we made + about mapping actions to keyboard events. + +From here, it is straightforward to turn this into a complete design: + +@schemeblock[ +(define (door-actions s k) + (cond + [(and (symbol=? 'locked s) (key=? #\u k)) 'closed] + [(and (symbol=? 'closed s) (key=? #\l k)) 'locked] + [(and (symbol=? 'closed s) (key=? #\space k)) 'open] + [else s])) + +(check-expect (door-actions 'locked #\u) 'closed) +(check-expect (door-actions 'closed #\l) 'locked) +(check-expect (door-actions 'closed #\space) 'open) +(check-expect (door-actions 'open 'any) 'open) +(check-expect (door-actions 'closed 'any) 'closed) +] + +Last but not least we need a function that renders the current state of the +world as a scene. For simplicity, let's just use a large text for this +purpose: + +@(begin +#reader scribble/comment-reader +(schemeblock +;; render : @tech{SD} -> @tech{scene} +;; translate the current state of the door into a large text +(define (render s) + (text (symbol->string s) 40 'red)) + +(check-expect (render 'closed) (text "closed" 40 'red)) +)) + The function @scheme[symbol->string] translates a symbol into a string, + which is needed because @scheme[text] can deal only with the latter, not + the former. A look into the language documentation revealed that this + conversion function exists, and so we use it. + +Once everything is properly designed, it is time to @emph{run} the +program. In the case of the universe teachpack, this means we must specify +which function takes care of tick events, key events, and drawing: + +@schemeblock[ +(big-bang 'locked + (on-tick automatic-closer) + (on-key door-actions) + (on-draw render)) +] + +Now it's time for you to collect the pieces and run them in DrScheme to see +whether it all works. + +Exercise: Design a data representation that closes the door over two (or +three or more) clock ticks instead of one. + +@; ----------------------------------------------------------------------------- +@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{World}. + +@deftech{Package} represents a pair consisting of a @tech{World} (state) + 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 @deftech{Package}.} + +@defproc[(make-package [w any/c][m sexp?]) package?]{ + create a @tech{Package} from a @tech{World} and an @tech{S-expression}.} + +As mentioned, all event handlers may return @tech{World}s or @tech{Package}s; +here are the revised specifications: + +@defform[(on-tick + [tick-expr (-> (unsyntax @tech{World}) (or/c (unsyntax @tech{World}) package?))])]{ +} + +@defform[(on-tick + [tick-expr (-> (unsyntax @tech{World}) (or/c (unsyntax @tech{World}) package?))] + [rate-expr natural-number/c])]{ +} + +@defform[(on-key + [change (-> (unsyntax @tech{World}) key-event? (or/c (unsyntax @tech{World}) package?))])]{ +} + +@defform[(on-mouse + [clack + (-> (unsyntax @tech{World}) natural-number/c natural-number/c (unsyntax @tech{MouseEvent}) + (or/c (unsyntax @tech{World}) 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{World} 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 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[(register [ip-expr string?] + [name-expr (or/c symbol? string?)])]{ + connect this world to a universe server @emph{under a specific} @scheme[name-expr].} +} + +] + +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 (-> (unsyntax @tech{World}) sexp? (or/c (unsyntax @tech{World}) package?))])]{ + tell DrScheme to call @scheme[receive-expr] for every message receipt, on the current + @tech{World} and the received message. The result of the call becomes the current + @tech{World}. + + Because @scheme[receive-expr] is (or evaluates to) a world-transforming + function, it too can produce a @tech{Package} instead of just a + @tech{World}. 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{World} and some + auxiliary arguments (a message in this case) and produces a + @tech{World} 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. There are two primary kinds of events: when a new @tech{world} + program joins the @tech{universe} that the server controls and when a + @tech{world} sends a message. + +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 three + concepts. + +@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[(world? [x any/c]) boolean?]{ + determines whether @scheme[x] is a @emph{world}. 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[(world=? [u world?][v world?]) boolean?]{ + compares two @emph{world}s for equality.} + +@defthing[world1 world?]{a world for testing your programs} +@defthing[world2 world?]{another world for testing your programs} +@defthing[world3 world?]{and a third one} + +The three sample worlds are provided so that you can test your functions +for universe programs. For example: + +@schemeblock[ +(check-expect (world=? world1 world2) false) +(check-expect (world=? world2 world2) true) +] +} + +@item{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 world?] [content sexp?]) mail?]{ + creates a @emph{mail} from a @emph{world} and an @tech{S-expression}.} +} + +@item{Each event handler produces a @emph{bundle}, which is a structure +that contains the @tech{server}'s state and a list of mails to other +worlds. Again, the teachpack provides only the predicate and a constructor: + +@defproc[(bundle? [x any/c]) boolean?]{ + determines whether @scheme[x] is a @emph{bundle}.} + +@defproc[(make-bundle [state any/c] [mails (listof mail?)]) bundle?]{ + creates a @emph{bundle} from a piece of data that represents a server + state and a list of mails.} + +} +] + +@; ----------------------------------------------------------------------------- +@subsection{Universe Descriptions} + +A @tech{server} keeps track of information about the @tech{universe} that + it manages. Of course, what kind of information it tracks and how it is + represented depends on the situation and the programmer, just as with + @tech{world} programs. + +@deftech{Universe} @scheme[any/c] represent 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 transformation functions 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 + (start stop max-worlds on-new on-msg on-tick + on-disconnect to-string) + (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) + (to-string render-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.} + +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. + + +Now it is possible to explain the clauses in a @scheme[universe] server +description. Two of them are mandatory: + +@itemize[ + +@item{ + @defform[(on-new + [new-expr (-> (unsyntax @tech{Universe}) world? + (cons (unsyntax @tech{Universe}) [listof mail?]))])]{ + tell DrScheme to call the function @scheme[new-expr] every time another world joins the + universe.}} + +@item{ + @defform[(on-msg + [msg-expr (-> (unsyntax @tech{Universe}) world? sexp? + (cons (unsyntax @tech{Universe}) [listof mail?]))])]{ + + tell DrScheme to apply @scheme[msg-expr] to the current state of the universe, the world + that sent the message, and the message itself. The handler must produce a state of the + universe and a list of mails.} + } +] + +The following picture provides a graphical overview of the server's workings. + +@image["server2.png"] + +In addition to the mandatory handlers, a program may wish to add some +optional handlers: + +@itemize[ + +@item{ +@defform[(on-tick + [tick-expr (-> (unsyntax @tech{Universe}) bundle?)])]{ + tell DrScheme to apply @scheme[tick-expr] to the current state of the + universe. The handler is expected to produce a bundle of the new state of + the universe and a list of mails. + } + +@defform[(on-tick + [tick-expr (-> (unsyntax @tech{Universe}) 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 (-> (unsyntax @tech{Universe}) world? 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; the second one is the world that got + disconnected. + } +} + +@item{ + @defform[(to-string + [render-expr (-> (unsyntax @tech{Universe}) string?)])]{ + tell DrScheme to render the state of the universe after each event and to + display this string in the universe console. + } +} + +] + +@; ----------------------------------------------------------------------------- +@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.gif"] + + 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} sends two messages in some + order, they arrive in the same order at the server. In contrast, it is + generally impossible to ensure whether one world joins before another or + whether a message from one world gets to the server before another world's + message gets there. 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 of course @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{Universe};} + +@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{MsgS2W} for messages +from the server to the worlds and @deftech{MsgW2S} 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. + +(interaction diagrams: tbd) + +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 +;; @tech{Universe} World -> (make-bundle @tech{Universe} [Listof mail?]) +;; create new @tech{Universe} when world w is joining the universe, +;; which is in state s; also send mails as needed +(define (add-world s w) ...) + +;; @tech{Universe} World MsgW2U -> (make-bundle @tech{Universe} [Listof mail?]) +;; create new @tech{Universe} when world w is sending message m +;; to universe in state s; also send mails as needed +(define (process s p m) ...) +)) + +Note how both functions return a bundle. + +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 choose to introduce @tech{Universe} as a list of @tech{world}s, and we + interpret non-empty lists as those where the first @tech{world} is active and the + remainder are the passive @tech{world}s. As for the two possible events, +@itemize[ + +@item{it is natural to add new @tech{world}s to the end of the list; and} + +@item{it is natural to move an active @tech{world} that relinquishes its turn to +the end of the list, too.} +] + +The server should send messages to the first @tech{world} of its list as + long as it wishes this @tech{world} to remain active. In turn, it should + expect to receive messages only from this one active @tech{world} and no + other @tech{world}. The content of these two messages is nearly irrelevant + because a message from the server to a @tech{world} means that it is the + @tech{world}'s turn and a message from the @tech{world} 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. + +@; ----------------------------------------------------------------------------- +@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 + +;; Universe is [Listof world?] +;; 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 Universe (list (make-mail world? GoMessage))) + +;; Universe world? -> Result +;; add world w to the universe, when server is in state u +(define (add-world u w) ...) + +;; Universe world? StopMessage -> Result +;; world w sent message m when server is in state u +(define (switch u w 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. For this reason, both functions return the +same kind of result: a bundle that contains the new state of the server +(@tech{Universe}) and a list that contains a single mail. 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 'it-is-your-turn) + (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 @tech{Universe} and 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 has already received such a 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 the next @tech{Universe} + (state). It is therefore acceptable to create a mail for this world. + +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['it-is-your-turn]. 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, + but consider alternative solutions, too.) + +@; ----------------------------------------------------------------------------- +@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 + +;; World is one of +;; -- Number %% representing the @emph{y} coordinate +;; -- @scheme['resting] + +(define WORLD0 'resting) +)) + The definition says that initially a @tech{world} is passive. + +The communication protocol and the refined data definition of @tech{World} + imply a number of contract and purpose statements: + +@(begin +#reader scribble/comment-reader +(schemeblock + +;; World GoMessage -> World or (make-package World StopMessage) +;; make sure the ball is moving +(define (receive w n) ...) + +;; World -> World or (make-package World StopMessage) +;; move this ball upwards for each clock tick +;; or stay @scheme['resting] +(define (move w) ...) + +;; World -> 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{World}: + +@(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=? 'resting w) HEIGHT] + [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 +; World -> World 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 +; World -> Scene +; render the state of the world as a scene + +(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 name) + (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 scene and abstracts over + the name at the same time: + +@(begin +#reader scribble/comment-reader +(schemeblock +; String -> (World -> Scene) +; render the state of the world as a scene + +(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 -> World +; create and hook up a world with the @scheme[LOCALHOST] server +(define (create-world name) + (big-bang WORLD0 + (on-receive receive) + (on-draw (draw name)) + (on-tick move) + (register LOCALHOST name))) +)) + + 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? + diff --git a/collects/teachpack/2htdp/scribblings/universe.ss b/collects/teachpack/2htdp/scribblings/universe.ss new file mode 100644 index 0000000000..f9c397e534 --- /dev/null +++ b/collects/teachpack/2htdp/scribblings/universe.ss @@ -0,0 +1,200 @@ +#lang slideshow + +(require slideshow/pict) + +(define DELTA 80) +(define FT 12) + +(define prgm + '("(big-bang World_0" + " (on-draw render WIDTH HEIGHT)" + " (on-tick tock RATE)" + " (on-mouse click)" + " (on-key react)" + " (on-receive receive)" + " (register LOCALHOST 'jimbob))")) + + +(define program + (apply vl-append (map (lambda (t) (text t '() (- FT 2))) prgm))) + +(define Program + (cc-superimpose + (rectangle (+ 5 (pict-width program)) (+ 5 (pict-height program))) + program)) + +(define (make-state txt) + (define t (text txt '() FT)) + (define e (rounded-rectangle (+ 10 (pict-width t)) (+ DELTA (pict-height t)))) + (cc-superimpose t e)) + +(define False (text "FALSE" '() FT)) +(define True (text "TRUE" '() FT)) +(define BOOL (rectangle (+ 5 (pict-width False)) (+ 5 (pict-height False)))) + +;; String Boolean -> Pict +(define (make-state0 txt b) + ;; create the basic state + (define t (text txt '() FT)) + (define s (if b + (cc-superimpose + (rounded-rectangle (+ 5 (pict-width t)) (+ (- DELTA 5) (pict-height t))) + t) + t)) + (define w + (cc-superimpose + s + (rounded-rectangle (+ 10 (pict-width t)) (+ DELTA (pict-height t))))) + ;; add the boolean + (define bb (cc-superimpose (if b True False) BOOL)) + (define ar (add-labeled-arrow (vc-append DELTA bb w) w ct-find bb cb-find "done")) + (define scene (text "Scene" '() FT)) + (define sc (cc-superimpose scene (rectangle (+ 20 (pict-width scene)) (+ 30 (pict-height scene))))) + (define br (add-labeled-arrow (vc-append DELTA ar sc) ar cb-find sc ct-find "render")) + br) + +(define (add-labeled-arrow nx locked lb-find closed lt-find txt) + (define-values (x0 y0) (lb-find nx locked)) + (define-values (x1 y1) (lt-find nx closed)) + (define lbl (text txt '() (- FT 2))) + (define wlbl (pict-width lbl)) + (define hlbl (pict-height lbl)) + (define x (- x0 (/ wlbl 2))) + (define y (+ y0 (/ ( - y1 y0 hlbl) 2))) + (pin-over (pin-arrow-line 4.0 nx locked lb-find closed lt-find) x y lbl)) + +(define (h-labeled-arrow t) + (define tock (text t '() (- FT 2))) + (define blk (blank (+ DELTA 4) 2)) + (vc-append tock (pin-arrow-line 4.0 blk blk lc-find blk rc-find))) + +(define message (text "Message" '() FT)) +(define (make-Message) + (cc-superimpose message (rectangle (+ 20 (pict-width message)) (+ 30 (pict-height message))))) + +(define Message (vc-append (make-Message) (arrowhead 4 (* 1/2 pi)))) +(define MessageK (vc-append (arrowhead 4 (* 3/2 pi)) (make-Message))) + +(define M (rb-superimpose Message (blank DELTA DELTA))) +(define K (rb-superimpose MessageK (blank DELTA DELTA))) + +(define (make-arrows M) + (define Tock (h-labeled-arrow "tock")) + (define Click (h-labeled-arrow "click")) + (define Clack (h-labeled-arrow "react")) + (define Receive (h-labeled-arrow "receive")) + (values Tock Click Clack Receive (vc-append (blank DELTA (/ DELTA 2)) Tock Click Clack Receive M))) + +(define-values (TockM ClickM ClackM ReceiveM arrowsR) (make-arrows M)) +(define-values (TockK ClickK ClackK ReceiveK arrowsL) (make-arrows K)) + +(define state0 (make-state0 "World_0" #f)) +(define state1 (make-state0 "World_1" #f)) +(define Server (hc-append (arrowhead 4 0) (cc-superimpose (cloud 160 80) (text "SERVER" '() FT )))) +(define dots (vc-append + (cc-superimpose (blank (pict-width state1) (pict-height state1)) (text "..." '() FT)) + Server)) +(define state2 (make-state0 "World_N-1" #f)) +(define stateN (make-state0 "World_N" #t)) +(define states (list state1 arrowsL dots arrowsR state2)) + +(define bg (blank (+ (apply + (map pict-width states)) DELTA) + (+ (pict-height state0) DELTA))) + +(define (center base state x) + (define w (pict-height state)) + (define d (quotient (- width w) 2)) + (pin-over base x d state)) + +(define width (pict-height bg)) + +(define x (* 1/2 DELTA)) +(define xx + (foldl (lambda (f ls s) + (define y (center s f x)) + (set! x (+ x ls)) + y) + bg + states + (map (lambda (x) (+ (pict-width x) #;(* 1/1 DELTA))) states))) + +(define zz xx) + +(require mred/mred) + +(define the-image + (ct-superimpose Program + (lt-superimpose + (dc (lambda (dc x y) + (define-values (mx my) (cb-find zz MessageK)) + (define-values (tx ty) (ct-find zz MessageK)) + (define-values (sx sy) (lc-find zz Server)) + (define-values (tockx tocky) (lb-find zz TockK)) + (define-values (clickx clicky) (lb-find zz ClickK)) + (define-values (clackx clacky) (lb-find zz ClackK)) + (define-values (rx ry) (lb-find zz ReceiveK)) + (define (add-curve rx ry) + (set! dcp (make-object dc-path%)) + (set! cx (max rx tx)) + (set! cy (min ry ty)) + (send dcp move-to tx ty) + (send dcp curve-to tx ty cx cy rx ry) + (send dc draw-path dcp)) + (define dcp (make-object dc-path%)) + ;; --- draw arc from Message to Server + (define cx (min sx mx)) + (define cy (max sy my)) + (send dc set-smoothing 'aligned) + (send dcp move-to mx my) + (send dcp curve-to mx my cx cy sx sy) + (send dc draw-path dcp) + ;; --- draw arc from Message to Receiver + (add-curve tockx tocky) + (add-curve clickx clicky) + (add-curve clackx clacky) + (add-curve rx ry) + ;; --- + dc) + (pict-width zz) (pict-height zz)) + (lt-superimpose + zz + (dc (lambda (dc x y) + (define-values (mx my) (cb-find zz Message)) + (define-values (tx ty) (ct-find zz Message)) + (define-values (sx sy) (rc-find zz Server)) + (define-values (rx ry) (rb-find zz ReceiveM)) + (define dcp (make-object dc-path%)) + ;; --- draw arc from Message to Server + (define cx (max sx mx)) + (define cy (max sy my)) + (send dc set-smoothing 'aligned) + (send dcp move-to mx my) + (send dcp curve-to mx my cx cy sx sy) + (send dc draw-path dcp) + ;; --- draw arc from Message to Receiver + (set! dcp (make-object dc-path%)) + (set! cx (min rx tx)) + (set! cy (min ry ty)) + (send dcp move-to tx ty) + (send dcp curve-to tx ty cx cy rx ry) + (send dc draw-path dcp) + ;; --- + dc) + (pict-width zz) (pict-height zz)))))) + +(define image-bm + (make-object bitmap% + (inexact->exact (round (pict-width the-image))) + (inexact->exact (round (pict-height the-image))))) + +(send image-bm ok?) + +(define image-dc + (new bitmap-dc% [bitmap image-bm])) +(send image-dc clear) + +(draw-pict the-image image-dc 0.0 0.0) + +(send image-bm save-file "universe.png" 'png) + +the-image diff --git a/collects/teachpack/2htdp/universe.ss b/collects/teachpack/2htdp/universe.ss new file mode 100644 index 0000000000..34dd9d540e --- /dev/null +++ b/collects/teachpack/2htdp/universe.ss @@ -0,0 +1,3 @@ +(module universe mzscheme + (provide (all-from 2htdp/universe)) + (require 2htdp/universe)) diff --git a/collects/teachpack/balls.gif b/collects/teachpack/balls.gif new file mode 100644 index 0000000000000000000000000000000000000000..5f883d373dd4df337244c41930ab548056fdfd02 GIT binary patch literal 215721 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 KY?{pV`~L&xx4gap literal 0 HcmV?d00001 diff --git a/collects/teachpack/universe.png b/collects/teachpack/universe.png new file mode 100644 index 0000000000000000000000000000000000000000..81cb2ef2dbcd442e4f7a3ad083fdc2f934622372 GIT binary patch literal 28263 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 literal 0 HcmV?d00001 diff --git a/collects/teachpack/universe2.png b/collects/teachpack/universe2.png new file mode 100644 index 0000000000000000000000000000000000000000..875756366493ec9ed26d02d3c36bae1d5742bbff GIT binary patch literal 19411 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 literal 0 HcmV?d00001