;; Snake Game (require 2htdp/image) (require 2htdp/universe) ;; Honors Lecture 10/3/2013 ;; SNAKE WORLD ;; (define-struct world (snake food)) (define-struct snake (dir segs)) ;; A World is (make-world Snake Food) ;; A Food is a Posn ;; A Snake is (make-snake Direction Segs) ;; Snake must contain at least one segment ;; The head is the first seg in the list ;; A Segs is one of: ; Note: Segs is a list of Posns! ;; - empty ;; - (cons Posn Segs) ;; food and seg positions are in grid coordinates ;; A Direction is one of: ;; - 'up ;; - 'down ;; - 'left ;; - 'right ;;;;;;;;;;;;;;;;;;;; ;; Templates #;(define (world-temp w) ; todo ... ) #;(define (food-temp f) ; todo ... ) #;(define (snake-temp snk) ... (snake-dir snk) ... (snake-segs snk)) #;(define (segs-temp segs) (cond [(empty? segs) ...] [else ... (first segs) ... (segs-temp (rest segs)) ...])) #;(define (direction-temp dir) (cond [(symbol=? dir 'up) ...] [(symbol=? dir 'down) ...] [(symbol=? dir 'left) ...] [(symbol=? dir 'right) ...])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Initial wish list: ;; world->scene : World -> Image ;; food+scene : Food Image -> Image ;; snake+scene : Snake Image -> Image ;; snake-move : Snake -> Snake ;; snake-grow : Snake -> Snake ;; world->world : World -> World ;; key-hndlr : World KeyEvent -> World ;; eating? : World -> Boolean ;; game-end? : World -> Boolean ;; self-collide? : Snake -> Boolean ;; wall-collide? : Snake -> Boolean ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; CONSTANTS: things we need to keep track of that never change (define GRID-SQSIZE 10) ; width of a game-board square (define BOARD-HEIGHT 20) ; height of board in grid squares (define BOARD-WIDTH 30) ; width of board in grid squares (define BOARD-HT/PIX (* GRID-SQSIZE BOARD-HEIGHT)) (define BOARD-WD/PIX (* GRID-SQSIZE BOARD-WIDTH)) (define BACKGROUND (empty-scene BOARD-WD/PIX BOARD-HT/PIX)) (define TICK-RATE 0.3) (define SEGMENT-RADIUS (quotient GRID-SQSIZE 2)) (define SEGMENT-IMAGE (circle SEGMENT-RADIUS 'solid 'red)) (define FOOD-RADIUS (floor (* 0.9 SEGMENT-RADIUS))) (define FOOD-IMAGE (circle FOOD-RADIUS 'solid 'green)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Example data (define Food1 (make-posn 5 3)) (define Snake1 (make-snake 'left (cons (make-posn 6 10) empty))) (define World1 (make-world Snake1 Food1)) (define Snake2 (make-snake 'left (cons (make-posn 5 3) empty))) (define World2 (make-world Snake2 Food1)) ; an eating scenario (define Food3 (make-posn 10 20)) (define Snake3 (make-snake 'left (cons (make-posn 5 3) (cons (make-posn 6 3) empty)))) ; 2-segment snake (define World3 (make-world Snake3 Food3)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Image/scene painting functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; todo: world->scene, food+scene, snake+scene, other helpers... ;; Examples/Tests: scene-painting functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Snake motion and growth ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; snake-move : Snake -> Snake ;; Move the snake by one step in the current direction (define (snake-move snk) (make-snake (snake-dir snk) (move-segs (snake-segs snk) (snake-dir snk)))) ;; A NESegs (non-empty segments) is one of: ;; -- (cons Posn empty) ;; -- (cons Posn NESegs) ;; move-segs : NESegs Direction -> NESegs ;; Move the snake's segments by one step ;; How: new head is old head moved by one step in approp direc ;; new tail is old segs minus last (define (move-segs nesegs dir) (cons (move-seg (first nesegs) dir) (segments-all-but-last nesegs))) ;; segments-all-but-last: NESegs -> Segs ;; remove the last segment in the list (define (segments-all-but-last nesegs) (cond [(empty? (rest nesegs)) empty] [else (cons (first nesegs) (segments-all-but-last (rest nesegs)))])) ;; move-seg : Posn Direction -> Posn ;; move the segment in the given direction (define (move-seg p dir) (cond [(symbol=? dir 'up) (make-posn (posn-x p) (add1 (posn-y p)))] [(symbol=? dir 'down) (make-posn (posn-x p) (sub1 (posn-y p)))] [(symbol=? dir 'left) (make-posn (sub1 (posn-x p)) (posn-y p))] [(symbol=? dir 'right) (make-posn (add1 (posn-x p)) (posn-y p))])) ;; snake-grow : Snake -> Snake ;; Grow snake one step ;; This is just like snake-move except we don't drop last seg (define (snake-grow snk) ...) ;; eat&grow : World -> World ;; Eat the current food, grow the snake and produce new food (define (eat&grow w) ...) ;; Examples/Tests for snake movement and growth (check-expect (segments-all-but-last (cons (make-posn 6 10) empty)) empty) (check-expect (segments-all-but-last (cons (make-posn 5 10) (cons (make-posn 6 10) empty))) (cons (make-posn 5 10) empty)) (check-expect (move-seg (make-posn 6 10) 'up) (make-posn 6 11)) (check-expect (move-seg (make-posn 6 10) 'down) (make-posn 6 9)) (check-expect (move-seg (make-posn 6 10) 'left) (make-posn 5 10)) (check-expect (move-seg (make-posn 6 10) 'right) (make-posn 7 10)) (check-expect (snake-move Snake1) (make-snake 'left (cons (make-posn 5 10) empty))) #;(check-expect (snake-grow Snake1) (make-snake 'left (cons (make-posn 5 10) (cons (make-posn 6 10) empty)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Collision detection ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; todo: eating?, self-collide?, wall-collide?, helpers... ;; Examples/Tests for collision functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; todo: world->world, key-handler, game-end?, #;(big-bang World1 (to-draw world->scene) (on-tick world->world TICK-RATE) (on-key key-handler) (stop-when game-end?))