/*
 * Class Board
 *
 */

import java.awt.*;
import java.util.*;

class Board extends Canvas implements Cloneable
{
	/* Constants */
	public static int BOARD_SIZE = 8;
	static int HEIGHT = 240;
	static int WIDTH = 240;
	static int NORTH_EDGE = 60;
	static int SOUTH_EDGE = 60;
	static int EAST_EDGE = 110;
	static int WEST_EDGE = 110;

	/* Strategy constants */
	static int CORNER_WEIGHT = 14;
	static int SIDE_WEIGHT = 4;

	/* Drawing constants */
	private static int hInc = HEIGHT / BOARD_SIZE;
	private static int wInc = WIDTH / BOARD_SIZE;
	private static int hOffset = (int)(0.5 + hInc * 0.125);
	private static int wOffset = (int)(0.5 + wInc * 0.125);
	private static int hSize = (int)(0.5 + hInc * 0.75);
	private static int wSize = (int)(0.5 + wInc * 0.75);

	/* Directional constants */
	public static int NORTH = 1;
	public static int NORTHEAST = 2;
	public static int EAST = 4;
	public static int SOUTHEAST = 8;
	public static int SOUTH = 16;
	public static int SOUTHWEST = 32;
	public static int WEST = 64;
	public static int NORTHWEST = 128;

	/* Double buffering stuff */
	Image bufImage = null;
	Graphics bufGraphics = null;

	/* Instance variables */
	int pieces[][];
	int score[];

	/* Player variables */
	boolean player1Auto = false, player2Auto = true,
		player1Turn, isGameOver;
	int player1Depth = 1, player2Depth = 1;

	/* Recent move tracker */
	int recentX, recentY;

	/* Font variables */
	Toolkit toolkit = Toolkit.getDefaultToolkit();
	Font smallFont = new Font("Serif", Font.PLAIN, 12);
	FontMetrics smallMetrics = toolkit.getFontMetrics(smallFont);
	Font mediumFont = new Font("Serif", Font.PLAIN, 18);
	FontMetrics mediumMetrics = toolkit.getFontMetrics(mediumFont);
	Font largeFont = new Font("Serif", Font.ITALIC, 24);
	FontMetrics largeMetrics = toolkit.getFontMetrics(largeFont);

	/* Constructor */
	Board()
	{
		super();
		setupInitialPosition();
	}

	/* Override Object.clone() so as to actually copy the pieces
	 * and score arrays, and not write over them using the new
	 * board, as the Object.clone() method would.  If that makes
	 * and sense.
	 */
	public Board clone()
	{
		try {
			Board b = (Board)(super.clone());

			b.pieces = new int[BOARD_SIZE][BOARD_SIZE];
			for(int i = 0; i < pieces.length; i++)
				for(int j = 0; j < pieces[i].length; j++)
					b.pieces[i][j] = pieces[i][j];
			b.score = new int[3];
			b.score[1] = score[1];
			b.score[2] = score[2];
			return b;
		} catch (CloneNotSupportedException e) {
			System.err.println(e);
			return null;
		}
	}

	/* What to do in case of a mouse click */
	public boolean mouseDown(Event e, int x, int y)
	{
		/* Is it the computer's turn? */
		if((player1Auto && player1Turn) ||
			(player2Auto && !player1Turn))
		{
			Player pl = new Player(
				(player1Turn?player1Depth:player2Depth), this);
			place(pl.getPreferredMove());
			return true;
		}

		if(x < WEST_EDGE || x > WEST_EDGE + WIDTH ||
			y < NORTH_EDGE || y > NORTH_EDGE + HEIGHT) return false;
		if(isGameOver) return true;

		int xc = (x - WEST_EDGE) * BOARD_SIZE / WIDTH;
		int yc = (y - NORTH_EDGE) * BOARD_SIZE / HEIGHT;
		
		place(xc, yc);
		return true;
	}

