package Rectangles;

import ui.*;
import java.util.*;
import java.io.*;
import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;

/**
 * Mondrian's Revenge
 * 
 * Each robot makes complete, straight passes across the board from one edge to
 * the opposite one.  These passes tend to occur through spaces with lots of
 * unfilled squares (method discussed below), resulting in rectangles.
 *
 * @author Mark Ayzenshtat
 * @author Edan Harel
 * @author Adam Rosenzweig
 */
public final class Group1Player1 implements IFCPlayer {
	private static final String kName = "Mondrian's Revenge";
	private static final Color kColor = Color.RED;
	private static final float kEarlyLateThreshold = 0.5f;
	private static final int kCandidateHoleLength = 6;

	private static final int scanRange = 15;

	private static final char[] kDirs =
		{
			IFCConstants._CEAST,
			IFCConstants._CSOUTH,
			IFCConstants._CWEST,
			IFCConstants._CNORTH };

	private Rectangles mRect;
	private int mPlayerIndex;
	private int mNumRobots;
	private int mBoardLength;
	private int mNumPlayers;
	private Random mRandom;
	private Bot[] mBots;
	private List mHoles;
	private Point mUpperLeft;
	private int mLength;
	private boolean mClockwise;

	public Robot[] register(Rectangles iRectangles) throws Exception {
		mRandom = new Random();

		// differentiate state from that of PRNG instances used by other players
		mRandom.nextInt();

		mRect = iRectangles;
		mNumRobots = mRect.numRobots();
		mBoardLength = mRect.size();
		mNumPlayers = mRect.numPlayers();
		mPlayerIndex = mRect.indexOf(this);
		int pos = (int) (0.15f * mBoardLength);
		//mUpperLeft = new Point(pos, pos);
		mLength = mBoardLength - 2;
		mUpperLeft = new Point(1, 1);
		//mLength = mBoardLength;

		mClockwise = mRandom.nextBoolean();

		return initializeBots(mUpperLeft, mLength);
	}

	/**
	 * Instantiate our robots and put them in their initial positions.  To start,
	 * half the robots are placed on the top/bottom perimeter (moving N/S) and
	 * the other half on the left/right perimeter (moving E/W).  Therefore,
	 * throughout the game, half the robots will move horizontally and the other
	 * half vertically.
	 */
	private Robot[] initializeBots(Point iUpperLeft, int iLength)
		throws Exception {
		mBots = new Bot[mNumRobots];

		float halfNumRobots = mNumRobots / 2f;
		int gap = (int) (iLength / halfNumRobots);
		Point p;

		for (int i = 0; i < mNumRobots; i++) {
			boolean flag = mRandom.nextBoolean();

			if (i % 2 == 0) {
				// west/east
				p =
					new Point(
						(flag ? iUpperLeft.x : iUpperLeft.x + iLength - 1),
						(i / 2) * gap + iUpperLeft.y);
			} else {
				// north/south
				p =
					new Point(
						(i / 2) * gap + iUpperLeft.y,
						(flag ? iUpperLeft.y : iUpperLeft.y + iLength - 1));
			}

			int edge = (i % 2) + (flag ? 2 : 0);
			mBots[i] = new Bot(this, p.x, p.y, kDirs[getOppositeDir(edge)]);
			mBots[i].setState(BotState.kCrossBoard);			
		}

		mUpperLeft.x = 0;
		mUpperLeft.y = 0;
		mLength = mBoardLength;

		return mBots;
	}

	private int getTotalScores() throws Exception {
		double scoreTotal = 0;

		MoveResult[] history = mRect.history();
		if (history.length == 0) {
			return 0;
		}

		double[] scores = history[history.length - 1].scores();

		for (int i = 0; i < scores.length; i++) {
			scoreTotal += scores[i];
		}

		return (int) scoreTotal;
	}

	private void rebuildHolesListIfNecessary() throws Exception {
		int numCandidateHoles = Math.min(mNumRobots * 10, 100);
		int numHoles = mNumRobots;

		if (mHoles == null) {
			mHoles = new ArrayList(numCandidateHoles);
		}

		if (mHoles.isEmpty()) {
			findCandidateHoles(numCandidateHoles, numHoles);
		}
	}

	public char[] move() throws Exception {
		char[] allMoves = new char[mNumRobots];

		for (int i = 0; i < mNumRobots; i++) {
			allMoves[i] = moveRobot(i);
		}

		return allMoves;
	}

