A Walk Through GameLand

Here, we explore the code offered in http://code.google.com/p/iccle2/source/browse/trunk/lib/game.

Globals.lisp

Two useful idioms is to store constants in one box and things that change in another.

The working memory is stuff you zap before the next run. And by resetting the working memory, everything goes back to some initial useful initial state.

(defstruct wm "runtime working memory"
  (toolong 1000)
  board)

(defparameter *w* (make-wm))

(defun zap ()
  (setf *w* (make-wm)))

Working memory should be kept separate to the things that never change. For GAMELAND, these are the size of infinity and a bunch of characters that map to types in our system:

(defstruct fixed 
  "fixed stuff"
  (inf  most-positive-fixnum)
  (ninf (* -1 most-positive-fixnum))
  (version 0.01)
  (width   21) ; counting from zero
  (height  10)  ; counting from zero
  (chars   '((#\a . axe)
             (#\k . key)
             (#\d . dynamite)
             (#\g . gold)
             (#\B . bush)
             (#\- . door)
             (#\. . blank)
             (#\* . wall)
             (#\  . blank)
             (#\~ . water)
             (#\^ . player)
             (#\> . player)
             (#\v . player)
             (#\< . player )))
  (steps   '(     ;deltax deltay
             (#\^  0     -1)
             (#\>  1      0)
             (#\v  0      1)
             (#\< -1      0))))
                     
(defparameter *f* (make-fixed))

Note the "steps". This shows how we can more from the current position.

Defstructs.lisp

After the glabls, the next best thing to look at are the data types.

Point

A "point" has slots "x" and "y".

(defstruct point 
  "why isn't this a listp build in?"
  (x 0) (y 0))

Board

A "board" contains the state of the board, where we can find the current player and the the gold.

(defstruct board
  g 
  h 
  gold       ; position of gold
  at         ; position of player
  width
  height
  (contents (make-grid)) ; an array height * width
  )

"Make-grid" is short-hand for "return an array of the height and width of the board".

(defun make-grid ()
  (make-array (list (fixed-height *f*) (fixed-width *f*))))

Things

There is also a type heirarchy of things you can place on the board. In the following table, indentation denotes sub-typing; column 2 shows the magic symbol for each type, and column 3 shows slots and default slot values.
thing
   player               #\^ #\< #\> #\v     has direction, holding
   piece                                    has cost=0
        blanks          #\. #\ 
        blocks                              has cost=inf, fixedp
             bush       #\B                 
             door       #\-
             wall       #\*
             water      #\~                 has fixedp=t
        tool
             axe        #\a
            key         #\k
            dynamite    #\d
            gold        #\g

Note that the characters make to types via the "chars" of "*f". There are two use functions for chars:

  1. "char->type" converts (e.g.) #\a into 'axe.
  2. "char->piece" maps (e.g.) #\a to the struct built from make-axe #\AXe

Creation.lisp

Contains code to read strings like:

~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
~~     B     B   k ~~
~~   ***     ***   ~~
~~*-*     v     *-*~~
~~  **         **  ~~
~~ g **   d   ** a ~~
~~    BB     BB    ~~
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~

and output "board"s:

(read1)

#S(BOARD
   :G NIL
   :H NIL
   :WIDTH 20
   :HEIGHT 9
   :GOLD #S(POINT :X 3 :Y 6)
   :AT #S(POINT :X 10 :Y 4)
   :CONTENTS #2A((#S(WATER :COST 536870911 :FIXEDP T)
                  #S(WATER :COST 536870911 :FIXEDP T)
                  #S(WATER :COST 536870911 :FIXEDP T)
                  ...
                  #S(WATER :COST 536870911 :FIXEDP T))
                 (#S(WATER :COST 536870911 :FIXEDP T)
                  #S(WATER :COST 536870911 :FIXEDP T)
                  ...
                  S(WATER :COST 536870911 :FIXEDP T))
                 (#S(WATER :COST 536870911 :FIXEDP T)
                  #S(WATER :COST 536870911 :FIXEDP T) 
                  #S(BLANK :COST 0)
                  #S(BLANK :COST 0) 
                  #S(BLANK :COST 0) 
                  #S(BLANK :COST 0)
                  #S(BLANK :COST 0) 
                  #S(BUSH :COST 536870911 :FIXEDP NIL)
                  #S(BLANK :COST 0) 
                  #S(BLANK :COST 0) 
                  #S(BLANK :COST 0)
                  #S(BLANK :COST 0)  
                  #S(BLANK :COST 0)
                  #S(BUSH :COST 536870911 :FIXEDP NIL) 
                  #S(BLANK :COST 0)
                  #S(BLANK :COST 0) 
                  #S(BLANK :COST 0) 
                  #S(KEY :COST 0)
                  #S(BLANK :COST 0) 
                  #S(WATER :COST 536870911 :FIXEDP T)
                  #S(WATER :COST 536870911 :FIXEDP T))
                ...
          )

This file also contains a "board-ok-p" function that checks that the generated board makes sense.

Macros.lisp

A recally common task is walk around the board. This is simplified by the "walk" macro

(walk (i k cell endp board)
    ...
)
which calls the body for each "cell" at position "i" and "j" in "board". The boolean "endp" is true if "cell" is the last cell in the row.

Printing.lisp

The code in this file pretty-prints a board. It is a nice example of polymorphism in LISP so we show it in its entirity.

(defun onstring (x) 
  (with-output-to-string (str)
    (onstream x str)))

(defmethod onstream ((x blank) &optional (str *standard-output*))
   (princ " " str))

(defmethod onstream ((x piece) &optional (str *standard-output*))
  (princ (piece->char x) str))

(defmethod onstream ((x board) &optional (str *standard-output*))
  (let (sep 
        (max (1- (fixed-width *f*))))
    (format str "~%")
    (walk (i j cell endrowp x)
      (princ (or sep "") str)
      (onstream cell str)
          (if endrowp (princ #\Newline str)))))

(defmethod onstream ((x player) &optional (str *standard-output*))
  (princ (player-direction x) str))

Roaming.lisp

Shows code for finding the neighbors and empty neighbors of a cell on the board.

Stagger.lisp

Stagger shows code for picking empty neighbors at random, then moving to them. Note that, at each step, "onstream" is called to pretty print the currrent state of the board.