snake.rkt
(module snake lang/htdp-beginner-abbr


(require 2htdp/image)
(require 2htdp/universe)


;; =================
;; =================
;; Constants:

(define BOARD-SIZE 10)   ; A 10x10 board

(define CELL-PIXELS 14)                         ; cells are square
(define BOARD-WIDTH (* BOARD-SIZE CELL-PIXELS)) ;
(define BOARD-HEIGHT BOARD-WIDTH)               ;

;;
;; Images for the head and body elements of the snake
;; as well as for the food.
;;

(define HEAD (circle (/ CELL-PIXELS 2) "solid" "green"))
(define BODY (circle (/ CELL-PIXELS 2) "solid" "red"))
(define FOOD (circle (/ CELL-PIXELS 2) "solid" "blue"))


(define MTS (empty-scene BOARD-WIDTH BOARD-HEIGHT))


;; =================
;; =================
;; Data Definitions:

;; Direction is one of:
;;  - "U"
;;  - "D"
;;  - "L"
;;  - "R"
;; interp. direction of movement
;; Examples redundant in enumeration
#;
(define (fn-for-dir dir)
  (cond [(string=? "U" dir) (...)]
        [(string=? "D" dir) (...)]
        [(string=? "L" dir) (...)]
        [(string=? "R" dir) (...)]))

;; Template Rules Used:
;; - One of: 4 cases
;; - Atomic Distinct: "U"
;; - Atomic Distinct: "D"
;; - Atomic Distinct: "L"
;; - Atomic Distinct: "R"

(define-struct cell (c r))   ; c and r stand for column and row
;; Cell is  (make-cell Integer[-1, BOARD-SIZE] Integer[-1, BOARD-SIZE])
;; interp. a cell at some column c and row r in the board
;;         -1 and BOARD-SIZE are the edges of the board and represent
;;         "going out of bounds"/game over condition
(define C1 (make-cell 0 0))
(define C2 (make-cell (- BOARD-SIZE 1) 0))
(define C3 (make-cell (/ BOARD-SIZE 2) (/ BOARD-SIZE 2)))
#;
(define (fn-for-cell c)
  (... (cell-c c)
       (cell-r c)))

;; Template Rules Used:
;; - Compound: 2 fields
;; - Atomic Non-Distinct: cell-c is Integer[-1, BOARD-SIZE]
;; - Atomic Non-Distinct: cell-r is Integer[-1, BOARD-SIZE]



;; Body is one of:
;;  - (cons Cell empty)
;;  - (cons Cell Body)
;; interp. body of the snake
(define B1 (cons (make-cell 1 1) empty))
(define B2 (cons (make-cell 1 1)
                 (cons (make-cell 1 2)
                       (cons (make-cell 1 3) empty))))

#;
(define (fn-for-body b)
  (cond [(empty? (rest b)) (... (fn-for-cell (first b)))]
        [else
         (... (fn-for-cell (first b))
              (fn-for-cell (first (rest b)))
              (fn-for-body (rest b)))]))

;; Template Rules Used:
;; - One of: 2 cases
;; - Compound: (cons Cell empty)
;; - Atomic Distinct: (rest b) is empty
;; - Reference: (first b) is Cell
;; - Compound: (cons Cell Body)
;; - Reference: (first b) is Cell
;; - Self-Reference: (rest b) is Body
;; - Compound: (rest b) is non-empty
;; - Reference: (first (rest b)) is Cell


(define-struct snake (dir head body)) 
;; Snake is (make-snake Direction Cell Body)
;; interp. a snake going in a certain direction with its head and body at a certain cell position
(define S1 (make-snake "U" C1 B1))
(define S2 (make-snake "L" C2 B2))
(define S3 (make-snake "D" C3 B2))
#;
(define (fn-for-snake s)
  (... (fn-for-dir (snake-dir s))
       (fn-for-cell (snake-head s))
       (fn-for-body (snake-body s))))

;; Template Rules Used:
;; - Compound: 3 fields
;; - Reference: snake-dir is Direction
;; - Reference: snake-head is Cell
;; - Reference: snake-body is Body


(define-struct game (snake)) ; later on we will add fields to game
;; Game is (make-game Snake)
;; interp. a game with a snake
(define G1 (make-game S1))
(define G2 (make-game S2))
(define G3 (make-game S3))

#;
(define (fn-for-game g)
  (... (fn-for-snake (game-snake g))))

;; Template Rules Used:
;; - Compound Data: 1 field
;; - Reference: game-snake is Snake


;; =================
;; =================
;; Functions:

;; Game -> Game
;; starts the game by calling (main (make-game (make-snake "U" (make-cell (/ BOARD-SIZE 2) (/ BOARD-SIZE 2)))))
;; <no tests for main function>

(define (main g)
  (big-bang g
            (on-tick tock-game .6)
            (to-draw render-game)
            (on-key  handle-key)))


;; Game -> Game
;; produces next game state by moving the snake appropriately
(check-expect (tock-game (make-game (make-snake "D" (make-cell 0 0) (cons (make-cell 1 0) empty))))
              (make-game (make-snake "D" (make-cell 0 1) (cons (make-cell 0 0) empty))))

; (define (tock-game g) g)
; Template from Game

(define (tock-game g) 
  (make-game (next-snake (game-snake g))))

;; Snake -> Snake
;; produces the next snake moved in s's direction by one cell
(check-expect (next-snake (make-snake "D" (make-cell 0 0) (cons (make-cell 1 0) empty)))
              (make-snake "D" (make-cell 0 1) (cons (make-cell 0 0) empty)))
(check-expect (next-snake (make-snake "D" (make-cell 1 2) (cons (make-cell 1 1) empty)))
              (make-snake "D" (make-cell 1 3) (cons (make-cell 1 2) empty)))
(check-expect (next-snake (make-snake "R" (make-cell 1 2) (cons (make-cell 1 1)
                                                                (cons (make-cell 1 0) empty))))
              (make-snake "R" (make-cell 2 2) (cons (make-cell 1 2)
                                                    (cons (make-cell 1 1) empty))))

;(define (next-snake s) s)
; Template from Snake

(define (next-snake s)
  (make-snake (snake-dir s)
              (next-cell (snake-dir s) (snake-head s))
              (next-body (snake-head s) (snake-body s))))


;; Cell Body -> Body
;; Produces the snake's next body by removing the last unit and adding the head's old position
(check-expect (next-body (make-cell 0 0) (cons (make-cell 1 0) empty))
              (cons (make-cell 0 0) empty))
(check-expect (next-body (make-cell 1 2) (cons (make-cell 1 1) (cons (make-cell 1 0) empty)))
              (cons (make-cell 1 2) (cons (make-cell 1 1) empty)))

; (define (next-body h b) b)

(define (next-body h b)
  (cons h (remove-last b)))


;; Body -> Body or empty
;; Produces the body without the very last element
(check-expect (remove-last (cons (make-cell 0 0) empty)) empty)
(check-expect (remove-last (cons (make-cell 1 1)
                                 (cons (make-cell 1 2)
                                       (cons (make-cell 1 3) empty))))
              (cons (make-cell 1 1)
                    (cons (make-cell 1 2) empty)))

(define (remove-last b)
  (cond [(empty? (rest b)) empty]
        [else
         (cons (first b)
               (remove-last (rest b)))]))
  
 
;; Direction Cell -> Cell
;; Produces the next cell moved one in the given direction
(check-expect (next-cell "U" (make-cell 1 2))
              (make-cell 1 1))
(check-expect (next-cell "U" (make-cell 1 -1))
              (make-cell 1 -1))
(check-expect (next-cell "D" (make-cell 1 2))
              (make-cell 1 3))
(check-expect (next-cell "D" (make-cell 1 BOARD-SIZE))
              (make-cell 1 BOARD-SIZE))
(check-expect (next-cell "L" (make-cell -1 2))
              (make-cell -1 2))
(check-expect (next-cell "L" (make-cell 1 2))
              (make-cell 0 2))
(check-expect (next-cell "R" (make-cell 1 2))
              (make-cell 2 2))
(check-expect (next-cell "R" (make-cell BOARD-SIZE 2))
              (make-cell BOARD-SIZE 2))

; (define (next-cell dir c) c)
; Template from Direction + Cell (see below)
#;
(define (fn-for-dir dir c)
  (cond [(string=? "U" dir) (... (cell-c c)
                                 (cell-r c))]
        [(string=? "D" dir) (... (cell-c c)
                                 (cell-r c))]
        [(string=? "L" dir) (... (cell-c c)
                                 (cell-r c))]
        [(string=? "R" dir) (... (cell-c c)
                                 (cell-r c))]))

(define (next-cell dir c)
  (cond [(string=? dir "U") (make-cell (cell-c c) (max -1 (sub1 (cell-r c))))]
        [(string=? dir "D") (make-cell (cell-c c) (min BOARD-SIZE (add1 (cell-r c))))]
        [(string=? dir "L") (make-cell (max -1 (sub1 (cell-c c))) (cell-r c))]
        [(string=? dir "R") (make-cell (min BOARD-SIZE (add1 (cell-c c))) (cell-r c))]))


;; Game -> Image
;; Draws the current game state on the MTS
(check-expect (render-game (make-game (make-snake "D" (make-cell 0 0) (cons (make-cell 0 1) empty))))
              (place-in-cell HEAD (make-cell 0 0) 
                             (place-in-cell BODY (make-cell 0 1) MTS)))

; (define (render-game g) MTS)
; Template from Game
(define (render-game g)
  (place-snake (game-snake g) MTS))

;; Snake -> Image
;; Draws the snake at its position on given image
(check-expect (place-snake (make-snake "D" (make-cell 0 0) (cons (make-cell 0 1) empty)) MTS)
              (place-in-cell HEAD (make-cell 0 0) 
                             (place-in-cell BODY (make-cell 0 1) MTS)))

; (define (place-snake s i) i)
; Template from Snake

(define (place-snake s i)
  (place-head (snake-head s)
              (place-body (snake-body s) i)))


;; Cell Image -> Image
;; Places HEAD at c on i
(check-expect (place-head (make-cell 0 0) MTS)
              (place-in-cell HEAD (make-cell 0 0) MTS))

; (define (place-head c i) i)
; Template from Cell

(define (place-head c i)
  (place-in-cell HEAD c i))

;; Body Image -> Image
;; Draws the body at each of its positions on the given image
(check-expect (place-body (cons (make-cell 0 1) empty) MTS)
              (place-in-cell BODY (make-cell 0 1) MTS))
(check-expect (place-body (cons (make-cell 0 1)
                                (cons (make-cell 1 1)
                                      (cons (make-cell 1 2) empty)))
                          MTS)
              (place-in-cell BODY (make-cell 0 1)
                             (place-in-cell BODY (make-cell 1 1)
                                            (place-in-cell BODY (make-cell 1 2) MTS))))

; (define (place-body b i) i)
; Template from Body

(define (place-body b i)
  (cond [(empty? (rest b)) (place-in-cell BODY (first b) i)]
        [else
         (place-in-cell BODY
                        (first b)
                        (place-body (rest b) i))]))

;; PROVIDED
;; Image Cell Scene -> Scene
;; place img in cell c given the board scene scn
(check-expect (place-in-cell HEAD (make-cell 5 6) MTS)
              (place-image HEAD
                           (* 5.5 CELL-PIXELS)
                           (* 6.5 CELL-PIXELS)
                           MTS))

(define (place-in-cell img c scn)
  (place-image img 
               (+ (* (cell-c c) CELL-PIXELS) (/ CELL-PIXELS 2))
               (+ (* (cell-r c) CELL-PIXELS) (/ CELL-PIXELS 2))
               scn))



;; Game KeyEvent -> Game
;; on arrow keys produce game w/ snake going in that direction
(define THEAD (make-cell 4 5))
(define TBODY (cons (make-cell 5 5) (cons (make-cell 6 5) empty)))
(check-expect (handle-key (make-game (make-snake "L" THEAD TBODY)) "up")
              (make-game (make-snake "U" THEAD TBODY)))
(check-expect (handle-key (make-game (make-snake "L" THEAD TBODY)) "down")
              (make-game (make-snake "D" THEAD TBODY)))
(check-expect (handle-key (make-game (make-snake "R" THEAD TBODY)) "left")
              (make-game (make-snake "L" THEAD TBODY)))
(check-expect (handle-key (make-game (make-snake "L" THEAD TBODY)) "right")
              (make-game (make-snake "R" THEAD TBODY)))
(check-expect (handle-key (make-game (make-snake "L" THEAD TBODY)) "1")
              (make-game (make-snake "L" THEAD TBODY)))

(define (handle-key g ke)
  (cond [(key=? ke "up")    (make-game (snake-up    (game-snake g)))]
        [(key=? ke "down")  (make-game (snake-down  (game-snake g)))]
        [(key=? ke "left")  (make-game (snake-left  (game-snake g)))]
        [(key=? ke "right") (make-game (snake-right (game-snake g)))]
        [else g]))

;; Snake -> Snake
;; Produce a snake that has turned in the direction of the function's name
(check-expect (snake-left  (make-snake "U" THEAD TBODY))  (make-snake "L" THEAD TBODY))
(check-expect (snake-right (make-snake "U" THEAD TBODY))  (make-snake "R" THEAD TBODY))
(check-expect (snake-up    (make-snake "D" THEAD TBODY))  (make-snake "U" THEAD TBODY))
(check-expect (snake-down  (make-snake "U" THEAD TBODY))  (make-snake "D" THEAD TBODY))

(define (snake-left  s) (make-snake "L" (snake-head s) (snake-body s)))
(define (snake-right s) (make-snake "R" (snake-head s) (snake-body s)))
(define (snake-up    s) (make-snake "U" (snake-head s) (snake-body s)))
(define (snake-down  s) (make-snake "D" (snake-head s) (snake-body s)))


(main (make-game (make-snake "R" 
                             (make-cell 5 5)
                             (cons (make-cell 4 5) 
                                   (cons (make-cell 3 5)
                                         empty)))))
  
  )