CS 2360 - Homework Assignments 7 and 8

Homework Assignments 7 and 8


CS 2360
Fall 1998
Homework Assignments 7 and 8
Assignment 7 is due no later than 8:00am, Friday, November 20, 1998
Assignment 7 is due no later than 8:00am, Monday, November 30, 1998

The happy game elves here at the Game Research Laboratory at Rancho
Kurt have concocted yet another game to keep all you LISPlings busy.
We call the game Capture the Flag, or just Capture for short.  You may 
eventually call it $%^#@&.

Capture is played on a square board with N spaces to a side.  The number
of pieces that each player may start with is variable, but each player
will start with one flag and at least one pawn.  Here's an example
of an initial Capture board where N = 5:

        --   WP   W*   WP   --

        --   WP   WP   WP   --

        --   --   --   --   --

        --   BP   BP   BP   --

        --   BP   B*   BP   --

In Capture, the two opponents are "white" and "black".  Each side starts 
with one flag (indicated by W* for the white flag and B* for the black
flag) and one or more pawns (indicated by WP or BP).  Players alternate 
moves and try to win by achieving one of four different goal states:

1)  A player wins if all the opponent's pawns have been captured
2)  A player wins if the opponent's flag has been captured
3)  A player wins if it's the opponent's turn to move and the
    opponent can't make a legal move
4)  A player wins if he moves his flag forward past all the opponent's
    pawns without his flag being captured.  

How does a piece move?  How does it capture another piece?  It depends
on what kind of piece it is.

Pawn movement:

Pawns can move one space forward (to the opponent's end of the board), 
to the left, or to the right, so long as the pawn moves to an unoccupied
space.  Pawns can never move backward (toward its own end of the board),
and they can never move diagonally.

Pawns can jump two spaces forward, to the left, or to the right, so long
as the pawn moves to an unoccupied space AND the pawn jumps over an
opponent's piece.  The opponent's piece is then captured and removed from
the board.  Pawns can never jump backward, and they can never jump
diagonally.  Multiple jumps are not permitted.

A flag can move one space forward, to the left, to the right, or backward.
A flag can never move diagonally, and it can never move more than one space
on a turn.  Thus, a flag can neither jump nor capture.

Oh, there's one more constraint on movement.  No piece may make a move that 
results in a board configuration that has occurred previously in the game.  
This constraint prevents the "infinite cha-cha" where one player moves left, 
the other player moves left, the first player moves back, the other
player moves back, the first player moves left, and so on.  It will
be easy for you to prevent this sort of annoying behavior by checking
the history list of moves that will be passed to your program.

Over the next 2.57 weeks, you are to construct a Common LISP function, 
along with all the necessary supporting functions, which takes as input a 
representation of the state of a Capture game (i.e., a board position), an 
indication as to which player is to move next, and an integer representing 
the number of moves to look ahead.  (As you read on, you'll find that the
current board position is actually the first element on a list containing
all the boards or states that the game has passed through, from the initial
board to the most recent board.)  Your function, which you will call 
"capture", will also be passed a parameter indicating whether a trace should 
be printed (described in a little bit more detail below), and an integer 
representing N (the number of spaces along one side of the board).
 
This function returns as output the best next move that the designated player 
can make from that given board position.  The output should be represented as 
a Capture board position in the same format that was used for the input board 
position.
 
Your function must select the best next move by using MiniMax search.  The use 
of Alpha-Beta pruning is optional, and will earn extra credit if done 
correctly (assuming the rest of the program works too).  You will need to 
devise a static board evaluation function to embody the strategy you want your 
Capture program to employ, and you'll need to construct the necessary move 
generation capability.  
 
As noted above, your Capture function should also be able to print a trace of 
its behavior as it carries out the search.  This trace should show at the very 
least what boards or nodes are being searched and what values are being 
propagated upwards.  This printing will be turned on or off via the 
parameter mentioned above.
 
Here's an example of how your Capture function will be called.  Assume
that we want your function to make the very first move in a game of
Capture, and that N is set to 5.  As we noted above, that beginning board 
might look like this:

        --   WP   W*   WP   --

        --   WP   WP   WP   --

        --   --   --   --   --

        --   BP   BP   BP   --

        --   BP   B*   BP   --

Your Capture function must then be ready to accept exactly five parameters 
when called.  The sample function call explains what goes where in the 
argument list:
 
(capture '((nil wp w* wp nil nil wp wp wp nil 
            nil nil nil nil nil nil bp bp bp nil nil bp b* bp nil))
                                             'w 2 nil 5)
                 ^                            ^ ^  ^  ^ 
                 |                            | |  |  | 
  The first argument is a list of lists.      | |  |  | 
  That list represents a history of the       | |  |  | 
  game, board by board.  The first sublist    | |  |  | 
  on this list will be the most recent board. | |  |  | 
  The last element of the list will be the    | |  |  | 
  initial board before either player has      | |  |  | 
  moved.  This history list is initialized    | |  |  | 
  as shown above.  Each sublist is a list     | |  |  | 
  of symbols which can be either wp, w*,      | |  |  | 
  bp, b*, or nil.                             | |  |  | 
                                              | |  |  | 
  Each of these elements represents a         | |  |  | 
  space on the board.  The first N elements   | |  |  | 
  are the first or "top" row (left to         | |  |  | 
  right), the next N elements are the         | |  |  | 
  second row, and so on.                      | |  |  | 
                                              | |  |  | 
  The second argument will always be 'w or 'b,+ |  |  | 
  to indicate whether your function is playing  |  |  | 
  the side of the white pieces or the side of   |  |  | 
  the black pieces.  There will never be any    |  |  | 
  other color used.                             |  |  | 
                                                |  |  | 
  The third argument is an integer to indicate -+  |  | 
  how many moves ahead your minimax search is to   |  | 
  look ahead.  Your function had better not look   |  | 
  any further than that.                           |  | 
                                                   |  | 
  The fourth argument is the flag to turn the -----+  | 
  trace function off or on.  Nil means don't          | 
  print the trace, and a non-nil value here means     | 
  that the trace should be displayed.                 | 
                                                      | 
  The fifth argument is an integer representing N, ---+ 
  the dimensions of the board.  The value 5 passed      
  here says that this board has 5 spaces along          
  each of its sides.                                    
 