	/**
	 * Initially, the robots will all arrive at the opposite side of the board at
	 * the same time.  When this happens, a list of "candidate holes" is compiled
	 * (inspired by last year's "Men on Mission" strategy).  A large number of
	 * random 6x6 squares are considered and ranked on the amount of unfilled
	 * space they contain.  The highest-ranking holes are kept and the others
	 * discarded.  As each robot arrives at the opposite edge, it selects the
	 * hole that it is closest to and moves along the edge toward that hole.
	 * Once it lines up with the hole, it sets off across the board to intersect
	 * the hole.  This process repeats once the robot reaches the opposite side
	 * of the board.	
	 */
	private boolean movedAroundCompletely = false;
	private char moveRobot(int iIndex) throws Exception {
		Bot b = mBots[iIndex];
		BotState state = b.getState();

		// defend against chasing parasites

		if (/*state != BotState.kChase && */
			b.isBeingChased()) {
			b.setPreviousState(state);
			b.setOldTargetPoint(b.getTargetPoint());
			//b.setState(BotState.kChase);
			b.setRoundsChasing(0);

			return IFCConstants._CSTAY;
		}

		b.recordPosition();

		// cross-board
		if (state == BotState.kCrossBoard) {
			b.setCrossBoardDir(b.getDir());

			if (b.willCollide()) {
				if (!movedAroundCompletely) {
					b.setSquaresMovedAround(0);
					b.setTargetPoint(null);

					state = BotState.kMoveAlongCompleteEdge;
				} else {
					/*
					int bestDist = Integer.MAX_VALUE;				
					int bestIndex = 0;
					
					rebuildHolesListIfNecessary();
					for (int j = 0; j < mHoles.size(); j++) {
						Point p = (Point) mHoles.get(j);					
						int dist = b.manhattanDistanceTo(p.x, p.y);
						if (dist < bestDist) {
							bestDist = dist;
							bestIndex = j;
						}
					}
					
					Point p = (Point) mHoles.remove(bestIndex);
					Point flattened = flattenPointToEdge(b.getCrossBoardDir(), p);				
					b.setTargetPoint(flattened);
					*/
					selectTarget(b);
					state = BotState.kMoveAlongEdge;
				}
			}
		}

		// build-locally
		if (state == BotState.kBuildLocally) {			
			//b.updateSamples();
			Point pos = new Point(b.xpos(), b.ypos());

			do {
				if (b.getMoveState() == MoveState.kChangingAnchor) {
					boolean done = b.moveToCurrentDestination();
					if (!done) {
						// check if current location is better than sample
						Point p = b.getBestTarget(pos);
						if (b.rectangleScore(pos.x, pos.y, p.x, p.y) > 0) {
							b.setOriginPoint(pos);
							b.setTargetPoint(p);
							b.setMoveState(MoveState.kMovingToTarget);
						} else
							break;
					} else {
						b.setMoveState(MoveState.kIdle);
					}
				}

				if (b.getMoveState() == MoveState.kIdle) {
					b.setOriginPoint(pos);
					b.setTargetPoint(b.getBestTarget(b.getOriginPoint()));

					if (b.rectangleScore(b.getOriginPoint(), b.getTargetPoint())
						< 0.9) {
/*							
						if (!b.mSamples.isEmpty()) {
							// we might have an alternate location to go to
							Double key = (Double) b.mSamples.lastKey();
							Point v = (Point) b.mSamples.get(key);
							
							// changeAnchor
							b.setMoveState(MoveState.kChangingAnchor);
							b.setOriginPoint(v);							
							continue;
						}
*/						
						break;
						// give up on this turn, no rectangles around					
					} else {
						// rectangle is good, we're in business
						b.setMoveState(MoveState.kMovingToTarget);
					}
				}

				Point anchor = b.getOriginPoint();
				Point target = b.getTargetPoint();

				if (b.getMoveState() != MoveState.kIdle) {
					if (b.rectangleScore(anchor, target) < 0.9) {
						b.setMoveState(MoveState.kIdle);
						//if we're almost at our destination, finish
						if (b.manhattanDistanceTo(target.x, target.y) == 1
							|| b.manhattanDistanceTo(anchor.x, anchor.y) == 1)
							break;
						else
							continue;
					} else {
						b.buildRectangle();
					}
				}

			} while (b.getMoveState() == MoveState.kIdle);
			
			while (b.willCollide()) {
				b.setDir((b.getDir() + (mClockwise ? 1 : 3)) % 4);				
			}
		}

		// move-along-edge
		if (state == BotState.kMoveAlongEdge) {
			b.moveToTargetPoint();

			if (b.getTargetPoint() == null) {
				// we've arrived
				b.setDir(getOppositeDir(b.getCrossBoardDir()));

				state = BotState.kCrossBoard;
			}
		}

		// move-along-complete-edge
		if (state == BotState.kMoveAlongCompleteEdge) {
			if (mNumPlayers == 2) {
				if (getTotalScores() >= ((mLength * mLength) * 0.5f)) {
					b.setState(BotState.kBuildLocally);
					return IFCConstants._CSTAY;
				}
			}
			
			int moved = b.getSquaresMovedAround();
			if (moved == mLength) {
				b.setCrossBoardDir((b.getDir() + (mClockwise ? 3 : 1)) % 4);
				b.setDir((b.getDir() + (mClockwise ? 1 : 3)) % 4);
				state = BotState.kCrossBoard;
				//b.setTargetPoint(new Point(b.xpos(), b.ypos()));
				//state = BotState.kMoveAlongEdge;
				movedAroundCompletely = true;
				
				state = BotState.kBuildLocally;
			} else {
				if (b.willCollide()) {
					int dir = b.getDir();

					Point target = new Point();

					if (mClockwise) {
						switch (kDirs[dir]) {
							case IFCConstants._CNORTH :
								target.x = mLength - 1;
								target.y = 0;
								break;
							case IFCConstants._CSOUTH :
								target.x = 0;
								target.y = mLength - 1;
								break;
							case IFCConstants._CEAST :
								target.x = mLength - 1;
								target.y = mLength - 1;
								break;
							case IFCConstants._CWEST :
								target.x = 0;
								target.y = 0;
								break;
						}
					} else {
						switch (kDirs[dir]) {
							case IFCConstants._CNORTH :
								target.x = 0;
								target.y = 0;
								break;
							case IFCConstants._CSOUTH :
								target.x = mLength - 1;
								target.y = mLength - 1;
								break;
							case IFCConstants._CEAST :
								target.x = mLength - 1;
								target.y = 0;
								break;
							case IFCConstants._CWEST :
								target.x = 0;
								target.y = mLength - 1;
								break;
						}
					}

					b.setTargetPoint(target);
				}

				b.moveToTargetPoint();
				b.setSquaresMovedAround(moved + 1);
			}
		}

		// chase
		if (state == BotState.kChase) {
			Robot r = b.closestEnemyRobot();

			int rounds = b.getRoundsChasing();
			if (rounds >= 5) {
				// stop chasing
				b.setTargetPoint(b.getOldTargetPoint());
				state = b.getPreviousState();
			} else {
				b.setTargetPoint(new Point(r.xpos(), r.ypos()));
				b.moveToTargetPoint();
				b.setRoundsChasing(rounds + 1);
			}
		}

		b.setState(state);

		int dir = b.getDir();
		return (dir >= 0) ? kDirs[dir] : IFCConstants._CSTAY;
	}

