racket/collects/htdp/elevator.ss
Matthias Felleisen fb644c1caf relevant teachpacks converted
svn: r9470
2008-04-25 00:50:03 +00:00

817 lines
24 KiB
Scheme

#lang scheme/gui
(require htdp/big-draw
htdp/error
lang/prim
lang/posn
mzlib/etc)
;; Implementation:
;; Stephanie Weirich (1994),
;; Mark Krentel (1995),
;; Matthias Felleisen (1996)
(provide run)
(define-higher-order-primitive run run/proc (elevator-controller))
;; There are really three distinct levels: graphics, hardware,
;; and software. Don't mix them!
;;
;; HARDWARE/GRAPHICS level for Elevator.
;; This level provides the basic primitives for writing an elevator.
;; The hardware level are the guards for the graphics level.
;;
;; Hardware functions:
;;
;; make-elevator max-floor
;; current-floor move-up-floor move-down-floor
;; update-input wait-for-input
;; open? open-doors close-doors
;; up-call? down-call? demand?
;; clear-up-call clear-down-call clear-demand
;; busy-wait info
;;
;; Compute the layout and draw the window.
(define init-graphics
(lambda ()
(init-layout-and-window)
(init-shaft)
(init-calls)
(init-info)
(init-demands)
(init-stop)
(init-car)
(busy-wait 800)
(draw-open-doors)))
;;
;; Overall layout for the window.
;; Currently need max-floor >= 4.
;;
(define BORDER 100)
(define SHAFT-INFO-GAP 40)
(define STOP-DEMAND-GAP 50)
(define init-layout-and-window
(lambda ()
(let*
([shaft (make-posn BORDER BORDER)]
[call (add-hv shaft SHAFT-WIDTH 0)]
[top (add-hv call CALL-WIDTH 0)]
[bot (add-hv top 0 (* (max-floor) FLOOR-SIZE))]
[info (add-hv top SHAFT-INFO-GAP 0)]
[stop (add-hv bot SHAFT-INFO-GAP (- STOP-HEIGHT))]
[demand (add-hv stop 0 (- 0 STOP-DEMAND-GAP DEMAND-HEIGHT))]
[horiz (+ (posn-x info) STOP-WIDTH BORDER)]
[vert (+ (posn-y bot) BORDER)])
(set! SHAFT-ORIGIN shaft)
(set! CALL-ORIGIN call)
(set! INFO-ORIGIN info)
(set! STOP-ORIGIN stop)
(set! DEMAND-ORIGIN demand)
(init-window horiz vert))))
;;
;; Window primitives for lines, strings, mouse clicks.
;; These should be the only functions that use elev-win.
;;
(define elev-win #f)
(define d-line #f)
(define c-line #f)
(define d-string #f)
(define c-string #f)
(define mouse-click? (lambda () (ready-mouse-click elev-win)))
(define wait-for-click (lambda () (get-mouse-click elev-win)))
(define init-window
(lambda (horiz vert)
(set! elev-win (open-viewport "Elevator Simulation" horiz vert))
(set! d-line (draw-line elev-win))
(set! c-line (clear-line elev-win))
(set! d-string (draw-string elev-win))
(set! c-string (clear-string elev-win))))
(define d-string-bf
(lambda (posn string)
(d-string posn string)
(d-string (add-hv posn 1 0) string)))
(define c-string-bf
(lambda (posn string)
(c-string posn string)
(c-string (add-hv posn 1 0) string)))
;;
;; Helper functions for position, offsets, etc.
;; SIXLib should provide better primitives here.
;;
(define under? (lambda (p1 p2) (> (posn-y p1) (posn-y p2))))
(define add-posn
(lambda (p1 p2)
(make-posn (+ (posn-x p1) (posn-x p2))
(+ (posn-y p1) (posn-y p2)))))
(define add-hv
(lambda (p horiz vert)
(make-posn (+ (posn-x p) horiz)
(+ (posn-y p) vert))))
;;
;; Low-level primitives for lines, rectangles, etc.
;;
(define paint-rect
(lambda (origin horiz vert)
(when (>= vert 0)
(let ([right (add-hv origin horiz 0)]
[next (add-hv origin 0 +1)])
(d-line origin right)
(paint-rect next horiz (- vert 1))))))
(define outline-rect
(lambda (origin horiz vert thick)
(let* ([x0 (posn-x origin)] [y0 (posn-y origin)])
(recur loop ([lf x0] [rt (+ x0 horiz thick -1)]
[top y0] [bot (+ y0 vert thick -1)]
[n thick])
(when (> n 0)
(d-line (make-posn lf top) (make-posn rt top))
(d-line (make-posn rt top) (make-posn rt bot))
(d-line (make-posn rt bot) (make-posn lf bot))
(d-line (make-posn lf bot) (make-posn lf top))
(loop (+ lf 1) (- rt 1) (+ top 1) (- bot 1) (- n 1)))))))
(define clear-inside-rect
(lambda (origin horiz vert thick)
(recur loop ([lf (add-hv origin thick thick)]
[rt (add-hv origin (- horiz 1) thick)]
[n thick])
(when (< n vert)
(c-line lf rt)
(loop (add-hv lf 0 +1) (add-hv rt 0 +1) (+ n 1))))))
;;
;; Elevator Shaft and Car
;;
(define SHAFT-ORIGIN #f)
(define STOP-ORIGIN #f)
(define SHAFT-WIDTH 64)
(define FLOOR-SIZE 64)
(define CAR-WIDTH 44)
(define CAR-HEIGHT 44)
(define MIN-DOOR-SEP 3)
(define NUMBER-POSN (make-posn -22 (floor (/ FLOOR-SIZE 2))))
(define init-shaft
(lambda ()
(let ([height (* (max-floor) FLOOR-SIZE)])
(outline-rect SHAFT-ORIGIN SHAFT-WIDTH height 2)
(recur loop ([p (add-posn SHAFT-ORIGIN NUMBER-POSN)]
[n (max-floor)])
(when (>= n 1)
(d-string-bf p (number->string n))
(loop (add-hv p 0 FLOOR-SIZE) (- n 1)))))))
(define init-car
(lambda ()
(let* ([origin (car-posn 1)])
(outline-rect origin CAR-WIDTH CAR-HEIGHT 1)
(outline-rect origin (floor (/ CAR-WIDTH 2)) CAR-HEIGHT 1))))
(define car-posn
(lambda (n)
(add-hv SHAFT-ORIGIN
(floor (/ (- SHAFT-WIDTH CAR-WIDTH) 2))
(+ (floor (/ (- FLOOR-SIZE CAR-HEIGHT) 2))
(* (- (max-floor) n) FLOOR-SIZE)))))
(define move-car-door
(lambda (horiz delta)
(let* ([origin (car-posn (current-floor))]
[old-top (add-hv origin horiz +1)]
[old-bot (add-hv origin horiz (- CAR-HEIGHT 1))]
[new-top (add-hv old-top delta 0)]
[new-bot (add-hv old-bot delta 0)])
(d-line new-top new-bot)
(c-line old-top old-bot))))
(define draw-open-doors
(lambda ()
(recur loop ([lf (floor (/ CAR-WIDTH 2))]
[rt (ceiling (/ CAR-WIDTH 2))])
(when (< MIN-DOOR-SEP lf)
(move-car-door lf -1)
(move-car-door rt +1)
(busy-wait)
(loop (- lf 1) (+ rt 1))))
(draw-little-man)))
(define draw-close-doors
(lambda ()
(recur loop ([lf MIN-DOOR-SEP]
[rt (- CAR-WIDTH MIN-DOOR-SEP)])
(when (>= (- rt lf) 2)
(move-car-door lf +1)
(move-car-door rt -1)
(busy-wait)
(loop (+ lf 1) (- rt 1))))))
(define move-fwd-edge
(lambda (origin delta)
(let* ([new-lf (add-hv origin 0 delta)]
[new-rt (add-hv origin CAR-WIDTH delta)]
[old-1-lf (add-hv origin +1 0)]
[old-1-rt (add-hv origin (- (floor (/ CAR-WIDTH 2)) 1) 0)]
[old-2-lf (add-hv origin (+ (ceiling (/ CAR-WIDTH 2)) 1) 0)]
[old-2-rt (add-hv origin (- CAR-WIDTH 1) 0)])
(d-line new-lf new-rt)
(c-line old-1-lf old-1-rt)
(c-line old-2-lf old-2-rt))))
(define move-back-edge
(lambda (origin delta)
(let* ([new-lf (add-hv origin 0 delta)]
[new-rt (add-hv origin CAR-WIDTH delta)]
[old-lf origin]
[old-rt (add-hv origin CAR-WIDTH 0)])
(d-line new-lf new-rt)
(c-line old-lf old-rt))))
(define draw-up-floor
(lambda ()
(let ([goal (car-posn (+ (current-floor) 1))])
(recur loop ([cur (car-posn (current-floor))])
(when (under? cur goal)
(move-fwd-edge cur -1)
(move-back-edge (add-hv cur 0 CAR-HEIGHT) -1)
(busy-wait)
(loop (add-hv cur 0 -1)))))))
(define draw-down-floor
(lambda ()
(let ([goal (car-posn (- (current-floor) 1))])
(recur loop ([cur (car-posn (current-floor))])
(when (under? goal cur)
(move-fwd-edge (add-hv cur 0 CAR-HEIGHT) +1)
(move-back-edge cur +1)
(busy-wait)
(loop (add-hv cur 0 +1)))))))
;; This is probably going too far ...
;; But he's more than just a list of lines!
(define MAN-POSN (make-posn 18 14))
(define LITTLE-MAN
(list (make-posn 5 0) (make-posn 9 0) ; head
(make-posn 9 0) (make-posn 9 4)
(make-posn 9 4) (make-posn 5 4)
(make-posn 5 4) (make-posn 5 0)
(make-posn 7 4) (make-posn 7 12) ; body
(make-posn 7 12) (make-posn 2 23) ; legs
(make-posn 7 12) (make-posn 12 23)
(make-posn 0 23) (make-posn 2 23) ; feet
(make-posn 12 23) (make-posn 14 23)
(make-posn 1 8) (make-posn 13 8) ; arms
(make-posn 0 9) (make-posn 1 8) ; hands
(make-posn 13 8) (make-posn 14 7)))
(define draw-little-man
(lambda ()
(let ([origin (add-posn (car-posn (current-floor)) MAN-POSN)])
(recur loop ([l LITTLE-MAN])
(unless (null? l)
(d-line (add-posn origin (car l))
(add-posn origin (cadr l)))
(loop (cddr l)))))))
;;
;; Call Buttons
;;
(define CALL-ORIGIN #f)
(define CALL-WIDTH 50)
(define CALL-HEIGHT (floor (/ FLOOR-SIZE 2)))
(define UP-CALL-SHAPE
(list (make-posn 13 28) (make-posn 25 4) (make-posn 37 28)))
(define DOWN-CALL-SHAPE
(list (make-posn 13 4) (make-posn 25 28) (make-posn 37 4)))
(define UP-CALL-POSN
(lambda (floor)
(add-hv CALL-ORIGIN 0 (* FLOOR-SIZE (- (max-floor) floor)))))
(define DOWN-CALL-POSN
(lambda (floor)
(add-hv (UP-CALL-POSN floor) 0 CALL-HEIGHT)))
(define init-calls
(lambda ()
(recur loop ([n 1])
(when (<= n (max-floor))
(outline-rect (UP-CALL-POSN n) CALL-WIDTH FLOOR-SIZE 2)
(outline-call (UP-CALL-POSN n) UP-CALL-SHAPE)
(outline-call (DOWN-CALL-POSN n) DOWN-CALL-SHAPE)
(loop (+ n 1))))))
(define draw-clear-up
(lambda (floor)
(clear-call (UP-CALL-POSN floor) UP-CALL-SHAPE)
(outline-call (UP-CALL-POSN floor) UP-CALL-SHAPE)))
(define draw-clear-down
(lambda (floor)
(clear-call (DOWN-CALL-POSN floor) DOWN-CALL-SHAPE)
(outline-call (DOWN-CALL-POSN floor) DOWN-CALL-SHAPE)))
(define paint-up-call
(lambda (floor)
(paint-call (UP-CALL-POSN floor) UP-CALL-SHAPE)))
(define paint-down-call
(lambda (floor)
(paint-call (DOWN-CALL-POSN floor) DOWN-CALL-SHAPE)))
(define outline-call
(lambda (origin l)
(let* ([p (add-posn origin (car l))]
[q (add-posn origin (cadr l))]
[r (add-posn origin (caddr l))])
(d-line p q)
(d-line q r)
(d-line r p))))
(define clear-call
(lambda (origin l)
(let* ([p (add-posn origin (car l))]
[q (add-posn origin (cadr l))]
[r (add-posn origin (caddr l))]
[top (min (posn-y p) (posn-y q))]
[bot (max (posn-y p) (posn-y q))])
(recur loop ([y (+ top 1)])
(when (< y bot)
(let ([lf (ceiling (x-val p q y))]
[rt (floor (x-val q r y))])
(c-line (make-posn lf y) (make-posn rt y))
(loop (+ y 1))))))))
(define paint-call
(lambda (origin l)
(let* ([p (add-posn origin (car l))]
[q (add-posn origin (cadr l))]
[r (add-posn origin (caddr l))]
[top (min (posn-y p) (posn-y q))]
[bot (max (posn-y p) (posn-y q))])
(recur loop ([y (+ top 1)])
(when (< y bot)
(let ([lf (ceiling (x-val p q y))]
[rt (floor (x-val q r y))])
(d-line (make-posn lf y) (make-posn rt y))
(loop (+ y 1))))))))
(define x-val
(lambda (a b y)
(let ([ax (posn-x a)] [ay (posn-y a)]
[bx (posn-x b)] [by (posn-y b)])
(+ ax (* (/ (- y ay) (- by ay))
(- bx ax))))))
;;
;; Demand Buttons
;;
(define DEMAND-ORIGIN #f)
(define DEMAND-WIDTH 40)
(define DEMAND-HEIGHT 40)
(define DEMAND-TEXT (make-posn 18 25))
(define DEMAND-POSN
(let ([HORIZ (+ DEMAND-WIDTH 20)]
[VERT (+ DEMAND-HEIGHT 20)])
(lambda (i)
(let ([x (remainder (sub1 i) 2)]
[y (quotient (sub1 i) 2)])
(add-hv DEMAND-ORIGIN (* x HORIZ) (* -1 y VERT))))))
(define init-demands
(lambda ()
(recur loop ([n 1])
(when (<= n (max-floor))
(outline-rect (DEMAND-POSN n) DEMAND-WIDTH DEMAND-HEIGHT 2)
(d-string-bf (add-posn (DEMAND-POSN n) DEMAND-TEXT) (number->string n))
(loop (+ n 1))))))
(define paint-demand
(lambda (k)
(let ([nw (DEMAND-POSN k)])
(paint-rect nw DEMAND-WIDTH DEMAND-HEIGHT)
(c-string-bf (add-posn nw DEMAND-TEXT) (number->string k)))))
(define draw-clear-demand
(lambda (k)
(clear-inside-rect (DEMAND-POSN k) DEMAND-WIDTH DEMAND-HEIGHT 2)
(d-string-bf (add-posn (DEMAND-POSN k) DEMAND-TEXT) (number->string k))))
;;
;; "Stop Program" Button
;;
(define STOP-WIDTH 100)
(define STOP-HEIGHT 40)
(define STOP-TEXT (make-posn 15 25))
(define init-stop
(lambda ()
(outline-rect STOP-ORIGIN STOP-WIDTH STOP-HEIGHT 2)
(d-string (add-posn STOP-ORIGIN STOP-TEXT) "Stop Program")))
;;
;; Mouse Clicks
;; Look for up/down calls, demands and stop-program button.
;; If you can get access to a real-time clock, then change the
;; delay loop to use sleep-for or real-time.
;; The units are (fake) milliseconds.
;; SCALE is the multiplier for waiting time.
;; SCALE > 1 slows down the simulation.
;;
(define DEFAULT-WAIT 25)
(define SCALE 0.75)
(define busy-wait
(lambda l
(let* ([cur-time (current-milliseconds)]
[wait-time (if (null? l) DEFAULT-WAIT (car l))]
[new-time (+ cur-time (* SCALE wait-time))])
(recur loop ()
(check-buttons)
(yield)
(when (< (current-milliseconds) new-time)
(loop))))))
(define check-buttons
(lambda ()
(let ([click (mouse-click?)])
(when click
(process-click click)
(check-buttons)))))
(define wait-for-button
(lambda ()
(let ([click (wait-for-click)])
(process-click click)
(check-buttons))))
(define process-click
(lambda (click)
(recur loop ([n 1])
(cond
[(> n (max-floor)) (void)]
[(click-here? click (UP-CALL-POSN n) CALL-WIDTH CALL-HEIGHT)
(push-up-call n)]
[(click-here? click (DOWN-CALL-POSN n) CALL-WIDTH CALL-HEIGHT)
(push-down-call n)]
[(click-here? click (DEMAND-POSN n) DEMAND-WIDTH DEMAND-HEIGHT)
(push-demand n)]
[(click-here? click STOP-ORIGIN STOP-WIDTH STOP-HEIGHT)
(push-stop)]
[else (loop (+ n 1))]))))
(define click-here?
(lambda (click origin horiz vert)
(let* ([x0 (posn-x origin)]
[y0 (posn-y origin)]
[x (posn-x (mouse-click-posn click))]
[y (posn-y (mouse-click-posn click))])
(and (<= x0 x (+ x0 horiz))
(<= y0 y (+ y0 vert))))))
;;
;; Info
;; Just floor, goal, dir.
;;
(define INFO-ORIGIN #f)
(define-struct einfo (sym posn label prev) #:mutable)
(define INFO-LIST
(list (make-einfo 'floor (make-posn 0 12) "floor = " #f)
(make-einfo 'goal (make-posn 0 28) "goal = " #f)
(make-einfo 'dir (make-posn 0 44) "dir = " #f)))
(define init-info
(lambda ()
(let loop ([l INFO-LIST])
(unless (null? l)
(set-einfo-prev! (car l) "")
(loop (cdr l))))
(info 'floor 1)
(info 'goal 1)
(info 'dir 'none)))
(define my-lookup
(lambda (sym)
(let loop ([l INFO-LIST])
(cond
[(null? l) (error 'info "Unknown info type: ~e" sym)]
[(eq? sym (einfo-sym (car l))) (car l)]
[else (loop (cdr l))]))))
(define info
(lambda (sym obj)
(let* ([item (my-lookup sym)]
[posn (add-posn INFO-ORIGIN (einfo-posn item))]
[str (if (string? obj) obj (format "~s" obj))]
[full-str (string-append (einfo-label item) str)])
(unless (string=? full-str (einfo-prev item))
(c-string posn (einfo-prev item))
(d-string posn full-str)
(set-einfo-prev! item full-str)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Functions to show to the outside world. ;;
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; This is really the (virtual) elevator hardware level.
;; This includes state and the basic elevator operations.
;; You write an elevator from these primitives.
;;
;; This could be a separate module, but you'd just write all of
;; these functions twice. Also, they make nice guards for the
;; graphics functions.
;; Elevator State
;;
;; the-floor = integer 1..THE-MAX-FLOOR
;; the-doors = 'open, 'closed
;; up-call-vec, down-call-vec, demand-vec = vectors 1..THE-MAX-FLOOR
;; for buttons, #t = pushed, #f = not pushed
(define max-floor #f)
(define the-floor #f)
(define the-doors #f)
(define up-call-vec #f)
(define down-call-vec #f)
(define demand-vec #f)
(define exit-continuation #f)
;; Initialize the hardware state and draw the picture.
;; f = THE-MAX-FLOOR, k = exit continuation
(define make-elevator
(lambda (f k)
(let ([n (add1 f)])
(set! max-floor (lambda () f))
(set! exit-continuation k)
(set! the-floor 1)
(set! the-doors 'open)
(set! up-call-vec (build-vector n (lambda (i) #f)))
(set! down-call-vec (build-vector n (lambda (i) #f)))
(set! demand-vec (build-vector n (lambda (i) #f)))
(init-graphics))))
(define push-stop
(lambda () (exit-continuation 'game-over)))
;; Functions that use the-floor.
;; Only (move-up-floor) and (move-down-floor) are allowed to use
;; the-floor and THE-MAX-FLOOR directly.
(define current-floor (lambda () the-floor))
(define move-up-floor
(lambda ()
(if (= the-floor (max-floor))
(error 'move-up-floor "Elevator already at MAX-FLOOR")
(begin (info 'dir 'up)
;; (info 'floor (format "~s~s" the-floor '+))
(when (open?) (close-doors))
(draw-up-floor)
(set! the-floor (add1 the-floor))
(info 'floor the-floor)))))
(define move-down-floor
(lambda ()
(if (= the-floor 1)
(error 'move-down-floor "Elevator already at ground floor")
(begin (info 'dir 'down)
;; (info 'floor (format "~s~s" the-floor '-))
(when (open?) (close-doors))
(draw-down-floor)
(set! the-floor (sub1 the-floor))
(info 'floor the-floor)))))
;; Functions that use the-doors.
;; Again, only (open-doors) and (close-doors) are allowed to use
;; the-doors directly.
(define open? (lambda () (eq? the-doors 'open)))
(define open-doors
(lambda ()
(unless (open?)
(draw-open-doors)
(set! the-doors 'open))))
(define close-doors
(lambda ()
(when (open?)
(draw-close-doors)
(set! the-doors 'closed))))
;; Functions that use buttons: up/down-calls, demands.
;; Again, these are the only functions that are allowed to use
;; up/down-calls and demands directly.
(define up-call? (lambda (floor) (vector-ref up-call-vec floor)))
(define down-call? (lambda (floor) (vector-ref down-call-vec floor)))
(define demand? (lambda (floor) (vector-ref demand-vec floor)))
(define update-input check-buttons)
(define wait-for-input wait-for-button)
(define clear-up-call
(lambda (floor)
(when (up-call? floor)
(draw-clear-up floor)
(vector-set! up-call-vec floor #f))))
(define clear-down-call
(lambda (floor)
(when (down-call? floor)
(draw-clear-down floor)
(vector-set! down-call-vec floor #f))))
(define clear-demand
(lambda (floor)
(when (demand? floor)
(draw-clear-demand floor)
(vector-set! demand-vec floor #f))))
;; The push functions are not visible outside, but they need to
;; be here because graphics calls them.
(define push-up-call
(lambda (floor)
(when (not (up-call? floor))
(paint-up-call floor)
(vector-set! up-call-vec floor #t))))
(define push-down-call
(lambda (floor)
(when (not (down-call? floor))
(paint-down-call floor)
(vector-set! down-call-vec floor #t))))
(define push-demand
(lambda (floor)
(when (not (demand? floor))
(paint-demand floor)
(vector-set! demand-vec floor #t))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; SOFTWARE level for elevator. ;;
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Here, you design your own elevator, using the primitives from
;; the graphics-module and the user's Control function.
(define THE-MAX-FLOOR 8)
(define start-program
(lambda ()
(dynamic-wind
(lambda ()
(unless (graphics-open?) (open-graphics)))
(lambda ()
(call/cc
(lambda (k)
(make-elevator THE-MAX-FLOOR k)
(elevator 'open #f 'up))))
close-graphics)
(lambda () #t)))
;;
;; Main Loop.
;;
;; This version gives the user complete control.
;; All we do is ask the user for a goal, move one floor closer,
;; check to see if we're at the goal (stop and open doors if so),
;; and ask for another goal.
;; Any kind of fairness or ignoring the down-calls when moving up
;; is totally up to Control.
;;
;; floor, goal = integer 1..THE-MAX-FLOOR
;; state = 'arrive, 'open, 'wait, 'move
;; 'arrive = just arrived at this floor, check if it's the goal
;; 'open = open doors, wait, ignore goal, get new one
;; 'wait = no requests, wait until there is one
;; 'move = start moving toward the goal
;; dir = 'up, 'down
;;
;; We call new-goal at each floor.
;;
(define OPEN-DOOR-WAIT-TIME 1500)
(define elevator
(lambda (state goal dir)
(let ([floor (current-floor)])
(cond
[(eq? state 'arrive)
(if (= floor goal)
(elevator 'open goal dir)
(elevator 'move (new-goal dir) dir))]
[(eq? state 'open)
(begin
(open-doors)
(clear-all-buttons floor)
(busy-wait OPEN-DOOR-WAIT-TIME)
(update-input)
(clear-all-buttons floor)
(elevator 'wait (new-goal dir) dir))]
[(eq? state 'wait)
(if (= floor goal)
(begin
(update-input)
(clear-all-buttons floor)
(wait-for-request)
(elevator 'wait (new-goal dir) dir))
(elevator 'move goal dir))]
[(= goal floor)
(elevator 'open goal dir)]
[(< goal floor)
(begin
(move-down-floor)
(elevator 'arrive goal 'down))]
[(> goal floor)
(begin
(move-up-floor)
(elevator 'arrive goal 'up))]
[else (error 'elevator "Internal error in main loop")]))))
;; Don't get stuck on the same floor forever.
(define clear-all-buttons
(lambda (floor)
(clear-up-call floor)
(clear-down-call floor)
(clear-demand floor)))
;; Don't return until at least one button is pushed.
(define wait-for-request
(lambda ()
(recur loop ([n 1])
(cond
[(> n (max-floor)) (wait-for-input)]
[(or (up-call? n) (down-call? n) (demand? n)) #t]
[else (loop (add1 n))]))))
;; Call the user's function Control and check that the floor is valid.
(define list-of-floors
(lambda ()
(recur loop ([f (max-floor)] [l null])
(cond
[(= f 0) l]
[(or (up-call? f) (down-call? f) (demand? f))
(loop (sub1 f) (cons f l))]
[else (loop (sub1 f) l)]))))
(define new-goal
(lambda (dir)
(update-input)
(let
([ans (Next-Floor dir (current-floor) (list-of-floors))])
(if (and (integer? ans) (exact? ans) (<= 1 ans (max-floor)))
(begin (info 'goal ans) ans)
(error 'Next-Floor "~e is not a valid floor number" ans)))))
;; Functions to show the user.
;; Remember, the elevator calls Next-Floor, not the other way.
(define Next-Floor (lambda x (error 'Next-Floor "undefined")))
(define run/proc
(lambda (f)
(check-proc 'run f 3 'first "3 arguments")
(set! Next-Floor f)
(start-program)))