/* @(#)TicTacToe.java   20 September 2007 */

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. */
    protected 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. */
    protected static final XRect bounds =
        new XRect(0, 0, blockSize, blockSize);
    
    /** The stroke color. */
    protected static final Color shapeColor = Colors.black;
    
    /** The stroke object. */
    protected static final Stroke stroke =
        new BasicStroke
            (strokeSize, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
    
    
    /** The blank block. */
    protected Paintable blank = null;
    
    /** The X block. */
    protected Paintable X = null;
    
    /** The O block. */
    protected Paintable O = null;
    
    /** The next block to place in the game. */
    protected Paintable next = null;
    
    
    /** The number of tiles clicked so far. */
    protected int clickCount = 0;
    
    /** Whether or not the game is over. */
    protected boolean gameOver = false;
    
    
    /** The 9 tiles on the game board in a 3x3 array. */
    protected TileBox[][] tiles = new TileBox[3][3];
    
    /** The paintable sequence with the 9 tiles on the game board. */
    protected PaintableSequence sequence = new PaintableSequence();
    
    /** The game board. */
    protected PaintableComponent board = new PaintableComponent(sequence);
    
    /** The mouse action adapter for the game board. */
    protected MouseActionAdapter adapter = board.getMouseActionAdapter();
    
    
    /** The action to handle mouse clicks on the tiles. */
    protected MouseAction clickAction =
        new MouseAction("Click Action") {
            public void mouseActionPerformed(MouseEvent mevt) {
                click(mevt);
            }
        };
    
        
    /** The action to reset for a new game. */
    protected SimpleAction newGame = new SimpleAction("New Game") {
        public void perform() { resetGame(); }
    };
    
    
    /** The contents of the main panel in the GUI. */
    protected Object[] mainStuff = { board, newGame };
    
    /** The main panel in the GUI. */
    protected VTable mainPanel =
        new VTable(mainStuff, gap, gap, CENTER);
    
    
    /** The constructor. */
    public TicTacToe() {
        makeBlocks();
        makeTiles();
        
        mainPanel.setBackground(panelColor);
        add(mainPanel);
        
        adapter.addMouseClickedAction(clickAction);
    }
    
    
    /** Makes the three blocks and initializes next. */
    protected void makeBlocks() {
        makeBlank();
        makeX();
        makeO();
        
        next = X;
    }
    
    
    /** Makes the blank block. */
    protected void makeBlank() {
        blank = new ShapePaintable();
        blank.setDefaultBounds2D(bounds);
    }
    
    
    /** Makes the X block. */
    protected 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. */
    protected 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. */
    protected 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. */
    protected 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. */
    protected 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.
     */
    protected Paintable getNext() {
        Paintable temp = next;
        
        next = (next == X) ? O : X;
        
        return temp;
    }
    
    
    /** Returns the block in the given row and col. */
    protected 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 <code>null</code>.
     */
    protected 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 <code>null</code>.
     */
    protected 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.
     */
    protected 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().frame("Tic Tac Toe");
    }
    
}