CIS300 Fall 2000: Project

40 points; due Monday, December 11, at 4:00pm

This project can be completed by one person or by a team of two. If you choose to work in a team, submit the names of the team members at classtime, Monday, November 20.


Stacks, linked lists, queues, and trees appear in implementations of game-playing programs. Here is a tic-tac-toe variant that gives you the opportunity to use one or more of these data structures. If you solve this assignment, you can readily apply your knowledge (and much of the solution) to more challenging games, like checkers and chess.

The XO Game

XO is a version of tic-tac-toe played on a larger board. In our version, a human plays the computer.

The playing board is 4-by-4 and starts empty:

  ---------------
 |   |   |   |   |
  ---------------
 |   |   |   |   |
  ---------------
 |   |   |   |   |
  ---------------
 |   |   |   |   |
  ---------------
The human player inserts X's into the board, and the computer inserts O's. The two players alternate at inserting their markers; the human plays first. For example, after 3 plays by the human and two by the computer, we might have this configuration:
current_board = 
  ---------------
 |   |   |   |   |
  ---------------
 | X | X | O |   |
  ---------------
 |   | X | O |   |
  ---------------
 |   |   |   |   |
  ---------------
Each player tries to place three or four of her markers in a row, aligned either vertically or horizontally or diagonally. Three markers in a row score 3 points; four markers in a row score 6 points.

Play proceeds until all squares are filled; the player with the higher score at the final board configuration ``wins.''

Strategy

Like tic-tac-toe, a player's objectives are to place markers in a row while preventing her opponent from doing so. Given a board configuration, like the one depicted above, we might formulate a numerical value that computes the strength of the board's position.

Here is a simple example of such a formula:

