/* @(#)TicTacToeTester.java 1.0 12 October 2004 */ import edu.neu.ccs.*; import edu.neu.ccs.gui.*; import edu.neu.ccs.jpf.*; import edu.neu.ccs.util.*; import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.awt.font.*; import javax.swing.*; /** The tester for tic-tac-toe. */ public class TicTacToeTester extends JPF { public static void main(String[] args) { // LookAndFeelTools.showSelectLookAndFeelDialog(); LookAndFeelTools.adjustAllDefaultFontSizes(12); new TicTacToeTester(); } public void TicTacToe() { TicTacToe.newGame(); } public void testBlankTile() { window.clearPanel(); Paintable p = TileFactory.getBlankTile(); p.paint(window.getBufferGraphics()); window.repaint(); } public void testXTile() { window.clearPanel(); Paintable p = TileFactory.getXTile(); p.paint(window.getBufferGraphics()); window.repaint(); } public void testOTile() { window.clearPanel(); Paintable p = TileFactory.getOTile(); p.paint(window.getBufferGraphics()); window.repaint(); } } /** *
Class TileFactory constructs as singleton objects the three tiles * that are needed for the tic-tac-toe game.
* *The three tiles are the blank tile plus the X and O tiles.
*/ class TileFactory { // The three tiles private static ShapePaintable blank = null; private static ShapePaintable X = null; private static ShapePaintable O = null; // Dimensions private static int tileSize = 100; private static int tileInset = 10; private static int strokeSize = 10; // Common objects private static Stroke stroke = new BasicStroke(strokeSize, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); private static Rectangle2D bounds = new Rectangle2D.Double(0, 0, tileSize, tileSize); private static Color shapeColor = Colors.black; /** Prevent instantiation of a TileFactory object. */ private TileFactory() { } /** Returns the blank tile. */ public static ShapePaintable getBlankTile() { if (blank != null) return blank; blank = new ShapePaintable(new GeneralPath()); blank.setDefaultBounds2D(bounds); return blank; } /** Returns the X tile. */ public static ShapePaintable getXTile() { if (X != null) return X; int x1 = tileInset; int y1 = tileInset; int x2 = tileSize - tileInset; int y2 = tileSize - tileInset; Line2D line1 = new Line2D.Double(x1, y1, x2, y2); Line2D line2 = new Line2D.Double(x1, y2, x2, y1); GeneralPath cross = Path.append(null, new Shape[] { line1, line2 }, false); X = new ShapePaintable (cross, PaintMode.DRAW, shapeColor, shapeColor, stroke); X.setDefaultBounds2D(bounds); return X; } /** Returns the O tile. */ public static ShapePaintable getOTile() { if (O != null) return O; int x1 = tileInset; int y1 = tileInset; int w1 = tileSize - 2 * tileInset; int h1 = tileSize - 2 * tileInset; Ellipse2D circle = new Ellipse2D.Double(x1, y1, w1, h1); O = new ShapePaintable (circle, PaintMode.DRAW, shapeColor, shapeColor, stroke); O.setDefaultBounds2D(bounds); return O; } } /** The class for the tic-tac-toe game. */ class TicTacToe implements JPTConstants { /** The frame object for this game; needed to permit auto-closure. */ private JPTFrame frame; /** The array of 3 by 3 tic-tac-toe cells. */ private PaintableComponent[][] cells; /** The panel for the 3 by 3 tic-tac-toe cells. */ private TablePanel tilePanel; /** The button panel. */ private TablePanel buttonPanel; /** The main enclosure panel. */ private TablePanel mainPanel; /** The next tile to be placed in the tic-tac-toe game. */ private ShapePaintable nextTile = TileFactory.getXTile(); /** The common background color for the tiles. */ private Color tileBackground = Colors.burlywood; /** The common highlight color for the winning tile sequence. */ private Color highlightBackground = Colors.yellow; /** The common panel background color. */ private Color panelBackground = Colors.dimgray; /** The flag that signifies that the game is over. */ private boolean gameOver = false; /** The number of tiles clicked so far. */ private int clickCount = 0; /** The large font for dialog box messages. */ private Font largeFont = new Font("serif", Font.ITALIC, 72); /** The text part of the announcement of a winner. */ private TextPaintable winnerText = new TextPaintable("Wins!", largeFont); /** The text part of the announcement of tie game. */ private TextPaintable tieGameText = new TextPaintable("Tie Game", largeFont); /** The action to handle mouse clicks on the tiles. */ private MouseAction clickAction = new MouseAction("TicTacToe Click Action") { public void mouseActionPerformed(MouseEvent mevt) { click(mevt); } }; /** The action to start a new game and end the current game. */ private SimpleAction newGameAction = new SimpleAction("New Game") { public void perform() { endGame(); TicTacToe.newGame(); } }; /** The action to quit. */ private SimpleAction quitAction = new SimpleAction("Quit") { public void perform() { endGame(); } }; /** The constructor. */ public TicTacToe() { initializeGame(); } /** The initializer. */ private void initializeGame() { // make cells, fill with blank tiles, place in panel cells = new PaintableComponent[3][3]; for (int row = 0; row < 3; row++) for (int col = 0; col < 3; col++) cells[row][col] = makeTileComponent (TileFactory.getBlankTile(), true); tilePanel = new TablePanel(cells, 10, 10, CENTER); // place actions in panel to make buttons buttonPanel = new TablePanel( new Object[] { newGameAction, quitAction }, HORIZONTAL, 10, 10, CENTER); // combine tiles and buttons into the main panel mainPanel = new TablePanel( new Object[] { tilePanel, buttonPanel }, VERTICAL, 10, 10, CENTER); // adjust panel background colors tilePanel.setBackground(panelBackground); buttonPanel.setBackground(panelBackground); mainPanel.setBackground(panelBackground); } /** * Make a new PaintableComponent for the given tile and * make this component active if the given boolean is true. */ private PaintableComponent makeTileComponent (ShapePaintable tile, boolean makeActive) { PaintableComponent cell = new PaintableComponent(tile); cell.setOpaque(true); cell.setBackground(tileBackground); if (makeActive) cell.getMouseActionAdapter().addMouseReleasedAction(clickAction); return cell; } /** Returns the current tile in the given row and col. */ private ShapePaintable getTile(int row, int col) { return (ShapePaintable) cells[row][col].getPaintable(); } /** Returns the next tile to be inserted in the game. */ private ShapePaintable getNextTile() { if (nextTile == TileFactory.getXTile()) { // reset next tile to 0 nextTile = TileFactory.getOTile(); // return X return TileFactory.getXTile(); } else { // reset next tile to X nextTile = TileFactory.getXTile(); // return 0 return TileFactory.getOTile(); } } /** * If a win occurs using the cells at (row1, col1), (row2, col2), and (row3, col3), * then return the winning tile and highlight each cell; otherwise return *null.
*/
private ShapePaintable checkCellsForWinner
(int row1, int col1, int row2, int col2, int row3, int col3)
{
ShapePaintable tile1 = getTile(row1, col1);
ShapePaintable tile2 = getTile(row2, col2);
ShapePaintable tile3 = getTile(row3, col3);
boolean isWinner = (tile1 != TileFactory.getBlankTile())
&& (tile1 == tile2) && (tile1 == tile3);
if (isWinner) {
cells[row1][col1].setBackground(highlightBackground);
cells[row2][col2].setBackground(highlightBackground);
cells[row3][col3].setBackground(highlightBackground);
return tile1;
}
else
return null;
}
/**
* If a win occurs using one of the eight possible cell sequences,
* then return the winning tile and highlight each cell in the sequence;
* otherwise return null.
*/
private ShapePaintable checkPanelForWinner() {
ShapePaintable result;
// check rows
for (int row = 0; row < 3; row++) {
result = checkCellsForWinner(row, 0, row, 1, row, 2);
if (result != null)
return result;
}
// check cols
for (int col = 0; col < 3; col++) {
result = checkCellsForWinner(0, col, 1, col, 2, col);
if (result != null)
return result;
}
// check one diagonal
result = checkCellsForWinner(0, 0, 1, 1, 2, 2);
if (result != null)
return result;
// check other diagonal
result = checkCellsForWinner(0, 2, 1, 1, 2, 0);
if (result != null)
return result;
// return null if no winner
return null;
}
/**
* If a winner exists then launch the winner dialog
* and terminate current game when dialog closes.
*/
private void checkForEndOfGame() {
ShapePaintable tile = checkPanelForWinner();
if (tile != null) {
gameOver = true;
winnerDialog(tile);
}
else {
clickCount++;
gameOver = (clickCount >= 9);
if (gameOver)
tieGameDialog();
}
}
/** Show the winner dialog corresponding to the given tile. */
private void winnerDialog(ShapePaintable tile) {
PaintableComponent component = makeTileComponent(tile, false);
TablePanel panel = new TablePanel(
new Object[] { component, winnerText },
HORIZONTAL, 10, 10, CENTER);
GeneralDialog.showOKDialog(panel, "Winner");
}
/** Show the tie game dialog. */
private void tieGameDialog() {
GeneralDialog.showOKDialog(tieGameText, "Tie Game");
}
/** The click action method. */
private void click(MouseEvent mevt) {
PaintableComponent component = (PaintableComponent) mevt.getComponent();
boolean doClick = !gameOver
&& component.getPaintable() == TileFactory.getBlankTile();
if (doClick) {
component.setPaintable(getNextTile());
checkForEndOfGame();
}
}
/** Launch a new tic-tac-toe game. */
public static void newGame() {
TicTacToe game = new TicTacToe();
game.frame = JPTFrame.createQuickJPTFrame("Tic-Tac-Toe", game.mainPanel, NORTH);
}
/** End the current tic-tac-toe game. */
private void endGame() {
frame.dispose();
}
}