	private void selectTarget(Bot b) throws Exception {

		Point p;
		int nonBlack;
		int maxNonBlack = 0;
		int maxLoc = -1;
		int row;
		int col;
		int[][] boardFilled = mRect.colors();
		int limit;
		int bIndex;
		boolean skip;

		switch (kDirs[b.getDir()]) {

			case IFCConstants._CEAST :
				if (b.ypos() < scanRange) {
					row = 0;
				} else {
					row = b.ypos() - scanRange;
				}
				limit = b.ypos() - 1;
				for (; row < limit; row++) {
					skip = false;
					for (bIndex = 0; bIndex < mNumRobots; bIndex++) {
						if (((kDirs[mBots[bIndex].getCrossBoardDir()]
							== IFCConstants._CEAST)
							|| (kDirs[mBots[bIndex].getCrossBoardDir()]
								== IFCConstants._CWEST))
							&& (((mBots[bIndex].getState()
								== BotState.kCrossBoard)
								&& (mBots[bIndex].ypos() == row))
								|| ((mBots[bIndex].getState()
									== BotState.kMoveAlongEdge)
									&& (((mBots[bIndex].getTargetPoint() == null)
										&& (mBots[bIndex].ypos() == row))
										|| (mBots[bIndex].getTargetPoint().y
											== row))))) {
							skip = true;
							break;
						}
					}
					if (skip) {
						row += 1;
						continue;
					}
					nonBlack = 0;
					for (col = mBoardLength - 1;
						col >= mBoardLength / 2;
						col--) {
						if ((boardFilled[row][col] != -1)
							&& (boardFilled[row][col] != mPlayerIndex)) {
							nonBlack += 1;
						}
					}
					if (nonBlack >= maxNonBlack) {
						maxNonBlack = nonBlack;
						maxLoc = row;
					}
				}
				if (limit + scanRange >= mBoardLength) {
					limit = mBoardLength;
				} else {
					limit += scanRange;
				}
				row += 3;
				for (; row < limit; row++) {
					skip = false;
					for (bIndex = 0; bIndex < mNumRobots; bIndex++) {
						if (((kDirs[mBots[bIndex].getCrossBoardDir()]
							== IFCConstants._CEAST)
							|| (kDirs[mBots[bIndex].getCrossBoardDir()]
								== IFCConstants._CWEST))
							&& (((mBots[bIndex].getState()
								== BotState.kCrossBoard)
								&& (mBots[bIndex].ypos() == row))
								|| ((mBots[bIndex].getState()
									== BotState.kMoveAlongEdge)
									&& (((mBots[bIndex].getTargetPoint() == null)
										&& (mBots[bIndex].ypos() == row))
										|| (mBots[bIndex].getTargetPoint().y
											== row))))) {
							skip = true;
							break;
						}
					}
					if (skip) {
						row += 1;
						continue;
					}
					nonBlack = 0;
					for (col = mBoardLength - 1;
						col >= mBoardLength / 2;
						col--) {
						if ((boardFilled[row][col] != -1)
							&& (boardFilled[row][col] != mPlayerIndex)) {
							nonBlack += 1;
						}
					}
					if (nonBlack >= maxNonBlack) {
						maxNonBlack = nonBlack;
						maxLoc = row;
					}
				}
				p = new Point(mBoardLength - 1, maxLoc);
				break;

			case IFCConstants._CWEST :
				if (b.ypos() < scanRange) {
					row = 0;
				} else {
					row = b.ypos() - scanRange;
				}
				limit = b.ypos() - 1;
				for (; row < limit; row++) {
					skip = false;
					for (bIndex = 0; bIndex < mNumRobots; bIndex++) {
						if (((kDirs[mBots[bIndex].getCrossBoardDir()]
							== IFCConstants._CEAST)
							|| (kDirs[mBots[bIndex].getCrossBoardDir()]
								== IFCConstants._CWEST))
							&& (((mBots[bIndex].getState()
								== BotState.kCrossBoard)
								&& (mBots[bIndex].ypos() == row))
								|| ((mBots[bIndex].getState()
									== BotState.kMoveAlongEdge)
									&& (((mBots[bIndex].getTargetPoint() == null)
										&& (mBots[bIndex].ypos() == row))
										|| (mBots[bIndex].getTargetPoint().y
											== row))))) {
							skip = true;
							break;
						}
					}
					if (skip) {
						row += 1;
						continue;
					}
					nonBlack = 0;
					for (col = 0; col < mBoardLength / 2; col++) {
						if ((boardFilled[row][col] != -1)
							&& (boardFilled[row][col] != mPlayerIndex)) {
							nonBlack += 1;
						}
					}
					if (nonBlack >= maxNonBlack) {
						maxNonBlack = nonBlack;
						maxLoc = row;
					}
				}
				if (limit + scanRange >= mBoardLength) {
					limit = mBoardLength;
				} else {
					limit += scanRange;
				}
				row += 3;
				for (; row < limit; row++) {
					skip = false;
					for (bIndex = 0; bIndex < mNumRobots; bIndex++) {
						if (((kDirs[mBots[bIndex].getCrossBoardDir()]
							== IFCConstants._CEAST)
							|| (kDirs[mBots[bIndex].getCrossBoardDir()]
								== IFCConstants._CWEST))
							&& (((mBots[bIndex].getState()
								== BotState.kCrossBoard)
								&& (mBots[bIndex].ypos() == row))
								|| ((mBots[bIndex].getState()
									== BotState.kMoveAlongEdge)
									&& (((mBots[bIndex].getTargetPoint() == null)
										&& (mBots[bIndex].ypos() == row))
										|| (mBots[bIndex].getTargetPoint().y
											== row))))) {
							skip = true;
							break;
						}
					}
					if (skip) {
						row += 1;
						continue;
					}
					nonBlack = 0;
					for (col = 0; col < mBoardLength / 2; col++) {
						if ((boardFilled[row][col] != -1)
							&& (boardFilled[row][col] != mPlayerIndex)) {
							nonBlack += 1;
						}
					}
					if (nonBlack >= maxNonBlack) {
						maxNonBlack = nonBlack;
						maxLoc = row;
					}
				}
				p = new Point(0, maxLoc);
				break;

			case IFCConstants._CNORTH :
				if (b.xpos() < scanRange) {
					col = 0;
				} else {
					col = b.xpos() - scanRange;
				}
				limit = b.xpos() + 1;
				for (; col < limit; col++) {
					skip = false;
					for (bIndex = 0; bIndex < mNumRobots; bIndex++) {
						if (((kDirs[mBots[bIndex].getCrossBoardDir()]
							== IFCConstants._CNORTH)
							|| (kDirs[mBots[bIndex].getCrossBoardDir()]
								== IFCConstants._CSOUTH))
							&& (((mBots[bIndex].getState()
								== BotState.kCrossBoard)
								&& (mBots[bIndex].xpos() == col))
								|| ((mBots[bIndex].getState()
									== BotState.kMoveAlongEdge)
									&& (((mBots[bIndex].getTargetPoint() == null)
										&& (mBots[bIndex].xpos() == col))
										|| (mBots[bIndex].getTargetPoint().x
											== col))))) {
							skip = true;
							break;
						}
					}
					if (skip) {
						col += 1;
						continue;
					}
					nonBlack = 0;
					for (row = 0; row < mBoardLength / 2; row++) {
						if ((boardFilled[row][col] != -1)
							&& (boardFilled[row][col] != mPlayerIndex)) {
							nonBlack += 1;
						}
					}
					if (nonBlack >= maxNonBlack) {
						maxNonBlack = nonBlack;
						maxLoc = col;
					}
				}
				if (limit + scanRange >= mBoardLength) {
					limit = mBoardLength;
				} else {
					limit += scanRange;
				}
				col += 3;
				for (; col < limit; col++) {
					skip = false;
					for (bIndex = 0; bIndex < mNumRobots; bIndex++) {
						if (((kDirs[mBots[bIndex].getCrossBoardDir()]
							== IFCConstants._CNORTH)
							|| (kDirs[mBots[bIndex].getCrossBoardDir()]
								== IFCConstants._CSOUTH))
							&& (((mBots[bIndex].getState()
								== BotState.kCrossBoard)
								&& (mBots[bIndex].xpos() == col))
								|| ((mBots[bIndex].getState()
									== BotState.kMoveAlongEdge)
									&& (((mBots[bIndex].getTargetPoint() == null)
										&& (mBots[bIndex].xpos() == col))
										|| (mBots[bIndex].getTargetPoint().x
											== col))))) {
							skip = true;
							break;
						}
					}
					if (skip) {
						col += 1;
						continue;
					}
					nonBlack = 0;
					for (row = 0; row < mBoardLength / 2; row++) {
						if ((boardFilled[row][col] != -1)
							&& (boardFilled[row][col] != mPlayerIndex)) {
							nonBlack += 1;
						}
					}
					if (nonBlack >= maxNonBlack) {
						maxNonBlack = nonBlack;
						maxLoc = col;
					}
				}
				p = new Point(maxLoc, 0);
				break;

			case IFCConstants._CSOUTH :
				if (b.xpos() < scanRange) {
					col = 0;
				} else {
					col = b.xpos() - scanRange;
				}
				limit = b.xpos() - 1;
				for (; col < limit; col++) {
					skip = false;
					for (bIndex = 0; bIndex < mNumRobots; bIndex++) {
						if (((kDirs[mBots[bIndex].getCrossBoardDir()]
							== IFCConstants._CNORTH)
							|| (kDirs[mBots[bIndex].getCrossBoardDir()]
								== IFCConstants._CSOUTH))
							&& (((mBots[bIndex].getState()
								== BotState.kCrossBoard)
								&& (mBots[bIndex].xpos() == col))
								|| ((mBots[bIndex].getState()
									== BotState.kMoveAlongEdge)
									&& (((mBots[bIndex].getTargetPoint() == null)
										&& (mBots[bIndex].xpos() == col))
										|| (mBots[bIndex].getTargetPoint().x
											== col))))) {
							skip = true;
							break;
						}
					}
					if (skip) {
						col += 1;
						continue;
					}
					nonBlack = 0;
					for (row = mBoardLength - 1;
						row >= mBoardLength / 2;
						row--) {
						if ((boardFilled[row][col] != -1)
							&& (boardFilled[row][col] != mPlayerIndex)) {
							nonBlack += 1;
						}
					}
					if (nonBlack >= maxNonBlack) {
						maxNonBlack = nonBlack;
						maxLoc = col;
					}
				}
				if (limit + scanRange >= mBoardLength) {
					limit = mBoardLength;
				} else {
					limit += scanRange;
				}
				col += 3;
				for (; col < limit; col++) {
					skip = false;
					for (bIndex = 0; bIndex < mNumRobots; bIndex++) {
						if (((kDirs[mBots[bIndex].getCrossBoardDir()]
							== IFCConstants._CNORTH)
							|| (kDirs[mBots[bIndex].getCrossBoardDir()]
								== IFCConstants._CSOUTH))
							&& (((mBots[bIndex].getState()
								== BotState.kCrossBoard)
								&& (mBots[bIndex].xpos() == col))
								|| ((mBots[bIndex].getState()
									== BotState.kMoveAlongEdge)
									&& (((mBots[bIndex].getTargetPoint() == null)
										&& (mBots[bIndex].xpos() == col))
										|| (mBots[bIndex].getTargetPoint().x
											== col))))) {
							skip = true;
							break;
						}
					}
					if (skip) {
						col += 1;
						continue;
					}
					nonBlack = 0;
					for (row = mBoardLength - 1;
						row >= mBoardLength / 2;
						row--) {
						if ((boardFilled[row][col] != -1)
							&& (boardFilled[row][col] != mPlayerIndex)) {
							nonBlack += 1;
						}
					}
					if (nonBlack >= maxNonBlack) {
						maxNonBlack = nonBlack;
						maxLoc = col;
					}
				}
				p = new Point(maxLoc, mBoardLength - 1);
				break;

			default :
				p = new Point(b.xpos(), b.ypos());
				break;

		}

		/* End change by Adam M. Rosenzweig 9/16/03 */

		b.setTargetPoint(p);

	}