This function should then return the next best move, according to your search 
function and static board evaluator.  So, in this case, the function might 
return:
 
(nil wp w* wp nil nil wp nil wp nil nil nil wp nil nil
 nil bp bp bp nil nil bp b* bp nil)

or some other board in this same format.  That result corresponds to
the following diagram:

        --   WP   W*   WP   --

        --   WP   --   WP   --

        --   --   WP   --   --

        --   BP   BP   BP   --

        --   BP   B*   BP   --

One other thing:  Your Capture program can't assume anything about
the initial board layout other than that the board will be square,
each side will start with only one flag, and each side will start
with at least one pawn.  So there's a whole lot of possible starting
boards for any given N.  Your program will just have to deal with it.

The deliverables for this assignment are due in two parts.  First, we'd like 
to see your static board evaluation function, your move generator, and
your board conversion functions by 8:00am, Friday, Nov. 20.  You can make 
upgrades to them after that, but you must submit credible working versions 
by 8:00am, Nov. 20.  When you submit these functions, make sure you provide 
sufficient documentation explaining what arguments these functions are 
expecting, so that your teaching assistants can test them out.  If your 
TAs can't figure out how to test these functions, your score for homework 7 
is likely to be pretty low.  And of course, the code for your board 
evaluation, move generation, and board conversion functions must adhere to 
functional programming guidelines.
 
Then, by 8:00am, Monday, Nov. 30, we'd like to see your entire Capture
program -- move generation, minimax, alpha-beta pruning if you want, and your 
possibly new, improved secret weapon static board evaluator, all merged into 
one lean, mean flag-capturing machine.  In the next two weeks, we'll be 
introducing you to some useful side effects, and you can use those 
non-functional programming techniques on everything but the static board 
evaluation function, move generator, and board conversion functions.
 
Some extra notes:
 
1)  We may need to modify the specifications a bit here or there in case
    we forgot something.  Try to be flexible.  Don't whine.  Remember,
    we just invented this game.  It may suck.  Do the best you can with it.
    The elves have already suggested a couple of rule changes.  If I decide
    they're necessary in the next couple of days, you'll need to accommodate
    the changes.  You and your code should be, as I said, flexible.
 
2)  A static board evaluation function is exactly that -- static.  It 
    doesn't search ahead.  Ever.
 
3)  You can convert our board representation to anything you want, just
    as long as when we talk to your functions or they talk to us, those
    functions are using our representation.  Thus the interface to your
    board evaluator and move generator must be in terms of our board 
    representation, and the interface to your top level function must be in 
    terms of our board representation.  This is not intended as an 
    endorsement of the board representation we've chosen here; you could
    easily come up with a more convenient representation scheme.  We're
    only using this representation to give you the opportunity to think
    about your knowledge representation scheme.
 
4)  We are not asking you to write the human-computer interface so that 
    your program can play against a human.  You can if you want to, just 
    for fun, but we don't want to see it.  We'll write a driver program
    that will allow your function to play against the functions written
    by your classmates.  Your function will be loaded alongside your 
    opponent's functions.  Yes, two functions, trying to do exactly the
    same thing, sharing the same space...yikes!  The potential for 
    disaster is tremendous, because you're now programming in a very 
    communal environment.  Practice safe programming...use packages
    (they'll be covered in a lab).  (Lurkers from previous quarters can
    stop snickering.  We really will run this tournament.  And we'll
    run the Crusher tournament in the interim.)
 
5)  Do not redefine existing LISP functions.  Before you "invent" a
    function name, make sure that LISP doesn't already use that name.
 
6)  Program early and often.  The board evaluator is easy.  The rest is much
    more difficult.  Get the board evaluator out of the way in a hurry, then
    start working on the rest of it as soon as you can.  Get everything
    else working, then go back and tune your evaluator.  
 
7)  Your top-level function should be named "capture".  Nothing else
    will do.  If you do the extra credit, submit an additional program
    called "capture-ec".
 
8)  Before writing any code, play the game a few times.  All you TAs out
    there should play a few games too.

9)  Very shortly, your TAs will post specific interface requirements for
    your move generator, your board evaluation function, and your board
    converters.  Stay tuned. 
 
Copyright 1998 by Kurt Eiselt, except where already copyrighted
by somebody else.  

Last revised: November 12, 1998