;; The first three lines of this file were inserted by DrScheme. They record metadata ;; about the language level of this file in a form that our tools can easily process. #reader(lib "htdp-intermediate-reader.ss" "lang")((modname lec20100225) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f ()))) ;; CS2500 - 2/25/10 #| DESIGN DOCUMENT: BOUNCING BALL BOX Jesse Tov High level description: Red and blue balls, with initially random position and velocity, bounce inside a box. When a red ball collides with a blue ball, both balls disappear. The user may add new balls, with random position and velocity, by pressing b for blue and r for red. If more than two balls collide at once, all are removed. Parameters: - The width and height of the box - The radius of a ball - Range from which initial velocities are chosen - Number of initial balls Data: At each point in time, there are a number of balls, each with a position and velocity. This is all the information needed in our world. Two possible representation of the ball world: 1) ;; A Velocity is (make-vel Number Number) (define-struct vel (dx dy)) ;; A Ball is (make-ball Posn Velocity) (define-struct ball (posn vel)) ;; A LoB is one of: ;; -- empty ;; -- (cons Ball LoB) ;; A BallWorld is (make-ball-world LoB LoB) ;; interp. one field contains red balls and the other blue (define-struct ball-world (reds blues)) 2) ;; A Velocity is (make-vel Number Number) (define-struct vel (dx dy)) ;; A Ball is (make-ball Posn Velocity Color) (define-struct ball (posn vel color)) ;; A LoB is one of: ;; -- empty ;; -- (cons Ball LoB) ;; A BallWorld is LoB Advantages of 1: - Possibly easier to find collisions. Advantages of 2: - Easier to extend with (many) more ball colors later. I intend to try 1, but if it gives me trouble, I might try 2. Tasks: - Generate random balls - Update ball positions over time - update x and y by dx and dy - Detect collisions between balls and walls - for ball collisions, detect if the distance between the centers is less than the sum of the radii - for wall collisions, detect if the distance from the ball center to the wall is less than the radius; what about corners? - Figure out which way a ball should bounce - ? - Respond to user input Possible factoring: render-ball-world : BallWorld -> Scene To render the ball world handle-keyboard : BallWorld KeyEvent -> BallWorld To add random balls to the world at user request handle-tick : BallWorld -> BallWorld To update to the next frame of the animation update-positions : BallWorld -> BallWorld To move each ball by its velocity remove-collisions : BallWorld -> BallWorld To remove balls that have collided from the ball world remove-collisions-many : LoB LoB -> LoB To remove all the balls in the first list that collide with a ball in the second list remove-collisions-one : LoB Ball -> LoB To remove all balls from the list that collide with the given ball collides? : Ball Ball -> Boolean Do the two balls collide? perform-reflections : BallWorld -> BallWorld Compute bounces for balls that hit the boundary reflect-ball : Ball -> Ball Compute the bounce for one ball, if necessary Questions: - What helper functions will reflect-ball require? - In what order should we move balls and check for collisions? - What should happen when all the balls run out? Anticipated difficulties: - I really don't know how to do reflection yet. Plan: 1. Rendering first, because it might make it easier to understand the rest incrementally. 2. Write some of the low level helpers, such as collide?, and test them. (If I turn out not to need them as I anticipate, this could be a waste of time.) 3. Figure out reflection. 4. Put together the high level tick handler. 5. Handle user interaction. |# (require htdp/image) (require 2htdp/universe) ;; A Velocity is (make-vel Number Number) (define-struct vel (dx dy)) ;; A Ball is (make-ball Posn Velocity) (define-struct ball (posn vel)) ;; A LoB is one of: ;; -- empty ;; -- (cons Ball LoB) ;; A BallWorld is (make-ball-world LoB LoB) ;; interp. one field contains red balls and the other blue (define-struct ball-world (reds blues)) (define WIDTH 500) (define HEIGHT 400) (define RADIUS 10) (define MIN-VEL 0) (define MAX-VEL 10) (define NBALLS0 5) (define MT (empty-scene WIDTH HEIGHT)) (define RED-BALL (circle RADIUS "solid" "red")) (define BLUE-BALL (circle RADIUS "solid" "blue")) (define WORLD-EX (make-ball-world (list (make-ball (make-posn 10 20) (make-vel 3 -2)) (make-ball (make-posn 100 10) (make-vel -10 10))) (list (make-ball (make-posn 20 40) (make-vel 10 0)) (make-ball (make-posn 100 300) (make-vel -10 10))))) ;; render-ball-world : BallWorld -> Scene ;; To draw the ball world (define (render-ball-world bw) (render-balls (ball-world-reds bw) RED-BALL (render-balls (ball-world-blues bw) BLUE-BALL MT))) ;; render-balls : LoB Image Scene -> Scene ;; To add the specified image at the specified locations. (define (render-balls lob ball scene) (cond [(empty? lob) scene] [else (place-ball (first lob) ball (render-balls (rest lob) ball scene))])) ;; place-ball : Ball Image Scene -> Scene ;; To place the given ball (using the image) in the scene. (define (place-ball ball ball-img scene) (place-image/posn ball-img (ball-posn ball) scene)) ;; place-image/posn : Image Posn Scene -> Scene ;; To place the image at the given position (define (place-image/posn img posn scene) (place-image img (posn-x posn) (posn-y posn) scene)) ;; collide? : Ball Ball -> Boolean ;; Are the balls touching. (define (collide? b1 b2) (< (distance (ball-posn b1) (ball-posn b2)) (* 2 RADIUS))) ;; distance : Posn Posn -> Number ;; The distance between two positions. (define (distance p1 p2) (sqrt (+ (sqr (- (posn-x p1) (posn-x p2))) (sqr (- (posn-y p1) (posn-y p2)))))) (check-expect (distance (make-posn 0 0) (make-posn 3 4)) 5) (check-expect (distance (make-posn -1 -1) (make-posn 2 3)) 5) ;; remove-collisions/one : Ball LoB -> LoB ;; To remove all the balls that collide with the given ball (define (remove-collisions/one ball lob) (cond [(empty? lob) empty] [else (local [(define result-rest (remove-collisions/one ball (rest lob)))] (if (collide? ball (first lob)) result-rest (cons (first lob) result-rest)))])) ;; remove-collisions/many : LoB LoB -> LoB ;; To remove balls from the second list that collide with the first (define (remove-collisions/many which lob) (cond [(empty? which) lob] [else (remove-collisions/one (first which) (remove-collisions/many (rest which) lob))])) ;; remove-collisions : BallWorld -> BallWorld ;; To remove any colliding balls from the world (define (remove-collisions bw) (make-ball-world (remove-collisions/many (ball-world-blues bw) (ball-world-reds bw)) (remove-collisions/many (ball-world-reds bw) (ball-world-blues bw)))) ;; update-positions : BallWorld -> BallWorld ;; To move each ball by one time step. (define (update-positions bw) (make-ball-world (move-ball-list (ball-world-reds bw)) (move-ball-list (ball-world-blues bw)))) ;; move-ball-list : LoB -> LoB ;; Move each ball in a list (define (move-ball-list lob) (cond [(empty? lob) empty] [else (cons (move-ball (first lob)) (move-ball-list (rest lob)))])) ;; move-ball : Ball -> Ball ;; To update the position of a ball by its velocity (define (move-ball ball) (make-ball (update-posn (ball-posn ball) (ball-vel ball)) (ball-vel ball))) ;; update-posn : Posn Velocity -> Posn ;; To move a position by a velocity (define (update-posn posn vel) (make-posn (+ (posn-x posn) (vel-dx vel)) (+ (posn-y posn) (vel-dy vel)))) ;; handle-tick : BallWorld -> BallWorld ;; To update the world at each tick (define (handle-tick bw) (remove-collisions (update-positions bw))) (big-bang WORLD-EX (on-draw render-ball-world) (on-tick handle-tick))