(require 2htdp/image) (require 2htdp/universe) (define-struct world (food snake)) ;; A World is a (make-world Food Snake) ;; A Food is a (make-posn Number Number) ;; interp. (make-posn x y) means food is at grid coords (x,y) ;; where grid origin is at top left (define-struct snake (segs direction)) ;; A Snake is a (make-snake Segs Direction) ;; A Segs is one of: ;; - empty ;; - (cons Posn Segs) ;; where: Snake must contain at least one segment ;; and first posn in Segs is the head. ;; A Direction is one of: ;; - 'up ;; - 'down ;; - 'left ;; - 'right ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; TEMPLATES #;(define (world-temp w) ... (world-food w) ... (world-snake w) ...) #;(define (food-temp f) ... (posn-x f) ... (posn-y f) ...) #;(define (snake-temp snk) ... (snake-segs snk) ... (snake-direction snk) ...) #;(define (segs-temp segs) (cond [(empty? segs) ...] [(cons? segs) ... (first segs) ... (segs-temp (rest segs)) ... ])) ;; dir-temp : Direction -> ? #;(define (dir-temp d) (cond [(symbol=? d 'up) ... ] [(symbol=? d 'down) ...] [(symbol=? d 'left) ...] [(symbol=? d 'right) ...])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; CONSTANTS (define GRIDSQ-SIZE 10) ;; in pixels (define HALF-GRIDSQ-SIZE (quotient GRIDSQ-SIZE 2)) (define BOARD-WIDTH 30) ;; in grid squares (define BOARD-HEIGHT 20) ;; in grid squares (define BOARD-WIDTH/PX (* BOARD-WIDTH GRIDSQ-SIZE)) ;; in pixels (define BOARD-HEIGHT/PX (* BOARD-HEIGHT GRIDSQ-SIZE)) ;; in pixels (define SEG-RADIUS (quotient GRIDSQ-SIZE 2)) (define SEG-IMAGE (circle SEG-RADIUS 'solid 'red)) (define FOOD-RADIUS (* 0.8 SEG-RADIUS)) (define FOOD-IMAGE (circle FOOD-RADIUS 'solid 'green)) (define BACKGROUND (empty-scene BOARD-WIDTH/PX BOARD-HEIGHT/PX)) (define TICK-RATE 0.3) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; EXAMPLES (define food1 (make-posn 5 3)) (define snake1 (make-snake (cons (make-posn 6 10) empty) 'left)) (define world1 (make-world food1 snake1)) (define snake2 (make-snake (cons (make-posn 5 3) empty) 'left)) (define world2 (make-world food1 snake2)) ; an eating scenario (define food3 (make-posn 10 20)) (define snake3 (make-snake (cons (make-posn 5 3) (cons (make-posn 6 3) empty)) 'left)) ; 2-segment snake (define world3 (make-world food3 snake3)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Wish List ;; eating? : ;; Is the snake eating the food? ;; eat&grow : ;; generate new food (at random position on board) and grow snake ;;--------------- ;; key-handler : World KeyEvent -> World ;; handle the key event in the current world ;;--------------- ;; game-over? : World -> Boolean ;; Is the game over? ;; wall-collide? : ;; Has the snake collided into a wall? ;; self-collide? : ;; Has snake collided into itself? ;; IMAGE RENDERING FUNCTIONS ;; world->image : World -> Image ;; draw the current world state (define (world->image w) (snake+scene (world-snake w) (food+scene (world-food w) BACKGROUND))) ; (check-expect (world->image world1) ?) ;; food+scene : Food Image -> Image ;; draw food on top of given scene (define (food+scene f img) (place-image-on-grid FOOD-IMAGE (posn-x f) (posn-y f) img)) ;; place-image-on-grid : Image Number Number Image -> Image ;; exactly like place-image except uses grid coordinates (where grid ;; origin is at top left (define (place-image-on-grid img1 x y img2) (place-image img1 (- (* GRIDSQ-SIZE x) HALF-GRIDSQ-SIZE) (- (* GRIDSQ-SIZE y) HALF-GRIDSQ-SIZE) img2)) ;; snake+scene : Snake Image -> Image ;; draw snake on top of given scene (define (snake+scene snk img) (segments+scene (snake-segs snk) img)) ;; segments+scene : Segs Image -> Image ;; draw the given segments on top of given image (define (segments+scene segs img) (cond [(empty? segs) img] [(cons? segs) (place-image-on-grid SEG-IMAGE (posn-x (first segs)) (posn-y (first segs)) (segments+scene (rest segs) img))])) ; Examples/tests: Image rendering functions (check-expect (food+scene food1 BACKGROUND) (place-image FOOD-IMAGE 45 25 BACKGROUND)) (check-expect (segments+scene empty BACKGROUND) BACKGROUND) (check-expect (segments+scene (snake-segs snake1) BACKGROUND) (place-image SEG-IMAGE 55 95 BACKGROUND)) (check-expect (snake+scene snake1 BACKGROUND) (place-image SEG-IMAGE 55 95 BACKGROUND)) (check-expect (snake+scene snake3 BACKGROUND) ; 2-segment snake (place-image SEG-IMAGE 45 25 (place-image SEG-IMAGE 55 25 BACKGROUND))) (check-expect (world->image world1) (place-image FOOD-IMAGE 45 25 (place-image SEG-IMAGE 55 95 BACKGROUND))) (check-expect (world->image world2) ; eating scenario: food is hidden! (place-image SEG-IMAGE 45 25 BACKGROUND)) ;; SNAKE MOTION & GROWTH ;; snake-slither : Snake -> Snake ;; Move the snake by one step in the appropriate direction (define (snake-slither snk) (make-snake (move-segs (snake-segs snk) (snake-direction snk)) (snake-direction snk))) (check-expect (snake-slither snake1) (make-snake (cons (make-posn 5 10) empty) 'left)) (check-expect (snake-slither snake3) (make-snake (cons (make-posn 4 3) (cons (make-posn 5 3) empty)) 'left)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; An NESegs (non-empty list of segments) is one of: ;; - (cons Posn empty) ;; - (cons Posn NESegs) ;; Template #;(define (nesegs-temp nesegs) (cond [(empty? (rest nesegs)) ... (first nesegs) ... ] [(cons? (rest nesegs)) ... (first nesegs) ... (nesegs-temp (rest nesegs)) ...])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; move-segs : NESegs Direction -> NESegs ;; move the snake's segments by one step in the given direction ;; How: new head is the old head moved by one step in approp direction ;; new tail is old segs minus last segment (define (move-segs segs dir) (cons (move-posn (first segs) dir) (segments-all-but-last segs))) ;; move-posn: Posn Direction -> Posn ;; move the given posn (segment) by one step in given direction (define (move-posn p d) (cond [(symbol=? d 'up) (make-posn (posn-x p) (sub1 (posn-y p)))] [(symbol=? d 'down) (make-posn (posn-x p) (add1 (posn-y p)))] [(symbol=? d 'left) (make-posn (sub1 (posn-x p)) (posn-y p))] [(symbol=? d 'right) (make-posn (add1 (posn-x p)) (posn-y p))])) ;; segments-all-but-last: NESegs -> Segs ;; remove the last segment from a NON-EMPTY list (define (segments-all-but-last nesegs) (cond [(empty? (rest nesegs)) empty] [(cons? (rest nesegs)) (cons (first nesegs) (segments-all-but-last (rest nesegs)))])) ;; snake-grow : Snake -> Snake ;; grow snake by one segment, exactly like snake-slither except ;; don't drop last seg (define (snake-grow snk) (make-snake (cons (move-posn (first (snake-segs snk)) (snake-direction snk)) ; Add new head (snake-segs snk)) ; to entire snake (snake-direction snk))) ;; eat&grow : World -> World ;; Eat the current food and grow the snake one segment. ;; The new world has food at some random coordinate. (define (eat&grow w) ...) ;;; Examples/tests: Snake motion & growth (check-expect (move-posn (make-posn 10 10) 'up) (make-posn 10 9)) (check-expect (segments-all-but-last (snake-segs snake1)) empty) (check-expect (segments-all-but-last (cons (make-posn 10 20) (cons (make-posn 10 21) empty))) (cons (make-posn 10 20) empty)) (check-expect (move-posn (make-posn 10 20) 'up) (make-posn 10 19)) (check-expect (snake-slither snake1) (make-snake (cons (make-posn 5 10) empty) 'left)) (check-expect (snake-grow snake1) (make-snake (cons (make-posn 5 10) (cons (make-posn 6 10) empty)) 'left)) ;;; Just check the new world's snake -- we can't test the new food, ;;; since it's randomly placed. #;(check-expect (world-snake (eat&grow world2)) (make-snake (cons (make-posn 4 3) (cons (make-posn 5 3) empty)) 'left)) ;; world->world : World -> World ;; Advances the current world state ;; THIS VERSION ONLY MOVES THE SNAKE. After you have designed ;; the eating? and eat&grow functions, you should update world->world ;; to handle eating. (define (world->world w) (make-world (world-food w) (snake-slither (world-snake w)))) (big-bang world1 (to-draw world->image) (on-tick world->world TICK-RATE) ; (on-key key-handler) ;; UNCOMMENT THIS ONCE YOU HAVE key-handler ; (stop-when game-over?) ;; UNCOMMENT ONCE YOU HAVE game-over? )