	/* Set the initial board state */
	public void setupInitialPosition()
	{
		int center = (BOARD_SIZE - 2) / 2;
		pieces = new int[BOARD_SIZE][BOARD_SIZE];

		pieces[center][center] = 1;
		pieces[center][center + 1] = 2;
		pieces[center + 1][center] = 2;
		pieces[center + 1][center + 1] = 1;
	
		score = new int[3];
		score[1] = score[2] = 2;

		recentX = recentY = -1;

		player1Turn = true;
		isGameOver = false;
	}

	public int moveResults(int x, int y)
	{
		int player = playerTurn();
		int opponent = 3 - player;

		int direction = 0;

		/* Is there already a piece there? */
		if(pieces[x][y] != 0) return direction;

		/* Check north */
		if(y > 1 && pieces[x][y - 1] == opponent)
		{
			for(int i = y - 2; i >= 0; i--)
			{
				if(pieces[x][i] == 0) break;
				if(pieces[x][i] == player)
				{
					direction += NORTH;
					break;
				}
			}
		}

		/* Check west */
		if(x > 1 && pieces[x - 1][y] == opponent)
		{
			for(int i = x - 2; i >= 0; i--)
			{
				if(pieces[i][y] == 0) break;
				if(pieces[i][y] == player)
				{
					direction += WEST;
					break;
				}
			}
		}

		/* Check south */
		if(y + 2 < BOARD_SIZE && pieces[x][y + 1] == opponent)
		{
			for(int i = y + 2; i < BOARD_SIZE; i++)
			{
				if(pieces[x][i] == 0) break;
				if(pieces[x][i] == player)
				{
					direction += SOUTH;
					break;
				}
			}
		}

		/* Check east */
		if(x + 2 < BOARD_SIZE && pieces[x + 1][y] == opponent)
		{
			for(int i = x + 2; i < BOARD_SIZE; i++)
			{
				if(pieces[i][y] == 0) break;
				if(pieces[i][y] == player)
				{
					direction += EAST;
					break;
				}
			}
		}

		/* Now try the diagonals... */
		int iterate;

		/* Check northwest */
		if(x > 1 && y > 1 && pieces[x - 1][y - 1] == opponent)
		{
			/* Which edge are we closest to? */
			if(x < y) iterate = x;
			else iterate = y;

			for(int i = 2; i <= iterate; i++)
			{
				if(pieces[x - i][y - i] == 0) break;
				if(pieces[x - i][y - i] == player)
				{
					direction += NORTHWEST;
					break;
				}
			}
		}

		/* Check southwest */
		if(x > 1 && y + 2 < BOARD_SIZE && pieces[x - 1][y + 1] == opponent)
		{
			if(x < BOARD_SIZE - y) iterate = x;
			else iterate = BOARD_SIZE - y - 1;

			for(int i = 2; i <= iterate; i++)
			{
				if(pieces[x - i][y + i] == 0) break;
				if(pieces[x - i][y + i] == player)
				{
					direction += SOUTHWEST;
					break;
				}
			}
		}

		/* Check southeast */
		if(x + 2 < BOARD_SIZE && y + 2 < BOARD_SIZE && pieces[x + 1][y + 1] == opponent)
		{
			if(x < y) iterate = BOARD_SIZE - y - 1;
			else iterate = BOARD_SIZE - x - 1;

			for(int i = 2; i <= iterate; i++)
			{
				if(pieces[x + i][y + i] == 0) break;
				if(pieces[x + i][y + i] == player)
				{
					direction += SOUTHEAST;
					break;
				}
			}
		}

		/* Check northeast */
		if(x + 2 < BOARD_SIZE && y > 1 && pieces[x + 1][y - 1] == opponent)
		{
			if(y < BOARD_SIZE - x) iterate = y;
			else iterate = BOARD_SIZE - x - 1;

			for(int i = 2; i <= iterate; i++)
			{
				if(pieces[x + i][y - i] == 0) break;
				if(pieces[x + i][y - i] == player)
				{
					direction += NORTHEAST;
					break;
				}
			}
		}

		return direction;
	}

	public void place(Move m)
	{
		place(m.getX(), m.getY());
	}

