5b: Helpers / Practice Problems 1

問題文

Design a simple interactive animation of rain falling down a screen. Wherever we click,
a rain drop should be created and as time goes by it should fall. Over time the drops
will reach the bottom of the screen and "fall off". You should filter these excess
drops out of the world state - otherwise your program is continuing to tick and
and draw them long after they are invisible.

In your design pay particular attention to the helper rules. In our solution we use
these rules to split out helpers:
  - function composition
  - reference
  - knowledge domain shift


NOTE: This is a fairly long problem.  While you should be getting more comfortable with
world problems there is still a fair amount of work to do here. Our solution has 9
functions including main. If you find it is taking you too long then jump ahead to the
next homework problem and finish this later.

問題文のリンクはこちら。

解答

;; Make it rain where we want it to.

;; =================
;; Constants:

(define WIDTH  300)
(define HEIGHT 300)

(define SPEED 1)

(define DROP (ellipse 4 8 "solid" "blue"))

(define MTS (rectangle WIDTH HEIGHT "solid" "light blue"))

;; =================
;; Data definitions:

(define-struct drop (x y))
;; Drop is (make-drop Integer Integer)
;; interp. A raindrop on the screen, with x and y coordinates.

(define D1 (make-drop 10 30))

#;
(define (fn-for-drop d)
  (... (drop-x d)
       (drop-y d)))

;; Template Rules used:
;; - compound: 2 fields


;; ListOfDrop is one of:
;;  - empty
;;  - (cons Drop ListOfDrop)
;; interp. a list of drops

(define LOD1 empty)
(define LOD2 (cons (make-drop 10 20) (cons (make-drop 3 6) empty)))

#;
(define (fn-for-lod lod)
  (cond [(empty? lod) (...)]
        [else
         (... (fn-for-drop (first lod))
              (fn-for-lod (rest lod)))]))

;; Template Rules used:
;; - one-of: 2 cases
;; - atomic distinct: empty
;; - compound: (cons Drop ListOfDrop)
;; - reference: (first lod) is Drop
;; - self reference: (rest lod) is ListOfDrop

;; =================
;; Functions:

;; ListOfDrop -> ListOfDrop
;; start rain program by evaluating (main empty)
(define (main lod)
  (big-bang lod
            (on-mouse handle-mouse)   ; ListOfDrop Integer Integer MouseEvent -> ListOfDrop
            (on-tick  tick-drops)     ; ListOfDrop -> ListOfDrop
            (to-draw  render-drops))) ; ListOfDrop -> Image


;; ListOfDrop Integer Integer MouseEvent -> ListOfDrop
;; if mevt is "button-down" add a new drop at that position
;; !!!
;; (define (handle-mouse lod x y mevt) empty) ; stub

(check-expect (handle-mouse empty 0 0 "drag") empty)
(check-expect (handle-mouse (cons (make-drop 0 0) empty)
                            0 0 "drag") (cons (make-drop 0 0) empty))
(check-expect (handle-mouse empty 0 0 "button-down")
              (cons (make-drop 0 0) empty))
(define (handle-mouse lod x y mevt)
  (cond [(mouse=? mevt "button-down") (cons (make-drop x y) lod)]
        [else lod]))

;; ListOfDrop -> ListOfDrop
;; produce filtered and ticked list of drops
;; !!!
;;(define (tick-drops lod) empty)
(define (tick-drops lod)
  (cond [(empty? lod) empty]
        [else (cons (tick-drop (first lod)) (rest lod))]))

(check-expect (tick-drops empty) empty)
(check-expect (tick-drops (cons (make-drop 0 0) empty))
              (cons (make-drop 0 (+ 0 SPEED)) empty))

;; Drop -> Drop
;; produce ticked drop which increased position of y
;; (define (tick-drop d) d) ; stub
(check-expect (tick-drop (make-drop 0 1)) (make-drop 0 (+ 1 SPEED)))
(define (tick-drop d)
  (make-drop (drop-x d) (+ (drop-y d) SPEED)))


;; ListOfDrop -> Image
;; Render the drops onto MTS
;; !!!
;; (define (render-drops lod) MTS) ; stub

(check-expect (render-drops empty) MTS)
(check-expect (render-drops (cons (make-drop 0 3) (cons (make-drop 0 10) empty)))
                            (place-image DROP 0 3 (place-image DROP 0 10 MTS)))
;; Drop -> Image
;; Render the drop onto MTS
;; (define (place-drop d img) img) ;stub
(define (place-drop d img)
  (place-image DROP (drop-x d) (drop-y d) img))
(check-expect (place-drop (make-drop 0 0) MTS)
              (place-image DROP 0 0 MTS))

(define (render-drops lod)
  (cond [(empty? lod) MTS]
        [else (place-drop (first lod) (render-drops (rest lod)))]))

;; ListOfDrop -> ListOfDrop
;; produce filtered list of drops
;; (define onscreen-all empty) ;stub
;;(define (onscreen-all lod) empty)
(check-expect (onscreen-all empty) empty)

;; Drop -> Boolean
;; produce true if drop is on the screen otherwise produce false
(define (onscreen? d)
  (< 0 (drop-y d) HEIGHT))
;; (define (onscreen? d) false) ; stub
(check-expect (onscreen? (make-drop 0 0)) false)
(check-expect (onscreen? (make-drop 0 1)) true)
(check-expect (onscreen? (make-drop 0 (+ HEIGHT 1))) false)

(define (onscreen-all lod)
  (cond [(empty? lod) empty]
        [(onscreen? (first lod)) (cons (first lod) (onscreen-all (rest lod)))]
        [else  (onscreen-all (rest lod))]))

(check-expect (cons (make-drop 0 1) empty)
              (onscreen-all (cons (make-drop 0 (+ HEIGHT 1)) (cons (make-drop 0 1) empty))))

配列のフィルタは再帰で実現できる。