/* @(#)TicTacToe.java 27 October 2005 */
import edu.neu.ccs.*;
import edu.neu.ccs.gui.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
public class TicTacToe
extends DisplayPanel
{
/** The default tile color. */
public static final Color tileColor = Colors.burlywood;
/** The highlight tile color. */
public static final Color highColor = Colors.yellow;
/** The panel background color. */
public static final Color panelColor = Colors.lightgray;
/** The gap between tiles and between panel items. */
public static final int gap = 12;
/** The size of a block. */
private static final int blockSize = 100;
/** The inset to the shape within a block. */
public static final int blockInset = 12;
/** The thickness of the stroke used to draw the shape. */
public static final int strokeSize = 12;
/** The bounds of a block. */
private static final XRect bounds =
new XRect(0, 0, blockSize, blockSize);
/** The stroke color. */
private static final Color shapeColor = Colors.black;
/** The stroke object. */
private static final Stroke stroke =
new BasicStroke
(strokeSize, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
/** The blank block. */
private Paintable blank = null;
/** The X block. */
private Paintable X = null;
/** The O block. */
private Paintable O = null;
/** The 9 tiles on the game board in a 3x3 array. */
private TileBox[][] tiles = new TileBox[3][3];
/** The paintable sequence with the 9 tiles on the game board. */
private PaintableSequence sequence = new PaintableSequence();
/** The game board. */
private PaintableComponent board = new PaintableComponent(sequence);
/** The mouse action adapter for the game board. */
private MouseActionAdapter adapter = board.getMouseActionAdapter();
/** The action to handle mouse clicks on the tiles. */
private MouseAction clickaction =
new MouseAction("Click Action") {
public void mouseActionPerformed(MouseEvent mevt) {
click(mevt);
}
};
/** The action to reset for a new game. */
private SimpleAction newgame = new SimpleAction("New Game") {
public void perform() { resetGame(); }
};
/** The contents of the main panel in the GUI. */
private Object[] mainStuff = { board, newgame };
/** The main panel in the GUI. */
private TablePanel mainPanel =
new TablePanel(mainStuff, VERTICAL, gap, gap, CENTER);
/** The next block to place in the game. */
private Paintable next = null;
/** The number of tiles clicked so far. */
private int clickcount = 0;
/** Whether or not the game is over. */
private boolean gameover = false;
/** The constructor. */
public TicTacToe() {
makeBlocks();
makeTiles();
mainPanel.setBackground(panelColor);
add(mainPanel);
adapter.addMouseClickedAction(clickaction);
frame("Tic Tac Toe");
}
/** Makes the three blocks and initializes next. */
private void makeBlocks() {
makeBlank();
makeX();
makeO();
next = X;
}
/** Makes the blank block. */
private void makeBlank() {
blank = new ShapePaintable();
blank.setDefaultBounds2D(bounds);
}
/** Makes the X block. */
private void makeX() {
int x1 = blockInset;
int y1 = blockInset;
int x2 = blockSize - blockInset;
int y2 = blockSize - blockInset;
XLine2D line1 = new XLine2D(x1, y1, x2, y2);
XLine2D line2 = new XLine2D(x1, y2, x2, y1);
ShapePaintable p1 = new ShapePaintable
(line1, PaintMode.DRAW, null, shapeColor, stroke);
ShapePaintable p2 = new ShapePaintable
(line2, PaintMode.DRAW, null, shapeColor, stroke);
Object[] cross = { p1, p2 };
X = new PaintableSequence(cross);
X.setDefaultBounds2D(bounds);
}
/** Makes the O block. */
private void makeO() {
int x = blockSize / 2;
int y = blockSize / 2;
int r = (blockSize - blockInset - strokeSize) / 2;
XCircle circle = new XCircle(x, y, r);
O = new ShapePaintable
(circle, PaintMode.DRAW, null, shapeColor, stroke);
O.setDefaultBounds2D(bounds);
}
/** The code to build and space the tiles. */
private void makeTiles() {
int spacing = blockSize + gap;
for (int row = 0; row < 3; row++)
for (int col = 0; col < 3; col++) {
tiles[row][col] = new TileBox(blank);
tiles[row][col].setBackgroundPaint(tileColor);
int x = spacing * col;
int y = spacing * row;
tiles[row][col].move(x, y);
sequence.appendPaintable(tiles[row][col]);
}
}
/** The code to reset the game. */
private void resetGame() {
next = X;
clickcount = 0;
gameover = false;
for (int row = 0; row < 3; row++)
for (int col = 0; col < 3; col++) {
tiles[row][col].setPaintable(blank);
tiles[row][col].setBackgroundPaint(tileColor);
}
}
/** The click action method. */
private void click(MouseEvent mevt) {
// do nothing if the game is over
if (gameover)
return;
int x = mevt.getX();
int y = mevt.getY();
TileBox tile = (TileBox) sequence.hits(x, y);
// do nothing if click is in the gap between tiles
if (tile == null)
return;
Paintable block = tile.getPaintable();
// do nothing if the block is already an X or an O
if (block != blank)
return;
tile.setPaintable(getNext());
checkForEndOfGame();
}
/**
* Returns the next block to be inserted in the game
* and resets the next block to its opposite.
*/
private Paintable getNext() {
Paintable temp = next;
if (next == X) {
next = O;
}
else {
next = X;
}
return temp;
}
/** Returns the block in the given row and col. */
private Paintable getBlock(int row, int col) {
return tiles[row][col].getPaintable();
}
/**
* If a win occurs using the block sequence at
* (row1,col1), (row2,col2), (row3,col3),
* return the winning block and highlight each
* corresponding tile;
* otherwise return null.
*/
private Paintable checkForWinner
(int row1, int col1, int row2, int col2, int row3, int col3)
{
Paintable block1 = getBlock(row1, col1);
Paintable block2 = getBlock(row2, col2);
Paintable block3 = getBlock(row3, col3);
boolean isWinner = (block1 != blank)
&& (block1 == block2) && (block1 == block3);
if (isWinner) {
tiles[row1][col1].setBackgroundPaint(highColor);
tiles[row2][col2].setBackgroundPaint(highColor);
tiles[row3][col3].setBackgroundPaint(highColor);
return block1;
}
else
return null;
}
/**
* If a win occurs using one of the eight possible tile sequences,
* return the winning block and highlight each tile in the sequence;
* otherwise return null.
*/
private Paintable checkForWinner() {
Paintable result;
// check rows
for (int row = 0; row < 3; row++) {
result = checkForWinner(row, 0, row, 1, row, 2);
if (result != null)
return result;
}
// check cols
for (int col = 0; col < 3; col++) {
result = checkForWinner(0, col, 1, col, 2, col);
if (result != null)
return result;
}
// check one diagonal
result = checkForWinner(0, 0, 1, 1, 2, 2);
if (result != null)
return result;
// check other diagonal
result = checkForWinner(0, 2, 1, 1, 2, 0);
if (result != null)
return result;
// return null if no winner
return null;
}
/**
* Highlight a winner if one exists and signal game over;
* also signal game over if no more moves are possible.
*/
private void checkForEndOfGame() {
Paintable block = checkForWinner();
if (block != null) {
gameover = true;
}
else {
clickcount++;
gameover = (clickcount >= 9);
}
}
/** Main launches a new Tic Tac Toe game */
public static void main(String[] args) {
new TicTacToe();
}
}