	private Point flattenPointToEdge(int iEdge, Point iP) throws Exception {
		Point f = new Point(iP);

		switch (kDirs[iEdge]) {
			case IFCConstants._CNORTH :
				f.y = mUpperLeft.y;
				break;
			case IFCConstants._CSOUTH :
				f.y = mUpperLeft.y + mLength - 1;
				break;
			case IFCConstants._CEAST :
				f.x = mUpperLeft.x + mLength - 1;
				break;
			case IFCConstants._CWEST :
				f.x = mUpperLeft.x;
				break;
			default :
				throw new Exception("Invalid direction index: " + iEdge);
		}

		return f;
	}

	private void findCandidateHoles(int iNumCandidates, int iNumHoles)
		throws Exception {
		int largestHoleStart = mLength - kCandidateHoleLength;

		if (largestHoleStart < 0) {
			// board is tiny - no need to spend time sampling for good holes
			int x = (mLength - 1) / 2;
			int y = x;
			for (int i = 0; i < iNumHoles; i++) {
				mHoles.add(new Point(x, y));
			}
			return;
		}

		int x;
		int y;
		for (int i = 0; i < iNumCandidates; i++) {
			x = mUpperLeft.x + mRandom.nextInt(largestHoleStart);
			y = mUpperLeft.y + mRandom.nextInt(largestHoleStart);
			int xOffset = 0;
			mRandom.nextInt(kCandidateHoleLength);
			int yOffset = 0;
			mRandom.nextInt(kCandidateHoleLength);

			Hole h =
				new Hole(
					new Point(x + xOffset, y + yOffset),
					measureFilledSpace(x, y));

			mHoles.add(h);
		}

		// sort candidate holes
		Collections.sort(mHoles);
		for (int i = 0; i < iNumHoles; i++) {
			Hole h = (Hole) mHoles.get(i);
			mHoles.set(i, h.mCenter);
		}

		// remove remaining candidates that we no longer care about
		for (int i = iNumCandidates - 1; i >= iNumHoles; i--) {
			mHoles.remove(i);
		}
	}

