Version: 4.1.3

1.18  Chat Noir – Puzzle Game

To play Chat Noir, run the PLT Games program. (Under Unix, it’s called plt-games).

The goal of the game is to stop the cat from escaping the board. Each turn you click on a circle, which prevents the cat from stepping on that space, and the cat responds by taking a step. If the cat is completely boxed in and thus unable reach the border, you win. If the cat does reach the border, you lose.

The game was inspired by this one the one at Game Design and has essentailly the same rules.

This game is written in the How to Design Programs Intermediate language. It is a model solution to the final project for the introductory programming course at the University of Chicago in the fall of 2008, as below.

  ; ;; constants
  (define circle-radius 20)
  (define circle-spacing 22)
  
  ; ;; data definitions
  
  ; ;; a world is:
  ; ;;  (make-world board posn state number)
  (define-struct world (board cat state size))
  
  ; ;; a state is either:
  ; ;;   - 'playing
  ; ;;   - 'cat-won
  ; ;;   - 'cat-lost
  
  ; ;; a board is
  ; ;;  (listof cell)
  
  ; ;; a cell is
  ; ;; (make-cell (make-posn int[0-board-size]
  ; ;;                       int[0-board-size])
  ; ;;            boolean)
  (define-struct cell (p blocked?))
  
  
  ; ;                                                                          
  ; ;                                                                          
  ; ;                                                                          
  ; ;                                                                          
  ; ;                                                                          
  ; ;          ;;                                     ;;;;                     
  ; ;       ;;;;                                      ;;;;;                    
  ; ;        ;;;                              ;                                
  ; ;    ;;; ;;;    ; ;;;;   ;;;;   ;;;;;;  ;;  ;;;;     ;;;;;;  ;;   ;;;; ;;; 
  ; ;   ;;;;;;;;;;;;;; ;;;; ;;;;;;;;;  ;;   ;;   ;   ;;;;;  ;;; ;;;;  ;;;;;;;  
  ; ;  ;;;;;;;;;  ;;; ;;;;;;;;;;;;;;; ;;;   ;;   ;;    ;;;  ;;; ;;;;  ;   ;;;; 
  ; ;  ;;;   ;;;  ;;; ;;;; ;;;    ;;; ;;;   ;;;  ;;;   ;;;  ;;;; ;;;  ;;   ;;; 
  ; ;  ;;   ;;;;  ;;;      ;;    ;;;; ;;;   ;;;; ;;;   ;;;  ;;;  ;;;   ;;;;;;; 
  ; ;  ;;;;;;;;   ;;;      ;;;;;;;;;; ;;;   ;; ;;;;;  ;;;;  ;;;  ;;;       ;;; 
  ; ;   ;;;;;;;;;;;;;;;;   ;;;;;;;;;;; ;;; ;;;  ;;;  ;;;;;;;;;;  ;;;  ;;;; ;;; 
  ; ;                       ;;;;;        ;;;;                   ;;;; ;;;;; ;;; 
  ; ;                                                             ;;;;;;; ;;;  
  ; ;                                                                 ;;;;;;   
  ; ;                                                                          
  
  
  ; ;; world->image : world -> image
  (define (world->image w)
    (chop-whiskers
     (overlay (board->image (world-board w) (world-size w))
              (move-pinhole
               (cond
                 [(equal? (world-state w) 'cat-won) happy-cat]
                 [(equal? (world-state w) 'cat-lost) sad-cat]
                 [else thinking-cat])
               (- (cell-center-x (world-cat w)))
               (- (cell-center-y (world-cat w)))))))
  
  (check-expect
   (world->image
    (make-world (list (make-cell (make-posn 0 1) false))
                (make-posn 0 1)
                'playing
                2))
   (overlay
    (board->image (list (make-cell (make-posn 0 1) false))
                  2)
    (move-pinhole thinking-cat
                  (- (cell-center-x (make-posn 0 1)))
                  (- (cell-center-y (make-posn 0 1))))))
  (check-expect
   (world->image
    (make-world (list (make-cell (make-posn 0 1) false))
                (make-posn 0 1)
                'cat-won
                2))
   (overlay
    (board->image (list (make-cell (make-posn 0 1) false))
                  2)
    (move-pinhole happy-cat
                  (- (cell-center-x (make-posn 0 1)))
                  (- (cell-center-y (make-posn 0 1))))))
  (check-expect
   (world->image
    (make-world (list (make-cell (make-posn 0 1) false))
                (make-posn 0 1)
                'cat-lost
                2))
   (overlay
    (board->image (list (make-cell (make-posn 0 1) false))
                  2)
    (move-pinhole sad-cat
                  (- (cell-center-x (make-posn 0 1)))
                  (- (cell-center-y (make-posn 0 1))))))
  
  ; ;; chop-whiskers : image -> image
  ; ;; crops the image so that anything above or to the left of the pinhole is gone
  (define (chop-whiskers img)
    (shrink img
            0
            0
            (- (image-width img) (pinhole-x img) 1)
            (- (image-height img) (pinhole-y img) 1)))
  
  (check-expect (chop-whiskers (rectangle 5 5 'solid 'black))
                (put-pinhole (rectangle 3 3 'solid 'black) 0 0))
  (check-expect (chop-whiskers (rectangle 6 6 'solid 'black))
                (put-pinhole (rectangle 3 3 'solid 'black) 0 0))
  
  (check-expect
   (pinhole-x
    (world->image
     (make-world
      (list (make-cell (make-posn 0 0) false)
            (make-cell (make-posn 0 1) false)
            (make-cell (make-posn 1 0) false))
      (make-posn 0 0)
      'playing
      2)))
   0)
  (check-expect
   (pinhole-x
    (world->image
     (make-world
      (list (make-cell (make-posn 0 0) false)
            (make-cell (make-posn 0 1) false)
            (make-cell (make-posn 1 0) false))
      (make-posn 0 1)
      'playing
      2)))
   0)
  
  
  ; ;; board->image : board number -> image
  (define (board->image cs world-size)
    (foldl (lambda (x y) (overlay y x))
           (nw:rectangle (world-width world-size)
                         (world-height world-size)
                         'solid
                         'white)
           (map cell->image cs)))
  
  (check-expect (board->image (list (make-cell (make-posn 0 0) false)) 3)
                (overlay
                 (nw:rectangle (world-width 3)
                               (world-height 3)
                               'solid
                               'white)
                 (cell->image (make-cell (make-posn 0 0) false))))
  
  
  ; ;; cell->image : cell -> image
  (define (cell->image c)
    (local [(define x (cell-center-x (cell-p c)))
            (define y (cell-center-y (cell-p c)))]
      (move-pinhole
       (cond
         [(cell-blocked? c)
          (circle circle-radius 'solid 'black)]
         [else
          (circle circle-radius 'solid 'lightblue)])
       (- x)
       (- y))))
  
  (check-expect (cell->image (make-cell (make-posn 0 0) false))
                (move-pinhole (circle circle-radius 'solid 'lightblue)
                              (- circle-radius)
                              (- circle-radius)))
  (check-expect (cell->image (make-cell (make-posn 0 0) true))
                (move-pinhole (circle circle-radius 'solid 'black)
                              (- circle-radius)
                              (- circle-radius)))
  
  ; ;; world-width : number -> number
  ; ;; computes the width of the drawn world in terms of its size
  (define (world-width board-size)
    (local [(define rightmost-posn
              (make-posn (- board-size 1) (- board-size 2)))]
      (+ (cell-center-x rightmost-posn) circle-radius)))
  
  (check-expect (world-width 3) 150)
  
  ; ;; world-height : number -> number
  ; ;; computes the height of the drawn world in terms of its size
  (define (world-height board-size)
    (local [(define bottommost-posn
              (make-posn (- board-size 1) (- board-size 1)))]
      (+ (cell-center-y bottommost-posn) circle-radius)))
  (check-expect (world-height 3) 116.208)
  
  
  ; ;; cell-center-x : posn -> number
  (define (cell-center-x p)
    (local [(define x (posn-x p))
            (define y (posn-y p))]
      (+ circle-radius
         (* x circle-spacing 2)
         (if (odd? y)
             circle-spacing
             0))))
  
  (check-expect (cell-center-x (make-posn 0 0))
                circle-radius)
  (check-expect (cell-center-x (make-posn 0 1))
                (+ circle-spacing circle-radius))
  (check-expect (cell-center-x (make-posn 1 0))
                (+ (* 2 circle-spacing) circle-radius))
  (check-expect (cell-center-x (make-posn 1 1))
                (+ (* 3 circle-spacing) circle-radius))
  
  ; ;; cell-center-y : posn -> number
  (define (cell-center-y p)
    (local [(define y (posn-y p))]
      (+ circle-radius
         (* y circle-spacing 2
            0.866))))
  
  
  (check-expect (cell-center-y (make-posn 1 1))
                (+ circle-radius (* 2 circle-spacing 0.866)))
  (check-expect (cell-center-y (make-posn 1 0))
                circle-radius)
  
  
  ; ;                                                     
  ; ;                                                     
  ; ;                                                     
  ; ;                                                     
  ; ;                                                     
  ; ;                                           ;;;;;     
  ; ;                                            ;;;;     
  ; ;                                            ;;;      
  ; ;   ;;;; ;;;    ; ;;;;   ;;;;   ;;  ; ;;;;   ;;;   ;  
  ; ;   ;;;;;;; ;;;;;; ;;;; ;;;;;;;;; ;;;;;;;;;  ;;;  ;;; 
  ; ;   ;   ;;;;  ;;; ;;;;;;;;;;;;;;;  ;;    ;;  ;;; ;;;; 
  ; ;   ;;   ;;;  ;;; ;;;; ;;;    ;;;  ;     ;;  ;;;; ;;; 
  ; ;    ;;;;;;;  ;;;      ;;    ;;;;  ;    ;;;  ;;;  ;;; 
  ; ;        ;;;  ;;;      ;;;;;;;;;;  ;;;;;;;    ;;  ;;; 
  ; ;   ;;;; ;;;;;;;;;;;   ;;;;;;;;;;; ;; ;;;     ;;  ;;; 
  ; ;  ;;;;; ;;;            ;;;;;      ;;        ;;;; ;;; 
  ; ;  ;;;; ;;;                        ;;               ;;
  ; ;   ;;;;;;                        ;                   
  ; ;                                                     
  
  ; ;; a distance-map is 
  ; ;;  (listof dist-cells)
  
  ; ;; a dist-cell is
  ; ;;  - (make-dist-cell posn (number or '∞))
  (define-struct dist-cell (p n))
  
  ; ;; build-table/fast : world -> distance-map
  (define (build-table/fast world)
    (local [(define board-size (world-size world))
            (define blocked (make-hash))
            (define ht (make-hash))
            (define (search p)
              (cond
                [(hash-ref blocked p)
                 ']
                [(on-boundary? p board-size)
                 ((lambda (a b) b)
                  (hash-set! ht p 0)
                  0)]
                [(not (boolean? (hash-ref ht p #f)))
                 (hash-ref ht p)]
                [else
                 ((lambda (a b c) c)
                  (hash-set! ht p ')
                  (hash-set!
                   ht
                   p
                   (add1/f (min-l (map search
                                       (adjacent p board-size)))))
                  (hash-ref ht p))]))]
      ((lambda (a b c) c)
       (for-each (lambda (cell)
                   (hash-set! blocked
                              (cell-p cell)
                              (cell-blocked? cell)))
                 (world-board world))
       (search (world-cat world))
       (hash-map ht make-dist-cell))))
  
  ; ;; build-table : world -> distance-map
  (define (build-table world)
    (build-distance (world-board world)
                    (world-cat world)
                    '()
                    '()
                    (world-size world)))
  
  ; ;; build-distance : board posn distance-map (listof posn) number -> distance-map
  (define (build-distance board p t visited board-size)
    (cond
      [(cell-blocked? (lookup-board board p))
       (add-to-table p ' t)]
      [(on-boundary? p board-size)
       (add-to-table p 0 t)]
      [(in-table? t p)
       t]
      [(member p visited)
       (add-to-table p ' t)]
      [else
       (local [(define neighbors (adjacent p board-size))
               (define neighbors-t (build-distances
                                    board
                                    neighbors
                                    t
                                    (cons p visited)
                                    board-size))]
         (add-to-table p
                       (add1/f
                        (min-l
                         (map (lambda (neighbor)
                                (lookup-in-table neighbors-t neighbor))
                              neighbors)))
                       neighbors-t))]))
  
  ; ;; build-distances : board (listof posn) distance-map (listof posn) number 
  ; ;;                -> distance-map
  (define (build-distances board ps t visited board-size)
    (cond
      [(empty? ps) t]
      [else
       (build-distances board
                        (rest ps)
                        (build-distance board (first ps) t visited board-size)
                        visited
                        board-size)]))
  
  (check-expect (build-distance (list (make-cell (make-posn 0 0) false))
                                (make-posn 0 0)
                                '()
                                '()
                                1)
                (list (make-dist-cell (make-posn 0 0) 0)))
  
  (check-expect (build-distance (list (make-cell (make-posn 0 0) true))
                                (make-posn 0 0)
                                '()
                                '()
                                1)
                (list (make-dist-cell (make-posn 0 0) ')))
  
  (check-expect (build-distance (list (make-cell (make-posn 0 1) false)
                                      (make-cell (make-posn 1 0) false)
                                      (make-cell (make-posn 1 1) false)
                                      (make-cell (make-posn 1 2) false)
                                      (make-cell (make-posn 2 0) false)
                                      (make-cell (make-posn 2 1) false)
                                      (make-cell (make-posn 2 2) false))
                                (make-posn 1 1)
                                '()
                                '()
                                3)
                (list (make-dist-cell (make-posn 1 0) 0)
                      (make-dist-cell (make-posn 2 0) 0)
                      (make-dist-cell (make-posn 0 1) 0)
                      (make-dist-cell (make-posn 2 1) 0)
                      (make-dist-cell (make-posn 1 2) 0)
                      (make-dist-cell (make-posn 2 2) 0)
                      (make-dist-cell (make-posn 1 1) 1)))
  
  (check-expect (build-distance (list (make-cell (make-posn 0 1) true)
                                      (make-cell (make-posn 1 0) true)
                                      (make-cell (make-posn 1 1) false)
                                      (make-cell (make-posn 1 2) true)
                                      (make-cell (make-posn 2 0) true)
                                      (make-cell (make-posn 2 1) true)
                                      (make-cell (make-posn 2 2) true))
                                (make-posn 1 1)
                                '()
                                '()
                                3)
                (list (make-dist-cell (make-posn 1 0) ')
                      (make-dist-cell (make-posn 2 0) ')
                      (make-dist-cell (make-posn 0 1) ')
                      (make-dist-cell (make-posn 2 1) ')
                      (make-dist-cell (make-posn 1 2) ')
                      (make-dist-cell (make-posn 2 2) ')
                      (make-dist-cell (make-posn 1 1) ')))
  
  (check-expect (build-distance
                 (append-all
                  (build-list
                   5
                   (lambda (i)
                     (build-list
                      5
                      (lambda (j)
                        (make-cell (make-posn i j) false))))))
                 (make-posn 2 2)
                 '()
                 '()
                 5)
                (list (make-dist-cell (make-posn 1 0) 0)
                      (make-dist-cell (make-posn 2 0) 0)
                      (make-dist-cell (make-posn 0 1) 0)
                      (make-dist-cell (make-posn 3 0) 0)
                      (make-dist-cell (make-posn 1 1) 1)
                      (make-dist-cell (make-posn 4 0) 0)
                      (make-dist-cell (make-posn 2 1) 1)
                      (make-dist-cell (make-posn 4 1) 0)
                      (make-dist-cell (make-posn 3 1) 1)
                      (make-dist-cell (make-posn 2 2) 2)
                      (make-dist-cell (make-posn 4 2) 0)
                      (make-dist-cell (make-posn 3 2) 1)
                      (make-dist-cell (make-posn 0 2) 0)
                      (make-dist-cell (make-posn 0 3) 0)
                      (make-dist-cell (make-posn 1 3) 1)
                      (make-dist-cell (make-posn 1 2) 1)
                      (make-dist-cell (make-posn 2 3) 1)
                      (make-dist-cell (make-posn 1 4) 0)
                      (make-dist-cell (make-posn 2 4) 0)
                      (make-dist-cell (make-posn 4 3) 0)
                      (make-dist-cell (make-posn 3 4) 0)
                      (make-dist-cell (make-posn 4 4) 0)
                      (make-dist-cell (make-posn 3 3) 1)))
  
  
  ; ;; lookup-board : board posn -> cell-or-false
  (define (lookup-board board p)
    (cond
      [(empty? board) (error 'lookup-board "did not find posn")]
      [else
       (cond
         [(equal? (cell-p (first board)) p)
          (first board)]
         [else
          (lookup-board (rest board) p)])]))
  
  (check-expect (lookup-board (list (make-cell (make-posn 2 2) false))
                              (make-posn 2 2))
                (make-cell (make-posn 2 2) false))
  (check-error (lookup-board '() (make-posn 0 0))
               "lookup-board: did not find posn")
  
  ; ;; add-to-table : posn (number or '∞) distance-map -> distance-map
  (define (add-to-table p n t)
    (cond
      [(empty? t) (list (make-dist-cell p n))]
      [else
       (cond
         [(equal? p (dist-cell-p (first t)))
          (cons (make-dist-cell p (min/f (dist-cell-n (first t)) n))
                (rest t))]
         [else
          (cons (first t) (add-to-table p n (rest t)))])]))
  
  (check-expect (add-to-table (make-posn 1 2) 3 '())
                (list (make-dist-cell (make-posn 1 2) 3)))
  (check-expect (add-to-table (make-posn 1 2)
                              3
                              (list (make-dist-cell (make-posn 1 2) 4)))
                (list (make-dist-cell (make-posn 1 2) 3)))
  (check-expect (add-to-table (make-posn 1 2)
                              3
                              (list (make-dist-cell (make-posn 1 2) 2)))
                (list (make-dist-cell (make-posn 1 2) 2)))
  (check-expect (add-to-table (make-posn 1 2)
                              3
                              (list (make-dist-cell (make-posn 2 2) 2)))
                (list (make-dist-cell (make-posn 2 2) 2)
                      (make-dist-cell (make-posn 1 2) 3)))
  
  ; ;; in-table : distance-map posn -> boolean
  (define (in-table? t p) (number? (lookup-in-table t p)))
  
  (check-expect (in-table? empty (make-posn 1 2)) false)
  (check-expect (in-table? (list (make-dist-cell (make-posn 1 2) 3))
                           (make-posn 1 2))
                true)
  (check-expect (in-table? (list (make-dist-cell (make-posn 2 1) 3))
                           (make-posn 1 2))
                false)
  
  ; ;; lookup-in-table : distance-map posn -> number or '∞
  ; ;; looks for the distance as recorded in the table t, 
  ; ;; if not found returns a distance of '∞
  (define (lookup-in-table t p)
    (cond
      [(empty? t) ']
      [else (cond
              [(equal? p (dist-cell-p (first t)))
               (dist-cell-n (first t))]
              [else
               (lookup-in-table (rest t) p)])]))
  
  (check-expect (lookup-in-table empty (make-posn 1 2)) ')
  (check-expect (lookup-in-table (list (make-dist-cell (make-posn 1 2) 3))
                                 (make-posn 1 2))
                3)
  (check-expect (lookup-in-table (list (make-dist-cell (make-posn 2 1) 3))
                                 (make-posn 1 2))
                ')
  
  ; ;; on-boundary? : posn number -> boolean
  (define (on-boundary? p board-size)
    (or (= (posn-x p) 0)
        (= (posn-y p) 0)
        (= (posn-x p) (- board-size 1))
        (= (posn-y p) (- board-size 1))))
  
  (check-expect (on-boundary? (make-posn 0 1) 13) true)
  (check-expect (on-boundary? (make-posn 1 0) 13) true)
  (check-expect (on-boundary? (make-posn 12 1) 13) true)
  (check-expect (on-boundary? (make-posn 1 12) 13) true)
  (check-expect (on-boundary? (make-posn 1 1) 13) false)
  (check-expect (on-boundary? (make-posn 10 10) 13) false)
  
  ; ;; adjacent : posn number -> (listof posn)
  (define (adjacent p board-size)
    (local [(define x (posn-x p))
            (define y (posn-y p))]
      (filter (lambda (x) (in-bounds? x board-size))
              (cond
                [(even? y)
                 (list (make-posn (- x 1) (- y 1))
                       (make-posn x (- y 1))
                       (make-posn (- x 1) y)
                       (make-posn (+ x 1) y)
                       (make-posn (- x 1) (+ y 1))
                       (make-posn x (+ y 1)))]
                [else
                 (list (make-posn x (- y 1))
                       (make-posn (+ x 1) (- y 1))
                       (make-posn (- x 1) y)
                       (make-posn (+ x 1) y)
                       (make-posn x (+ y 1))
                       (make-posn (+ x 1) (+ y 1)))]))))
  
  (check-expect (adjacent (make-posn 1 1) 11)
                (list (make-posn 1 0)
                      (make-posn 2 0)
                      (make-posn 0 1)
                      (make-posn 2 1)
                      (make-posn 1 2)
                      (make-posn 2 2)))
  (check-expect (adjacent (make-posn 2 2) 11)
                (list (make-posn 1 1)
                      (make-posn 2 1)
                      (make-posn 1 2)
                      (make-posn 3 2)
                      (make-posn 1 3)
                      (make-posn 2 3)))
  
  ; ;; in-bounds? : posn number -> boolean
  (define (in-bounds? p board-size)
    (and (<= 0 (posn-x p) (- board-size 1))
         (<= 0 (posn-y p) (- board-size 1))
         (not (equal? p (make-posn 0 0)))
         (not (equal? p (make-posn 0 (- board-size 1))))))
  (check-expect (in-bounds? (make-posn 0 0) 11) false)
  (check-expect (in-bounds? (make-posn 0 1) 11) true)
  (check-expect (in-bounds? (make-posn 1 0) 11) true)
  (check-expect (in-bounds? (make-posn 10 10) 11) true)
  (check-expect (in-bounds? (make-posn 0 -1) 11) false)
  (check-expect (in-bounds? (make-posn -1 0) 11) false)
  (check-expect (in-bounds? (make-posn 0 11) 11) false)
  (check-expect (in-bounds? (make-posn 11 0) 11) false)
  (check-expect (in-bounds? (make-posn 10 0) 11) true)
  (check-expect (in-bounds? (make-posn 0 10) 11) false)
  
  ; ;; min-l : (listof number-or-symbol) -> number-or-symbol
  (define (min-l ls) (foldr (lambda (x y) (min/f x y)) ' ls))
  (check-expect (min-l (list)) ')
  (check-expect (min-l (list 10 1 12)) 1)
  
  ; ;; <=/f : (number or '∞) (number or '∞) -> boolean
  (define (<=/f a b) (equal? a (min/f a b)))
  (check-expect (<=/f 1 2) true)
  (check-expect (<=/f 2 1) false)
  (check-expect (<=/f ' 1) false)
  (check-expect (<=/f 1 ') true)
  (check-expect (<=/f ' ') true)
  
  ; ;; min/f : (number or '∞) (number or '∞) -> (number or '∞)
  (define (min/f x y)
    (cond
      [(equal? x ') y]
      [(equal? y ') x]
      [else (min x y)]))
  (check-expect (min/f ' 1) 1)
  (check-expect (min/f 1 ') 1)
  (check-expect (min/f ' ') ')
  (check-expect (min/f 1 2) 1)
  
  ; ;; add1/f : number or '∞ -> number or '∞
  (define (add1/f n)
    (cond
      [(equal? n ') ']
      [else (add1 n)]))
  (check-expect (add1/f 1) 2)
  (check-expect (add1/f ') ')
  
  ; ;                                          
  ; ;                                          
  ; ;                                          
  ; ;                                          
  ; ;                                          
  ; ;          ;;;;;  ;;;;         ;;;;;;      
  ; ;            ;;;  ;;;;;          ;;;;      
  ; ;            ;;;                 ;;;       
  ; ;    ;;;;;;  ;;;     ;   ;;;;;;  ;;;  ;;;; 
  ; ;   ;;; ;;;; ;;; ;;;;;  ;;; ;;;; ;;; ;;;;;;
  ; ;  ;;; ;;;;; ;;;   ;;; ;;; ;;;;; ;;; ;;;;; 
  ; ;  ;;;  ;;;; ;;;   ;;; ;;;  ;;;; ;;;;      
  ; ;  ;;;       ;;    ;;; ;;;        ;;;;;;   
  ; ;  ;;;    ; ;;;   ;;;; ;;;    ;   ;;    ;; 
  ; ;   ;;;  ;  ;;;; ;;;;;;;;;;  ;  ;;;;;  ;;;;
  ; ;    ;;;;                ;;;;              
  ; ;                                          
  ; ;                                          
  ; ;                                          
  
  
  (define (clack world x y evt)
    (cond
      [(and (equal? evt 'button-up)
            (equal? 'playing (world-state world))
            (point-in-circle? (world-board world) x y))
       (move-cat
        (make-world (add-obstacle (world-board world) x y)
                    (world-cat world)
                    (world-state world)
                    (world-size world)))]
      [else
       world]))
  
  (check-expect (clack (make-world '() (make-posn 0 0) 'playing 1)
                       10
                       10
                       'button-down)
                (make-world '() (make-posn 0 0) 'playing 1))
  
  (check-expect (clack (make-world '() (make-posn 0 0) 'playing 1)
                       0
                       0
                       'button-up)
                (make-world '() (make-posn 0 0) 'playing 1))
  
  (check-expect (clack (make-world '() (make-posn 0 0) 'cat-lost 1)
                       10
                       10
                       'button-up)
                (make-world '() (make-posn 0 0) 'cat-lost 1))
  (check-expect (clack
                 (make-world
                  (list (make-cell (make-posn 1 0) false)
                        (make-cell (make-posn 2 0) true)
                        (make-cell (make-posn 0 1) true)
                        (make-cell (make-posn 1 1) false)
                        (make-cell (make-posn 2 1) true)
                        (make-cell (make-posn 1 2) true)
                        (make-cell (make-posn 2 2) true))
                  (make-posn 1 1)
                  'playing
                  3)
                 (cell-center-x (make-posn 1 0))
                 (cell-center-y (make-posn 1 0))
                 'button-up)
                (make-world
                 (list (make-cell (make-posn 1 0) true)
                       (make-cell (make-posn 2 0) true)
                       (make-cell (make-posn 0 1) true)
                       (make-cell (make-posn 1 1) false)
                       (make-cell (make-posn 2 1) true)
                       (make-cell (make-posn 1 2) true)
                       (make-cell (make-posn 2 2) true))
                 (make-posn 1 1)
                 'cat-lost
                 3))
  
  ; ;; move-cat : world -> world
  (define (move-cat world)
    (local [(define cat-position (world-cat world))
            (define table (build-table/fast world))
            (define neighbors (adjacent cat-position (world-size world)))
            (define next-cat-positions
              (find-best-positions neighbors
                                   (map (lambda (p) (lookup-in-table table p))
                                        neighbors)))
            (define next-cat-position
              (cond
                [(boolean? next-cat-positions) false]
                [else
                 (list-ref next-cat-positions
                           (random (length next-cat-positions)))]))]
      (make-world (world-board world)
                  (cond
                    [(boolean? next-cat-position)
                     cat-position]
                    [else next-cat-position])
                  (cond
                    [(boolean? next-cat-position)
                     'cat-lost]
                    [(on-boundary? next-cat-position (world-size world))
                     'cat-won]
                    [else 'playing])
                  (world-size world))))
  
  
  (check-expect
   (move-cat
    (make-world (list (make-cell (make-posn 1 0) false)
                      (make-cell (make-posn 2 0) false)
                      (make-cell (make-posn 3 0) false)
                      (make-cell (make-posn 4 0) false)
  
                      (make-cell (make-posn 0 1) false)
                      (make-cell (make-posn 1 1) true)
                      (make-cell (make-posn 2 1) true)
                      (make-cell (make-posn 3 1) false)
                      (make-cell (make-posn 4 1) false)
  
                      (make-cell (make-posn 0 2) false)
                      (make-cell (make-posn 1 2) true)
                      (make-cell (make-posn 2 2) false)
                      (make-cell (make-posn 3 2) true)
                      (make-cell (make-posn 4 2) false)
  
                      (make-cell (make-posn 0 3) false)
                      (make-cell (make-posn 1 3) true)
                      (make-cell (make-posn 2 3) false)
                      (make-cell (make-posn 3 3) false)
                      (make-cell (make-posn 4 3) false)
  
                      (make-cell (make-posn 1 4) false)
                      (make-cell (make-posn 2 4) false)
                      (make-cell (make-posn 3 4) false)
                      (make-cell (make-posn 4 4) false))
                (make-posn 2 2)
                'playing
                5))
   (make-world (list (make-cell (make-posn 1 0) false)
                     (make-cell (make-posn 2 0) false)
                     (make-cell (make-posn 3 0) false)
                     (make-cell (make-posn 4 0) false)
  
                     (make-cell (make-posn 0 1) false)
                     (make-cell (make-posn 1 1) true)
                     (make-cell (make-posn 2 1) true)
                     (make-cell (make-posn 3 1) false)
                     (make-cell (make-posn 4 1) false)
  
                     (make-cell (make-posn 0 2) false)
                     (make-cell (make-posn 1 2) true)
                     (make-cell (make-posn 2 2) false)
                     (make-cell (make-posn 3 2) true)
                     (make-cell (make-posn 4 2) false)
  
                     (make-cell (make-posn 0 3) false)
                     (make-cell (make-posn 1 3) true)
                     (make-cell (make-posn 2 3) false)
                     (make-cell (make-posn 3 3) false)
                     (make-cell (make-posn 4 3) false)
  
                     (make-cell (make-posn 1 4) false)
                     (make-cell (make-posn 2 4) false)
                     (make-cell (make-posn 3 4) false)
                     (make-cell (make-posn 4 4) false))
               (make-posn 2 3)
               'playing
               5))
  
  ; ;; find-best-positions : (nelistof posn) (nelistof number or '∞) -> (nelistof posn) or false
  (define (find-best-positions posns scores)
    (local [(define best-score (foldl (lambda (x sofar)
                                        (if (<=/f x sofar)
                                            x
                                            sofar))
                                      (first scores)
                                      (rest scores)))]
      (cond
        [(symbol? best-score) false]
        [else
         (map
          second
          (filter (lambda (x) (equal? (first x) best-score))
                  (map list scores posns)))])))
  (check-expect (find-best-positions (list (make-posn 0 0)) (list 1))
                (list (make-posn 0 0)))
  (check-expect (find-best-positions (list (make-posn 0 0)) (list '))
                false)
  (check-expect (find-best-positions (list (make-posn 0 0)
                                           (make-posn 1 1))
                                     (list 1 2))
                (list (make-posn 0 0)))
  (check-expect (find-best-positions (list (make-posn 0 0)
                                           (make-posn 1 1))
                                     (list 1 1))
                (list (make-posn 0 0)
                      (make-posn 1 1)))
  (check-expect (find-best-positions (list (make-posn 0 0)
                                           (make-posn 1 1))
                                     (list ' 2))
                (list (make-posn 1 1)))
  (check-expect (find-best-positions (list (make-posn 0 0)
                                           (make-posn 1 1))
                                     (list ' '))
                false)
  
  ; ;; add-obstacle : board number number -> board
  (define (add-obstacle board x y)
    (cond
      [(empty? board) board]
      [else
       (local [(define cell (first board))
               (define cx (cell-center-x (cell-p cell)))
               (define cy (cell-center-y (cell-p cell)))]
         (cond
           [(and (<= (- cx circle-radius) x (+ cx circle-radius))
                 (<= (- cy circle-radius) y (+ cy circle-radius)))
            (cons (make-cell (cell-p cell) true)
                  (rest board))]
           [else
            (cons cell (add-obstacle (rest board) x y))]))]))
  
  (check-expect (add-obstacle (list (make-cell (make-posn 0 0) false))
                              circle-spacing circle-spacing)
                (list (make-cell (make-posn 0 0) true)))
  (check-expect (add-obstacle (list (make-cell (make-posn 0 0) false)) 100 100)
                (list (make-cell (make-posn 0 0) false)))
  (check-expect (add-obstacle (list (make-cell (make-posn 0 0) false)
                                    (make-cell (make-posn 0 1) false))
                              circle-spacing circle-spacing)
                (list (make-cell (make-posn 0 0) true)
                      (make-cell (make-posn 0 1) false)))
  
  ; ;; point-in-circle? : board number number -> boolean
  (define (point-in-circle? board x y)
    (cond
      [(empty? board) false]
      [else
       (local [(define cell (first board))
               (define center (+ (cell-center-x (cell-p cell))
                                 (* (sqrt -1) (cell-center-y (cell-p cell)))))
               (define p (+ x (* (sqrt -1) y)))]
         (or (<= (magnitude (- center p)) circle-radius)
             (point-in-circle? (rest board) x y)))]))
  (check-expect (point-in-circle? empty 0 0) false)
  (check-expect (point-in-circle? (list (make-cell (make-posn 0 0) false))
                                  (cell-center-x (make-posn 0 0))
                                  (cell-center-y (make-posn 0 0)))
                true)
  (check-expect (point-in-circle? (list (make-cell (make-posn 0 0) false))
                                  0 0)
                false)
  
  
  
  
  
  ; ;                                
  ; ;                                
  ; ;                                
  ; ;                                
  ; ;                                
  ; ;                        ;;;;    
  ; ;                        ;;;     
  ; ;                        ;;;   ; 
  ; ;    ;;;;;;   ;;;;   ;;;;;;;;;;; 
  ; ;   ;;; ;;;; ;;;;;;;;;   ;;;   ;;
  ; ;  ;;; ;;;;;;;;;;;;;;;  ;;;      
  ; ;  ;;;  ;;;;;;;    ;;;  ;;; ;;;; 
  ; ;  ;;;      ;;    ;;;;  ;;; ;;;;;
  ; ;  ;;;    ; ;;;;;;;;;;  ;;;  ;;;;
  ; ;   ;;;  ;  ;;;;;;;;;;; ;;;   ;; 
  ; ;    ;;;;    ;;;;;       ;;;;;   
  ; ;                                
  ; ;                                
  ; ;                                
  
  
  ; ;; cat : symbol -> image
  (define (cat mode)
    (local [(define face-color
              (cond
                [(symbol=? mode 'sad) 'pink]
                [else 'lightgray]))
  
            (define left-ear (regular-polygon 3 8 'solid 'black (/ pi -3)))
            (define right-ear (regular-polygon 3 8 'solid 'black 0))
            (define ear-x-offset 14)
            (define ear-y-offset 9)
  
            (define eye (overlay (ellipse 12 8 'solid 'black)
                                 (ellipse 6 4 'solid 'limegreen)))
            (define eye-x-offset 8)
            (define eye-y-offset 3)
  
            (define nose (regular-polygon 3 5 'solid 'black (/ pi 2)))
  
            (define mouth-happy
              (overlay (ellipse 8 8 'solid face-color)
                       (ellipse 8 8 'outline 'black)
                       (move-pinhole
                        (rectangle 10 5 'solid face-color)
                        0
                        4)))
            (define mouth-no-expression
              (overlay (ellipse 8 8 'solid face-color)
                       (ellipse 8 8 'outline face-color)
                       (rectangle 10 5 'solid face-color)))
  
            (define mouth
              (cond
                [(symbol=? mode 'happy) mouth-happy]
                [else mouth-no-expression]))
            (define mouth-x-offset 4)
            (define mouth-y-offset -5)]
  
      (add-line
       (add-line
        (add-line
         (add-line
          (add-line
           (add-line
            (overlay (move-pinhole left-ear (- ear-x-offset) ear-y-offset)
                     (move-pinhole right-ear (- ear-x-offset 1) ear-y-offset)
                     (ellipse 40 26 'solid 'black)
                     (ellipse 36 22 'solid face-color)
                     (move-pinhole mouth (- mouth-x-offset) mouth-y-offset)
                     (move-pinhole mouth mouth-x-offset mouth-y-offset)
                     (move-pinhole eye (- eye-x-offset) eye-y-offset)
                     (move-pinhole eye eye-x-offset eye-y-offset)
                     (move-pinhole nose -1 -4))
            6 4 30 12 'black)
           6 4 30 4 'black)
          6 4 30 -4 'black)
         -6 4 -30 12 'black)
        -6 4 -30 4 'black)
       -6 4 -30 -4 'black)))
  
  (define happy-cat (cat 'happy))
  (define sad-cat (cat 'sad))
  (define thinking-cat (cat 'thinking))
  
  
  ; ;                                                        
  ; ;                                                        
  ; ;                                                        
  ; ;                                                        
  ; ;                                                        
  ; ;   ;;;;            ;;;;   ;;;;     ;;;;           ;;;;; 
  ; ;   ;;;;;           ;;;;;  ;;;      ;;;;;            ;;; 
  ; ;                          ;;;   ;                   ;;; 
  ; ;      ;;;;;;  ;;      ; ;;;;;;;;;     ;   ;;;;   ;; ;;; 
  ; ;  ;;;;;  ;;; ;;;; ;;;;;   ;;;   ;;;;;;;  ;;;;;;;;;  ;;; 
  ; ;    ;;;  ;;; ;;;;   ;;;  ;;;        ;;; ;;;;;;;;;;  ;;; 
  ; ;    ;;;  ;;;; ;;;   ;;;  ;;; ;;;;   ;;; ;;;    ;;;  ;;; 
  ; ;    ;;;  ;;;  ;;;   ;;;  ;;; ;;;;;  ;;; ;;    ;;;;  ;;  
  ; ;   ;;;;  ;;;  ;;;  ;;;;  ;;;  ;;;; ;;;; ;;;;;;;;;; ;;;  
  ; ;  ;;;;;;;;;;  ;;; ;;;;;;;;;;   ;; ;;;;;;;;;;;;;;;;;;;;; 
  ; ;             ;;;;         ;;;;;          ;;;;;          
  ; ;               ;;;                                      
  ; ;                                                        
  ; ;                                                        
  ; ;                                                    
  ; ;                                                    
  ; ;                                                    
  ; ;                                                    
  ; ;                                                    
  ; ;  ;;;;;                                           ;;
  ; ;   ;;;;                                        ;;;; 
  ; ;   ;;;                                          ;;; 
  ; ;   ;;; ;;;    ;;;;;    ;;;;   ;;   ; ;;;;   ;;; ;;; 
  ; ;   ;;;;;;;;  ;;;;;;;  ;;;;;;;;;;;;;;; ;;;; ;;;;;;;; 
  ; ;   ;;;;;;;; ;;;;;;;; ;;;;;;;;;;  ;;; ;;;;;;;;;;;;;; 
  ; ;   ;;;   ;; ;;     ; ;;;    ;;;  ;;; ;;;; ;;;   ;;; 
  ; ;   ;;;   ;; ;     ;; ;;    ;;;;  ;;;      ;;   ;;;; 
  ; ;   ;;;;;;;  ;;;;;;;; ;;;;;;;;;;  ;;;      ;;;;;;;;  
  ; ;  ;;;;;;;    ;;;;;;  ;;;;;;;;;;;;;;;;;;    ;;;;;;;;;
  ; ;              ;;;;    ;;;;;                         
  ; ;                                                    
  ; ;                                                    
  ; ;                                                    
  
  ; ;; append-all : (listof (list X)) -> (listof X)
  (define (append-all ls)
    (foldr append empty ls))
  
  (check-expect (append-all empty) empty)
  (check-expect (append-all (list (list 1 2 3))) (list 1 2 3))
  (check-expect (append-all (list (list 1) (list 2) (list 3)))
                (list 1 2 3))
  
  ; ;; add-n-random-blocked-cells : number (listof cell) number -> (listof cell)
  (define (add-n-random-blocked-cells n all-cells board-size)
    (cond
      [(zero? n) all-cells]
      [else
       (local [(define unblocked-cells
                 (filter (lambda (x)
                           (let ([cat-cell? (and (= (posn-x (cell-p x)) (quotient board-size 2))
                                                 (= (posn-y (cell-p x)) (quotient board-size 2)))])
  
                             (and (not (cell-blocked? x))
                                  (not cat-cell?))))
                         all-cells))
               (define to-block (list-ref unblocked-cells
                                          (random (length unblocked-cells))))]
         (add-n-random-blocked-cells
          (sub1 n)
          (map (lambda (c) (if (equal? to-block c)
                               (make-cell (cell-p c) true)
                               c))
               all-cells)
          board-size))]))
  
  (check-expect (add-n-random-blocked-cells 0 (list (make-cell (make-posn 0 0) true)) 10)
                (list (make-cell (make-posn 0 0) true)))
  (check-expect (add-n-random-blocked-cells 1 (list (make-cell (make-posn 0 0) false)) 10)
                (list (make-cell (make-posn 0 0) true)))
  
  (define dummy
    (local
      [(define board-size 11)
       (define initial-board
         (add-n-random-blocked-cells
          6
          (filter
           (lambda (c)
             (not (and (= 0 (posn-x (cell-p c)))
                       (or (= 0 (posn-y (cell-p c)))
                           (= (- board-size 1)
                              (posn-y (cell-p c)))))))
           (append-all
            (build-list
             board-size
             (lambda (i)
               (build-list
                board-size
                (lambda (j)
                  (make-cell (make-posn i j)
                             false)))))))
          board-size))
       (define initial-world
         (make-world initial-board
                     (make-posn (quotient board-size 2)
                                (quotient board-size 2))
                     'playing
                     board-size))]
  
      (and
       (big-bang (world-width board-size)
                 (world-height board-size)
                 1
                 initial-world)
       (on-redraw world->image)
       (on-mouse-event clack))))