Dissecting the Program

Non-OO programs (like C programs) are organized in methods, which access common global variables. (OO programs are organized in classes.) All the variables/methods shall be declared static (belong to the class instead of instances). The program starts at the main() method. No instance will be created.

A board game (such as Tic-tac-toe) is typically programmed as a state machine. Depending on the current-state and the player's move, the game goes into the next-state. In this example, I use a variable currentState to keep track of the current-state of the game, and define named-constants to denote the various states of the game (PLAYING, DRAW, CROSS_WON, and NOUGHT_WON). A method called updateGame() is defined, which will be called after every move to update this currentState, by checking the status of the game-board.

Two methods are defined for printing the game board, printBoard() and printCell(). The printBoard() shall call printCell() to print each of the 9 cells. This seems trivial here, but will be useful in the object-oriented design to separate the board and cells into separate classes.

[TODO] more explanation

TRY: Prompt the user whether to play again after gameover.

// in main()
do {
// Play the game once
initGame();
......
......
// Prompt the user whether to play again
System.out.print("Play again (y/n)? ");
char ans = in.next().charAt(0);
if (ans != 'y' && ans != 'Y') {
System.out.println("Bye!");
System.exit(0); // terminate the program
}
} while (true); // repeat until user did not answer yes

A Console OO Tic-Tac-Toe

Let us convert the earlier non-OO version of Tic-Tac-Toe to object-oriented. The OO version of this simple Tic-Tac-Toe is more complex than the non-OO version, because Tic-Tac-Toe is a rather simple application. But OO design is a necessity to build a complex application.

Enumerations State and Seed

In our earlier version, we used int named-constants to represent the various game states, as follows:

// Named-constants to represent the various states of the game
public static final int PLAYING = 0;
public static final int DRAW = 1;
public static final int CROSS_WON = 2;
public static final int NOUGHT_WON = 3;
// The current state of the game
public static int currentState = PLAYING; // Assigned to a name, which is easier to read and understand,
// instead of an int number 0

This approach of using int named-constants is better than using number in the programming statements, but it is not ideal. This is because you may inadvertently assign an int value outside the valid range to the variable currentState. For example,

currentState = 99; // A logical error but can compile

JDK 1.5 introduces a new feature called enumeration, which is a special class for storing an enumeration (list) of items. In our case, we can define an enumeration called GameState as follows:

1
2
3
4
5
6

/**
* Enumerations for the various states of the game
*/
public enum GameState { // to save as "GameState.java"
PLAYING, DRAW, CROSS_WON, NOUGHT_WON
}

To reference an item in an enum, use enumName.itemName (e.g., GameState.PLAYING and GameState.DRAW), just like referencing static variables of a class (e.g., Math.PI).

You can create an instance for an enum (just like creating an instance of a class) and assign a value into it. We shall now declare the variable currentState as an instance of GameState, which can take the value of GameState.PLAYING, GameState.DRAW, GameState.CROSS_WON, and GameState.NOUGHT_WON.

In brief, an enum is just a special class with a list of named-constants. But enum is safe, compared with name-constants.

Classes Board and Cell

Next, let's design the OO classes needed for our Tic-Tac-Toe game. Each class shall maintain its own attributes and operations (variables and methods), and it can paint itself in a graphics program.

We begin with two classes, a class Cell for each individual cell of the game board, and a class Board for the 3x3 game board.

The Cell class has an instance variable called content (with package access), of the type enumSeed. You can only assign a value from the enum's constants, such as Seed.EMPTY, Seed.CROSS, and Seed.NOUGHT, into content. A Cell can paint() itself and has its own operations such as clear().

The Board class composes of nine Cell instances, arranged in an 3×3 array called cells (with package access), of the type Cell[][]. A Board can paint() itself, and supports its own operations such as checking the status of the current board (isDraw(), hasWon()).

Take note that the OO-version and the non-OO version have the same codes, but are organized differently. The organization in OO enables you to design and develop complex system.

A Graphics Simple-OO Tic-Tac-Toe

Let's rewrite the "console" version into a "graphics" version - a Java Swing application, as illustrated. In this initial design, we do not separate the cell and board into dedicated classes, but include them in the main class. We used an inner class DrawCanvas (that extends JPanel) to do the custom drawing, and an anonymous inner class for MouseListener.

The content-pane (of the top-level container JFrame) is set to BorderLayout. The DrawCanvas (JPanel) is placed at the CENTER; while a status-bar (a JLabel) is placed at the SOUTH (PAGE_END).

Othello (Reversi)

Change ROWS and COLS to 8. Run the program. You shall see a 8×8 grid. Try clicking on the cells, "cross" and "nought" shall be displayed alternately.

Modify the updateGame(Seed theSeed, int rowSelected, int colSelect) to flip the opponent's seeds along the row, column, diagonal and opposite diagonal - centered at (rowSelected, colSelected) - after the player with "theSeed" has placed on (rowSelected, colSelected). If there is no more empty space, the game is over. Decide the winner by counting the numbers of black and white seeds.
HINTS:

Sudoku

You could wiki "Sudoku" to understand the rules of the game.