	private int measureFilledSpace(int iX, int iY) throws Exception {
		boolean[][] filled = mRect.filled();
		int[][] colors = mRect.colors();
		float score = 0f;

		for (int i = iX; i < iX + kCandidateHoleLength; i++) {
			for (int j = iY; j < iY + kCandidateHoleLength; j++) {
				if (!filled[i][j]) {
					score++;

					if (colors[i][j] != mPlayerIndex) {
						score += 0.3;
					}
				}
			}
		}

		return Math.round(score);
	}

	private int getOppositeDir(int iDir) {
		return (iDir + 2) % 4;
	}

	public String name() throws Exception {
		return kName;
	}

	public Color color() throws Exception {
		return kColor;
	}

	public boolean interactive() throws Exception {
		return false;
	}

	private static int indexOfDir(char iDir) {
		switch (iDir) {
			case IFCConstants._CNORTH :
				return 3;
			case IFCConstants._CSOUTH :
				return 1;
			case IFCConstants._CEAST :
				return 0;
			case IFCConstants._CWEST :
				return 2;
			default :
				return -1;
		}
	}

	private class Bot extends Robot {
		private int mDir;
		private int mCrossBoardDir;
		private BotState mState;
		private BotState mPreviousState;
		private MoveState mMoveState;
		private Point mTargetPoint;
		private Point mOldTargetPoint;
		private Robot mLastClosestEnemy;
		private int mRoundsClosestPlayerUnchanged;
		private int mSquaresMovedAround;
		private List mPosHistory;
		private int mLastTrailLength;
		private int mRoundsTrailLengthUnchanged;
		private int mRoundsChasing;
		private SortedMap mSamples = new TreeMap();
		private int mLastSamplesUpdate = -1;
		private Point mOriginPoint;

