some progress on the bus
svn: r13663
This commit is contained in:
parent
c6b92782e2
commit
e17f7347e7
|
@ -28,3 +28,5 @@ Problems:
|
||||||
eventually be merged together with ../scribblings/chat-noir.scrbl.
|
eventually be merged together with ../scribblings/chat-noir.scrbl.
|
||||||
|
|
||||||
- hyperlink bound top-level identifiers to their bindings?
|
- hyperlink bound top-level identifiers to their bindings?
|
||||||
|
|
||||||
|
- do unbound chunk ids signal syntax errors? How about unused ones?
|
||||||
|
|
|
@ -5,14 +5,46 @@
|
||||||
|
|
||||||
@title{Chat Noir}
|
@title{Chat Noir}
|
||||||
|
|
||||||
Chat Noir. What a game.
|
The goal of Chat Noir 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.
|
||||||
|
|
||||||
@chunk[#:part section <main>
|
To get some insight into the cat's behavior, hold down the ``h''
|
||||||
|
key. It will show you the cells that are on the cat's shortest path to
|
||||||
|
the edge, assuming that the cell underneath the mouse has been
|
||||||
|
blocked, so you can experiment to see how the shortest paths change
|
||||||
|
by moving your mouse around.
|
||||||
|
|
||||||
|
The game was inspired by this one the one at
|
||||||
|
@link["http://www.gamedesign.jp/flash/chatnoir/chatnoir.html"]{Game
|
||||||
|
Design} and has essentially the same rules. It also inspired the final
|
||||||
|
project for the introductory programming course at the University of
|
||||||
|
Chicago in the fall of 2008.
|
||||||
|
|
||||||
|
The remainder of this document explains the implementation of
|
||||||
|
the Chat Noir game.
|
||||||
|
|
||||||
|
@section{Overview}
|
||||||
|
|
||||||
|
Chat Noir is implemented using @link["http://www.htdp.org/"]{HtDP}'s world
|
||||||
|
library: @schememodname[htdp/world]. The program is divided up into
|
||||||
|
six parts: the world data definition, an implementation of breadth-first search,
|
||||||
|
code that handles drawing of the world, code that handles user input,
|
||||||
|
and some code that builds an initial world and starts the game.
|
||||||
|
|
||||||
|
@chunk[<main>
|
||||||
<init-junk>
|
<init-junk>
|
||||||
<data-definitions>
|
<data-definitions>
|
||||||
<graph>
|
<graph>
|
||||||
<everything-else>]
|
<everything-else>]
|
||||||
|
|
||||||
|
Each section also comes with a series of test cases that are collected into the
|
||||||
|
@scheme[<tests>] chunk at the end of the program in order to be run in a
|
||||||
|
sensible order, namely testing helper functions before testing the functions
|
||||||
|
that use them.
|
||||||
|
|
||||||
@section{The World}
|
@section{The World}
|
||||||
|
|
||||||
The main data structure for Chat Noir is @tt{world}.
|
The main data structure for Chat Noir is @tt{world}.
|
||||||
|
@ -75,10 +107,26 @@ The graph's nodes are the cells on the board, and there are edges
|
||||||
between each pair of adjacent cells, unless one of the cells is
|
between each pair of adjacent cells, unless one of the cells is
|
||||||
blocked, in which case there are no edges.
|
blocked, in which case there are no edges.
|
||||||
|
|
||||||
The breadth-first function constructs a @scheme[distance-map], which
|
The code for the breadth-first search is organized into
|
||||||
is a list of @scheme[dist-cell] structs:
|
X parts ....
|
||||||
|
|
||||||
@chunk[<graph>
|
@chunk[<graph>
|
||||||
|
<dist-cell>
|
||||||
|
<build-bfs-table>
|
||||||
|
<bfs-tests>
|
||||||
|
<same-sets>
|
||||||
|
<lookup-in-table>
|
||||||
|
<on-cats-path?>
|
||||||
|
<neighbors>
|
||||||
|
<adjacent>
|
||||||
|
<in-bounds?>
|
||||||
|
<on-boundary?>
|
||||||
|
<graph-the-rest>]
|
||||||
|
|
||||||
|
The breadth-first function constructs a @scheme[distance-map],
|
||||||
|
which is a list of @scheme[dist-cell] structs:
|
||||||
|
|
||||||
|
@chunk[<dist-cell>
|
||||||
(define-struct dist-cell (p n) #:transparent)]
|
(define-struct dist-cell (p n) #:transparent)]
|
||||||
|
|
||||||
Each @tt{p} field in the @scheme[dist-cell] is a position on the board
|
Each @tt{p} field in the @scheme[dist-cell] is a position on the board
|
||||||
|
@ -88,7 +136,12 @@ the board. The fixed point is not represented in the
|
||||||
@scheme[distance-map], but is required when constructing one.
|
@scheme[distance-map], but is required when constructing one.
|
||||||
|
|
||||||
The core of the breadth-first search is this function,
|
The core of the breadth-first search is this function,
|
||||||
@scheme[bst]. It accepts a @scheme[queue] and a
|
@scheme[bst]. It accepts a queue of the pending nodes to visit
|
||||||
|
and a @scheme[dist-table] that records the same information as a
|
||||||
|
@scheme[distance-map], but in an immutable hash-table. The
|
||||||
|
@scheme[dist-map] is an accumulator, recording the distances
|
||||||
|
to all of the nodes that have already been visited in the graph,
|
||||||
|
and is used here to speed up the compuation.
|
||||||
|
|
||||||
@chunk[<bfs>
|
@chunk[<bfs>
|
||||||
(define (bfs queue dist-table)
|
(define (bfs queue dist-table)
|
||||||
|
@ -102,21 +155,13 @@ The core of the breadth-first search is this function,
|
||||||
(define p (queue-ent-posn hd))]
|
(define p (queue-ent-posn hd))]
|
||||||
(bfs
|
(bfs
|
||||||
(append (rest queue)
|
(append (rest queue)
|
||||||
(map (lambda (p) (make-queue-ent p (+ dist 1)))
|
(map (λ (p) (make-queue-ent p (+ dist 1)))
|
||||||
(neighbors/w p)))
|
(neighbors/w p)))
|
||||||
(hash-set dist-table p dist)))]
|
(hash-set dist-table p dist)))]
|
||||||
[else
|
[else
|
||||||
(bfs (rest queue) dist-table)]))]))]
|
(bfs (rest queue) dist-table)]))]))]
|
||||||
|
|
||||||
@chunk[<graph>
|
@chunk[<build-bfs-table>
|
||||||
|
|
||||||
;; a distance-map is
|
|
||||||
;; (listof dist-cells)
|
|
||||||
|
|
||||||
;; a dist-cell is
|
|
||||||
;; - (make-dist-cell posn (number or '∞))
|
|
||||||
;(define-struct dist-cell (p n) #:transparent)
|
|
||||||
|
|
||||||
|
|
||||||
;; build-bfs-table : world (or/c 'boundary posn) -> distance-table
|
;; build-bfs-table : world (or/c 'boundary posn) -> distance-table
|
||||||
(define (build-bfs-table world init-point)
|
(define (build-bfs-table world init-point)
|
||||||
|
@ -132,7 +177,9 @@ The core of the breadth-first search is this function,
|
||||||
(bfs (list (make-queue-ent init-point 0))
|
(bfs (list (make-queue-ent init-point 0))
|
||||||
(make-immutable-hash/list-init))
|
(make-immutable-hash/list-init))
|
||||||
make-dist-cell)))
|
make-dist-cell)))
|
||||||
|
]
|
||||||
|
|
||||||
|
@chunk[<same-sets>
|
||||||
;; same-sets? : (listof X) (listof X) -> boolean
|
;; same-sets? : (listof X) (listof X) -> boolean
|
||||||
(define (same-sets? l1 l2)
|
(define (same-sets? l1 l2)
|
||||||
(and (andmap (lambda (e1) (member e1 l2)) l1)
|
(and (andmap (lambda (e1) (member e1 l2)) l1)
|
||||||
|
@ -143,12 +190,11 @@ The core of the breadth-first search is this function,
|
||||||
(check-expect (same-sets? (list) (list 1)) false)
|
(check-expect (same-sets? (list) (list 1)) false)
|
||||||
(check-expect (same-sets? (list 1) (list)) false)
|
(check-expect (same-sets? (list 1) (list)) false)
|
||||||
(check-expect (same-sets? (list 1 2) (list 2 1)) true)
|
(check-expect (same-sets? (list 1 2) (list 2 1)) true)
|
||||||
|
]
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
@chunk[<bfs-tests>
|
||||||
|
|
||||||
(check-expect (same-sets?
|
(check-expect (same-sets?
|
||||||
(build-bfs-table (make-world (empty-board 3) (make-posn 1 1)
|
(build-bfs-table (make-world (empty-board 3) (make-posn 1 1) 'playing 3 (make-posn 0 0) false)
|
||||||
'playing 3 (make-posn 0 0) false)
|
|
||||||
'boundary)
|
'boundary)
|
||||||
(list
|
(list
|
||||||
(make-dist-cell 'boundary 0)
|
(make-dist-cell 'boundary 0)
|
||||||
|
@ -332,8 +378,9 @@ The core of the breadth-first search is this function,
|
||||||
(make-posn 2 2))
|
(make-posn 2 2))
|
||||||
(make-posn 1 4))
|
(make-posn 1 4))
|
||||||
2)
|
2)
|
||||||
|
]
|
||||||
|
|
||||||
|
@chunk[<lookup-in-table>
|
||||||
;; lookup-in-table : distance-map posn -> number or '∞
|
;; lookup-in-table : distance-map posn -> number or '∞
|
||||||
;; looks for the distance as recorded in the table t,
|
;; looks for the distance as recorded in the table t,
|
||||||
;; if not found returns a distance of '∞
|
;; if not found returns a distance of '∞
|
||||||
|
@ -352,10 +399,11 @@ The core of the breadth-first search is this function,
|
||||||
3)
|
3)
|
||||||
(check-expect (lookup-in-table (list (make-dist-cell (make-posn 2 1) 3))
|
(check-expect (lookup-in-table (list (make-dist-cell (make-posn 2 1) 3))
|
||||||
(make-posn 1 2))
|
(make-posn 1 2))
|
||||||
'∞)
|
'∞)]
|
||||||
|
|
||||||
|
|
||||||
;; p : world -> posn -> boolean
|
@chunk[<on-cats-path?>
|
||||||
|
;; on-cats-path? : world -> posn -> boolean
|
||||||
;; returns true when the posn is on the shortest path
|
;; returns true when the posn is on the shortest path
|
||||||
;; from the cat to the edge of the board, in the given world
|
;; from the cat to the edge of the board, in the given world
|
||||||
(define (on-cats-path? w)
|
(define (on-cats-path? w)
|
||||||
|
@ -403,8 +451,9 @@ The core of the breadth-first search is this function,
|
||||||
(make-posn 0 0)
|
(make-posn 0 0)
|
||||||
true))
|
true))
|
||||||
(make-posn 0 1))
|
(make-posn 0 1))
|
||||||
false)
|
false)]
|
||||||
|
|
||||||
|
@chunk[<neighbors>
|
||||||
;; neighbors : world -> (or/c 'boundary posn) -> (listof (or/c 'boundary posn))
|
;; neighbors : world -> (or/c 'boundary posn) -> (listof (or/c 'boundary posn))
|
||||||
;; computes the neighbors of a posn, for a given board size
|
;; computes the neighbors of a posn, for a given board size
|
||||||
(define (neighbors w)
|
(define (neighbors w)
|
||||||
|
@ -486,8 +535,9 @@ The core of the breadth-first search is this function,
|
||||||
false))
|
false))
|
||||||
(make-posn 1 0))
|
(make-posn 1 0))
|
||||||
(list 'boundary (make-posn 2 0) (make-posn 0 1)))
|
(list 'boundary (make-posn 2 0) (make-posn 0 1)))
|
||||||
|
]
|
||||||
|
|
||||||
|
@chunk[<adjacent>
|
||||||
;; adjacent : posn number -> (listof posn)
|
;; adjacent : posn number -> (listof posn)
|
||||||
;; returns a list of the posns that are adjacent to
|
;; returns a list of the posns that are adjacent to
|
||||||
;; `p' on an infinite hex grid
|
;; `p' on an infinite hex grid
|
||||||
|
@ -523,10 +573,10 @@ The core of the breadth-first search is this function,
|
||||||
(make-posn 1 2)
|
(make-posn 1 2)
|
||||||
(make-posn 3 2)
|
(make-posn 3 2)
|
||||||
(make-posn 1 3)
|
(make-posn 1 3)
|
||||||
(make-posn 2 3)))
|
(make-posn 2 3)))]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@chunk[<on-boundary?>
|
||||||
;; on-boundary? : posn number -> boolean
|
;; on-boundary? : posn number -> boolean
|
||||||
(define (on-boundary? p board-size)
|
(define (on-boundary? p board-size)
|
||||||
(or (= (posn-x p) 0)
|
(or (= (posn-x p) 0)
|
||||||
|
@ -539,8 +589,9 @@ The core of the breadth-first search is this function,
|
||||||
(check-expect (on-boundary? (make-posn 12 1) 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 12) 13) true)
|
||||||
(check-expect (on-boundary? (make-posn 1 1) 13) false)
|
(check-expect (on-boundary? (make-posn 1 1) 13) false)
|
||||||
(check-expect (on-boundary? (make-posn 10 10) 13) false)
|
(check-expect (on-boundary? (make-posn 10 10) 13) false)]
|
||||||
|
|
||||||
|
@chunk[<in-bounds?>
|
||||||
|
|
||||||
;; in-bounds? : posn number -> boolean
|
;; in-bounds? : posn number -> boolean
|
||||||
(define (in-bounds? p board-size)
|
(define (in-bounds? p board-size)
|
||||||
|
@ -557,8 +608,9 @@ The core of the breadth-first search is this function,
|
||||||
(check-expect (in-bounds? (make-posn 0 11) 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 11 0) 11) false)
|
||||||
(check-expect (in-bounds? (make-posn 10 0) 11) true)
|
(check-expect (in-bounds? (make-posn 10 0) 11) true)
|
||||||
(check-expect (in-bounds? (make-posn 0 10) 11) false)
|
(check-expect (in-bounds? (make-posn 0 10) 11) false)]
|
||||||
|
|
||||||
|
@chunk[<graph-the-rest>
|
||||||
;; <=/f : (number or '∞) (number or '∞) -> boolean
|
;; <=/f : (number or '∞) (number or '∞) -> boolean
|
||||||
(define (<=/f a b)
|
(define (<=/f a b)
|
||||||
(cond
|
(cond
|
||||||
|
@ -616,7 +668,9 @@ The core of the breadth-first search is this function,
|
||||||
|
|
||||||
(define (run-check-expects)
|
(define (run-check-expects)
|
||||||
(for-each (λ (t) (t))
|
(for-each (λ (t) (t))
|
||||||
(reverse check-expects)))
|
(reverse check-expects))
|
||||||
|
(printf "passed ~s tests\n" check-expect-count)
|
||||||
|
(flush-output))
|
||||||
|
|
||||||
(define (make-immutable-hash/list-init [init '()])
|
(define (make-immutable-hash/list-init [init '()])
|
||||||
(make-immutable-hash
|
(make-immutable-hash
|
||||||
|
|
Loading…
Reference in New Issue
Block a user