Sudoku's graphics does not involve custom drawing (such as drawing lines or circles). Hence, the above Tic-Tac-Toe graphics example is not really applicable. You can simply use a 9x9 JTextFields arranged in a 9x9 GridLayout - the GUI codes is simple!

The steps for producing the display are:

Set the JFrame's content-pane to 9×9 GridLayout. Create 9×9 JTextFields and add to the content-pane. You need to set up two 9×9 arrays. One int[9][9] to store the numbers (1-9, or 0 if empty). Another JTextField[9][9] to do the display (print blank if the number is 0).

Initialize the game by reading in an input puzzle with blank cells, and populate the int[9][9] and JTextField[9][9] arrays. Set the non-empty cells to non-editable.

setBackground(Color c) // Set the background color of the component
setForeground(Color c) // Set the text color of the JTextField
setFont(Font f) // Set the font used by the JTextField
setHorizontalAlignment(int align); // align: JTextField.CENTER, JTextField.LEFT, JTextField.RIGHT

Next, write the event handler actionPerformed() for the ActionEvent fired by the JTextField. You may use one listener to listen to all the 9×9 JTextFields. In order to ascertain the JTextField that has fired the ActionEvent. You could use the event.getSource() method to retrieve the source object that has fired the event and compare with all the 9×9 JTextFields:

Triggering JTextField's ActionEvent involves hitting the "enter" key. That is, without hitting the enter key, the number is not captured by actionPerformed(), although it may appear on the text field. Try using the KeyEvent, which is fired after every key stroke.

Mine Sweeper

Similar to Sudoku, the graphics for Mine Sweeper does not involve custom drawings. For the basic version with 10x10 cells, construct a 10x10 JButton array and arranged in GridLayout. Study the sample code in Sudoku to create the display.

In Mine Sweeper, you need to response to left-mouse click and right-mouse-click differently. Hence, instead of listening to the ActionEvent, you shall listen to the MouseEvent with mouse-clicked handler so as to response to the left-click and right-click. Since ActionEvent is not used, you probably can use 10x10 JLabel instead of JButton, as JLabel can also trigger mouse-event.

You need to provide an HMTL file to run the applet in production. (For testing, You could run your applet directly under Eclipse/NetBeans using the so-called "appletviewer" without an HTML file.) For example,

Running as a Standalone Program

Simply run the class containing the entry main() method.

Deploying an Application via a JAR file

To deploy an application containing many classes, you have to pack (i.e., jar) all classes and resources into a single file, with a manifest that specifies the main class (containing the entry main() method).

tictactoe.jar

To deploy an applet which contains more than one classes, you need to pack all the classes and resources into a JAR file (e.g., via Eclipse's "Export" option or "jar" command described earlier), but you need not use a manifest (for specify a main class as applet does not need a main() method). Then, use the following <applet> tag with an "archive" attribute to specify the JAR filename:

JDK supports only a sampled audio format, ".wav", ".au", and ".aiff". It does not support ".mp3" (You will get an error message "Audio Format not supported: xxx". There used to be a "Java Media Framework (JMF)" that supports MP3.

You may try to download the pronunciations for the words "game" and "over", and join them into a "wav" file.

We need to test the sound effect under a Swing application, instead of placing all the codes under the main(). This is because main() exits before the sound gets a chance to play.

Adding Sound Effect to the Tic-Tac-Toe Graphics Version

Two sound clips were used in the demo: one for the move ("move.wav") and the other for game-over ("gameover.wav"). (Google to find some interesting "wav" files.)

Animation

Fast Matching of Winning Patterns with Bit-Masks (Advanced)

A much more efficient method for matching with a winning pattern in a Tic-tac-toe is to use a 9-bit binary number (stored as an int or short type) to denote the placement of the seeds, and use bit operations to perform the matching.

The following table summaries all the bit-wise operations, which are efficient and fast.

Operator

Description

Usage

Example

&

Bit-wise AND

expr1 & expr2

0b0110 0001 & Ob1110 0000 gives 0b0110 0000

|

Bit-wise OR

expr1 | expr2

0b0110 0001 | Ob0000 1000 gives 0b0110 1001

!

Bit-wise NOT

!expr

^0b0110 0001 gives 0b1001 1110

^

Bit-wise XOR

expr1 ^ expr2

0b0110 0001 ^ Ob0000 0001 gives 0b0110 1001

<<

Left-shift and padded with zeros

operand << number

0b0000 0001 << 4 gives 0b0001 0000

>>

Right-shift and padded with the "sign-bit"
(Signed-extended right-shift)

(0x1 << bitPosition) shifts a binary 0b 000 000 001 to the left by the bitPosition number of bits, so as to place a '1' bit at the proper position. It is then bit-OR with the existing pattern to include the new bit, without modifying the existing bits. For example, suppose rowSelect = 2 and colSelected = 0, then bitPosition = 6. (0x1 << bitPosition) gives 0b 001 000 000.

(aWinningPattern & playerPattern) masks out all the bits in the playerPattern except those having 1's in aWinningPattern. For example, suppose that playerPattern = 0b111 000 101, it matches the aWinningPattern = 0b111 000 000. This is because (playerPattern & aWinningPattern) returns 0b111 000 000, which is the same the the aWinningPattern.

This code is very much more efficient as it involves only comparison with 8 integers (plus 8 efficient bit-AND operations).