		public Bot(Group1Player1 iOwner, int iX, int iY, char iDir)
			throws Exception {
			super(iX, iY);
			setDir(indexOfDir(iDir));
			setPlayerIndex(iOwner.mPlayerIndex);

			mLastClosestEnemy = null;
			mRoundsClosestPlayerUnchanged = 0;

			mLastTrailLength = Integer.MAX_VALUE;
			mRoundsTrailLengthUnchanged = 0;

			mPosHistory = new ArrayList(500);

			mMoveState = MoveState.kIdle;
		}

		public void moveToTargetPoint() throws Exception {
			Point p = mTargetPoint;
			int dx = p.x - xpos(); // if dx < 0, move W, if dx > 0, move E
			int dy = p.y - ypos();
			// if dy < 0, move N, if dy > 0, move S			

			char dir;

			if (dx < 0) {
				dir = IFCConstants._CWEST;
			} else if (dx > 0) {
				dir = IFCConstants._CEAST;
			} else {
				if (dy < 0) {
					dir = IFCConstants._CNORTH;
				} else if (dy > 0) {
					dir = IFCConstants._CSOUTH;
				} else {
					mTargetPoint = null;
					return;
				}
			}

			setDir(indexOfDir(dir));
		}

		/**
		 * Robots cannot move diagonally, so Manhattan distance is technically more
		 * correct than Euclidean distance.
		 */
		public int manhattanDistanceTo(int iX, int iY) throws Exception {
			return Math.abs(iX - xpos()) + Math.abs(iY - ypos());
		}

		public boolean isBeingChased() throws Exception {
			// original trail length tracking scheme is courtesy of Vlad in group 3
			// some parameters have been tweaked

			int len = getTrailLength();

			if (mLastTrailLength == len || (mLastTrailLength - len) == 1) {
				mRoundsTrailLengthUnchanged++;
			} else {
				mRoundsTrailLengthUnchanged = 0;
			}
			mLastTrailLength = len;

			Robot closestEnemy = closestEnemyRobot();
			if (mRoundsTrailLengthUnchanged > 5) {
				if (len > 0
					|| manhattanDistanceTo(
						closestEnemy.xpos(),
						closestEnemy.ypos())
						<= 1) {
					return true;
				}
			}

			//if (true) return false;

			if (robotsEqual(closestEnemy, mLastClosestEnemy)
				&& getLastMove(closestEnemy) == getLastMove(this)) {
				mRoundsClosestPlayerUnchanged++;
			} else {
				mRoundsClosestPlayerUnchanged = 0;
			}

			mLastClosestEnemy = closestEnemy;

			if (mRoundsClosestPlayerUnchanged >= 15
				&& manhattanDistanceTo(closestEnemy.xpos(), closestEnemy.ypos())
					<= 6) {
				return true;
			}

			return false;

		}

		private char getLastMove(Robot r) throws Exception {
			MoveResult[] history = mRect.history();
			MoveResult last = history[history.length - 1];
			return last.moves()[r.playerIndex()][r.ID()];
		}