strength(Board b, Player p) =
    ( p's current score on b )
  - ( opponent's current score on b )
  + ( empty spaces adjacent to 2 of p's markers on b )
  - ( empty spaces adjacent to 2 of opponent's markers on b )
The formula takes into account the current score as well as the possibility of future scoring. The formula is a bit simplistic, but it will do for now. Given such a formula, we can calculate the value of a possible next move by inserting a piece into an empty space and calculating the strength of the new board.

For example, it is O's turn to move in the above board---call the board current_board. For the record,

strength(current_board, O) = 0 - 0 + 2 - 3 = -1
which suggests that the X-player has a slight advantage at this point. Let's consider two possible next moves for O and the strengths of the resulting boards:
board1 =                   board2 =
  ---------------             --------------- 
 |   |   |   |   |           |   |   |   |   | 
  ---------------             --------------- 
 | X | X | O |   |           | X | X | O |   |  
  ---------------             ---------------
 |   | X | O |   |           |   | X | O |   |
  ---------------             --------------- 
 |   |   | O |   |           |   | O |   |   | 
  ---------------             --------------- 

 strength(board1, O) = 3 - 0 + 1 - 2 = 2

 strength(board2, O) = 0 - 0 + 3 - 2 = 1
The strength formula suggests that the first move is slightly better than the second. For this reason, the O-player might take the first move.

Of course, a computer player would use the strength formula to calculate the strengths of all possible next moves before selecting a best move.

A player of any game does better if she considers how her opponent might react to her move. A computer player can be programmed to do this---the computer searches into the future two moves, that is, that for each possible O-move by the computer, all possible opponent's X-moves are calculated too. The board positions for the computer's next move and the opponent's response moves can be drawn as this search tree:

                              current_board (as above)
next                   /            |                 \
possible
O moves:         board1          board2      ....      board11
                / ... \         / ... \               / ... \
possible X
responses:  b1_1 ... b1_10    b2_1 ... b2_10        b11_1 ... b11_10
The tree shows that, given the current board situation, there are 11 possible next moves for O. (Why 11 possible next moves? Look again at current_board---there are 11 empty spaces.) For each possible O-move, X might respond in 10 different ways. This means there are 11*10 = 110 possible boards to consider.

Obviously, it is best for computer player, O, to make a move now that is likely to lead it to a strongest board position two moves from now. This requires that the computer player search the above space of all possible 2-move sequences and calculate the strength values of all the resulting boards.

Here is a crude algorithm for doing this:

  1. Say that player O has N possible next moves; for each possible O-move, K, from this collection of N moves, let boardK be the resulting board. (See the tree above, where there are 11 possible O-moves.)
  2. Generate all the possible boards for the X-moves that might happen in response. This produces the boards, bK_1, bK_2, .... bK_N
  3. Calculate
    total_strength_for_move_K = strength(bK_1, O)  +  strength(bK_2, O) 
                                  + .... +  strength(bK_N , O)
    
  4. Compute
    average_strength_for_move_K =  total_strength_for_move_K / N
    
  5. Pick as the next O-move, the move J such that average_strength_for_move_J has the highest value of all the possible next moves.

The computer player can be made even smarter by searching into the future for the next 3 moves. Then, the computer player selects the move that leads to the strongest board position three moves from now. The search tree gets larger still:

                              current_board (as above)
next                   /            |                 \
possible
O moves:         board1          board2      ....      board 11
                / ... \         / ... \               / ... \
possible X
responses:   b1,1 ... b1,10   b2,1 ... b2,10       b11,1 ... b11,10
             /...\    /...\   /...\    /...\       /...\     /...\
possible
O moves:    1***9    1****9  1****9   1****9      1****9    1****9
But the same principle lies underneath: generate all the 3-move sequences, calculate the resulting board strengths, and select as the next move the one whose average board strength is maximal in 3 moves.

Of course, a search of 4- or 5-move sequences will be even better, but ultimately, the time it takes the computer to search the sequences slows down the game and frustrates the human player. Also, a computer that searches far into the future may be such a good player that the human almost always loses! For this reason, computer games come with ``skill levels.'' A skill level is basically a limit on the length of the sequence of moves that is searched by the computer when it must move.

Assignment

Your assignment is to implement the XO-game so that a human can play the computer. Here is a crude drawing of the architecture you might use:

You should build the game in stages; complete each stage before starting the next:
  1. Construct the simplest form of the game, where the computer player uses no strategy at all to insert its O-markers. (Say, make the computer player pick the first empty space for its marker when it moves.) Ensure that the controller lets the players take turns and that the final score of the game is announced.

    A pretty graphical interface is not required; it is acceptable to have input and output handled in the command window. (If you want to use a graphical interface, this is fine, but don't spend a lot of time on it, because your grade will not be based on the interface.)

  2. Insert a strength function into the computer player and make the computer player choose its next move based purely on the strongest board position that results from its one, next move.

    After experimenting with this version of the game, improve the strength function so that the computer player plays better. (The strength function defined earlier is too simplistic to help the computer play well in the initial rounds of the game.)

  3. Extend the computer player with a helper data structure (stack or queue or ...) that helps it search all 2-move sequences, as described above, to help it select its next move.

    This step is most interesting, because you will have to save in the data structure (stack or queue or ... ) objects of this format:

    class MoveSequence
    { private MoveList proposed_sequence_of_moves; 
      private Board board_after_the_proposed_sequence_of_moves;
       ...
    }
    
    You might review the travelling-salesman assignment to see how objects like this can be saved in a data structure, like a stack. For this assignment, a queue might be more appropriate, however.

  4. Modify the application so that the user who starts it can set the computer player's search depth to 1, 2, 3, ..., etc., levels of searching.

  5. Make the application run faster by ``pruning'' the search paths the computer player examines: only the best 2-3 initial moves are expanded into two-move sequences, and for each two-move sequence, only the best 2-3 next moves are saved for further expansion, etc.
Complete as many stages as you can by the deadline. If you are working alone, you are not expected to complete as many stages as a two-person team.

Submission

Create a directory, Project, that holds the following:

Create the .jar file, Project.jar, and submit at http://www.cis.ksu.edu/classes/300/Assign/submit.html.