Computer Science 120
Introduction to Programming

Spring 2011, Siena College

TicTacToe Demo

A working demo of TicTacToe will appear below. Click inside the applet to interact with it.



TicTacToe BlueJ Project

Click here to download a BlueJ project for TicTacToe.


TicTacToe Source Code

The Java source code for TicTacToe is below. Click on a file name to download it.


TicTacToe.java

import java.awt.Color;
import objectdraw.*;

/* $Id: TicTacToe.java 1590 2011-04-05 02:58:20Z terescoj $ */

/**
 * TicTacToe game to demonstrate two-dimensional arrays.
 * 
 * Jim Teresco, CSIS 120, Siena College
 * Based on example from CS 134, Williams College
 *
 */

public class TicTacToe extends WindowController {
    // Top, left corner of grid
    private static final int TOP = 40;
    private static final int LEFT = 40;

    // Number of rows in the grid
    private static final int NUM_ROWS = 3;
    private static final int NUM_COLS = NUM_ROWS;

    // Size of each grid square
    private static final int CELL_SIZE = 40;

    // Size of the marks to draw in the grid and offset from square edge
    private static final int MARK_SIZE = CELL_SIZE * 3 / 4;
    private static final int MARK_OFFSET = (CELL_SIZE - MARK_SIZE) / 2;

    // Length of the lines making up the grid
    private static final int LINE_LENGTH = CELL_SIZE * NUM_ROWS;

    // Denote value of a square to show who has played there.
    private static final int EMPTY = 0;
    private static final int X_MARK = 1;
    private static final int O_MARK = 2;

    // The grid contents
    private int[][] marks = new int[NUM_ROWS][NUM_COLS];

    // Whose turn it is next
    private int nextMark = X_MARK;

    // Whether the game has been won or not
    private boolean gameOver = false;

    // Next player label
    private Text theLabel;

    /** 
     * Begin execution by drawing an empty grid 
     */
    public void begin() {
        for (int i = 1; i < NUM_ROWS; i++) {
            new Line(LEFT, TOP + i * CELL_SIZE,
                LEFT + LINE_LENGTH, TOP + i * CELL_SIZE, canvas);
            new Line(LEFT + i * CELL_SIZE, TOP,
                LEFT + i * CELL_SIZE, TOP + LINE_LENGTH, canvas);
        }

        theLabel = new Text("X's Turn", LEFT, 
            (NUM_ROWS + 2) * CELL_SIZE, canvas);
        theLabel.setFontSize(32);

    }

    /**
     * Draw an X with the given upper left hand corner to fill
     * a space MARK_SIZE x MARK_SIZE in the Tic-Tac-Toe board
     * 
     * @param left the x coordinate of the upper-left corner of the X
     * @param top the y coordinate of the upper-left corner of the X
     */
    private void drawX(int left, int top) {
        new Line(left, top, left + MARK_SIZE, top + MARK_SIZE, canvas);
        new Line(left + MARK_SIZE, top, left, top + MARK_SIZE, canvas);
    }

    /**
     * Draw an O with the given upper left hand corner to fill
     * a space MARK_SIZE x MARK_SIZE in the Tic-Tac-Toe board
     * 
     * @param left the x coordinate of the upper-left corner of the O
     * @param top the y coordinate of the upper-left corner of the O
     */ 
    private void drawO(int left, int top) {
        new FramedOval(left, top, MARK_SIZE, MARK_SIZE, canvas);
    }

    /**
     * Draw the appropriate mark in the appropriate row/col in
     * the Tic-Tac-Toe board
     * 
     * @param row the row number (0, 1, 2) of the cell to mark
     * @param col the column number (0, 1, 2) of the cell to mark
     * @param mark what to mark, X_MARK or O_MARK
     */
    private void drawMark(int row, int col, int mark) {
        if (mark == X_MARK) {
            drawX(LEFT + col * CELL_SIZE + MARK_OFFSET,
                TOP + row * CELL_SIZE + MARK_OFFSET);
        } else {
            drawO(LEFT + col * CELL_SIZE + MARK_OFFSET,
                TOP + row * CELL_SIZE + MARK_OFFSET);
        }
    }

