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