	public void place(int x, int y)
	{
		int directions = moveResults(x, y);
		if(directions == 0) return;

		int player = player1Turn?1:2, opponent = 3 - player;

		recentX = x;
		recentY = y;

		/* Set the play square */
		pieces[x][y] = player;
		score[player]++;

		/* North... */
		if((directions & NORTH) != 0)
		{
			for(int i = y - 1; true; i--)
				if(pieces[x][i] == opponent)
				{
					pieces[x][i] = player;
					score[player]++;
					score[opponent]--;
				}
				else break;
		}
				
		/* West... */
		if((directions & WEST) != 0)
		{
			for(int i = x - 1; true; i--)
				if(pieces[i][y] == opponent) 
				{
					pieces[i][y] = player;
					score[player]++;
					score[opponent]--;
				}
				else break;
		}
				
		/* South... */
		if((directions & SOUTH) != 0)
		{
			for(int i = y + 1; true; i++)
				if(pieces[x][i] == opponent)
				{
					pieces[x][i] = player;
					score[player]++;
					score[opponent]--;
				}
				else break;
		}
				
		/* East... */
		if((directions & EAST) != 0)
		{
			for(int i = x + 1; true; i++)
				if(pieces[i][y] == opponent)
				{
					pieces[i][y] = player;
					score[player]++;
					score[opponent]--;
				}
				else break;
		}

		/* Check northwest */
		if((directions & NORTHWEST) != 0)
		{
			for(int i = 1; true; i++)
				if(pieces[x - i][y - i] == opponent)
				{
					pieces[x - i][y - i] = player;
					score[player]++;
					score[opponent]--;
				}
				else break;
		}

		/* Check southwest */
		if((directions & SOUTHWEST) != 0)
		{
			for(int i = 1; true; i++)
				if(pieces[x - i][y + i] == opponent)
				{
					pieces[x - i][y + i] = player;
					score[player]++;
					score[opponent]--;
				}
				else break;
		}

		/* Check southeast */
		if((directions & SOUTHEAST) != 0)
		{
			for(int i = 1; true; i++)
				if(pieces[x + i][y + i] == opponent)
				{
					pieces[x + i][y + i] = player;
					score[player]++;
					score[opponent]--;
				}
				else break;
		}

		/* Check northeast */
		if((directions & NORTHEAST) != 0)
		{
			for(int i = 1; true; i++)
				if(pieces[x + i][y - i] == opponent)
				{
					pieces[x + i][y - i] = player;
					score[player]++;
					score[opponent]--;
				}
				else break;
		}

		player1Turn = !player1Turn;
		if(!isMovePossible())
		{
			player1Turn = !player1Turn;
			if(!isMovePossible()) isGameOver = true;
		}

		repaint();
  }

	public boolean isMovePossible()
	{
		int player = playerTurn(), opponent = 3 - player;

		/* If there is a move available, return true */
		for(int x = 0; x < BOARD_SIZE; x++)
			for(int y = 0; y < BOARD_SIZE; y++)
				if(moveResults(x, y) != 0)
					return true;

		/* Otherwise... */
		return false;
	}

	public boolean getPlayerAuto(int p)
	{
		if(p == 1 && player1Auto) return true;
		else if(p == 2 && player2Auto) return true;
		else return false;
	}

	public void setPlayerAuto(int p, boolean auto)
	{
		if(p == 1) player1Auto = auto;
		else player2Auto = auto;
	}

	public int getPlayerDepth(int p)
	{
		if(p == 1) return player1Depth;
		else return player2Depth;
	}

	public void setPlayerDepth(int p, int d)
	{
		if(p == 1) player1Depth = d;
		else player2Depth = d;
	}

	public int relativeScore(int p)
	{
		return (score[p] - score[3 - p]);
	}

