/* @(#)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(); } }