;; CMPU101, Spring 2013 ;; Lecture #17 (require 2htdp/image) (require 2htdp/universe) #| Writing functions that consume a list of posns: Recall the posn struct introduced before exam I. The fields of a posn are numbers, the x and y coordinate of a point on the plane. There are 3 functions you should remember for working with a posn: The posn CONSTRUCTOR: make-posn The posn ACCESSOR functions: posn-x and posn-y First, we'll write a data definition for a list of posns: A list of posns (LOPS) is either: 1. empty, or 2. (cons p lops) when p is a posn and lops is an LOPS |# ;; In order to test functions that consume a list of posns, we need a ;; list of posns: (define POSLST (list (make-posn 110 20) (make-posn 50 200) (make-posn 140 75) (make-posn 160 340) (make-posn 100 365) (make-posn 105 300) (make-posn 10 20) (make-posn 20 40) (make-posn 40 80) (make-posn 60 120) (make-posn 105 205) (make-posn 110 210) (make-posn 115 215))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Ex 1: Write a function that consumes a list of posns and places a ;; ellipse of random width, height, and color on a blank ;; scene such that one ellipse is centered at each posn in ;; the list. Call the function ellipses. ;; ;; Before writing this function, you need to make decisions about the ;; allowable range of random numbers for width and height. The random ;; numbers to create colors are between 0 and 255 ;; ;; Constants: (define WD 200) (define HT 400) (define EMTY (empty-scene WD HT)) (define MODE 'outline) (define RGB 256) ;; Contract: (ellipses list-of-posns) -> image ;; Header: (define ellipses (lambda (lip) ...)) ;; Purpose: Draw ellipses of random color at random positions on a scene ;; Pre-function tests: By visual inspection because return is an image ;; and contents of the image are randomized. ;; Function definition: (define ellipses (lambda (lip) (cond [(empty? lip) EMTY] [else (place-image (ellipse (random WD) (random HT) MODE (make-color (random RGB) (random RGB) (random RGB))) (posn-x (first lip)) (posn-y (first lip)) (ellipses (rest lip)))]))) ;; Post-function displays and tests: (display "\n(ellipses POSLST)\n") ;(ellipses POSLST) ; ; The BIG-BANG function: ; ; Big-bang can take any data type as an input argument. The initial ; argument to big-bang should be the starting state of the world. ; ; More complex animations require the world state to be a struct ; that contains more than just a number. In order to display many ; components that change independently, we need to create a struct ; that has fields for each independent value that must change. ; ; The clauses of big-bang change the state and create new worlds. ; Clauses to big-bang are functions that can't be used anywhere ; outside big-bang: ; ; on-draw: Draws and redraws the world whenever it changes. ; The function argument to on-draw must have the ; contract: ; (draw-func w) -> scene, where w is the world type. ; ; on-tick: Executes changes to the world at regular intervals. ; The function argument to on-tick must have the ; contract: ; (tick-func w) -> w, where w is the type of the world ; and the result is a new world. ; ; The on-tick clause takes a second, numerical argu- ; ment, which is the clock rate (usually used to make ; the clock tick slower). ; ; stop-when: Consumes a predicate function that stops the ; simulation when the predicate returns true. ; The function argument to on-tick must have the ; contract: ; (last-world? w) -> boolean, where w is the ; type of the world. ; ; If stop-when has a second argument, it is a ; function that draws the final scene. ; (final-scene w) -> scene (image) ; ; on-key: Changes the world when particular keys are pressed. ; The function argument to on-key must have the ; contract: ; (key-func w string) -> w, where w is the type of ; the world and string is a string representation ; of a key on the keyboard. ; ; on-mouse: Changes the world when mouse events occur. ; The function argument to on-mouse must have the ; contract: ; (mouse-func w number number event) -> w, ; where w is the type of the world, the numbers are ; the x,y coordinates of the mouse event, and even ; is a string representing a particular mouse event: ; "button-down", "button-up", "move", "drag", "enter", ; "leave". ; ; ; Writing an animated world: ; ; Step 1 - Identify what needs to change during the animation. The ; initial world state should be an instance of the type ; of the world state that changes. ; ; Clearly specify type of world state before starting to ; code. ; ; Step 2 - Define constants for every part of the world state that ; will not change. These should be written together at ; the top of the program or separated according to the ; function in which they occur. ; ; Step 3 - Decide what event handlers you will need. The on-draw ; handler is needed to give your scene content, so plan ; on writing a function to draw the scene in every ; animation. ; ; If the scene will change over time, you will need the ; tick handler, on-tick. ; ; If the animation should respond to mouse or keyboard ; events, include a mouse and key handler (on-mouse, ; on-key) ; ; Step 4 - For each event handler used by your big-bang expression, ; you need to write a function that consumes an input of ; the world state type. ; ;; Big-bang example: animating a cat (cats rule!!) Suppose we want to take ;; an image of a cat and create an animation in which the cat moves from left ;; to right across a 500 X 150 pixel scene, changing direction when it touches ;; the right or left side of the scene. ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ;;; ;; ; ;;; ; ; ; ;; ; ; ;;; ; ;; ; ; ; ; ;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ;;;; ;;; ; ; ; ; ;;; ;; ;; ;; ;; ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; World state will be a (make-catdir x dx), where x and dx are numbers (define-struct catdir (x dx)) ; Contracts of functions created by define-struct above: ; (make-catdir number number) -> catdir ; (catdir-x catdir) -> number ; (catdir-dx catdir) -> number ; (set-catdir-x! catdir number) -> void; x field of given catdir changed ; (set-catdir-dx! catdir number) -> void; dx field of given catdir changed ; (catdir? anything) -> boolean ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;; ; ;;;; ; ;;; ;;; ; ;; ;;;; ; ; ; ; ; ; ; ; ; ; ;; ; ;;; ; ; ; ; ;; ; ; ; ; ;; ; ; ; ;; ; ; ; ; ; ; ; ; ; ;;; ; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;;; ;; ; ; ; ;;; ; ; ; ; ; ;; ; ;; ;; ;;; ; ; ;; ;; ;; ; ;; ;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ;; ; ; FUNCTION TO CALL BIG-BANG WITH INITIAL STATE OF WORLD: ; Contract: (cat-main catdir) -> catdir ; Header: (define cat-main (lambda (w) ...)) ; Purpose: To call the big-bang function, passing in the initial state of ; the world (define cat-main (lambda (w) (big-bang w (on-draw create-cat-scene) (on-tick move-cat-mut)))) ; could also call move-cat-new here ; ; ; ; ;; ;; ; ; ; ; ;;; ;;; ; ;; ;;; ;;;; ;;; ; ;; ;;;; ;;; ; ; ; ; ; ;;; ; ; ; ;; ; ;;; ; ; ; ; ; ; ;; ; ;; ; ; ; ; ; ;; ; ; ; ; ; ; ; ;; ; ;; ; ; ; ;; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ;; ; ; ; ; ;;; ; ; ; ; ; ;; ; ; ;;; ;;; ; ;; ;;;; ;; ;; ;; ; ;; ;; ;;;; ; (define CAT .) (define CAT-WIDTH/2 (/ (image-width CAT) 2)) ;; to detect collision with edge (define CS-WIDTH 500) (define CS-HEIGHT 150) (define HALF-HT (/ CS-HEIGHT 2)) (define CSMT (empty-scene CS-WIDTH CS-HEIGHT)) (define DX 3) ;; change in x coordinate on each clock tick (define INIT-WORLD (make-catdir (* -1 CAT-WIDTH/2) DX)) ;; initial world state ;; The initial catdir is CAT-WIDTH/2 to the left of the scene and moving to ;; right at 3 pixels per clock tick. ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ;;; ; ;; ;;; ;; ;;; ; ; ; ; ; ; ;;; ; ;; ; ;;; ;; ; ; ; ;; ; ;; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ; ; ; ;;; ; ; ; ; ;;; ; ; ; ;; ; ;;; ; ;; ;; ;; ;; ;; ;; ; ; ; FUNCTION CONSUMED BY ON-DRAW: ; Contract: (create-cat-scene catdir) -> image (scene) ; Header: (define create-cat-scene (lambda (w) ... ; Purpose: draw image CAT on an empty scene (define create-cat-scene (lambda (w) (place-image CAT (catdir-x w) HALF-HT CSMT))) ; Post-function printf: (printf "(create-cat-scene (make-catdir (image-width CAT) 1))~%") (create-cat-scene (make-catdir (image-width CAT) 1)) ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ;;; ; ;; ;;;; ; ;;; ; ;; ; ; ; ;;; ; ; ; ; ; ; ;; ; ;; ; ;; ; ; ; ; ; ;; ; ; ; ;; ; ;;; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;;;; ;;; ; ; ; ;;; ; ;; ;; ;; ;;; ; ;; ; ; ; There are two ways to change the state of the world when writing ; a function like that consumed by on-tick (when the state is not a ; primitive Racket type): ; ; Either: ; ; 1. make a new world by calling the constructor for the state of ; the world. In this case, the constructor is make-posdir and ; the function creates a new posdir struct from the fields of ; the old one. ; ; 2. change the fields of the world to hold new values using the ; set...! functions produced when the struct was defined using ; define-struct. ; ; An example of the first technique is shown in the move-cat-new ; function below. ; ; An example of the second technique is shown in the move-cat-mut ; function. ; ; FUNCTION CONSUMED BY ON-TICK: ; Contract: (move-cat-new catdir) -> catdir ; Header: (define move-cat-new (lambda (w) ...)) ; Purpose: Create a new world when the clock ticks (define move-cat-new (lambda (w) (cond ;; First check if image is touching right edge and moving to right or ;; image is touching left edge and moving to left. If either is true, ;; make a new world with negated dx field. [(or (and (>= (catdir-x w) (- CS-WIDTH CAT-WIDTH/2)) (> (catdir-dx w) 0)) (and (<= (catdir-x w) CAT-WIDTH/2) (< (catdir-dx w) 0))) (make-catdir (catdir-x w) (* -1 (catdir-dx w)))] ;; Otherwise, make a new world with the catdir dx added to x and ;; the catdir dx unchanged. [else (make-catdir (+ (catdir-x w) (catdir-dx w)) (catdir-dx w))]))) ; ALTERNATE VERSION OF FUNCTION CONSUMED BY ON-TICK: ; Contract: (move-cat-mut catdir) -> catdir ; Header: (define move-cat-mut (lambda (w) ...)) ; Purpose: Change the state of the world when the clock ticks (define move-cat-mut (lambda (w) (cond ;; First check if image is touching right edge and moving to right or ;; image is touching left edge and moving to left. If either is true, ;; mutate dx field of the world by negating it. [(or (and (>= (catdir-x w) (- CS-WIDTH CAT-WIDTH/2)) (> (catdir-dx w) 0)) (and (<= (catdir-x w) CAT-WIDTH/2) (< (catdir-dx w) 0))) (begin (set-catdir-dx! w (* -1 (catdir-dx w))) ; negate the dx w)] ; return the mutated world ;; Otherwise, mutate the x field of the world [else (begin (set-catdir-x! w (+ (catdir-x w) (catdir-dx w))) w)] ; return the mutated world ))) ; ; ; ; ; ; ; ; ; ; ; ;; ; ; ; ; ; ;;; ;;; ; ; ;;; ;;; ;;;; ; ;; ;; ;;; ; ; ;; ; ; ; ;; ; ; ; ; ; ;; ; ; ;;; ; ; ; ;; ; ; ;;; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ;; ; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ;;; ;; ;; ; ; ; ; ;; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;; ; ;;; ; ;;;;;; ; ;; ; ;;; ; ; ; ; ; ; ;; ;;; ; ;;;; ; ; ; ;;; ;; ;; ;; ;; ;;; ;; ;; ;; ; ; ;; ;; ;; ;; ; ;; ; (printf "(cat-main INIT-WORLD)~%") (cat-main INIT-WORLD)