	public int weightedRelativeScore(int p)
	{
		/* Get base relative score */
		int result = score[p] - score[3 - p];

		/* Check the corners, and award CORNER_WEIGHT */
		/* weighted points each */
		for(int i = 0; i < BOARD_SIZE; i += (BOARD_SIZE - 1))
			for(int j = 0; j < BOARD_SIZE; j += (BOARD_SIZE - 1))
			{
				if(pieces[i][j] == p)
					result += CORNER_WEIGHT;
				else if(pieces[i][j] == 3 - p)
					result -= CORNER_WEIGHT;
			}

		/* Check the sides, and award SIDE_WEIGHT */
		/* weighted points each */
		for(int i = 1; i < BOARD_SIZE - 1; i++)
		{
			if(pieces[i][0] == p)
				result += SIDE_WEIGHT;
			else if(pieces[i][0] == 3 - p)
				result -= SIDE_WEIGHT;

			if(pieces[0][i] == p)
				result += SIDE_WEIGHT;
			else if(pieces[0][i] == 3 - p)
				result -= SIDE_WEIGHT;

			if(pieces[i][BOARD_SIZE - 1] == p)
				result += SIDE_WEIGHT;
			else if(pieces[i][BOARD_SIZE - 1] == 3 - p)
				result -= SIDE_WEIGHT;

			if(pieces[BOARD_SIZE - 1][i] == p)
				result += SIDE_WEIGHT;
			else if(pieces[BOARD_SIZE - 1][i] == 3 - p)
				result -= SIDE_WEIGHT;
		}

		return result;
	}

	public int playerTurn()
	{
		if(player1Turn) return 1;
		else return 2;
	}

	public Dimension getPreferredSize()
	{
		return new Dimension(WIDTH + WEST_EDGE + EAST_EDGE,
							 HEIGHT + NORTH_EDGE + SOUTH_EDGE);
	}
	public Dimension preferredSize() { return getPreferredSize(); }
	public Dimension getMinimumSize() { return getPreferredSize(); }
	public Dimension minimumSize() { return getPreferredSize(); }

	public void repaint(Graphics g) { paint(g); }
	public void update(Graphics g) { paint(g); }
	public void paint(Graphics g)
	{
		String s;

		/* If we don't have a back buffer, get one */
		if(bufImage == null)
		{
			bufImage = createImage(
				WEST_EDGE + WIDTH + EAST_EDGE,
				NORTH_EDGE + HEIGHT + SOUTH_EDGE
			);
			bufGraphics = bufImage.getGraphics();
		}

		/* Draw the background */
		bufGraphics.setColor(Color.white);
		bufGraphics.fillRect(0, 0, WIDTH + WEST_EDGE + EAST_EDGE,
			HEIGHT + NORTH_EDGE + SOUTH_EDGE);
		bufGraphics.setColor(Color.green);
		bufGraphics.fillRect(0 + WEST_EDGE, 0 + NORTH_EDGE, WIDTH, HEIGHT);

		/* Draw the grid lines */
		bufGraphics.setColor(Color.black);
		for(int i = 1; i < BOARD_SIZE; i++)
		{
			bufGraphics.drawLine(i * wInc + WEST_EDGE, NORTH_EDGE,
				i * wInc + WEST_EDGE, HEIGHT + NORTH_EDGE - 1);
			bufGraphics.drawLine(WEST_EDGE, i * hInc + NORTH_EDGE,
				WIDTH + WEST_EDGE - 1, i * hInc + NORTH_EDGE);
		}

		/* Print data to screen */
		bufGraphics.setFont(largeFont);
		s = "OTHELLO v1.0";
		bufGraphics.drawString(
			s,
			(WIDTH - largeMetrics.stringWidth(s)) / 2 + WEST_EDGE,
			largeMetrics.getAscent() + largeMetrics.getLeading()
		);

		s = "White";
		bufGraphics.drawString(
			s,
			(WEST_EDGE - largeMetrics.stringWidth(s)) / 2,
			NORTH_EDGE + largeMetrics.getAscent() + largeMetrics.getLeading()
		);
		s = Integer.toString(score[1]);
		bufGraphics.drawString(
			s,
			(WEST_EDGE - largeMetrics.stringWidth(s)) / 2,
			NORTH_EDGE + largeMetrics.getHeight() + 2 * largeMetrics.getLeading() +	largeMetrics.getAscent()
		);

		s = "Black";
		bufGraphics.drawString(
			s,
			WEST_EDGE + WIDTH + (EAST_EDGE - largeMetrics.stringWidth(s)) / 2,
			NORTH_EDGE + largeMetrics.getAscent() + largeMetrics.getLeading()
		);
		s = Integer.toString(score[2]);
		bufGraphics.drawString(
			s,
			WEST_EDGE + WIDTH + (EAST_EDGE - largeMetrics.stringWidth(s)) / 2,
			NORTH_EDGE + largeMetrics.getHeight() + 2 * largeMetrics.getLeading() +	largeMetrics.getAscent()
		);

		bufGraphics.setFont(mediumFont);
		s = "by John J. Toomey, Esq.";
		bufGraphics.drawString(
			s,
			(WIDTH - mediumMetrics.stringWidth(s)) / 2 + WEST_EDGE,
			largeMetrics.getHeight() + largeMetrics.getLeading() +
				mediumMetrics.getAscent() + mediumMetrics.getLeading()
		);

		if(isGameOver)
		{
			s = "Game Over... ";
			if(score[1] > score[2]) s += "White wins!";
			else if(score[2] > score[1]) s += "Black wins!";
			else s += "Draw!";
			bufGraphics.drawString(
				s,
				(WIDTH - mediumMetrics.stringWidth(s)) / 2 + WEST_EDGE,
				NORTH_EDGE + HEIGHT + (mediumMetrics.getHeight() + SOUTH_EDGE) / 2
			);
		}

		/* Whose turn? */
		if(!isGameOver)
		{
			if(player1Turn) drawPlayer1Indicator(bufGraphics);
			else drawPlayer2Indicator(bufGraphics);
		}

		/* Draw the pieces */
		for(int h = 0; h < BOARD_SIZE; h++)
			for(int w = 0; w < BOARD_SIZE; w++)
			{
				if(pieces[w][h] != 0)
				{
					if(pieces[w][h] == 1)
						bufGraphics.setColor(Color.white);
					else if(pieces[w][h] == 2)
						bufGraphics.setColor(Color.black);
					bufGraphics.fillOval(
						WEST_EDGE + w * wInc + wOffset,
						NORTH_EDGE + h * hInc + hOffset,
						wSize,
						hSize
					);
				}

				if(recentX == w && recentY == h)
				{
					bufGraphics.setColor(Color.red);
					bufGraphics.drawOval(
						WEST_EDGE + w * wInc + wOffset,
						NORTH_EDGE + h * hInc + hOffset,
						wSize,
						hSize
					);
				}
			}

		/* Copy the back buffer */
		g.drawImage(bufImage, 0, 0, null);
	}