    /**
     * Place the next mark at the click point and check for a win.
     * If the click is within a cell of the Tic-Tac-Toe board and
     * that cell is not already occupied, the current player's 
     * mark is placed in that cell and we check to see if the player
     * has now won.
     * 
     * @param point the Location where the mouse was clicked
     */
    public void onMouseClick(Location point) {
        double x = point.getX();
        double y = point.getY();

        // Ignore clicks outside the grid
        if (x > LEFT && x < LEFT + LINE_LENGTH && 
        y > TOP && y < TOP + LINE_LENGTH) {

            // Figure out which cell in the grid was clicked in
            int col = (int) ((x - LEFT) / CELL_SIZE);
            int row = (int) ((y - TOP) / CELL_SIZE);

            // Make sure the grid is empty before adding a mark.
            // Also make sure the game is not already over
            if (marks[row][col] == EMPTY && !gameOver) {
                // Add the mark
                marks[row][col] = nextMark;

                drawMark(row, col, nextMark);

                // See if the game is won
                if (checkGameWon(row, col, nextMark)) {
                    if (nextMark == X_MARK) {
                        theLabel.setText("X Wins!");
                    } else {
                        theLabel.setText("O Wins!");
                    }
                    gameOver = true;
                }

                // see if it's a tie
                if (checkAllFilled()) {
                    theLabel.setText("It's a tie!");
                    gameOver = true;
                }

                if (!gameOver) {
                    // switch players
                    if (nextMark == X_MARK) {
                        nextMark = O_MARK;
                        theLabel.setText("O's Turn");
                    } else {
                        nextMark = X_MARK;
                        theLabel.setText("X's Turn");
                    }
                }
            }
        }
    }

    /**
     * Check to see if the game is won as a result of a mark being placed
     * at position row, col.  The game is over if an entire row, column, or
     * diagonal has the same pieces.
     * 
     * @param row the row number (0, 1, 2) of the cell just marked
     * @param col the column number (0, 1, 2) of the cell just marked
     * @param matchMark the mark, X_MARK or O_MARK, just placed
     * 
     * @return true if the current player now has won the game, false otherwise
     */
    private boolean checkGameWon(int row, int col, int matchMark) {
        return checkForRowWin(row, matchMark)
        || checkForColWin(col, matchMark)
        || checkForDiagWin(matchMark)
        || checkForDiag2Win(matchMark); 
    }

    /**
     * Check to see if the game is a tie: all positions are occupied,
     * but no one has won.
     * 
     * @return true if all positions are occupied
     */
    private boolean checkAllFilled() {
        for (int row = 0; row < NUM_ROWS; row++) {
            for (int col = 0; col < NUM_ROWS; col++) {
                if (marks[row][col] == EMPTY) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Check for a win in the given row by the player with the 
     * given mark.
     * 
     * @param row the row number to check
     * @param matchMark the mark, X_MARK or O_MARK, just placed
     * 
     * @return whether the given row contains all matchMark entries
     */
    private boolean checkForRowWin(int row, int matchMark) {
        for (int col = 0; col < NUM_COLS; ++col) {
            if (marks[row][col] != matchMark) {
                return false;
            }
        }
        return true;
    }

    /**
     * Check for a win in the given column by the player with the 
     * given mark.
     * 
     * @param col the column number to check
     * @param matchMark the mark, X_MARK or O_MARK, just placed
     * 
     * @return whether the given column contains all matchMark entries
     */
    private boolean checkForColWin(int col, int matchMark) {
        for (int row=0; row < NUM_ROWS; ++row) {
            if (marks[row][col] != matchMark) {
                return false;
            }   
        }
        return true;
    }

    /**
     * Check for a win in the main diagonal by the player with the 
     * given mark.  This is the diagonal from the top left to bottom right.
     * 
     * @param matchMark the mark, X_MARK or O_MARK, just placed
     * 
     * @return whether the main diagonal contains all matchMark entries
     */
    private boolean checkForDiagWin(int matchMark) {
        for (int i = 0; i < NUM_COLS; ++i) {
            if (marks[i][i] != matchMark) {
                return false;   
            }
        }
        return true;
    }

    /**
     * Check for a win in the off diagonal by the player with the 
     * given mark.  This is the diagonal from the bottom left to top right.
     * 
     * @param matchMark the mark, X_MARK or O_MARK, just placed
     * 
     * @return whether the off diagonal contains all matchMark entries
     */
    private boolean checkForDiag2Win(int matchMark) {
        for (int i = 0; i < NUM_ROWS; ++i) {
            if (marks[i][NUM_ROWS-i-1] != matchMark) {
                return false;   
            }
        }
        return true;
    }

}