;; CMPU 101 Spring 2013 ;; Lecture 19 (require 2htdp/image) (require 2htdp/universe) ; ; Example: Write a function to move a 4-segment worm around on the ; grid such that it changes direction when one of the 4 arrow keys ; is pressed. ; ; This problem will involve making the world contain both a list ; of posns for the worm segments and a direction for the worm to move. ; ; We will still use a stop-when clause that displays a final scene ; when the leading segment hits a wall. ; ; ; ; ; ; ;; ;; ; ; ; ; ;;; ;;; ; ;; ;;; ;;;; ;;; ; ;; ;;;; ;;; ; ; ; ; ; ;;; ; ; ; ;; ; ;;; ; ; ; ; ; ; ;; ; ;; ; ; ; ; ; ;; ; ; ; ; ; ; ; ;; ; ;; ; ; ; ;; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ;; ; ; ; ; ;;; ; ; ; ; ; ;; ; ; ;;; ;;; ; ;; ;;;; ;; ;; ;; ; ;; ;; ;;;; ; ; Constants (define RADIUS 10) ;\ (define DIAMETER (* RADIUS 2)) ; worm-related (define SEGMENT (circle RADIUS 'solid 'red)) ;/ (define WIDTH 800) ;\ (define HEIGHT 800) ; scene-related (define MT (empty-scene WIDTH HEIGHT)) ; (define COLOR 'black) ;/ (define CENTER (make-posn (/ WIDTH 2) (/ HEIGHT 2))) ; NEW-center of grid (define CLOCK-RATE 1/4) ; NEW-rate of clock ticks (every 1/4 sec) ;; Contract: (make-grid-hor-lines-v2) -> list of posn pairs ;; Header: (define make-grid-hor-lines-v2 (lambda () ...)) ;; Purpose: To make horizontal lines at every DIAMETER position in ;; grid (uses build-list for recursion). (define make-grid-hor-lines-v2 (lambda () (build-list ;; NOTE: using higher-order function (sub1 (/ WIDTH DIAMETER)) (lambda (c) (list (make-posn 0 (* (add1 c) DIAMETER)) (make-posn WIDTH (* (add1 c) DIAMETER))))))) ;; Contract: (make-grid-vert-lines-v2) -> list of posn pairs ;; Header: (define make-grid-vert-lines-v2 (lambda () ...)) ;; Purpose: To make vertical lines at every DIAMETER position in ;; grid (uses build-list). (define make-grid-ver-lines-v2 (lambda () (build-list ;; NOTE: using higher-order function (sub1 (/ HEIGHT DIAMETER)) (lambda (c) (list (make-posn (* (add1 c) DIAMETER) 0) (make-posn (* (add1 c) DIAMETER) HEIGHT)))))) ;; Contract: (draw-lines list-of-posn-pairs) -> image ;; Header: (define draw-lines (lambda (lopp) ...)) ;; Purpose: To draw lines between endpoints specified by each posn ;; pair in input list (define draw-lines (lambda (lopp) (cond [(empty? lopp) MT] [else (scene+line (draw-lines (rest lopp)) (posn-x (first (first lopp))) (posn-y (first (first lopp))) (posn-x (first (rest (first lopp)))) (posn-y (first (rest (first lopp)))) COLOR)]))) ;; Make the endpoints of all the lines to be in grid (define LOPS (append (make-grid-hor-lines-v2) (make-grid-ver-lines-v2))) ;; The game board: the equivalent of the empty-scene (define GRID (draw-lines LOPS)) ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ;;; ;; ; ;;; ; ; ; ;; ; ; ;;; ; ;; ; ; ; ; ;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ;;;; ;;; ; ; ; ; ;;; ;; ;; ;; ;; ; ; The state of the world is a (define-struct worm (lop dx dy)) ; where lop is a list of posns and dx and dy are numbers ; representing the change in the position as time passes. ; Contracts: ; CONSTRUCTOR FUNCTION: ; (make-worm list-of-posns number number) -> worm ; ACCESSOR FUNCTIONS: ; (worm-lop worm) -> list-of-posns ; (worm-dx worm) -> number ; (worm-dy worm) -> number ; MUTATOR FUNCTIONS: ; (set-worm-lop! worm list-of-posns) -> void ; (set-worm-dx! worm number) -> void ; (set-worm-dy! worm number) -> void ; TYPE-CHECKER FUNCTION: ; (worm? anything) -> boolean ; To start the simulation, we need to make an initial worm, so ; we need a list of posns in which each one differs from the previous ; by at most DIAMETER in either the x or the y direction. Also, try ; to make the initial worm a straight line. ; The posn list below starts at the center point of the scene and extends ; to the right to make a horizontal 4-segment worm. (define INIT-POS-LIST (list CENTER (make-posn (+ (posn-x CENTER) DIAMETER) (posn-y CENTER)) (make-posn (+ (posn-x CENTER) (* 2 DIAMETER)) (posn-y CENTER)) (make-posn (+ (posn-x CENTER) (* 3 DIAMETER)) (posn-y CENTER)))) ;; CREATE AN INITIAL WORM WITH HEAD CENTERED ON SCENE, MOVING LEFT (define INIT-WORM (make-worm INIT-POS-LIST (- 0 DIAMETER) 0)) ; printf to check the segments of INIT-WORM: (printf "INITIAL-WORM SEGMENTS: (worm-lop INIT-WORM): ") (worm-lop INIT-WORM) ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;; ; ;;;; ; ;;; ;;; ; ;; ;;;; ; ; ; ; ; ; ; ; ; ; ;; ; ;;; ; ; ; ; ;; ; ; ; ; ;; ; ; ; ;; ; ; ; ; ; ; ; ; ; ;;; ; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;;; ;; ; ; ; ;;; ; ; ; ; ; ;; ; ;; ;; ;;; ; ; ;; ;; ;; ; ;; ;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ;; ; ; Contract: (main worm) -> worm ; Header: (define main (lambda (w) ... )) ; Purpose: starts big-bang with initial world. (define main (lambda (w) (big-bang w (on-draw draw-segments) ;; Show every segment in worm (on-tick go CLOCK-RATE) ;; The clock will tick every quarter sec. (on-key worm-key)))) ;; This function will be triggered with every key press (stop-when at-edge? final-scene)))) ;; make the worm stop and show final ; scene ; ; For the on-draw clause, you created a function called ; draw-segment that consumed a positive natural number called ; w. This function placed the SEGMENT image at (w, DIAMETER) ; on top of the GRID scene created above. How should we change ; draw-segment to work with a list of posns? ANSWER: MAKE IT A ; LIST-PROCESSING FUNCTION ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ;;; ; ;; ;;; ;; ;;; ; ; ; ; ; ; ;;; ; ;; ; ;;; ;; ; ; ; ;; ; ;; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ; ; ;;; ; ; ; ; ;;; ; ; ; ;; ; ;;; ; ;; ;; ;; ;; ;; ;; ; ; ;;Contract: (draw-segments worm) -> scene (image) ;;Header: (define draw-segments (lambda (w) ...)) ;;Purpose: Render result of placing SEGMENT at positions indicated in worm ;; lop field on the background GRID. ;;Function definition: (define draw-segments (lambda (w) (local ;; Only the worm-lop is needed to draw all the segments, ;; so pass only the worm-lop into the recursive helper ;; function [(define help-draw (lambda (lsp) (cond [(empty? lsp) GRID] [else (place-image SEGMENT (posn-x (first lsp)) (posn-y (first lsp)) (help-draw (rest lsp)))])))] ;; Initial call to internal helper funciton (help-draw (worm-lop w))))) ;; printf to draw the segments of INIT-WORM: ;(printf "DRAW INITIAL WORM: (draw-segments INIT-WORM): ") ;(draw-segments INIT-WORM) ;; called to check position of worm segment in ;; initial world ; ; For the on-tick clause, you should move all the segments of the worm. ; ; If we decided to use a list-processing function here, we could keep ; track of where each previous segment was when we create the worm in ; a different position, create a new posn with those coordinates, and ; remove the last posn. ; ; Another idea is to create one new segment at the head of the list ; of segments and remove the last segment. This requires less code ; and shorter code is widely acknowledged to be better code (assuming ; it works correctly.) ; ; The following input function to on-tick, go, has two helper ; functions: move-worm and remove-last ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ;;; ; ;; ;;;; ; ;;; ; ;; ; ; ; ;;; ; ; ; ; ; ; ;; ; ;; ; ;; ; ; ; ; ; ;; ; ; ; ;; ; ;;; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;;; ;;; ; ; ; ;;; ; ;; ;; ;; ;;; ; ;; ; ;;Contract: (go worm) -> worm ;;Header: (define go (lambda (w) ...)) ;;Purpose: Move worm in direction indicated in the dx and dy fields of w. ;;Function definition: This function must create a new worm, so we ;;start with a make-worm. (define go (lambda (w) ;; The move-worm function is where we'll create the new head segment. ;; This function will consume a worm and return a worm-lop. Alternately, ;; it could consume the worm-lop, worm-dx and worm-dy fields. (make-worm (move-worm w) (worm-dx w) (worm-dy w)))) ; Contract: (move-worm worm) -> list-of-posns ; Header: (define move-worm (lambda (w) .... )) ; Purpose: Make new posn for head of worm and call remove-last to remove last (define move-worm (lambda (w) ;; make new posn for head of worm by adding the worm-dx onto the posn-x ;; of the first worm posn and adding worm-dy onto the posn-y of the ;; first worm posn. Returns a list of posns. (cons (make-posn (+ (worm-dx w) (posn-x (first (worm-lop w)))) (+ (worm-dy w) (posn-y (first (worm-lop w))))) (remove-last (worm-lop w))))) ; Contract: (remove-last lop) -> lop ; Header: (define remove-last (lambda (lp) .... )) ; Purpose: remove last posn on lp. (define remove-last (lambda (lp) (cond ;; base case 1: Should never be run because the worm will always have ;; at least 1 segment (unless we change the rules). [(empty? lp) empty] ;; base case 2: We are at last segment of worm; don't add it back ;; on to list of posns by returning empty. [(empty? (rest lp)) empty] ;; recursive case: Keep consing posns back onto the list. [else (cons (first lp) (remove-last (rest lp)))]))) ; post function printf: (printf "(move-worm INIT-WORM) ") (move-worm INIT-WORM) ; post function printf: (printf "(remove-last INIT-POS-LIST) ") (remove-last INIT-POS-LIST) ; ; ; ; ; ; ; ; ; ; ; ;;; ; ;; ; ;; ;;; ; ; ; ; ; ;;; ; ; ;; ; ; ; ; ; ;; ; ;; ; ;; ; ;; ; ; ; ; ; ;; ; ;;; ;; ; ; ;; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;; ; ;;; ; ;; ; ;; ;;; ;; ;; ; ; ; ; ; ; ; ; ; ; ;;; ; ; Contract: (worm-key worm string) -> worm ; Header: (define worm-key (lambda w k) ...)) ; Purpose: Change the direction of movement based on the key presses ; of the 4 direction keys: up, down, left, right. (define worm-key (lambda (w k) (cond ; if up arrow pressed, change dx to 0 and dy to -DIAMETER [(key=? k "up") (make-worm (worm-lop w) 0 (* -1 DIAMETER))] ; else if down arrow pressed, change dx to 0 and dy to DIAMETER [(key=? k "down") (make-worm (worm-lop w) 0 DIAMETER)] ; else if left arrow pressed, change dx to -DIAMETER and dy to 0 [(key=? k "left") (make-worm (worm-lop w) (* -1 DIAMETER) 0)] ; else if right arrow pressed, change dx to DIAMETER and dy to 0 [(key=? k "right") (make-worm (worm-lop w) DIAMETER 0)] ; else if any other key pressed, return worm unchanged. [else w]))) ; ; ; ;;; ; ;; ; ;; ;; ;;; ; ; ;;; ;;; ; ; ; ;;; ; ;;; ; ; ; ; ; ; ; ; ; ; ; ;; ; ;; ; ;; ;; ; ;; ; ; ; ; ; ;; ; ; ; ;; ; ;;; ;; ;; ; ; ; ; ; ;; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ; ; ; ;; ; ; ;; ; ; ; ; ; ;;; ; ;; ; ; ;; ;;; ;;; ;;;;;; ;;; ; ; ; ; Write a function that adds a segment to the worm whenever a mouse ; button is pressed. ; ; Contract: (add-segment worm number number string) -> worm ; Header: (define add-segment (lambda (w x y mevent) ... )) ; Purpose: Make the worm grow by one segment whever a mouse button ; is pressed. ; ; ; ; ;; ; ; ; ; ; ;;; ;;;; ;;; ; ;;; ; ; ; ; ;; ;;; ; ;; ; ; ; ; ; ;; ; ; ; ;;; ;; ; ; ; ;;; ; ; ; ; ;; ; ;; ; ; ; ;;; ; ; ;; ;; ; ; ;; ; ; ; ; ; ;;; ; ; ; ;; ; ; ;; ;; ; ; ; ; ; ; ; ; ; ;; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ;; ; ; ; ; ; ;;; ;; ;;; ; ;; ; ; ; ;; ;;; ; ;; ; ; ; ; ; ; ; Contract: (at-edge? worm) -> boolean ; Header: (define at-edge? (lambda (w) ... )) ;(define at-edge? ; (lambda (w) ;; return true if the x,y coordinate of first worm segment is at left, right, top ;; or bottom of grid ; Contract: (final-scene worm) -> scene (image) ; Header: (define final-scene (lambda (w) ... )) ; Purpose: Write a text image on the middle of the scene ;(define final-scene ; (lambda (w) ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;; ;;; ; ; ; ;; ;; ;;; ; ; ;; ; ; ; ;; ; ; ; ;;; ; ; ; ;; ; ; ;;; ; ; ; ; ; ; ; ; ;; ;; ; ; ; ; ;; ; ; ; ; ; ; ; ;; ;; ; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ;;; ; ;;;;;; ; ; ; ;; ;;; ; ;;;; ; ; ; ;;; ;; ;; ;; ;; ; ; ;; ;; ;; ;; ; ;; ; ;; Call the main function, passing in the initial state of the world. ;(printf "(main INIT-WORM)~%") (main INIT-WORM)