;; The first three lines of this file were inserted by DrRacket. They record metadata ;; about the language level of this file in a form that our tools can easily process. #reader(lib "htdp-beginner-reader.ss" "lang")((modname 2015-10-14-lerner) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f () #f))) (require 2htdp/image) (require 2htdp/universe) ;; CONSTANTS (define CELL-SIZE 15) (define BOARD-WIDTH 30) ;; in cells (define BOARD-HEIGHT 20) ;; in cells (define BACKGROUND-IMAGE (empty-scene (* CELL-SIZE BOARD-WIDTH) (* CELL-SIZE BOARD-HEIGHT))) (define SEGMENT-RADIUS (quotient CELL-SIZE 2)) (define FOOD-RADIUS (floor (* 0.9 SEGMENT-RADIUS))) (define SEGMENT-IMAGE (circle SEGMENT-RADIUS "solid" "blue")) (define FOOD-IMAGE (circle FOOD-RADIUS "solid" "red")) ;; DATA DEFINITIONS ;; A Food is a Posn ;; INTERPRETATION: location in Grid coordinates of the food ;; A Snake is associated with the positions it is in ;; A SnakeBody is NELoSeg (non-empty list of segments), ;; where the neighboring segments are adjacent ;; INTERPRETATION: The head of the list is the head of the snake ;; A Snake is a (make-snake Direction SnakeBody) ;; A Segment is a Posn ;; A Direction is one of ;; - 'up ;; - 'down ;; - 'left ;; - 'right (define-struct snake (dir body)) ;; A World is a (make-world Snake Food) (define-struct world (snake food)) ;; DRAWING FUNCTIONS ;; place-image-grid : Image Number Number Image -> Image ;; Just like place-image, except position is in grid coordinates instead of pixels (define (place-image-grid img x y bkg) (place-image img (* (+ x 0.5) CELL-SIZE) (- (* BOARD-HEIGHT CELL-SIZE) (* (+ y 0.5) CELL-SIZE)) bkg)) ;; Draws the world ;; world->image : World -> Image (define (world->image world) (snake+image (world-snake world) (food+image (world-food world) BACKGROUND-IMAGE))) ;; food+image : Food Image -> Image ;; draws the given food on to the existing image (define (food+image food image) (place-image-grid FOOD-IMAGE (posn-x food) (posn-y food) image)) ;; NOTE: snake+image destructs the snake and draws its body using snake-body+image, ;; which in turn recurs over the body and draws each segment using segment+image ;; snake+image : Snake Image -> Image ;; draws a snake onto an existing image (define (snake+image snake image) (snake-body+image (snake-body snake) image)) ;; snake-body+image : SnakeBody Image -> Image ;; draws and entire snake body onto an existing image (define (snake-body+image body image) (cond ;; NOTE: Deliberately ignoring the non-emptiness [(empty? body) image] [(cons? body) (segment+image (first body) (snake-body+image (rest body) image))])) ;; segment+image : Segment Image -> Image ;; draws a single segment onto an existing image (define (segment+image segment image) (place-image-grid SEGMENT-IMAGE (posn-x segment) (posn-y segment) image)) ;; turn-snake : Snake Key -> Snake (define (turn-snake s key) (if (or (string=? key "up") (string=? key "down") (string=? key "left") (string=? key "right")) (make-snake (string->symbol key) (snake-body s)) s)) ;; Some basic data to use later... (define SNAKE0 (make-snake 'right (cons (make-posn 3 4) (cons (make-posn 3 3) empty)))) (define FOOD0 (make-posn 3 2)) (define WORLD0 (make-world SNAKE0 FOOD0)) (check-expect (snake+image SNAKE0 (rectangle 40 20 'solid 'green)) (place-image-grid SEGMENT-IMAGE 3 4 (place-image-grid SEGMENT-IMAGE 3 3 (rectangle 40 20 'solid 'green)))) (check-expect (world->image WORLD0) (place-image-grid SEGMENT-IMAGE 3 4 (place-image-grid SEGMENT-IMAGE 3 3 (place-image-grid FOOD-IMAGE 3 2 BACKGROUND-IMAGE)))) ;; GENERAL HELPER FUNCTIONS ;; same-posn? : Posn Posn -> Boolean (define (same-posn? p1 p2) (and (= (posn-x p1) (posn-x p2)) (= (posn-y p1) (posn-y p2)))) ;; Is the snake currently eating the given food? ;; eating-food? : Snake Food -> Boolean (define (eating-food? snake food) (same-posn? (first (snake-body snake)) food)) ;; TODO! (define (hit-wall? snake) false) ;; Has the snake hit itself? ;; hit-self? : Snake -> Boolean (define (hit-self? snake) (same-as-seg? (first (snake-body snake)) (rest (snake-body snake)))) ;; Is the given head of the snake in the same position as any part of the body? ;; same-as-seg : Seg LoSeg -> Boolean (define (same-as-seg? head body) (cond [(empty? body) false] [(cons? body) (or (same-posn? head (first body)) (same-as-seg? head (rest body)))])) ;; Grows the snake body by one segment in the given direction ;; grow-body : SnakeBody Direction -> SnakeBody (define (grow-body body dir) (cons (new-head (first body) dir) body)) ;; Grows the snake by one segment in its current direction ;; grow : Snake -> Snake (define (grow snake) (make-snake (snake-dir snake) (grow-body (snake-body snake) (snake-dir snake)))) ;; move-snake : Snake -> Snake ;; moves a snake in its current direction (define (move-snake snake) (make-snake (snake-dir snake) (move-snake-body (snake-body snake) (snake-dir snake)))) ;; move-snake-body : SnakeBody Direction -> SnakeBody ;; moves a snake body in the given direction (define (move-snake-body body dir) (cons (new-head (first body) dir) (all-but-last-seg body))) ;; all-but-last-seg : NELoSeg -> LoSeg ;; Chops the last segment off the given non-empty list of segments (define (all-but-last-seg nelos) (cond [(empty? (rest nelos)) empty] [(cons? (rest nelos)) (cons (first nelos) (all-but-last-seg (rest nelos)))])) ;; new-head : Seg Direction -> Seg (define (new-head seg dir) (cond [(symbol=? dir 'up) (make-posn (posn-x seg) (add1 (posn-y seg)))] [(symbol=? dir 'down) (make-posn (posn-x seg) (sub1 (posn-y seg)))] [(symbol=? dir 'left) (make-posn (sub1 (posn-x seg)) (posn-y seg))] [(symbol=? dir 'right) (make-posn (add1 (posn-x seg)) (posn-y seg))])) ; For example, (check-expect (new-head (make-posn 3 4) 'up) (make-posn 3 5)) ;; WORLD FUNCTIONS ;; update-world : World -> World (define (update-world w) ;; If eating food, grow snake, place food! ;; If hit wall, reset game ;; If hit self, reset game ;; otherwise move (cond [(eating-food? (world-snake w) (world-food w)) (make-world (grow (world-snake w)) (make-posn (random BOARD-WIDTH) (random BOARD-HEIGHT)))] [(hit-wall? (world-snake w)) WORLD0] [(hit-self? (world-snake w)) WORLD0] [else (make-world (move-snake (world-snake w)) (world-food w) )])) ;; key-handler : World Key -> World (define (key-handler w key) (make-world (turn-snake (world-snake w) key) (world-food w))) ;; GO! (big-bang WORLD0 (on-tick update-world 0.25) (on-key key-handler) (to-draw world->image))