		public void recordPosition() throws Exception {
			mPosHistory.add(new Point(xpos(), ypos()));
		}

		public int getTrailLength() throws Exception {
			int len = 0;
			int[][] colors = mRect.colors();

			for (int i = mPosHistory.size() - 1; i >= 0; i--) {
				Point p = (Point) mPosHistory.get(i);
				if (colors[p.x][p.y] != mPlayerIndex) {
					break;
				}
				len++;
			}

			return len;
		}

		private boolean robotsEqual(Robot iR1, Robot iR2) throws Exception {
			if (iR1 == null) {
				return (iR2 == null);
			}

			if (iR2 == null) {
				return false;
			}

			return (
				iR1.playerIndex() == iR2.playerIndex() && iR1.ID() == iR2.ID());
		}

		// local build routine courtesy of Vlad in group 3
		private void updateSamples() throws Exception {
			if (mRect.rounds() == mLastSamplesUpdate)
				return;

			Point probe, target;
			double score;

			Collection old = mSamples.values();
			mSamples.clear();

			for (Iterator iter = old.iterator(); iter.hasNext();) {
				probe = (Point) iter.next();
				score = pointScore(probe);
				if (score >= 9) {
					mSamples.put(new Double(score), probe);
				}
			}

			for (int i = 0; i < 5; i++) {
				probe =
					new Point(
						mRandom.nextInt(mLength),
						mRandom.nextInt(mLength));
				score = pointScore(probe);
				if (score >= 9) {
					mSamples.put(new Double(score), probe);
				}
			}

			mLastSamplesUpdate = mRect.rounds();
		}
		
		public void buildRectangle() throws Exception {
			if (mMoveState != MoveState.kMovingToAnchor
				&& mMoveState != MoveState.kMovingToTarget) {

					mMoveState = MoveState.kMovingToTarget;
				setOriginPoint(new Point(xpos(), ypos()));				
			}

			boolean done = moveToCurrentDestination();

			if (done) {
				if (mMoveState == MoveState.kMovingToTarget) {
					mMoveState = MoveState.kMovingToAnchor;
					moveToCurrentDestination();
				} else if (mMoveState == MoveState.kMovingToAnchor) {
					mMoveState = MoveState.kIdle;
				}
			}
		}

		public boolean moveToCurrentDestination() throws Exception {
			if (mMoveState == MoveState.kChangingAnchor) {
				mTargetPoint = mOriginPoint;
				moveToTargetPoint();
				return mTargetPoint == null;
			} else {
				mTargetPoint =
					(mMoveState == MoveState.kMovingToAnchor)
						? mOriginPoint
						: mTargetPoint;
				moveToTargetPoint();
				return mTargetPoint == null;
			}
		}

		public Point getBestTarget(Point iOrigin) throws Exception {
			float riskOfFailure = 0.3f;
			int tx, ty, bestTx = 0, bestTy = 0;
			double bestScore = -1, score;
			int maxDelta = (int) (mLength * riskOfFailure);
			int vectors[][] = { { 1, 1 }, {
					1, -1 }, {
					-1, 1 }, {
					-1, -1 }
			};

			for (int k = 0; k < vectors.length; k++) {
				for (int i = 2; i < maxDelta; i++) {
					for (int j = 2; j < maxDelta; j++) {
						tx = iOrigin.x + i * vectors[k][0];
						ty = iOrigin.y + j * vectors[k][1];
						score = rectangleScore(iOrigin.x, iOrigin.y, tx, ty);
						if (score > bestScore) {
							bestScore = score;
							bestTx = tx;
							bestTy = ty;
						}
					}
				}
			}

			return new Point(bestTx, bestTy);
		}

		private double rectangleScore(Point iP1, Point iP2) throws Exception {
			return rectangleScore(iP1.x, iP1.y, iP2.x, iP2.y);
		}

		private double rectangleScore(int iX1, int iY1, int iX2, int iY2)
			throws Exception {
			int temp;
			double sum = 0;
			int[][] colors = mRect.colors();

			if (iX1 > iX2) {
				temp = iX2;
				iX2 = iX1;
				iX1 = temp;
			}
			if (iY1 > iY2) {
				temp = iY2;
				iY2 = iY1;
				iY1 = temp;
			}

			if (iX1 < 0 || iY1 < 0 || iX2 >= mLength || iY2 >= mLength)
				return 0;

			for (int i = iX1; i <= iX2; i++) {
				if (colors[i][iY1] == _CFILLED || colors[i][iY2] == _CFILLED)
					return 0;
			}

			for (int i = iY1; i <= iY2; i++) {
				if (colors[iX1][i] == _CFILLED || colors[iX2][i] == _CFILLED)
					return 0;
			}

			for (int i = iX1 + 1; i < iX2; i++) {
				for (int j = iY1 + 1; j < iY2; j++) {
					if (colors[i][j] != _CFILLED) {
						sum++;
					}
				}
			}

			return sum;
		}

		private double pointScore(Point iP) throws Exception {
			int[][] colors = mRect.colors();
			double score = 0;

			for (int i = iP.x - 4; i <= iP.x + 4; i++) {
				for (int j = iP.y - 4; j <= iP.y + 4; j++) {
					if (i < 0 || j < 0 || i >= mLength || j >= mLength)
						continue;
					if (colors[i][j] != _CFILLED)
						score++;
				}
			}

			return score;
		}

