Tic-tac-Toe - Java Game Programming Case Study
Tic-tac-Toe - Java Game Programming Case Study
Programming
4. A Graphical Tic-Tac-Toe with OO
4.1 Running as a Standalone Prog
4.2 Deploying an Application via a
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 1/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
For this Tic-tac-toe, I shall try to closely follow this state diagram.
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 2/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
O | | X
-----------
| X |
-----------
O | |
TTTConsoleNonOO.java
import java.util.Scanner;
/**
* Tic-Tac-Toe: Two-player, console-based, non-graphics, non-OO version.
* All variables/methods are declared as static (i.e., class)
* in this non-OO version.
*/
public class TTTConsoleNonOO {
// Define named constants for:
// 1. Player: using CROSS and NOUGHT
// 2. Cell contents: using CROSS, NOUGHT and NO_SEED
public static final int CROSS = 0;
public static final int NOUGHT = 1;
public static final int NO_SEED = 2;
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 3/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
// Play the game once
do {
// currentPlayer makes a move
// Update board[selectedRow][selectedCol] and currentState
stepGame();
// Refresh the display
paintBoard();
// Print message if game over
if (currentState == CROSS_WON) {
System.out.println("'X' won!\nBye!");
} else if (currentState == NOUGHT_WON) {
System.out.println("'O' won!\nBye!");
} else if (currentState == DRAW) {
System.out.println("It's a Draw!\nBye!");
}
// Switch currentPlayer
currentPlayer = (currentPlayer == CROSS) ? NOUGHT : CROSS;
} while (currentState == PLAYING); // repeat if not game over
}
/** Initialize the board[][], currentState and currentPlayer for a new game*/
public static void initGame() {
for (int row = 0; row < ROWS; ++row) {
for (int col = 0; col < COLS; ++col) {
board[row][col] = NO_SEED; // all cells empty
}
}
currentPlayer = CROSS; // cross plays first
currentState = PLAYING; // ready to play
}
/**
* Helper function of stepGame().
* The given player makes a move at (selectedRow, selectedCol).
* Update board[selectedRow][selectedCol]. Compute and return the
* new game state (PLAYING, DRAW, CROSS_WON, NOUGHT_WON).
* @return new game state
*/
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 4/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
public static int stepGameUpdate(int player, int selectedRow, int selectedCol) {
// Update game board
board[selectedRow][selectedCol] = player;
How it works?
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 5/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
Non-OO programs (like C programs) are organized in methods (or functions), which access common global variables. In
non-OO Java, all the variables/methods shall be declared static (i.e., they belong to the class instead of instances). The
program starts at the main() method. No instances are 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 transits 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 stepGame() is defined, which will be called to transit into next state.
Two methods are defined for printing the game board, paintBoard() and paintCell(). The paintBoard() shall call
paintCell() 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.
// 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
2. 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.
Enumeration State
In our earlier version, we used int named-constants to represent the various game states, as follows:
This approach of using int named-constants is better than hardcoding the numbers 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,
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 6/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
JDK 5 introduces a new feature called enumeration, which is a special class for storing an enumeration (list) of fixed items. In
our case, we can define an enumeration called State as follows:
/**
* The enum State defines the various game states of the TTT game
*/
public enum State { // to save as "State.java"
PLAYING, DRAW, CROSS_WON, NOUGHT_WON
}
To reference an item in an enum, use enumName.itemName (e.g., State.PLAYING and State.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 State, which can take the value of State.PLAYING, State.DRAW,
State.CROSS_WON, and State.NOUGHT_WON.
Take note that you can only assign a value defined in the enumeration (such as State.PLAYING, State.DRAW), and NOT an
arbitrary int value in the earlier example. In other words, enum is SAFE!
Enumerations Seed
We shall also create an enum called Seed as follows.
/**
* This enum is used by:
* 1. Player: takes value of CROSS or NOUGHT
* 2. Cell content: takes value of CROSS, NOUGHT, or NO_SEED.
*
* We also attach a display icon (text or image) for each of the item,
* and define the related variable/constructor/getter.
*
* Ideally, we should define two enums with inheritance, which is,
* however, not supported.
*/
public enum Seed { // to save as "Seed.java"
CROSS("X"), NOUGHT("O"), NO_SEED(" ");
// Private variable
private String icon;
// Constructor (must be private)
private Seed(String icon) {
this.icon = icon;
}
// Public Getter
public String getIcon() {
return icon;
}
}
(Ideally, we should create 2 enums with inheritance, but enum inheritance is not supported in Java.)
Again, you need to use Seed.NO_SEED, Seed.CROSS, Seed.NOUGHT to refer to these values, just like any public static
final constants.
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 7/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
We also attach an icon to each of the enum items, by defining a private variable, a private constructor and a public getter
(as in a regular class). We can get the icon via enumItem.getIcon().
We shall declare the variables currentPlayer and content as instances of enum Player and CellContent.
In brief, an enum is just a special class with a list of named-constants. But enum is safe with additional features!
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 enum Seed. You can only assign a
value from the enum's constants, such as Seed.NO_SEED, Seed.CROSS, and Seed.NOUGHT, into content. A Cell can
paint() itself. You can use newGame() to reset all its properties, ready for a new game.
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 the state transition functions such as initGame(), newGame() and
stepGame() (see game state diagram).
Cell.java
/**
* The Cell class models each individual cell of the TTT 3x3 grid.
*/
public class Cell { // save as "Cell.java"
// Define properties (package-visible)
/** Content of this cell (CROSS, NOUGHT, NO_SEED) */
Seed content;
/** Row and column of this cell, not used in this program */
int row, col;
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 8/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
}
/** Reset the cell content to EMPTY, ready for a new game. */
public void newGame() {
this.content = Seed.NO_SEED;
}
Board.java
/**
* The Board class models the TTT game-board of 3x3 cells.
*/
public class Board { // save as "Board.java"
// Define named constants for the grid
public static final int ROWS = 3;
public static final int COLS = 3;
/** Reset the contents of the game board, ready for new game. */
public void newGame() {
for (int row = 0; row < ROWS; ++row) {
for (int col = 0; col < COLS; ++col) {
cells[row][col].newGame(); // The cells init itself
}
}
}
/**
* The given player makes a move on (selectedRow, selectedCol).
* Update cells[selectedRow][selectedCol]. Compute and return the
* new game state (PLAYING, DRAW, CROSS_WON, NOUGHT_WON).
*/
public State stepGame(Seed player, int selectedRow, int selectedCol) {
// Update game board
cells[selectedRow][selectedCol].content = player;
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 9/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
Class GameMain
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 10/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
Finally, let's write a main class called GameMain to pull all the pieces together. GameMain acts as the overall controller for
the game.
GameMain.java
import java.util.Scanner;
/**
* The main class for the Tic-Tac-Toe (Console-OO, non-graphics version)
* It acts as the overall controller of the game.
*/
public class GameMain {
// Define properties
/** The game board */
private Board board;
/** The current state of the game (of enum State) */
private State currentState;
/** The current player (of enum Seed) */
private Seed currentPlayer;
/** Reset the game-board contents and the current states, ready for new game */
public void newGame() {
board.newGame(); // clear the board contents
currentPlayer = Seed.CROSS; // CROSS plays first
currentState = State.PLAYING; // ready to play
}
OO vs non-OO
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.
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).
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 12/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
TTTGraphics.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* Tic-Tac-Toe: Two-player Graphics version with Simple-OO in one class
*/
public class TTTGraphics extends JFrame {
private static final long serialVersionUID = 1L; // to prevent serializable warning
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 13/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
public static final int BOARD_WIDTH = CELL_SIZE * COLS; // the drawing canvas
public static final int BOARD_HEIGHT = CELL_SIZE * ROWS;
public static final int GRID_WIDTH = 10; // Grid-line's width
public static final int GRID_WIDTH_HALF = GRID_WIDTH / 2;
// Symbols (cross/nought) are displayed inside a cell, with padding from border
public static final int CELL_PADDING = CELL_SIZE / 5;
public static final int SYMBOL_SIZE = CELL_SIZE - CELL_PADDING * 2; // width/height
public static final int SYMBOL_STROKE_WIDTH = 8; // pen's stroke width
public static final Color COLOR_BG = Color.WHITE; // background
public static final Color COLOR_BG_STATUS = new Color(216, 216, 216);
public static final Color COLOR_GRID = Color.LIGHT_GRAY; // grid lines
public static final Color COLOR_CROSS = new Color(211, 45, 65); // Red #D32D41
public static final Color COLOR_NOUGHT = new Color(76, 181, 245); // Blue #4CB5F5
public static final Font FONT_STATUS = new Font("OCR A Extended", Font.PLAIN, 14);
// This enum (inner class) contains the various states of the game
public enum State {
PLAYING, DRAW, CROSS_WON, NOUGHT_WON
}
private State currentState; // the current game state
// UI Components
private GamePanel gamePanel; // Drawing canvas (JPanel) for the game board
private JLabel statusBar; // Status Bar
if (currentState == State.PLAYING) {
if (row >= 0 && row < ROWS && col >= 0
&& col < COLS && board[row][col] == Seed.NO_SEED) {
// Update board[][] and return the new game state after the move
currentState = stepGame(currentPlayer, row, col);
// Switch player
currentPlayer = (currentPlayer == Seed.CROSS) ? Seed.NOUGHT : Seed.CROSS;
}
} else { // game over
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 14/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
newGame(); // restart the game
}
// Refresh the drawing canvas
repaint(); // Callback paintComponent().
}
});
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack(); // pack all the components in this JFrame
setTitle("Tic Tac Toe");
setVisible(true); // show this JFrame
newGame();
}
/** Reset the game-board contents and the status, ready for new game */
public void newGame() {
for (int row = 0; row < ROWS; ++row) {
for (int col = 0; col < COLS; ++col) {
board[row][col] = Seed.NO_SEED; // all cells empty
}
}
currentPlayer = Seed.CROSS; // cross plays first
currentState = State.PLAYING; // ready to play
}
/**
* The given player makes a move on (selectedRow, selectedCol).
* Update cells[selectedRow][selectedCol]. Compute and return the
* new game state (PLAYING, DRAW, CROSS_WON, NOUGHT_WON).
*/
public State stepGame(Seed player, int selectedRow, int selectedCol) {
// Update game board
board[selectedRow][selectedCol] = player;
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 15/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
&& board[1][1] == player
&& board[2][2] == player
|| selectedRow + selectedCol == 2 // 3-in-the-opposite-diagonal
&& board[0][2] == player
&& board[1][1] == player
&& board[2][0] == player) {
return (player == Seed.CROSS) ? State.CROSS_WON : State.NOUGHT_WON;
} else {
// Nobody win. Check for DRAW (all cells occupied) or PLAYING.
for (int row = 0; row < ROWS; ++row) {
for (int col = 0; col < COLS; ++col) {
if (board[row][col] == Seed.NO_SEED) {
return State.PLAYING; // still have empty cells
}
}
}
return State.DRAW; // no empty cell, it's a draw
}
}
/**
* Inner class DrawCanvas (extends JPanel) used for custom graphics drawing.
*/
class GamePanel extends JPanel {
private static final long serialVersionUID = 1L; // to prevent serializable warning
@Override
public void paintComponent(Graphics g) { // Callback via repaint()
super.paintComponent(g);
setBackground(COLOR_BG); // set its background color
// Draw the Seeds of all the cells if they are not empty
// Use Graphics2D which allows us to set the pen's stroke
Graphics2D g2d = (Graphics2D)g;
g2d.setStroke(new BasicStroke(SYMBOL_STROKE_WIDTH,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
for (int row = 0; row < ROWS; ++row) {
for (int col = 0; col < COLS; ++col) {
int x1 = col * CELL_SIZE + CELL_PADDING;
int y1 = row * CELL_SIZE + CELL_PADDING;
if (board[row][col] == Seed.CROSS) { // draw a 2-line cross
g2d.setColor(COLOR_CROSS);
int x2 = (col + 1) * CELL_SIZE - CELL_PADDING;
int y2 = (row + 1) * CELL_SIZE - CELL_PADDING;
g2d.drawLine(x1, y1, x2, y2);
g2d.drawLine(x2, y1, x1, y2);
} else if (board[row][col] == Seed.NOUGHT) { // draw a circle
g2d.setColor(COLOR_NOUGHT);
g2d.drawOval(x1, y1, SYMBOL_SIZE, SYMBOL_SIZE);
}
}
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 16/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
}
How it Works?
[TODO]
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 17/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
Enumeration Seed.java
/**
* This enum is used by:
* 1. Player: takes value of CROSS or NOUGHT
* 2. Cell content: takes value of CROSS, NOUGHT, or NO_SEED.
*
* Ideally, we should define two enums with inheritance, which is,
* however, not supported.
*/
public enum Seed { // to save as "Seed.java"
CROSS, NOUGHT, NO_SEED
}
Enumeration State.java
/**
* The enum State contains the various game states of the TTT game
*/
public enum State { // to save as "State.java"
PLAYING, DRAW, CROSS_WON, NOUGHT_WON
}
Class Cell.java
import java.awt.*;
/**
* The Cell class models each individual cell of the game board.
*/
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 18/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
public class Cell {
// Define named constants for drawing
public static final int SIZE = 120; // cell width/height (square)
// Symbols (cross/nought) are displayed inside a cell, with padding from border
public static final int PADDING = SIZE / 5;
public static final int SEED_SIZE = SIZE - PADDING * 2;
public static final int SEED_STROKE_WIDTH = 8; // pen's stroke width
/** Constructor to initialize this cell with the specified row and col */
public Cell(int row, int col) {
this.row = row;
this.col = col;
content = Seed.NO_SEED;
}
/** Reset this cell's content to EMPTY, ready for new game */
public void newGame() {
content = Seed.NO_SEED;
}
/** Paint itself on the graphics canvas, given the Graphics context */
public void paint(Graphics g) {
// Use Graphics2D which allows us to set the pen's stroke
Graphics2D g2d = (Graphics2D)g;
g2d.setStroke(new BasicStroke(SEED_STROKE_WIDTH,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
// Draw the Seed if it is not empty
int x1 = col * SIZE + PADDING;
int y1 = row * SIZE + PADDING;
if (content == Seed.CROSS) {
g2d.setColor(GameMain.COLOR_CROSS); // draw a 2-line cross
int x2 = (col + 1) * SIZE - PADDING;
int y2 = (row + 1) * SIZE - PADDING;
g2d.drawLine(x1, y1, x2, y2);
g2d.drawLine(x2, y1, x1, y2);
} else if (content == Seed.NOUGHT) { // draw a circle
g2d.setColor(GameMain.COLOR_NOUGHT);
g2d.drawOval(x1, y1, SEED_SIZE, SEED_SIZE);
}
}
}
Class Board.java
import java.awt.*;
/**
* The Board class models the ROWS-by-COLS game board.
*/
public class Board {
// Define named constants
public static final int ROWS = 3; // ROWS x COLS cells
public static final int COLS = 3;
// Define named constants for drawing
public static final int CANVAS_WIDTH = Cell.SIZE * COLS; // the drawing canvas
public static final int CANVAS_HEIGHT = Cell.SIZE * ROWS;
public static final int GRID_WIDTH = 8; // Grid-line's width
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 19/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
public static final int GRID_WIDTH_HALF = GRID_WIDTH / 2; // Grid-line's half-width
public static final Color COLOR_GRID = Color.LIGHT_GRAY; // grid lines
public static final int Y_OFFSET = 1; // Fine tune for better display
/**
* The given player makes a move on (selectedRow, selectedCol).
* Update cells[selectedRow][selectedCol]. Compute and return the
* new game state (PLAYING, DRAW, CROSS_WON, NOUGHT_WON).
*/
public State stepGame(Seed player, int selectedRow, int selectedCol) {
// Update game board
cells[selectedRow][selectedCol].content = player;
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 20/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
for (int col = 0; col < COLS; ++col) {
if (cells[row][col].content == Seed.NO_SEED) {
return State.PLAYING; // still have empty cells
}
}
}
return State.DRAW; // no empty cell, it's a draw
}
}
/** Paint itself on the graphics canvas, given the Graphics context */
public void paint(Graphics g) {
// Draw the grid-lines
g.setColor(COLOR_GRID);
for (int row = 1; row < ROWS; ++row) {
g.fillRoundRect(0, Cell.SIZE * row - GRID_WIDTH_HALF,
CANVAS_WIDTH - 1, GRID_WIDTH,
GRID_WIDTH, GRID_WIDTH);
}
for (int col = 1; col < COLS; ++col) {
g.fillRoundRect(Cell.SIZE * col - GRID_WIDTH_HALF, 0 + Y_OFFSET,
GRID_WIDTH, CANVAS_HEIGHT - 1,
GRID_WIDTH, GRID_WIDTH);
}
Classes GameMain.java
For better flexibility, we shall extend ths main class from JPanel (instead of JFrame); allocate a JFrame in main(); and set
this as the contentPane of the JFrame.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* Tic-Tac-Toe: Two-player Graphic version with better OO design.
* The Board and Cell classes are separated in their own classes.
*/
public class GameMain extends JPanel {
private static final long serialVersionUID = 1L; // to prevent serializable warning
if (currentState == State.PLAYING) {
if (row >= 0 && row < Board.ROWS && col >= 0 && col < Board.COLS
&& board.cells[row][col].content == Seed.NO_SEED) {
// Update cells[][] and return the new game state after the move
currentState = board.stepGame(currentPlayer, row, col);
// Switch player
currentPlayer = (currentPlayer == Seed.CROSS) ? Seed.NOUGHT : Seed.CROSS;
}
} else { // game over
newGame(); // restart the game
}
// Refresh the drawing canvas
repaint(); // Callback paintComponent().
}
});
super.setLayout(new BorderLayout());
super.add(statusBar, BorderLayout.PAGE_END); // same as SOUTH
super.setPreferredSize(new Dimension(Board.CANVAS_WIDTH, Board.CANVAS_HEIGHT + 30));
// account for statusBar in height
super.setBorder(BorderFactory.createLineBorder(COLOR_BG_STATUS, 2, false));
// Set up Game
initGame();
newGame();
}
/** Reset the game-board contents and the current-state, ready for new game */
public void newGame() {
for (int row = 0; row < Board.ROWS; ++row) {
for (int col = 0; col < Board.COLS; ++col) {
board.cells[row][col].content = Seed.NO_SEED; // all cells empty
}
}
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 22/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
currentPlayer = Seed.CROSS; // cross plays first
currentState = State.PLAYING; // ready to play
}
How it Works?
[TODO]
For example:
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 23/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
via the Eclipse's "Export" option: Right-click on the project ⇒ Export ⇒ Java ⇒ JAR file ⇒ Next ⇒ Specify the JAR
filename ⇒ Next ⇒ Next ⇒ Select "Generate the manifest file" ⇒ Browse to select the main class "GameMain" ⇒ Finish.
via the "jar" command.
First, create a manifest file called "tictactoe.mf", as follow:
Manifest-Version: 1.0
Main-Class: GameMain
Next, issue a "jar" command (form CMD/Terminal shell) where options 'c' for create, 'm' for manifest, 'f' for output
jar filename, and 'v' for verbose:
You can run the program from a JAR file directly (without unpacking the JAR file) by:
1. In Windows' Explorer, right-click on the JAR file ⇒ Open with ⇒ Java Platform SE Binary; or
2. From the CMD/Terminal shell, run java.exe with -jar option, i.e.,
Note: JAR file uses the ZIP algorithm. In other words, you could use WinZIP/7-Zip to open and extract the contents of a JAR
file.
AppletMain.java
Provide a main class (says AppletMain.java) for the applet that extends javax.swing.JApplet:
import javax.swing.*;
TicTacToe.html
Provide an HTML file (says "TicTacToe.html") that embeds the "AppletMain.class":
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 24/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
<html>
<head>
<title>Tic Tac Toe</title>
</head>
<body>
<h1>Tic Tac Toe</h1>
<applet code="AppletMain.class" width="300" height="330" alt="Error Loading Applet?!">
Your browser does not seem to support <APPLET> tag!
</applet>
</body>
</html>
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:
<applet code="AppletMain.class"
archive="JarFileName.jar"
width="300" height="300"
alt="Error Loading Applet?!" >
Your browser does not seem to support <APPLET> tag!
</applet>
SoundEffect.java
import java.io.IOException;
import java.net.URL;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
/**
* This enum encapsulates all the sound effects of a game, so as to separate the sound playing
* codes from the game codes.
* 1. Define all your sound effect names and the associated wave file.
* 2. To play a specific sound, simply invoke SoundEffect.SOUND_NAME.play().
* 3. You might optionally invoke the static method SoundEffect.initGame() to pre-load all the
* sound files, so that the play is not paused while loading the file for the first time.
* 4. You can the static variable SoundEffect.volume to SoundEffect.Volume.MUTE
* to mute the sound.
*
* For Eclipse, place the audio file under "src", which will be copied into "bin".
*/
public enum SoundEffect {
EAT_FOOD("audio/eatfood.wav"),
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 25/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
EXPLODE("audio/explode.wav"),
DIE("audio/die.wav");
/** Each sound effect has its own clip, loaded with its own sound file. */
private Clip clip;
/** Private Constructor to construct each element of the enum with its own sound file. */
private SoundEffect(String soundFileName) {
try {
// Use URL (instead of File) to read from disk and JAR.
URL url = this.getClass().getClassLoader().getResource(soundFileName);
// Set up an audio input stream piped from the sound file.
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(url);
// Get a clip resource.
clip = AudioSystem.getClip();
// Open audio clip and load samples from the audio input stream.
clip.open(audioInputStream);
} catch (UnsupportedAudioFileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (LineUnavailableException e) {
e.printStackTrace();
}
}
/** Play or Re-play the sound effect from the beginning, by rewinding. */
public void play() {
if (volume != Volume.MUTE) {
if (clip.isRunning())
clip.stop(); // Stop the player if it is still running
clip.setFramePosition(0); // rewind to the beginning
clip.start(); // Start playing
}
}
Enumeration State.java
Save as above
Enumeration Seed.java
import java.awt.Image;
import java.net.URL;
import javax.swing.ImageIcon;
/**
* This enum is used by:
* 1. Player: takes value of CROSS or NOUGHT
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 26/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
* 2. Cell content: takes value of CROSS, NOUGHT, or NO_SEED.
*
* We also attach a display image icon (text or image) for the items.
* and define the related variable/constructor/getter.
* To draw the image:
* g.drawImage(content.getImage(), x, y, width, height, null);
*
* Ideally, we should define two enums with inheritance, which is,
* however, not supported.
*/
public enum Seed { // to save as "Seed.java"
CROSS("X", "images/cross.gif"), // displayName, imageFilename
NOUGHT("O", "images/not.gif"),
NO_SEED(" ", null);
// Private variables
private String displayName;
private Image img = null;
if (imageFilename != null) {
URL imgURL = getClass().getClassLoader().getResource(imageFilename);
ImageIcon icon = null;
if (imgURL != null) {
icon = new ImageIcon(imgURL);
//System.out.println(icon); // debugging
} else {
System.err.println("Couldn't find file " + imageFilename);
}
img = icon.getImage();
}
}
// Public getters
public String getDisplayName() {
return displayName;
}
public Image getImage() {
return img;
}
}
Cell.java
import java.awt.*;
/**
* The Cell class models each individual cell of the game board.
*/
public class Cell {
// Define named constants for drawing
public static final int SIZE = 120; // cell width/height (square)
// Symbols (cross/nought) are displayed inside a cell, with padding from border
public static final int PADDING = SIZE / 5;
public static final int SEED_SIZE = SIZE - PADDING * 2;
/** Constructor to initialize this cell with the specified row and col */
public Cell(int row, int col) {
this.row = row;
this.col = col;
content = Seed.NO_SEED;
}
/** Reset this cell's content to EMPTY, ready for new game */
public void newGame() {
content = Seed.NO_SEED;
}
/** Paint itself on the graphics canvas, given the Graphics context */
public void paint(Graphics g) {
// Draw the Seed if it is not empty
int x1 = col * SIZE + PADDING;
int y1 = row * SIZE + PADDING;
if (content == Seed.CROSS || content == Seed.NOUGHT) {
g.drawImage(content.getImage(), x1, y1, SEED_SIZE, SEED_SIZE, null);
}
}
}
Board.java
Same as above
GameMain.java
Play the sound effect after the stepGame().
How It Works?
[TODO]
6.1 Sudoku
See the "Sudoku" article.
6.3 Snake
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 28/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
6.4 Tetris
See the "Tetris" article.
6.5 Connect-Four
Wiki "Connect-4" to understand the rules of the game.
To write a Connect-Four game, let's start from Tic-Tac-Toe's "Graphics Version". Do the
following changes on "TTTGraphics.java":
1. Change constants ROWS to 6 and COLS to 7. Run the program. You shall see a 6×7 grid.
Try clicking on the cells, "cross" and "nought" shall be displayed alternately.
2. Modify the mouseClicked() event-handler to position the seed at the "bottom" row
of the column clicked, instead of on the the cell clicked. You need to check that there is
empty cell on that column.
3. Modify the hasWon() method to check for 4-in-a-line (along row, column, diagonal or opposite-diagonal).
// HINTS:
public boolean hasWon(Seed theSeed, int rowSelected, int colSelected) {
// Check for 4-in-a-line on the rowSelected
int count = 0;
for (int col = 0; col < COLS; ++col) {
if (board[rowSelected][col] == theSeed) {
++count;
if (count == 4) return true; // found
} else {
count = 0; // reset and count again if not consecutive
}
}
// Check column and diagonals
......
return false; // no 4-in-a-line found
}
That's all!
Next,
1. Tidy up the names (In Eclipse, Refactor ⇒ Rename).
2. Tidy up the display (using red and yellow discs, instead of cross and nought).
3. Add more features. For example, sound effect; or buttons to control the game.
4. Re-design your classes (Read the "Graphics Advanced-OO Tic-Tac-Toe").
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 29/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
Next,
1. Tidy up the names (Refactor ⇒ Rename).
2. Tidy up the display (using black and white discs, instead of cross and nought).
3. Add more features. For example, sound effect; or buttons to control the game.
4. Re-design your classes (Read the "Graphics Advanced-OO Tic-Tac-Toe").
5. Improve your display (e.g., using images, animation etc).
6.7 MasterMind
[TODO]
6.8 Checker
[TODO]
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 30/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
7. Animation
Read "Animation" of "Custom Graphics".
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.
int[] winningPatterns = {
0x1c0, // 0b111 000 000 (row 2)
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 31/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
0x038, // 0b000 111 000 (row 1)
0x007, // 0b000 000 111 (row 0)
0x124, // 0b100 100 100 (col 2)
0x092, // 0b010 010 010 (col 1)
0x049, // 0b001 001 001 (col 0)
0x111, // 0b100 010 001 (diagonal)
0x054}; // 0b001 010 100 (opposite diagonal)
// msb is (2, 2); lsb is (0, 0)
Note: JDK 1.7 supports binary literals beginning with prefix "0b". Pre-JDK 1.7 does not support binary literals but supports
hexadecimal literals beginning with prefix "0x". Eclipse IDE supports JDK 1.7 only after Eclipse 3.7.2. Hence, try 0b... but
fall back to 0x... if compilation fails.
We define two placement binary patterns for the cross and nought respectively.
int crossPattern;
int noughtPattern;
(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).
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 32/33
5/14/24, 11:36 AM Tic-tac-toe - Java Game Programming Case Study
[TODO]
Feedback, comments, corrections, and errata can be sent to Chua Hock-Chuan (ehchua@ntu.edu.sg) | HOME
https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html 33/33