	private void drawPlayer1Indicator(Graphics g)
	{
		int xOffset = (WEST_EDGE - 40) / 2;
		int yOffset = (SOUTH_EDGE - 40) / 2;

		int[] x = {xOffset, WEST_EDGE - xOffset, WEST_EDGE - xOffset, xOffset};
		int[] y = {NORTH_EDGE + HEIGHT + SOUTH_EDGE / 2, NORTH_EDGE + HEIGHT + yOffset, NORTH_EDGE + HEIGHT + SOUTH_EDGE - yOffset, NORTH_EDGE + HEIGHT + SOUTH_EDGE / 2};

		g.setColor(Color.black);
		g.drawLine(x[0], y[0], x[1], y[1]);
		g.drawLine(x[1], y[1], x[2], y[2]);
		g.drawLine(x[2], y[2], x[0], y[0]);
	}

	private void drawPlayer2Indicator(Graphics g)
	{
		int xOffset = (WEST_EDGE - 40) / 2;
		int yOffset = (SOUTH_EDGE - 40) / 2;

		int[] x = {WEST_EDGE + WIDTH + xOffset, WEST_EDGE + WIDTH + xOffset, WEST_EDGE + WIDTH + EAST_EDGE - xOffset};
		int[] y = {NORTH_EDGE + HEIGHT + yOffset, NORTH_EDGE + HEIGHT + SOUTH_EDGE - yOffset, NORTH_EDGE + HEIGHT + SOUTH_EDGE / 2};
		Polygon p = new Polygon(x, y, 3);

		g.setColor(Color.black);
		g.fillPolygon(p);
	}
}

