From 723dc269030a666b78edd4681c18ef5b7d8578a0 Mon Sep 17 00:00:00 2001 From: Robby Findler Date: Tue, 17 Feb 2009 01:09:06 +0000 Subject: [PATCH] I think I'm finally getting the hang of this stuff ... svn: r13686 --- .../games/chat-noir/chat-noir-literate.ss | 885 ++++++++++-------- 1 file changed, 473 insertions(+), 412 deletions(-) diff --git a/collects/games/chat-noir/chat-noir-literate.ss b/collects/games/chat-noir/chat-noir-literate.ss index 661bb91bdb..bf1bcb7b79 100644 --- a/collects/games/chat-noir/chat-noir-literate.ss +++ b/collects/games/chat-noir/chat-noir-literate.ss @@ -113,10 +113,13 @@ A @scheme[cell] is a structure with two fields: (define-struct cell (p blocked?) #:transparent)] The first field contains a @scheme[posn] struct. The coordinates of -the posn indicate a position on the hexagonal grid. The @tt{y} field +the posn indicate a position on the hexagonal grid. +This program reprsents the hexagon grid as a series of rows that +are offset from each other by 1/2 the size of the each cell. +The @tt{y} field of the @scheme[posn] refers to the row of the cell, and the @tt{x} coordinate the position in the row. This means that, for example, -@scheme[(make-posn 0 1)] is centered above @scheme[(make-posn 1 0)] +@scheme[(make-posn 1 0)] is centered above @scheme[(make-posn 1 0)] and @scheme[(make-posn 1 1)]. (See @scheme[cell-center-x] and @scheme[cell-center-y] below for the conversion of those positions to screen coordinates.) @@ -126,63 +129,29 @@ clicked on, thus blocking the cat from stepping there. The @scheme[empty-board] function builds a list of @scheme[cell]s that correspond to an empty board. For example, here's what an empty -3x3 board looks like, as a list of cells. +7x7 board looks like, as a list of cells. -@chunk[ - - (test (empty-world 3) - (make-world (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) - 'playing - 3 - (make-posn 0 0) - false))] - -and here is what that board look like, when rendered. - -@image["3x3-empty-board.png"] -@image["5x5-empty-board.png"] @image["7x7-empty-board.png"] -@chunk[ - ;; empty-board : number -> (listof cell) - (define (empty-board board-size) - (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))))))))] +It contains 7 rows and, with the exception of the first and last rows, +each row contains 7 cells. Notice how the even and odd rows are offset +from each other by 1/2 of the size of the cell. +The first and last row are missing their left-most cells +because those cells are useless, from the perspective of the gameplay, +Specifically, all of the neighbors of the missing cells +are also on the boundary and thus +the cat would win if it ever steps on one of those neighboring cells, +ending the game. -@chunk[ - - ;; empty-world : number -> world - (define (empty-world board-size) - (make-world (empty-board board-size) - (make-posn (quotient board-size 2) - (quotient board-size 2)) - 'playing - board-size - (make-posn 0 0) - false))] +The 3x3 board also has the same property that it consists of three +rows, each with three cells, but where the first and last row are missing +their left-most cells. -@chunk[ +@image["3x3-empty-board.png"] + +And here is how that board looks as a list of cells. + +@chunk[ (test (empty-board 3) (list @@ -194,6 +163,73 @@ and here is what that board look like, when rendered. (make-cell (make-posn 2 1) false) (make-cell (make-posn 2 2) false)))] +The @scheme[empty-board] function consists +of two (nested) calls to @scheme[build-list] +that build a list of lists of cells, one for +each pair of coordinates between @scheme[0] +and @scheme[board-size]. Then, @scheme[append] +flattens the nested lists and the +@scheme[filter] expression removes the corners. + +@chunk[ + ;; empty-board : number -> (listof cell) + (define (empty-board board-size) + (filter + (not-corner? board-size) + (apply + append + (build-list + board-size + (lambda (i) + (build-list + board-size + (lambda (j) + (make-cell (make-posn i j) + false)))))))) + + (define ((not-corner? board-size) c) + (not (and (= 0 (posn-x (cell-p c))) + (or (= 0 (posn-y (cell-p c))) + (= (- board-size 1) + (posn-y (cell-p c)))))))] + +Building an empty world is simply +a matter of building an empty board, finding +the initial position of the cat and filling +in all of the fields of the @scheme[world] struct. +For example, this is the empty world of size @scheme[3]. +It puts the cat at @scheme[(make-posn 1 1)], +sets the state to @scheme['playing], records the +size @scheme[3], and sets the current mouse position +to @scheme[false] and the state of the ``h'' key to +@scheme[false]. + +@chunk[ + + (test (empty-world 3) + (make-world (empty-board 3) + (make-posn 1 1) + 'playing + 3 + false + false))] + + +The @scheme[empty-world] function +generalizes the exmaple by computing the +cats initial position as the center spot on the board. + +@chunk[ + + (define (empty-world board-size) + (make-world (empty-board board-size) + (make-posn (quotient board-size 2) + (quotient board-size 2)) + 'playing + board-size + false + false))] + @section{Graph} @@ -212,9 +248,10 @@ X parts .... @chunk[ + + - @@ -241,10 +278,31 @@ which is a list of @scheme[dist-cell] structs: Each @tt{p} field in the @scheme[dist-cell] is a position on the board and the @tt{n} field is a natural number or @scheme['∞], indicating the distance of the shortest path from the node to some fixed point on -the board. The fixed point is not represented in the -@scheme[distance-map], but is required when constructing one. +the board. -The @scheme[build-bfs-table] accepts a world and +The @scheme[build-bfs-table] accepts a world and a cell +(indicating the fixed point) +and returns a distance map encoding the distance to that cell. +For example, here is the distance map for the distance to the boundary. + +@chunk[ + (test/set (build-bfs-table (empty-world 3) + 'boundary) + (list + (make-dist-cell 'boundary 0) + + (make-dist-cell (make-posn 1 0) 1) + (make-dist-cell (make-posn 2 0) 1) + + (make-dist-cell (make-posn 0 1) 1) + (make-dist-cell (make-posn 1 1) 2) + (make-dist-cell (make-posn 2 1) 1) + + (make-dist-cell (make-posn 1 2) 1) + (make-dist-cell (make-posn 2 2) 1)))] + +The boundary is zero steps away; each of the cells that are on the boundary +are one step away and the center is two steps away. The core of the breadth-first search is this function, @scheme[bst]. It accepts a queue of the pending nodes to visit @@ -291,29 +349,6 @@ The @scheme[build-bfs-table] function packages up @scheme[bfs] function. It accepts a @tt{world} and an initial position and returns a @scheme[distance-table]. -As an example, here is one of the test cases. It supplies -an empty world of size @scheme[3] to @scheme[build-bfs-table] -and @scheme['boundary], thus asking for the distance from -the boundary to each cell. - -@chunk[ - (test/set (build-bfs-table (empty-world 3) - 'boundary) - (list - (make-dist-cell 'boundary 0) - - (make-dist-cell (make-posn 1 0) 1) - (make-dist-cell (make-posn 2 0) 1) - - (make-dist-cell (make-posn 0 1) 1) - (make-dist-cell (make-posn 1 1) 2) - (make-dist-cell (make-posn 2 1) 1) - - (make-dist-cell (make-posn 1 2) 1) - (make-dist-cell (make-posn 2 2) 1)))] - -The result is a list - @chunk[ (define (build-bfs-table world init-point) @@ -327,16 +362,365 @@ The result is a list As you can see, the first thing it does is bind the free variable in @scheme[bfs] to the result of calling the @scheme[neighbors] function (defined in the chunk -@chunkref[]) and then it has the @scheme[bfs] function. In the body - -and finally it calls the bfs function +@chunkref[]) and then it has the @chunkref[] chunk. In the body +it calls the @scheme[bfs] function and then transforms the result, using -@scheme[hash-map]. +@scheme[hash-map], into a list of @scheme[cell]s. + +As far as the @scheme[build-bfs-table] function goes, +all of the information specific to Chat Noir is +encoded in the neighbors function. +It accepts a world and returns a function +that computes the neighbors of the boundary +and of nodes. + +For example, @scheme[(make-posn 1 0)] has four +neighbors: + +@chunk[ + (test ((neighbors (empty-world 7)) (make-posn 1 0)) + (list 'boundary + (make-posn 2 0) + (make-posn 0 1) + (make-posn 1 1)))] + +and @scheme[(make-posn 0 1)] has four neighbors: + +@chunk[ + (test ((neighbors (empty-world 7)) (make-posn 0 1)) + (list 'boundary + (make-posn 1 0) + (make-posn 1 1) + (make-posn 0 2) + (make-posn 1 2)))] + +as you can see from the pictures of the empty boards above. + +This is the neighbors function. It first accepts a @scheme[world] +and then builds a list of the blocked cells in the world and a +list of the cells that are on the boundary (and not blocked). + +The result is a function that accepts a @scheme[posn] or @scheme['boundary]. +If @scheme[p] is blocked, the function returns the empty list. If it is +on the boundary, the function simply returns @scheme[boundary-cells]. +Finally, + +@chunk[ +;; neighbors : world -> (or/c 'boundary posn) -> (listof (or/c 'boundary posn)) +(define (neighbors w) + (define blocked + (map cell-p + (filter (lambda (c) + (or (cell-blocked? c) + (equal? (cell-p c) (world-mouse-posn w)))) + (world-board w)))) + (define boundary-cells + (filter (lambda (p) + (and (not (member p blocked)) + (on-boundary? p (world-size w)))) + (map cell-p (world-board w)))) + (λ (p) + (cond + [(member p blocked) + '()] + [(equal? p 'boundary) + boundary-cells] + [else + (let* ([x (posn-x p)] + [adjacent-posns + (filter (λ (x) (not (member x blocked))) + (adjacent p (world-size w)))] + [in-bounds + (filter (λ (x) (in-bounds? x (world-size w))) + adjacent-posns)]) + (cond + [(equal? in-bounds adjacent-posns) + in-bounds] + [else + (cons 'boundary in-bounds)]))])))] + +@chunk[ + (test ((neighbors (empty-world 11)) (make-posn 1 1)) + (adjacent (make-posn 1 1) 11)) + (test ((neighbors (empty-world 11)) (make-posn 2 2)) + (adjacent (make-posn 2 2) 11)) + (test ((neighbors (empty-world 3)) 'boundary) + (list (make-posn 0 1) + (make-posn 1 0) + (make-posn 1 2) + (make-posn 2 0) + (make-posn 2 1) + (make-posn 2 2))) + (test ((neighbors (make-world (list + (make-cell (make-posn 0 1) false) + (make-cell (make-posn 1 0) false) + (make-cell (make-posn 1 1) true) + (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) + 'playing + 3 + (make-posn 0 0) + false)) + (make-posn 1 1)) + '()) + (test ((neighbors (make-world (list + (make-cell (make-posn 0 1) false) + (make-cell (make-posn 1 0) false) + (make-cell (make-posn 1 1) true) + (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) + 'playing + 3 + (make-posn 0 0) + false)) + (make-posn 1 0)) + (list 'boundary (make-posn 2 0) (make-posn 0 1))) + ] + +@chunk[ +;; 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)])]))] + +@chunk[ + +(test (lookup-in-table empty (make-posn 1 2)) '∞) +(test (lookup-in-table (list (make-dist-cell (make-posn 1 2) 3)) + (make-posn 1 2)) + 3) +(test (lookup-in-table (list (make-dist-cell (make-posn 2 1) 3)) + (make-posn 1 2)) + '∞)] + + +@chunk[ +;; on-cats-path? : world -> posn -> boolean +;; returns true when the posn is on the shortest path +;; from the cat to the edge of the board, in the given world +(define (on-cats-path? w) + (cond + [(world-h-down? w) + (let () + (define edge-distance-map (build-bfs-table w 'boundary)) + (define cat-distance-map (build-bfs-table w (world-cat w))) + (define cat-distance (lookup-in-table edge-distance-map + (world-cat w))) + (cond + [(equal? cat-distance '∞) + (lambda (p) false)] + [else + (lambda (p) + (equal? (+/f (lookup-in-table cat-distance-map p) + (lookup-in-table edge-distance-map p)) + cat-distance))]))] + [else + (lambda (p) false)]))] + +@chunk[ +(test ((on-cats-path? (make-world (empty-board 5) (make-posn 1 1) + 'playing 5 (make-posn 0 0) true)) + (make-posn 1 0)) + true) +(test ((on-cats-path? (make-world (empty-board 5) (make-posn 1 1) + 'playing 5 (make-posn 0 0) false)) + (make-posn 1 0)) + false) +(test ((on-cats-path? (make-world (empty-board 5) (make-posn 1 1) + 'playing 5 (make-posn 0 0) true)) + (make-posn 2 1)) + false) +(test ((on-cats-path? + (make-world (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) + 'cat-lost + 3 + (make-posn 0 0) + true)) + (make-posn 0 1)) + false)] + + + +@chunk[ +;; adjacent : posn number -> (listof posn) +;; returns a list of the posns that are adjacent to +;; `p' on an infinite hex grid +(define (adjacent p board-size) + (local [(define x (posn-x p)) + (define y (posn-y p))] + (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)))])))] + +@chunk[ +(test (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))) +(test (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)))] + +@chunk[ +;; 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))))] + +@chunk[ +(test (on-boundary? (make-posn 0 1) 13) true) +(test (on-boundary? (make-posn 1 0) 13) true) +(test (on-boundary? (make-posn 12 1) 13) true) +(test (on-boundary? (make-posn 1 12) 13) true) +(test (on-boundary? (make-posn 1 1) 13) false) +(test (on-boundary? (make-posn 10 10) 13) false)] + +@chunk[ + +;; 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))))))] + +@chunk[ +(test (in-bounds? (make-posn 0 0) 11) false) +(test (in-bounds? (make-posn 0 1) 11) true) +(test (in-bounds? (make-posn 1 0) 11) true) +(test (in-bounds? (make-posn 10 10) 11) true) +(test (in-bounds? (make-posn 0 -1) 11) false) +(test (in-bounds? (make-posn -1 0) 11) false) +(test (in-bounds? (make-posn 0 11) 11) false) +(test (in-bounds? (make-posn 11 0) 11) false) +(test (in-bounds? (make-posn 10 0) 11) true) +(test (in-bounds? (make-posn 0 10) 11) false)] + +@chunk[ +;; <=/f : (number or '∞) (number or '∞) -> boolean +(define (<=/f a b) + (cond + [(equal? b '∞) true] + [(equal? a '∞) false] + [else (<= a b)])) + +(define (+/f x y) + (cond + [(or (equal? x '∞) (equal? y '∞)) + '∞] + [else + (+ x y)]))] + +@chunk[ +(test (<=/f 1 2) true) +(test (<=/f 2 1) false) +(test (<=/f '∞ 1) false) +(test (<=/f 1 '∞) true) +(test (<=/f '∞ '∞) true) + +(test (+/f '∞ '∞) '∞) +(test (+/f '∞ 1) '∞) +(test (+/f 1 '∞) '∞) +(test (+/f 1 2) 3)] + +@section{Tests} + +@chunk[ + +(define-syntax (test stx) + (syntax-case stx () + [(_ actual expected) + (with-syntax ([line (syntax-line stx)]) + #'(test/proc (λ () actual) + (λ () expected) + equal? + line))])) + +(define-syntax (test/set stx) + (syntax-case stx () + [(_ actual expected) + (with-syntax ([line (syntax-line stx)]) + #'(test/proc (λ () actual) + (λ () expected) + (λ (x y) (same-sets? x y)) + line))])) + +(define test-count 0) +(define test-procs '()) + +(define (test/proc actual-thunk expected-thunk cmp line) + (set! test-procs + (cons + (λ () + (set! test-count (+ test-count 1)) + (let ([actual (actual-thunk)] + [expected (expected-thunk)]) + (unless (cmp actual expected) + (error 'check-expect "test ~a on line ~a failed:\n ~s\n ~s\n" + test-count + line + actual + expected)))) + test-procs))) + + +(define (same-sets? l1 l2) + (and (andmap (lambda (e1) (member e1 l2)) l1) + (andmap (lambda (e2) (member e2 l1)) l2) + #t)) + +(test (same-sets? (list) (list)) true) +(test (same-sets? (list) (list 1)) false) +(test (same-sets? (list 1) (list)) false) +(test (same-sets? (list 1 2) (list 2 1)) true) + +(define (run-tests) + (for-each (λ (t) (t)) (reverse test-procs)) + (printf "passed ~s tests\n" test-count) + (flush-output))] -The test suite for the @scheme[build-bfs-table] function -uses @scheme[test/set] to avoid having to deal with the -ordering issues in @scheme[build-bfs-table]'s result. - @chunk[ (test/set (build-bfs-table (make-world (empty-board 3) (make-posn 1 1) @@ -503,329 +887,6 @@ ordering issues in @scheme[build-bfs-table]'s result. (make-posn 1 4)) 2)] -@chunk[ -;; 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)])]))] - -@chunk[ - -(test (lookup-in-table empty (make-posn 1 2)) '∞) -(test (lookup-in-table (list (make-dist-cell (make-posn 1 2) 3)) - (make-posn 1 2)) - 3) -(test (lookup-in-table (list (make-dist-cell (make-posn 2 1) 3)) - (make-posn 1 2)) - '∞)] - - -@chunk[ -;; on-cats-path? : world -> posn -> boolean -;; returns true when the posn is on the shortest path -;; from the cat to the edge of the board, in the given world -(define (on-cats-path? w) - (cond - [(world-h-down? w) - (local [(define edge-distance-map (build-bfs-table w 'boundary)) - (define cat-distance-map (build-bfs-table w (world-cat w))) - (define cat-distance (lookup-in-table edge-distance-map - (world-cat w)))] - (cond - [(equal? cat-distance '∞) - (lambda (p) false)] - [else - (lambda (p) - (equal? (+/f (lookup-in-table cat-distance-map p) - (lookup-in-table edge-distance-map p)) - cat-distance))]))] - [else - (lambda (p) false)]))] - -@chunk[ -(test ((on-cats-path? (make-world (empty-board 5) (make-posn 1 1) - 'playing 5 (make-posn 0 0) true)) - (make-posn 1 0)) - true) -(test ((on-cats-path? (make-world (empty-board 5) (make-posn 1 1) - 'playing 5 (make-posn 0 0) false)) - (make-posn 1 0)) - false) -(test ((on-cats-path? (make-world (empty-board 5) (make-posn 1 1) - 'playing 5 (make-posn 0 0) true)) - (make-posn 2 1)) - false) -(test ((on-cats-path? - (make-world (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) - 'cat-lost - 3 - (make-posn 0 0) - true)) - (make-posn 0 1)) - false)] - -@chunk[ -;; neighbors : world -> (or/c 'boundary posn) -> (listof (or/c 'boundary posn)) -;; computes the neighbors of a posn, for a given board size -(define (neighbors w) - (local [(define blocked - (map cell-p - (filter (lambda (c) - (or (cell-blocked? c) - (equal? (cell-p c) (world-mouse-posn w)))) - (world-board w)))) - (define boundary-cells - (filter (lambda (p) - (and (not (member p blocked)) - (on-boundary? p (world-size w)))) - (map cell-p (world-board w))))] - (lambda (p) - (cond - [(member p blocked) - '()] - [(equal? p 'boundary) - boundary-cells] - [else - (local [(define x (posn-x p)) - (define y (posn-y p)) - (define adjacent-posns (adjacent p (world-size w))) - (define in-bounds - (filter (lambda (x) (in-bounds? x (world-size w))) - adjacent-posns))] - (filter - (lambda (x) (not (member x blocked))) - (cond - [(equal? in-bounds adjacent-posns) - in-bounds] - [else - (cons 'boundary in-bounds)])))]))))] - -@chunk[ -(test ((neighbors (empty-world 11)) (make-posn 1 1)) - (adjacent (make-posn 1 1) 11)) -(test ((neighbors (empty-world 11)) (make-posn 2 2)) - (adjacent (make-posn 2 2) 11)) -(test ((neighbors (empty-world 3)) 'boundary) - (list (make-posn 0 1) - (make-posn 1 0) - (make-posn 1 2) - (make-posn 2 0) - (make-posn 2 1) - (make-posn 2 2))) -(test ((neighbors (empty-world 11)) (make-posn 1 0)) - (list 'boundary - (make-posn 2 0) - (make-posn 0 1) - (make-posn 1 1))) -(test ((neighbors (make-world (list - (make-cell (make-posn 0 1) false) - (make-cell (make-posn 1 0) false) - (make-cell (make-posn 1 1) true) - (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) - 'playing - 3 - (make-posn 0 0) - false)) - (make-posn 1 1)) - '()) -(test ((neighbors (make-world (list - (make-cell (make-posn 0 1) false) - (make-cell (make-posn 1 0) false) - (make-cell (make-posn 1 1) true) - (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) - 'playing - 3 - (make-posn 0 0) - false)) - (make-posn 1 0)) - (list 'boundary (make-posn 2 0) (make-posn 0 1))) -] - -@chunk[ -;; adjacent : posn number -> (listof posn) -;; returns a list of the posns that are adjacent to -;; `p' on an infinite hex grid -(define (adjacent p board-size) - (local [(define x (posn-x p)) - (define y (posn-y p))] - (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)))])))] - -@chunk[ -(test (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))) -(test (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)))] - -@chunk[ -;; 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))))] - -@chunk[ -(test (on-boundary? (make-posn 0 1) 13) true) -(test (on-boundary? (make-posn 1 0) 13) true) -(test (on-boundary? (make-posn 12 1) 13) true) -(test (on-boundary? (make-posn 1 12) 13) true) -(test (on-boundary? (make-posn 1 1) 13) false) -(test (on-boundary? (make-posn 10 10) 13) false)] - -@chunk[ - -;; 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))))))] - -@chunk[ -(test (in-bounds? (make-posn 0 0) 11) false) -(test (in-bounds? (make-posn 0 1) 11) true) -(test (in-bounds? (make-posn 1 0) 11) true) -(test (in-bounds? (make-posn 10 10) 11) true) -(test (in-bounds? (make-posn 0 -1) 11) false) -(test (in-bounds? (make-posn -1 0) 11) false) -(test (in-bounds? (make-posn 0 11) 11) false) -(test (in-bounds? (make-posn 11 0) 11) false) -(test (in-bounds? (make-posn 10 0) 11) true) -(test (in-bounds? (make-posn 0 10) 11) false)] - -@chunk[ -;; <=/f : (number or '∞) (number or '∞) -> boolean -(define (<=/f a b) - (cond - [(equal? b '∞) true] - [(equal? a '∞) false] - [else (<= a b)])) - -(define (+/f x y) - (cond - [(or (equal? x '∞) (equal? y '∞)) - '∞] - [else - (+ x y)]))] - -@chunk[ -(test (<=/f 1 2) true) -(test (<=/f 2 1) false) -(test (<=/f '∞ 1) false) -(test (<=/f 1 '∞) true) -(test (<=/f '∞ '∞) true) - -(test (+/f '∞ '∞) '∞) -(test (+/f '∞ 1) '∞) -(test (+/f 1 '∞) '∞) -(test (+/f 1 2) 3)] - -@section{Init Junk} - -@chunk[ -] - -@chunk[ - -(define-syntax (test stx) - (syntax-case stx () - [(_ actual expected) - (with-syntax ([line (syntax-line stx)]) - #'(test/proc (λ () actual) - (λ () expected) - equal? - line))])) - -(define-syntax (test/set stx) - (syntax-case stx () - [(_ actual expected) - (with-syntax ([line (syntax-line stx)]) - #'(test/proc (λ () actual) - (λ () expected) - (λ (x y) (same-sets? x y)) - line))])) - -(define test-count 0) -(define test-procs '()) - -(define (test/proc actual-thunk expected-thunk cmp line) - (set! test-procs - (cons - (λ () - (set! test-count (+ test-count 1)) - (let ([actual (actual-thunk)] - [expected (expected-thunk)]) - (unless (cmp actual expected) - (error 'check-expect "test ~a on line ~a failed:\n ~s\n ~s\n" - test-count - line - actual - expected)))) - test-procs))) - - -(define (same-sets? l1 l2) - (and (andmap (lambda (e1) (member e1 l2)) l1) - (andmap (lambda (e2) (member e2 l1)) l2) - #t)) - -(test (same-sets? (list) (list)) true) -(test (same-sets? (list) (list 1)) false) -(test (same-sets? (list 1) (list)) false) -(test (same-sets? (list 1 2) (list 2 1)) true) - -(define (run-tests) - (for-each (λ (t) (t)) (reverse test-procs)) - (printf "passed ~s tests\n" test-count) - (flush-output))] - @section{Everything Else}