COMP24011 Lab1: Reversi
Introduction
In this exercise, you will write a Java program to play the game of reversi, sometimes known by
its trademark name of Othello. Reversi is played on an 8 by 8 board, initially set up as shown
in Figure 1 on the left, with the players moving alternately by placing their own pieces (black or
white) on the board, one at a time. By default Black always goes first.
Figure 1: The opening position in a reversi game; Black moves to (5, 4).
In the lab1 branch of your COMP24011_2023 GitLab repo you will find the Java code for the game
of reversi in Figure 1. To compile and run this program you can use the following commands
$ javac *.java
$ java Othello
In addition to the game board, the main window also shows
? on the top left, the player agents that control the white and black pieces
? on the top right, the current board score
i.e. the difference between the number of white and black pieces
? on the bottom left, a message indicating who’s turn it is and how to proceed with the game
? on the bottom right, a copyright notice; please do not distribute the code for this lab exercise.
Note that the board score value will be positive when White is winning and negative when Black
is winning. By default the Java program allows a human player (always Black) to play against a
computer agent that moves randomly (always White). You can change the code in Othello.java
to configure these game settings. In fact, you will need to do this to test your player agent!
1
The graphical interface of the Java reversi game is not very responsive and a bit basic, to put it
mildly. For example:
? The human agent waits for the user to click on a square and then makes that move. There is
a 2 seconds delay before non-human agents can move, but the user can also click to request
the agent to move.
? Clicking on squares which do not represent legal moves just produces an irritating beep.
? If it is the turn of the human player and there is no legal move, then the user must click
anywhere in the green area outside the grid of the board, to generate a skip move and allow
play to pass to the other player.
? The interaction between mouse click events from the user and timer delay events that trigger
non-human agents is not perfect. . . It’s better not to click while the computer is ‘thinking’
? If you want to play another game, you need to close the window and re-run the program.
But these are details (which you are welcome to improve, though no extra marks are available).
By default, as the game progresses there is some debug output printed on the console. If you close
the window after making the move shown in Figure 1 on the right, the terminal will display
$ java Othello
Loading game...
Human: Move (5,4), valid, 0.2 secs, no error
Current boardState: {........|........|........|...wb...|...bbb..|........|....
....|........}
Current boardScore: -3
This reports that the user attempted to move to the square (5, 4), that this move was valid and
hence made. The program times how long the agent took to select a move in response to the mouse
or timer event, in increments of 0.2 seconds, and if this generated any runtime error. Note that ??
and ?? move coordinates can only have values from 0 to 7, and the top left square is at (0, 0).
When you close the window, an ASCII representation of the current state of the game is written. It
shows the squares of the board row-by-row, with the letters ‘w’ and ‘b’ representing white and black
pieces respectively, and dots representing empty squares. This representation is enclosed by curly
braces or square brackets depending on whose turn it is to move, White or Black respectively. The
Java program accepts these ASCII representations as an optional parameter on the command-line.
If you execute
$ java Othello "{........|........|........|...wb...|...bbb..|........|........
|........}"
you will restart the game exactly where you’ve left off. You’ll probably find this very useful while
testing and debugging the Java code for your agent.
Let’s review how reversi is played. In the following description, a line segment is a sequence of
board squares forming a contiguous straight line (horizontal, vertical or diagonal). The rule for a
player to place a piece is:
The piece must be placed on an empty square such that there is a line segment passing through
the piece played, then through one or more pieces of the opposing colour, and ending on a
piece of the player’s own colour.
When such a line segment exists, we say that the opponent’s pieces on that line segment are
bracketed. When a piece is played, bracketed pieces change colour according to the following rule:
For every a line segment passing through the piece played, then through one or more pieces of
the opposing colour, and ending on a piece of the player’s own colour, the pieces of opposing
colour through which that line segment passes are all changed to pieces of the player’s own
colour.
2
In Figure 2, the left-hand picture shows a game state where (3, 5) is one of the possible moves
for White. This move brackets three of the opponent’s pieces, namely (3, 6) horizontally, (3, 4)
vertically, and (4, 4) diagonally, resulting in the position shown in the right-hand picture.
Figure 2: A move by White in a reversi game, and its result.
If, and only if, a player cannot move, but his opponent can, he misses a turn. The game ends when
neither player can move. (This usually, but not always, happens because all the squares have been
occupied.) The winner is the player with the greater number of pieces of his own colour on the
board; if there is no such player, the result is a draw.
Assignment
Try out the Java reversi game, and play a few games. You will see that the computer is playing
terribly. In fact, it computes the possible moves in the current position, and then simply selects one
at random! Your task is to implement a player agent that uses the minimax search with alpha-beta
pruning to play a better game.
Before starting this exercise, make sure you understand generic types in Java, e.g. things like
ArrayList<SomeClass>. Otherwise, the Java code is very simple. To complete this exercise you
do not need to understand the code in OthelloDisplay.java or MoveStats.java which runs the
game UI and the collection of debug stats.
The main game logic is in the class BoardState. An instance of this class represents a situation in
the game. The field colour encodes whose turn it is (1 for White; ?1 for Black), while the low-level
methods int getContents(int x,int y) and void setContents(int x,int y,int piece)
allow retrieval and setting on the individual board squares. Here, a value of 1, ?1 or 0 represents presence of, respectively, a white piece, a black piece or no piece at all at the square on rank
?? and file ??.
A separate class Move is used to encode the actual moves; it has two public fields, x and y; as well
as a public method boolean isSkip(). As long as isSkip() is false, x and y give the coordinates
of a newly placed piece in the obvious way. If, on the other hand, isSkip() is true, then the move
represented is the act of ‘skipping one’s go’ (passing control to the opponent); and the values of the
coordinate fields have no meaning. Note that an instance of Move is just a move — not necessarily
a legal one. In particular, the skip move is legal if and only if the current player cannot put a piece
down, but his opponent can.
3
Both of these classes include the code necessary to be used as Java HashMap keys. To make things
easier for you, the BoardState class has the method boolean checkLegalMove(Move m), which
checks whether it is possible for the current player to make the given move, as well as the method
void makeLegalMove(Move m), which actually executes the move. In fact, to make things really
easy, we have provided the method ArrayList<Move> getLegalMoves() that returns the list of
all and only those legal moves for the current player. Notice that this list cannot be empty as long
as the game is not over.
All game player agents correspond to implementations of the abstract Java class MoveChooser.
The rest of the control is handled by the program. You are given 3 working implementations of
this class:
? HumanMoveChooser that simply relays the moves chosen by the user on the UI.
? FirstMoveChooser is a computer player that always selects the first available legal move.
? RandomMoveChooser is a computer player that selects one of available legal moves at random.
To complete this exercise you need to implement a computer player called AlphaBetaMoveChooser.
Note that the sample code that you’re provided compiles but doesn’t work. You cannot change the
public interface of the sample code; the parameters for each method are documented in the source.
The constructor AlphaBetaMoveChooser(int searchDepth) needs to set a name for your player
agent (to be used on the UI), and record the search depth that will control the minimax search
algorithm. The method Move chooseMove(BoardState boardState,Move hint) is the most important that you need to implement. This is called when it’s the computer’s turn to move in the
board state boardState. As you’re coding a non-human player, the 2nd parameter giving the move
hint of the triggering UI event should be ignored. All you have to do is write a move selection
procedure that uses the minimax algorithm with ????-pruning discussed in the lectures. Note that
you are allowed to code auxiliary functions to achieve this. The depth of the minimax search should
be controlled by the searchDepth field of your agent’s instance.
We want you to proceed in 2 stages. In the first stage you implement a board evaluation function,
using the following method. Each square in the playing area is assigned a number as follows.