#lang scheme/base
(require scheme/class
scheme/local
scheme/bool
mred
(prefix-in error: htdp/error)
htdp/image
(only-in htdp/world nw:rectangle place-image empty-scene scene+line)
mrlib/cache-image-snip
lang/prim
(for-syntax scheme/base)
"../version-case.ss"
"../compiler/effect-struct.ss"
"world-effects.ss")
(version-case
[(version<= (version) "4.2.1")
(require (only-in lang/htdp-beginner image?))]
[else
(void)])
(require mrlib/gif)
(require mzlib/runtime-path)
(require mrlib/bitmap-label
string-constants)
(provide (all-from-out htdp/image))
(provide
nw:rectangle place-image empty-scene scene+line )
(provide big-bang )
(provide
key-event? key=? )
(provide-higher-order-primitive
run-simulation (_ _ _ create-scene) )
(provide
run-movie )
(define big-bang
(lambda x
(define args (length x))
(if (>= args 3)
(apply big-bang0 x)
(error 'big-bang msg))))
(define msg
(string-append
"big-bang consumes at least 3 or 4 arguments:\n"
"-- (big-bang <width> <height> <world0> handlers ...)\n"
"-- (big-bang <width> <height> <world0> <animated-gif> handlers ...)\n"
"see Help Desk."))
(define *running?* #f)
(define (big-bang0 w h world . args)
(let ([animated-gif #f])
(when (and (not (null? args))
(boolean? (car args)))
(set! animated-gif (car args))
(set! args (cdr args)))
(check-pos 'big-bang w "first")
(check-pos 'big-bang h "second")
(error:check-arg 'big-bang
(boolean? animated-gif)
"boolean expected"
"fourth"
animated-gif)
(for-each (lambda (x)
(error:check-arg 'big-bang
(procedure? x)
"configuration option (on-tick, stop-when, ...) expected"
"nth"
x))
args)
(let ([w (coerce w)]
[h (coerce h)])
(when *running?* (error 'big-bang "the world is still running"))
(set! *running?* #t)
(callback-stop!)
(install-world world) (set-and-show-frame w h animated-gif) (unless animated-gif (set! add-event void)) (for-each (lambda (x) (x)) args)
(yield last-world-channel))))
(define (coerce x) (inexact->exact (floor x)))
(define *the-delta* 0.0)
(define (key-event? k)
(or (char? k) (symbol? k)))
(define (key=? k m)
(error:check-arg 'key=? (key-event? k) 'KeyEvent "first" k)
(error:check-arg 'key=? (key-event? m) 'KeyEvent "first" m)
(eqv? k m))
(define (run-movie movie)
(error:check-arg 'run-movie (list? movie) "list (of images)" "first" movie)
(for-each (lambda (cand)
(check-image 'run-movie cand "first" "list of images"))
movie)
(let* ([fst (car movie)]
[wdt (image-width fst)]
[hgt (image-height fst)])
(big-bang wdt hgt (/ 1 27) movie)
(let run-movie ([movie movie])
(cond
[(null? movie) #t]
[(pair? movie)
(update-frame (car movie))
(sleep/yield .05)
(run-movie (cdr movie))]))))
(define run-simulation
(lambda x
(define args (length x))
(if (or (= args 5) (= args 4))
(apply run-simulation0 x)
(error 'run-simulation msg-run-simulation))))
(define msg-run-simulation
(string-append
"consumes 4 or 5 arguments:\n"
"-- (run-simulation <width> <height> <rate> <world-to-world-function>)\n"
"-- (run-simulation <width> <height> <rate> <world-to-world-function> <create-animated-gif?>)\n"
"see Help Desk."))
(define run-simulation0
(case-lambda
[(width height rate f record?)
(error:check-pos 'run-simulation width "first")
(error:check-pos 'run-simulation height "second")
(error:check-arg 'run-simulation (number? rate) 'number "third" rate)
(error:check-proc 'run-simulation f 1 "fourth" "one argument")
(error:check-arg 'run-simulation (boolean? record?) 'number "fifth [and optional]" record?)
(big-bang width height rate 1 record?)
(on-redraw f)
(on-tick add1)]
[(width height rate f)
(run-simulation width height rate f #f)]))
(define (check-pos tag c rank)
(error:check-arg tag (and (number? c) (> (coerce c) 0))
"positive integer" rank c))
(define (check-image tag i rank . other-message)
(if (and (pair? other-message) (string? (car other-message)))
(error:check-arg tag (image? i) (car other-message) rank i)
(error:check-arg tag (image? i) "image" rank i)))
(define (check-scene tag i rank)
(if (image? i)
(unless (scene? i)
(error tag "scene expected, given image whose pinhole is at (~s,~s) instead of (0,0)"
(pinhole-x i) (pinhole-y i)))
(error:check-arg tag #f "image" rank i)))
(define (scene? i) (and (= 0 (pinhole-x i)) (= 0 (pinhole-y i))))
(define (check-color tag width rank)
(error:check-arg tag (or (symbol? width) (string? width))
"color symbol or string" rank width))
(define (check-mode tag s rank)
(error:check-arg tag (or (eq? s 'solid)
(eq? s 'outline)
(string=? "solid" s)
(string=? "outline" s)) "mode (solid or outline)" rank s))
(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 sh)))
(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))))
(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 w) (<= 0 x1 w) (<= 0 y0 w) (<= 0 y1 w))
(shrink (add-line img x0 y0 x1 y1 c) 0 0 (- w 1) (- h 1))]
[(= x0 x1) (if (<= 0 x0 w) (add-line img x0 (app y0 h) x0 (app y1 h) c) img)]
[(= y0 y1) (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)) (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 w 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)) (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])]))]))
(define (app y h)
(cond
[(and (<= 0 y) (< y h)) y]
[(< y 0) 0]
[else (- h 1)]))
(define (direction x0 y0 x1 y1)
(string->symbol
(string-append
(if (<= y0 y1) "lower" "upper") "-" (if (<= x0 x1) "right" "left"))))
(define-struct lyne (slope y0))
(define (points->line x0 y0 x1 y1)
(local ((define slope (/ (- y1 y0) (- x1 x0))))
(make-lyne slope (- y0 (* slope x0)))))
(define (of ln x) (+ (* (lyne-slope ln) x) (lyne-y0 ln)))
(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)))
(define (opt z lft) (if (<= 0 z lft) z false))
(define (X ln h) (/ (- h (lyne-y0 ln)) (lyne-slope ln)))
(define unique-world (cons 1 1))
(define (check-world tag)
(when (eq? unique-world the-world)
(error tag "evaluate (big-bang Number Number Number World) first")))
(define last-world-channel (make-channel))
(define the-world unique-world)
(define the-world0 unique-world)
(define (install-world w)
(reset-event-history)
(set! the-world w)
(set! the-world0 w)
(vw-setup))
(define visible-world #f)
(define (vw-setup)
(set! visible-world (new pasteboard%))
(send visible-world set-cursor (make-object cursor% 'arrow)))
(define (vw-init?) (is-a? visible-world pasteboard%))
(define (update-frame pict)
(send visible-world begin-edit-sequence)
(send visible-world lock #f)
(let ([s (send visible-world find-first-snip)])
(when s
(send visible-world delete s)))
(let ([c (send visible-world get-canvas)])
(let-values ([(px py)
(if (is-a? pict cache-image-snip%)
(send pict get-pinhole)
(values 0 0))]
[(cw ch)
(send c get-client-size)])
(send visible-world insert (send pict copy) (- px) (- py))))
(send visible-world lock #t)
(send visible-world end-edit-sequence))
(define (set-and-show-frame w h animated-gif)
(define the-play-back-custodian (make-custodian))
(define frame (create-frame the-play-back-custodian))
(set! WIDTH w)
(set! HEIGHT h)
(when animated-gif
(add-stop-and-image-buttons frame the-play-back-custodian))
(add-editor-canvas frame visible-world w h)
(send frame show #t))
(define WIDTH 0)
(define HEIGHT 0)
(define (create-frame the-play-back-custodian)
(new (class frame%
(super-new)
(define/augment (on-close)
(callback-stop!)
(thread (lambda () (channel-put last-world-channel the-world)))
(custodian-shutdown-all the-play-back-custodian)))
(label "DrScheme")
(stretchable-width #f)
(stretchable-height #f)
(style '(no-resize-border metal))))
(define IMAGES "Images")
(define-runtime-path s:pth '(lib "icons/break.png"))
(define-runtime-path i:pth '(lib "icons/file.gif"))
(define (add-stop-and-image-buttons frame the-play-back-custodian)
(define p (new horizontal-pane% [parent frame][alignment '(center center)]))
(define S ((bitmap-label-maker (string-constant break-button-label) s:pth) '_))
(define I ((bitmap-label-maker IMAGES i:pth) '_))
(define stop-button
(new button% [parent p] [label S] [style '(border)]
[callback (lambda (this-button e)
(callback-stop!)
(send this-button enable #f)
(send image-button enable #t))]))
(define image-button
(new button% [parent p] [enabled #f] [label I] [style '(border)]
[callback (lambda (b e)
(parameterize ([current-custodian the-play-back-custodian])
(define th (thread play-back))
(send b enable #f)))]))
(void))
(define (add-editor-canvas frame visible-world w h)
(define c
(new (class editor-canvas%
(super-new)
(define/override (on-char e)
(key-effect-callback (send e get-key-code))
(key-callback (send e get-key-code)))
(define/override (on-event e)
(mouse-effect-callback e)
(mouse-callback e)))
(parent frame)
(editor visible-world)
(style '(no-hscroll no-vscroll))
(horizontal-inset INSET)
(vertical-inset INSET)))
(send c min-client-width (+ w INSET INSET))
(send c min-client-height (+ h INSET INSET))
(send c focus))
(define INSET 5)
(define TICK 'tick)
(define MOUSE 'mouse)
(define KEY 'key)
(define event-history '())
(define (reset-event-history)
(set! event-history '()))
(define (add-event type . stuff)
(set! event-history (cons (cons type stuff) event-history)))
(define (zfill a-num a-len)
(let ([n (number->string a-num)])
(string-append (build-string (max (- a-len (string-length n)) 0)
(lambda (i) #\0))
n)))
(define (play-back)
(define (world-transition world fst)
(case (car fst)
[(tick) (timer-callback0 world)]
[(key) (key-callback0 world (cadr fst))]
[(mouse) (mouse-callback0 world (cadr fst) (caddr fst) (cadddr fst))]
[else (error 'play-back "bad type of event: ~s" fst)]))
(define total (+ (length event-history) 1))
(define image-count 0)
(define bitmap-list '())
(define (save-image img)
(define-values (w h) (send img get-size))
(define (make-bitmap)
(define bm (make-object bitmap% w h))
(define dc (make-object bitmap-dc% bm))
(send dc clear)
(send img draw dc 0 0 0 0 w h 0 0 #f)
bm)
(define bm (make-bitmap))
(set! bitmap-list (cons bm bitmap-list))
(set! image-count (+ image-count 1))
(send bm save-file (format "i~a.png" (zfill image-count (string-length (number->string total)))) 'png))
(define target:dir
(let* ([cd (current-directory)]
[dd (get-directory "Select directory for images" #f cd)])
(if dd dd cd)))
(parameterize ([current-directory target:dir])
(let replay ([ev (reverse event-history)][world the-world0])
(define img (redraw-callback0 world))
(update-frame (text (format "~a/~a created" image-count total) 18 'red))
(save-image img)
(cond
[(null? ev)
(update-frame (text (format "creating ~a" ANIMATED-GIF-FILE) 18 'red))
(create-animated-gif (reverse bitmap-list))
(update-frame img)]
[else
(let ([world1 (world-transition world (car ev))])
(replay (cdr ev) world1))]))))
(define (create-animated-gif bitmap-list)
(define intv (if (> +inf.0 *the-delta* 0) (inexact->exact (floor (* 100 *the-delta*))) 5))
(when (file-exists? ANIMATED-GIF-FILE)
(delete-file ANIMATED-GIF-FILE))
(write-animated-gif bitmap-list intv ANIMATED-GIF-FILE #:one-at-a-time? #t #:loop? #f))
(define ANIMATED-GIF-FILE "i-animated.gif")
(define-syntax (define-callback stx)
(syntax-case stx ()
[(_ n msg (f esp ...) para body ...)
(let* ([n:str (symbol->string (syntax-e (syntax n)))]
[callback (lambda (before after)
(string->symbol
(string-append before n:str "-callback" after)))]
[name (datum->syntax stx (callback "" ""))]
[name0 (datum->syntax stx (callback "" "0"))]
[set-name (datum->syntax stx (callback "set-" ""))])
#`(define-values (#,name #,name0 #,set-name)
(values
void void
(lambda (f esp ...)
(when (callback-set? #,name)
(error (format "the ~a has already been specified") msg))
(set! #,name0 f)
(set! #,name (lambda para body ...))))))]))
(define (callback-stop!)
(send the-time stop)
(set! timer-callback void)
(set! mouse-callback void)
(set! key-callback void)
(set! stop-when-callback (lambda () #f))
(set! redraw-callback void)
(set! *running?* #f))
(define (callback-set? cb) (not (eq? cb void)))
(define the-time (new timer% [notify-callback (lambda ()
(timer-effect-callback)
(timer-callback))]))
(define-callback timer "tick-event hander" (f) ()
(with-handlers ([exn:break? break-handler][exn? exn-handler])
(set! the-world (f the-world))
(add-event TICK)
(redraw-callback)))
(define-callback timer-effect "tick-effect event hander" (f) ()
(with-handlers ([exn:break? break-handler][exn? exn-handler])
(let ([an-effect (f the-world)])
(effect-apply! an-effect))))
(define-callback redraw "redraw function" (f) ()
(with-handlers ([exn:break? break-handler][exn? exn-handler])
(define result (f the-world))
(define fname (object-name f))
(define tname (if fname fname 'your-redraw-function))
(if (image? result)
(error:check-result tname scene? "scene" result
(format "image with pinhole at (~s,~s)"
(pinhole-x result) (pinhole-y result)))
(error:check-result tname (lambda (x) (image? x)) "scene" result))
(update-frame result)
(when (stop-when-callback)
(callback-stop!)
(thread (lambda () (channel-put last-world-channel the-world))))))
(define-callback stop-when "is end of world check" (f) ()
(define result (f the-world))
(define fname (object-name f))
(define tname (if fname fname 'your-redraw-function))
(error:check-result fname boolean? "boolean" result)
result)
(define-callback key "key-event handler" (f evt-space) (e)
(parameterize ([current-eventspace evt-space])
(queue-callback
(lambda ()
(with-handlers ([exn:break? break-handler][exn? exn-handler])
(let ([new-world (f the-world e)])
(unless (equal? new-world the-world)
(set! the-world new-world)
(add-event KEY e)
(redraw-callback))))))))
(define-callback key-effect "key-effect event handler" (f evt-space) (e)
(parameterize ([current-eventspace evt-space])
(queue-callback
(lambda ()
(with-handlers ([exn:break? break-handler][exn? exn-handler])
(let ([new-effect (f the-world e)])
(effect-apply! new-effect)))))))
(define-callback mouse "mouse event handler" (f evt-space) (e)
(parameterize ([current-eventspace evt-space])
(queue-callback
(lambda ()
(define x (- (send e get-x) INSET))
(define y (- (send e get-y) INSET))
(define m (mouse-event->symbol e))
(when (and (<= 0 x WIDTH) (<= 0 y HEIGHT))
(with-handlers ([exn:break? break-handler][exn? exn-handler])
(let ([new-world (f the-world x y m)])
(unless (equal? new-world the-world)
(set! the-world new-world)
(add-event MOUSE x y m)
(redraw-callback)))))))))
(define-callback mouse-effect "mouse event handler" (f evt-space) (e)
(parameterize ([current-eventspace evt-space])
(queue-callback
(lambda ()
(define x (- (send e get-x) INSET))
(define y (- (send e get-y) INSET))
(define m (mouse-event->symbol e))
(when (and (<= 0 x WIDTH) (<= 0 y HEIGHT))
(with-handlers ([exn:break? break-handler][exn? exn-handler])
(let ([new-effect (f the-world x y m)])
(effect-apply! new-effect))))))))
(define (mouse-event->symbol e)
(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 (error 'on-mouse
(format
"Unknown event type: ~a"
(send e get-event-type)))]))
(define (exn-handler e)
(callback-stop!)
(raise e))
(define (break-handler . _)
(printf "animation stopped")
(callback-stop!)
the-world)
(define (number->integer x)
(inexact->exact (floor x)))
(define (on-tick the-delta f)
(on-tick* the-delta f (lambda (w) (make-effect:none))))
(define (on-tick* the-delta f-world f-effect)
(error:check-arg 'on-tick
(and (number? the-delta) (<= 0 the-delta 1000))
"number [of seconds] between 0 and 1000"
"first"
the-delta)
(error:check-proc 'on-tick f-world 1 "on-tick" "one argument")
(lambda ()
(set! *the-delta* the-delta)
(set-timer-callback f-world)
(set-timer-effect-callback f-effect)
(send the-time start
(let* ([w (ceiling (* 1000 the-delta))])
(if (exact? w) w (inexact->exact w))))
#t))
(define (on-redraw f)
(error:check-proc 'on-redraw f 1 "on-redraw" "one argument")
(lambda ()
(set-redraw-callback f)
(redraw-callback)
#t))
(define (on-key f)
(on-key* f (lambda (w k)
(make-effect:none))))
(define (on-key* f f-effect)
(error:check-proc 'on-key f 2 "on-key" "two arguments")
(lambda ()
(set-key-callback f (current-eventspace))
(set-key-effect-callback f-effect (current-eventspace))
#t))
(define (on-mouse f)
(on-mouse* f (lambda (w x y b)
(make-effect:none))))
(define (on-mouse* f f-effect)
(error:check-proc 'on-mouse f 4 "on-mouse" "four arguments")
(lambda ()
(set-mouse-callback f (current-eventspace))
(set-mouse-effect-callback f-effect (current-eventspace))
#t))
(define (stop-when f)
(error:check-proc 'stop-when f 1 "stop-when" "one argument")
(lambda ()
(set-stop-when-callback f)
#t))
(define (on-tilt handler)
(lambda ()
#t))
(define (on-tilt* handler effect-handler)
(lambda ()
#t))
(define (on-acceleration handler)
(lambda ()
#t))
(define (on-acceleration* handler effect-handler)
(lambda ()
#t))
(define (on-shake handler)
(lambda ()
#t))
(define (on-shake* handler effect-handler)
(lambda ()
#t))
(define (on-location-change f)
(on-location-change* f (lambda (w x y) (make-effect:none))))
(define (on-location-change* f effect-f)
(error:check-proc 'on-location-change f 3 "on-location-change"
"three arguments")
(error:check-proc 'on-location-change effect-f 3 "on-location-change"
"three arguments")
(lambda ()
(set-location-callback f (current-eventspace))
(set-location-effect-callback effect-f (current-eventspace))
(show-location-gui)
#t))
(define-callback location "location-change handler" (f evt-space)
(lat long)
(parameterize ([current-eventspace evt-space])
(queue-callback
(lambda ()
(with-handlers ([exn:break? break-handler][exn? exn-handler])
(let ([new-world (f the-world lat long)])
(unless (equal? new-world the-world)
(set! the-world new-world)
(redraw-callback))))))))
(define-callback location-effect "location-change handler" (f evt-space)
(lat long)
(parameterize ([current-eventspace evt-space])
(queue-callback
(lambda ()
(with-handlers ([exn:break? break-handler][exn? exn-handler])
(let ([new-effect (f the-world lat long)])
(effect-apply! new-effect)))))))
(define (show-location-gui)
(define a-frame (new frame%
[label "Location Stub"]))
(define t-x (new text-field%
[parent a-frame]
[label "lat"]
[init-value "0"]))
(define t-y (new text-field%
[parent a-frame]
[label "long"]
[init-value "0"]))
(define b (new button%
[parent a-frame]
[label "Set!"]
[callback (lambda (b e)
(let ([x (string->number
(send t-x get-value))]
[y (string->number
(send t-y get-value))])
(location-effect-callback x y)
(location-callback x y)))]))
(send a-frame show #t))