		/**
		 * Routine for determining the closest enemy robot was taken from
		 * OldGroup5Robot.java and refactored to save time. 
		 */
		public Robot closestEnemyRobot() throws Exception {
			Robot[][] allRobots = mRect.allRobots();
			int closest = Integer.MAX_VALUE;
			Robot closestRobot = null;

			for (int i = 0; i < allRobots.length; i++) {
				if (i == mPlayerIndex) {
					continue;
				}

				for (int j = 0; j < allRobots[i].length; j++) {
					Robot enemyRobot = allRobots[i][j];
					int dist =
						manhattanDistanceTo(
							enemyRobot.xpos(),
							enemyRobot.ypos());
					if (dist < closest) {
						closest = dist;
						closestRobot = enemyRobot;
					}
				}
			}

			return closestRobot;
		}

		public boolean willCollide() throws Exception {
			switch (kDirs[mDir]) {
				case IFCConstants._CNORTH :
					return ypos() <= mUpperLeft.y;
				case IFCConstants._CSOUTH :
					return ypos() >= mUpperLeft.y + (mLength - 1);
				case IFCConstants._CEAST :
					return xpos() >= mUpperLeft.x + (mLength - 1);
				case IFCConstants._CWEST :
					return xpos() <= mUpperLeft.x;
				default :
					return false;
			}
		}

		/**
		 * @return
		 */
		public int getDir() {
			return mDir;
		}

		/**
		 * @param iI
		 */
		public void setDir(int iI) {
			mDir = iI;
		}
		/**
		 * @return
		 */
		public BotState getState() {
			return mState;
		}

		/**
		 * @param iState
		 */
		public void setState(BotState iState) {
			mState = iState;
		}

		/**
		 * @return
		 */
		public Point getTargetPoint() {
			return mTargetPoint;
		}

		/**
		 * @param iPoint
		 */
		public void setTargetPoint(Point iPoint) {
			mTargetPoint = iPoint;
		}

		/**
		 * @return
		 */
		public int getCrossBoardDir() {
			return mCrossBoardDir;
		}

		/**
		 * @param iI
		 */
		public void setCrossBoardDir(int iI) {
			mCrossBoardDir = iI;
		}
		/**
		 * @return
		 */
		public int getSquaresMovedAround() {
			return mSquaresMovedAround;
		}

		/**
		 * @param iI
		 */
		public void setSquaresMovedAround(int iI) {
			mSquaresMovedAround = iI;
		}

		/**
		 * @return
		 */
		public int getRoundsChasing() {
			return mRoundsChasing;
		}

		/**
		 * @param iI
		 */
		public void setRoundsChasing(int iI) {
			mRoundsChasing = iI;
		}

		/**
		 * @return
		 */
		public BotState getPreviousState() {
			return mPreviousState;
		}

		/**
		 * @param iState
		 */
		public void setPreviousState(BotState iState) {
			mPreviousState = iState;
		}

		/**
		 * @return
		 */
		public Point getOldTargetPoint() {
			return mOldTargetPoint;
		}

		/**
		 * @param iPoint
		 */
		public void setOldTargetPoint(Point iPoint) {
			mOldTargetPoint = iPoint;
		}

		/**
		 * @return
		 */
		public MoveState getMoveState() {
			return mMoveState;
		}

		/**
		 * @param iState
		 */
		public void setMoveState(MoveState iState) {
			mMoveState = iState;
		}

		/**
		 * @return
		 */
		public Point getOriginPoint() {
			return mOriginPoint;
		}

		/**
		 * @param iPoint
		 */
		public void setOriginPoint(Point iPoint) {
			mOriginPoint = iPoint;
		}

	}

	private static class BotState {
		public static final BotState kCrossBoard = new BotState("cross board");
		public static final BotState kMoveAlongEdge =
			new BotState("move along edge");
		public static final BotState kMoveAlongCompleteEdge =
			new BotState("move along complete edge");
		public static final BotState kChase = new BotState("chase");
		public static final BotState kBuildLocally =
			new BotState("build locally");

		private final String mDesc;

		private BotState(String iDesc) {
			mDesc = iDesc;
		}

		public String toString() {
			return mDesc;
		}
	}

	private static class Hole implements Comparable {
		private Point mCenter;
		private int mUnfilledSpace;

		public Hole(Point iCenter, int iUnfilledSpace) {
			mCenter = iCenter;
			mUnfilledSpace = iUnfilledSpace;
		}

		public int compareTo(Object iO) {
			Hole other = (Hole) iO;
			return other.mUnfilledSpace - mUnfilledSpace;
		}
	}

	private static class MoveState {
		public static final MoveState kMovingToTarget = new MoveState("target");
		public static final MoveState kMovingToAnchor = new MoveState("anchor");
		public static final MoveState kIdle = new MoveState("idle");
		public static final MoveState kChangingAnchor =
			new MoveState("change anchor");

		private final String mState;

		private MoveState(String s) {
			mState = s;
		}

		public String toString() {
			return mState;
		}
	}
}
