//***********************************************************
//*
//* File:           And1Player.java
//* Author:         Eric Li
//* Contact:        ewl34
//* Update:         9/8/03
//*
//* Description:    The sickest player ever
//*                 We implemented a pretty complicated chase
//*                 strategy for 2 player games.
//*                 We took Group5's cavalry player for our
//*                 multiplayer strategy. Props to them.
//*                 We robbed Group8's search and fill strat
//*                 egy for certain scenarios, too.
//*                 
//*
//***********************************************************

package Rectangles;

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

/*  - Even distribution in a grid pattern as the starting points
 *  - Proximity detection and backoff in effect
 *  - Chase most distant at start
 *  - Take no action if game is frozen due to anti-chasing 
 */
public final class Group2PlayerF extends StolenGroup5 {

    // The board
    Rectangles _rect;

    static final String _CNAME = "And1 Player";
    static final Color  _CCOLOR = new Color(0.0f, 1.0f, 0.0f);

    StolenGroup8 sg8 = new StolenGroup8();

    double density;

    // Choose starting positions
    public Robot[] register(Rectangles __rectangles) throws Exception {
        _rect = __rectangles;

        Robot[] RET;

        // Get board stats
        int boardSize = _rect.size();
        int numRobots = _rect.numRobots();
        int numPlayers = _rect.numPlayers();

        density = numRobots * numPlayers;

        RET = new Robot[numRobots];

        if ( numPlayers == 2 || numRobots == 1)
        {

            int bps = ( numRobots + 3 ) / 4;  // bots per side 
            int spacing = boardSize / bps;
    
            int side = 0;
            int dist = 0;
            int margin = boardSize / 6;
            for ( int i = 0; i < numRobots; i++ )
            {
                side = i % 4;
                dist = ( i % ( 4 * boardSize ) ) / 4 * spacing;
                if ( side == 0 )
                {
                    RET[i] = new Robot( dist, 0 + margin );
                }
                else if ( side == 1 )
                {
                    RET[i] = new Robot( boardSize - 1 - margin, dist );
                }
                else if ( side == 2 )
                {
                    RET[i] = new Robot( boardSize - 1 - dist, boardSize - 1 - margin );
                }
                else
                {
                    RET[i] = new Robot( 0 + margin, boardSize - 1 - dist );
                }
            }

        }
        else
        {
            if ( numRobots <= 10 )
            {
                if ( numPlayers > 5 )
                    return super.register(__rectangles);
                else
                    return sg8.register(__rectangles, this);
            }
            else
            {
                return super.register(__rectangles);
            }
        }
        
        return RET;
    }

    // Some constants we use
    int MAX_PROX = 2;
    
    // Stuff used by move(), put them outside for perf reasons
    int boardSize;
    int numRobots;
    int numPlayers;
    int oppIndex;
    int round;
    Robot[] myBots;
    Robot[] oppBots;
    int[] target;
    char[] direction;
    int proximity;
    boolean backoff;

    // Decide how to move
    public char[] move() throws Exception 
    {
        char[] RET;

        boardSize = _rect.size();

        if ( _rect.numPlayers() == 2 )
        {
    
            round = _rect.rounds();
    
            if ( round == 0 )
            {
                boardSize = _rect.size();
                numRobots = _rect.numRobots();
                numPlayers = _rect.numPlayers();
                oppIndex = (_rect.indexOf(this) == 0) ? 1 : 0;
    
                myBots = (_rect.allRobots())[_rect.indexOf(this)];
                oppBots = (_rect.allRobots())[oppIndex];
    
                target = findLeastDistant(myBots, oppBots);
                direction = new char[numRobots];
                proximity = 0;
                backoff = false;
            }
            else
            {
                
                if ( round % 8 == 0 )
                {
                    if ( proximity > 0 )
                        proximity--;
                }
    
                boolean reacquire = false;
                if ( round % 50 == 0 && numPlayers > 2 )
                {
                    MoveResult[] allhist = _rect.history();
                    double[] scores = allhist[allhist.length-1].scores();
                    double highest = 0.0;
                    int index = 0;
                    boolean found = false;
                    for ( int i = 0; i < scores.length; i++ )
                    {
                        if ( scores[i] > highest )
                        {
                            highest = scores[i];
                            index = i;
                            found = true;
                        }
                    }
                    if ( index != _rect.indexOf(this) && index != oppIndex && found )
                    {
                        oppIndex = index;
                        reacquire = true;
                    }
                }
    
                myBots = (_rect.allRobots())[_rect.indexOf(this)];
                oppBots = (_rect.allRobots())[oppIndex];
    
                if ( reacquire )
                {
                    target = findLeastDistant(myBots, oppBots);
                    reacquire = false;
                }
    
                // Switch targets if all enemy bots have stopped moving
                boolean frozen = true;
                MoveResult[] allhist = _rect.history();
                char[] prevmoves = (allhist[allhist.length-1].moves())[oppIndex];
                for ( int i = 0; i < prevmoves.length; i++ )
                {
                    if ( prevmoves[i] != 'Y' )
                    {
                        frozen = false;
                        break;
                    }
                }
                if ( frozen )
                {
                    double[] scores = allhist[allhist.length-1].scores();
                    if ( scores[_rect.indexOf(this)] < scores[oppIndex] )
                    {
                        target = findMostDistant(myBots, oppBots);
                        MAX_PROX = 0;
                    }
                }
                
            }
    
            RET = new char[numRobots];
    
            int xdist = 0;
            int ydist = 0;
    
            for ( int i = 0; i < numRobots; i++ )
            {
                xdist = myBots[i].xpos() - oppBots[target[i]].xpos();
                ydist = myBots[i].ypos() - oppBots[target[i]].ypos();
    
                if ( round == 0 )
                {
                    direction[i] = findDirection(xdist, ydist);
                    RET[i] = direction[i];
                }
                else
                {
                    if ( Math.abs(xdist) + Math.abs(ydist) <= proximity )
                    {
                        MoveResult[] allhist = _rect.history();
                        char[][] prevmoves = allhist[allhist.length-1].moves();
                        if ( prevmoves[oppIndex][target[i]] == 'Y' ) // add score check 
                        {
                            if ( proximity < MAX_PROX )
                            {
                                proximity++; 
                                RET[i] = oppDir(direction[i]);
                            }
                            else
                            {
                                RET[i] = 'Y';
                            }
                        }
                        else
                        {
                            direction[i] = 'Y';
                            RET[i] = direction[i];
                        }
                        continue;
                    }
    
                    if ( direction[i] == 'W' )
                    {
                        if ( xdist <= 0 )
                            direction[i] = findDirection(xdist, ydist);
                    }
                    else if ( direction[i] == 'E' )
                    {
                        if ( xdist >= 0 )
                            direction[i] = findDirection(xdist, ydist);
                    }
                    else if ( direction[i] == 'N' )
                    {
                        if ( ydist <= 0 )
                            direction[i] = findDirection(xdist, ydist);
                    }
                    else if ( direction[i] == 'S' )
                    {
                        if ( ydist >= 0 )
                            direction[i] = findDirection(xdist, ydist);
                    }
                    else 
                        direction[i] = findDirection(xdist, ydist);
    
                    RET[i] = direction[i];
                }
            }

        }
        else
        {
            // check the percentage of black spaces on the board
            boolean[][] filled = _rect.filled();
            int count = 0;
            for ( int i = 0; i < boardSize; i++ )
            {
                for ( int j = 0; j < boardSize; j++ )
                {
                    if ( filled[i][j] )
                    {
                        count++;
                    }
                }
            }

            if ( numRobots <= 10 )
            {
                if ( numPlayers > 5 )
                    return super.move();
                else
                    return sg8.move();
            }
            else
            {
                return super.move();
            }
            

            //sg8.move();
            //return super.move();
        }

        return RET;
    }

    public char oppDir(char dir)
    {
        switch ( dir )
        {
            case 'N':
                return 'S';
            case 'S':
                return 'N';
            case 'E':
                return 'W';
            case 'W':
                return 'E';
            default:
                return 'Y';
        }
    }

    public char findDirection( int xdist, int ydist ) throws Exception
    {
        char RET = 'Y';
        int axdist = (xdist >= 0) ? xdist : -xdist;
        int aydist = (ydist >= 0) ? ydist : -ydist;

        if ( axdist > aydist )
        {
            if ( xdist > 0 )
                RET = 'W';
            else if ( xdist < 0 )
                RET = 'E';
            else
                RET = 'Y';
        }
        else
        {
            if ( ydist > 0 )
                RET = 'N';
            else if ( ydist < 0 )
                RET = 'S';
            else
                RET = 'Y';
        }

        return RET;
    }

    public int[] findMostDistant( Robot[] playerA, Robot[] playerB ) throws Exception
    {
        if ( playerA.length != playerB.length )
        {
            throw new Exception("2 players do not have the same number of robots.");
        }

        int length = playerA.length;
        double[][] distances = new double[length][length];
        int[] mostDistant = new int[length];

        ArrayList enemyBots = new ArrayList(length);

        for ( int i = 0; i < length; i++ )
        {
            enemyBots.add( new Integer(i) );
            for ( int j = 0; j < length; j++ )
            {
                distances[i][j] = Math.abs( playerA[i].xpos() - playerB[j].xpos() ) + Math.abs( playerA[i].ypos() - playerB[j].ypos() );
            }
        }

        double[] values = new double[length];
        Iterator itr = null;
        double val = 0.0;
        int index = 0;
        for ( int i = 0; i < length; i++ )
        {
            itr = enemyBots.iterator();
            while ( itr.hasNext() )
            {
                index = ((Integer)(itr.next())).intValue();
                val = distances[i][index];
                if ( val > values[i] )
                {
                    mostDistant[i] = index;
                    values[i] = val;
                }
            }
            enemyBots.remove( new Integer( mostDistant[i] ) );
        }

        return mostDistant;
    }    
    

    public int[] findLeastDistant( Robot[] playerA, Robot[] playerB ) throws Exception
    {
        if ( playerA.length != playerB.length )
        {
            throw new Exception("2 players do not have the same number of robots.");
        }

        int length = playerA.length;
        double[][] distances = new double[length][length];
        int[] leastDistant = new int[length];

        ArrayList enemyBots = new ArrayList(length);

        for ( int i = 0; i < length; i++ )
        {
            enemyBots.add( new Integer(i) );
            for ( int j = 0; j < length; j++ )
            {
                distances[i][j] = Math.abs( playerA[i].xpos() - playerB[j].xpos() ) + Math.abs( playerA[i].ypos() - playerB[j].ypos() );
            }
        }

        double[] values = new double[length];
        for ( int i = 0; i < length; i++ )
        {
            values[i] = boardSize + boardSize;
        }
        Iterator itr = null;
        double val = 0.0;
        int index = 0;
        for ( int i = 0; i < length; i++ )
        {
            itr = enemyBots.iterator();
            while ( itr.hasNext() )
            {
                index = ((Integer)(itr.next())).intValue();
                val = distances[i][index];
                if ( val < values[i] )
                {
                    leastDistant[i] = index;
                    values[i] = val;
                }
            }
            enemyBots.remove( new Integer( leastDistant[i] ) );
        }

        return leastDistant;
    }    


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

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

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

// Some Group5 code we used for multiplayer

class StolenGroup5 extends StolenG5Cavalry implements IFCPlayer {

    int[][] _hist_x = new int[3][];
    int[][] _hist_y = new int[3][];

    public Robot[] register( Rectangles rect ) throws Exception {
	init( rect, rect.numRobots() );

	int gap = ( (_size + _nr_robots/2) / _nr_robots - 1 ) & (~1);
	if ( gap < 0 )
	    gap = 0;
	setParam( gap, gap+gap+1, _rand.nextInt(8) );
	return initRobots();
    }

    public void rewind() throws Exception {
	int gap = _gap - 2;
	if ( gap < 0 )
	    gap = 0;
	setParam( gap, gap+gap+1, _orientation );
	if ( _yreturn )
	    _ypos = _size - _width;
	else
	    _ypos = 0;
    }

    public char[] move() throws Exception {
	try 
	{
            if ( _game.numPlayers() == 2 ) {
                if ( chased() )
                    return stay();

                savePosition();
            }
	    return super.move();
	}
	catch ( Exception e )
	{
	    e.printStackTrace();
	}
	return stay();
    }

    public String name() throws Exception {
	return "Annealing Cavalry";
    }

    public Color color() throws Exception {
	return new Color( 0.5f, 1.0f, 1.0f );
    }

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

    void savePosition() throws Exception {
        for ( int i=_hist_x.length-1; i>0; i-- ) {
            _hist_x[i] = _hist_x[i-1];
            _hist_y[i] = _hist_y[i-1];
        }
        _hist_x[0] = new int[_nr_robots];
        _hist_y[0] = new int[_nr_robots];
        
        for ( int j=0; j<_nr_robots; j++ ) {
            _hist_x[0][j] = _my_robots[j].xpos();
            _hist_y[0][j] = _my_robots[j].ypos();
        }
    }

    boolean chased() throws Exception {
        int n = 0;
        if ( _hist_x[0] == null || _hist_x[1] == null )
            return false;

        Robot[][] all_robots = _game.allRobots();
        for ( int i=0; i<_game.numPlayers(); i++ ) {
            if ( i == _game.indexOf( this ) )
                continue;
            for ( int j=0; j<_nr_robots; j++ ) {
                int x = all_robots[i][j].xpos();
                int y = all_robots[i][j].ypos();
                
                for ( int k=0; k<_nr_robots; k++ ) {

                    if ( ( x == _my_robots[k].xpos() 
                           && y == _my_robots[k].ypos() )
                         || (x == _hist_x[0][k]) && (y == _hist_y[0][k])
                         || (x==_hist_x[1][k]) && (y==_hist_y[1][k]) )
                        n++;
                }
            }
        }

        return n > _nr_robots / 2;
    }
}


class StolenG5Base implements IFCConstants {
    public static final int _CISVERT = 0x4;

    Rectangles _game;
    static Random _rand;
    int _size;
    int _nr_robots;
    Robot[] _my_robots = null;

    public StolenG5Base() {
        if ( null == _rand )
            _rand = new Random();
    }
    
    public final void init( Rectangles rect, int num ) throws Exception{
	_game = rect;
	_size = _game.size();
	_nr_robots = num;
    }

    public Robot[] initRobots() throws Exception {
	return randomInitRobots();
    }

    public final Robot[] randomInitRobots() throws Exception {
	_my_robots = new Robot[ _nr_robots ];
	for ( int i=0; i<_nr_robots; i++ )
	    _my_robots[i] = new Robot( _rand.nextInt(_size), 
				       _rand.nextInt(_size) );
	return _my_robots;
    }

    void validatePos( Vertex pos ) throws Exception {
	int x = pos.xpos();
	int y = pos.ypos();

	if ( x < 0 )
	    pos.setXpos( 0 );
	else if ( x >= _size )
	    pos.setXpos( _size-1 );
	    
	if ( y < 0 )
	    pos.setYpos( 0 );
	else if ( y >= _size )
	    pos.setYpos( _size-1 );
    }

    char[] stay() {
        char[] moves = new char[ _nr_robots ];
        for ( int i=0; i<_nr_robots; i++ )
            moves[i] = _CSTAY;
        return moves;
    }

    char[] moveTo( Vertex[] positions ) throws Exception {
	char[] moves = new char[ _nr_robots ];

	boolean moved = false;

	for ( int i=0; i<_nr_robots; i++ )
	{
	    if ( _my_robots[i].xpos() == positions[i].xpos() ) {
		if ( _my_robots[i].ypos() == positions[i].ypos() )
		    moves[i] = _CSTAY;
		else 
		{
		    if ( _my_robots[i].ypos() < positions[i].ypos() )
			moves[i] = _CSOUTH;
		    else
			moves[i] = _CNORTH;
		    moved = true;
		}
	    } 
	    else 
	    {
		if ( _my_robots[i].xpos() < positions[i].xpos() )
		    moves[i] = _CEAST;
		else
		    moves[i] = _CWEST;

		moved = true;
	    }
	}

	if ( moved )
	    return moves;
	return null;
    }

    /*
     * HF - note: These tables translate directions according to the
     * location of the origin and the x/y orientation.  We have
     * totally 8 orientations.
     *
     * origin at: SE(southeast), NE(northeast), SW(southwest), NW(northwest)
     * x/y orientation: V(landscape), default: portrait
     * corresponding actions - F(flipping vertically) 
     *                         M(mirroring horizontally)
     *                         T(transpose x and y)
     *  index      orientation      actions
     *    0            SE             FM  (rotate 180)
     *    1            NE             M
     *    2            SW             F
     *    3            NW             -
     *    4            SEV            TFM (tranpose anti-diagonally)
     *    5            NEV            TM  (rotate 90CW)
     *    6            SWV            TF  (rotate 90CCW)
     *    7            NWV            T
     */
    private char[] __rotate_east = { 'W', 'W', 'E', 'E', 'N', 'S', 'N', 'S' };
    private char[] __rotate_west = { 'E', 'E', 'W', 'W', 'S', 'N', 'S', 'N' };
    private char[] __rotate_south = { 'N', 'S', 'N', 'S', 'W', 'W', 'E', 'E' };
    private char[] __rotate_north = { 'S', 'N', 'S', 'N', 'E', 'E', 'W', 'W' };

    final char rotateMove( char move, int orient ) throws Exception {
	switch ( move )
	{
	case _CEAST:
	    return __rotate_east[ orient ];
	case _CWEST:
	    return __rotate_west[ orient ];
	case _CNORTH:
	    return __rotate_north[ orient ];
	case _CSOUTH:
	    return __rotate_south[ orient ];
	default:
	    return move;
	}
    }

    final Vertex rotatePos( int x, int y, int orient ) throws Exception {
	if ( ( orient & _CISVERT ) != 0 )
	{
	    int t = x;
	    x = y;
	    y = t;
	}

        switch ( orient & 0x3 ) 
	{
	case _CSOUTHEAST:
	    y = _size - y - 1;
	case _CNORTHEAST:
	    x = _size - x - 1;
	    break;
	case _CSOUTHWEST:
	    y = _size - y - 1;
	    break;
	default:
	    break;
	}

	return new Vertex( x, y );
    }
}


class StolenG5Cavalry extends StolenG5Base {
    int _orientation;

    int _gap;
    int _gap_s;
    int _step = 1;
    int _width;

    int _xpos;
    int _ypos;
    int _round;

    boolean _return, _yreturn;
    Vertex[] _new_pos;

    private int calcWidth( int gap ) {
	int width = ( gap + 2 ) * ( _nr_robots / 2 );
	if ( (_nr_robots & 1 ) != 0 )
	    width += ( ( gap + 1 ) / 2 ) + 1;
	return width;
    }

    public void setParam( int gap, int step, int orientation ) {
	_gap = gap;
	_step = step;
	_orientation = orientation;

	if ( step <= 0 )
	    _step = 1;

	_width = calcWidth( gap );
	if ( _width >= _size ) 
	{
	    _gap = _size / ( _nr_robots/2 + 1) - 2;
	    if ( _gap < 0 )
		_gap = 0;
	    _width = calcWidth( _gap );
	}

	_gap_s = ( _gap + 1 ) / 2;
    }

    private final int calcYOff( int idx ) {
	if ( ( _nr_robots & 1 ) != 0 && idx == _nr_robots-1 ) 
	    return ( _gap + 2 ) * ( _nr_robots/2 );

	return _gap_s + ( _gap + 2 ) * ( idx/2 ) + ( ~_gap & idx & 1 );
    }

    private final int calcYOff1( int idx ) {
	if ( idx == _nr_robots - 1 ) 
	    return _width-1;

	return ( _gap + 2 ) * ( (idx + 1)/2 ) - ( idx & 1 );
    }

    public Robot[] initRobots() throws Exception {
	_my_robots = new Robot[ _nr_robots ];
	
	for ( int i=0; i<_nr_robots; i++ ) 
        {
	    Vertex pos = rotatePos( 0, calcYOff(i), _orientation );
	    validatePos( pos );
	    _my_robots[i] = new Robot( pos.xpos(), pos.ypos() );
	}

	_xpos = _ypos = _round = 0;
	_return = _yreturn = false;
	_new_pos = null;

	return _my_robots;
    }

    public char[] move() throws Exception {
	char[] moves;

	if ( _new_pos != null ) 
	{
	    moves = moveTo( _new_pos );
	    if ( moves != null )
		return moves;
	    _new_pos = null;
	}

	moves = moveDefault();
	for ( int i=0; i<moves.length; i++ )
	    moves[i] = rotateMove( moves[i], _orientation );
	return moves;
    }

    public void rewind() throws Exception {
    }

    private final Vertex[] calcNewPos() throws Exception {
	if ( _yreturn )
	{
	    _ypos = ( _ypos > _width ? _ypos - _width : 0 );
	    if ( 0 == _ypos )
	    {
		_yreturn = false;
		rewind();
	    }
	}
	else
	{
	    _ypos = ( _ypos + _width + _width >= _size ? 
		      _size - _width : _ypos + _width );
	    if ( _ypos + _width == _size )
	    {
		_yreturn = true;
		rewind();
	    }
	}

	_new_pos = new Vertex[ _nr_robots ];

	for ( int i=0; i<_nr_robots; i++ )
	{
	    _new_pos[i] = rotatePos( _return ? 0 : _size-1,
				     _ypos + calcYOff1( i ),
				     _orientation );
	    validatePos( _new_pos[i] );
	}

	_round = _gap_s;
	_return = !_return;

	return _new_pos;
    }

    private final char[] moveDefault() throws Exception {
	char[] moves = new char[ _nr_robots ];

	if ( _round < _gap_s ) 
	{
	    for ( int i=0; i<_nr_robots/2; i++ )
	    {
		moves[i+i] = _CNORTH;
		moves[i+i+1] = _CSOUTH;
	    }
	    
	    if ( ( _nr_robots & 1 ) != 0 )
		moves[ _nr_robots - 1 ] = _CSOUTH;
	}
	else if ( _round < _gap_s + _step )
        {	    
	    if ( _return )
	    {
		for ( int i=0; i<_nr_robots; i++ )
		    moves[i] = _CWEST;
		_xpos--;

		if ( 0 == _xpos )
		    calcNewPos();
	    }
	    else
	    {
		for ( int i=0; i<_nr_robots; i++ )
		    moves[i] = _CEAST;
		_xpos++;

		if ( _xpos == _size - 1 )
		    calcNewPos();
	    }
	}
	else if ( _round < _gap_s*2 + _step )
	{
	    for ( int i=0; i<_nr_robots/2; i++ )
	    {
		moves[i+i] = _CSOUTH;
		moves[i+i+1] = _CNORTH;
	    }
	    
	    if ( ( _nr_robots & 1 ) != 0 )
		moves[ _nr_robots - 1] = _CNORTH;
	}
	else 
	{
	    if ( _return )
	    {
		for ( int i=0; i<_nr_robots; i++ )
		    moves[i] = _CWEST;
		_xpos--;
	    }
	    else
	    {
		for ( int i=0; i<_nr_robots; i++ )
		    moves[i] = _CEAST;
		_xpos++;
	    }

	    _round = -1;
	}

	if ( _round == _gap_s - 1 )
	{
	    if ( ( _return && 0 == _xpos ) 
		 || ( !_return && _xpos == _size - 1 ) )
		calcNewPos();
	}

	_round++;
	return moves;
    }
}


// Start of Group 8's code


class StolenGroup8 implements IFCPlayer {

  public final class emptyRec{
    private int nR;//local numRobots
    public int topLeftX,topLeftY;
    public int width,height;
    private int borderx,bordery; //next border pos
    private int robotDistances[]; //for each roobt, what its distnace from the rec
    public int orderedRobotsIndexes[]; //to store the robots available to work on this rec
    public int size;
    private int closestAvailableDistance;
    public emptyRec(emptyRec orig){
      nR=orig.nR;
      topLeftX=orig.topLeftX;
      topLeftY=orig.topLeftY;
      width=orig.width;
      height=orig.height; //no need to copy Arrays
      size=orig.size;
      borderx=bordery=-1;
    }
    public emptyRec(int numRobots){
      nR=numRobots;
      this.orderedRobotsIndexes= new int[numRobots];
      this.robotDistances= new int[numRobots];
      for(int i=0;i<numRobots;i++)
        orderedRobotsIndexes[i]=i;
    }
    public char getDirectionToNextBorder(int x,int y,int[][] colors,int me) throws Exception{
      if (borderx == -1 || bordery == -1 || (x == borderx && y == bordery)) {
        //find next location
        int cnt=0;
        int[] xs=new int[2*width+2*(height-2)];
        int[] ys=new int[2*width+2*(height-2)];
        int[] ds=new int [2*width+2*(height-2)]; //distnaces
        int i;
        for(i=topLeftX;i<(topLeftX+width);i++){
          if (colors[i][topLeftY] != me) {
            xs[cnt] = i;
            ys[cnt] = topLeftY;
            ds[cnt] = distance(x, y, i, topLeftY);
            cnt++;
          }
          if (colors[i][topLeftY+height-1] != me) {
            xs[cnt] = i;
            ys[cnt] = topLeftY+height-1;
            ds[cnt] = distance(x, y, i, topLeftY+height-1);
            cnt++;
          }
          if (colors[i][topLeftY] == _CFILLED||colors[i][topLeftY+height-1]== _CFILLED) {
            return 'B'; //we have a unattainable rec
          }


        }
        for(i=topLeftY+1;i<(topLeftY+height-1);i++){
          if (colors[topLeftX][i] != me) {
           xs[cnt] = topLeftX;
           ys[cnt] = i;
           ds[cnt] = distance(x, y, topLeftX, i);
           cnt++;
         }
         if (colors[topLeftX+width-1][i] != me) {
           xs[cnt] = topLeftX+width-1;
           ys[cnt] = i;
           ds[cnt] = distance(x, y, topLeftX+width-1, i);
           cnt++;
         }
         if (colors[topLeftX][i] == _CFILLED||colors[topLeftX+width-1][i]== _CFILLED) {
           return 'B'; //we have a unattainable rec
         }
        }
        //check if size is still more than 0...
        size = getRecSize(topLeftX, topLeftY, width, height);
        if(size<=0)
           return 'B'; //we have a unattainable rec
        //find closes nonfilled...
        int minD=ds[0];
        int minP=0;
        for(i=1;i<cnt;i++)
          if(ds[i]<minD){
            minD=ds[i];
            minP=i;
          }
        borderx=xs[minP];
        bordery=ys[minP];
      }
      if (x < borderx)
        return 'E';
      else if (x > borderx)
        return 'W';
      if (y < bordery)
        return 'S';
      else if (y > bordery)
        return 'N';
      //shouldn't be here
      return 'Y';
    }

    public char getDirectionFrom(int x,int y){

      if(x<topLeftX)
        return 'E';
      else if(x>(topLeftX+width-1))
        return 'W';
      //above or below
      if(y<topLeftY)
        return 'S';
      else if(y>(topLeftY+height-1))
        return 'N';
      return 'Y';


    }
    public void getCloseRobots(Robot[] robots) throws Exception {
      //populate the distnces Vector
      int i;
      closestAvailableDistance=2*size;
      for(i=0;i<numRobots;i++){
        int totDist = 0;
        if (robots[i].xpos() < topLeftX)
          totDist += (topLeftX - robots[i].xpos());
        if (robots[i].xpos() > (topLeftX + width - 1))
          totDist += (robots[i].xpos() - (topLeftX + width - 1));
        if (robots[i].ypos() < topLeftY)
          totDist += (topLeftY - robots[i].ypos());
        if (robots[i].ypos() > (topLeftY + height - 1))
          totDist += (robots[i].ypos() - (topLeftY + height - 1));
        robotDistances[i] = totDist;
        if(method[i]==3&&totDist<closestAvailableDistance){ //if available...
          closestAvailableDistance=totDist;
        }
      }
      //sort the indexes by Distances
      //bubble sort... too small do bother with something else
      for(i=nR-1;i>0;i--)
        for(int j=0;j<i;j++)
          if(robotDistances[j]>robotDistances[j+1]){
            int tDist,tInd;
            tDist=robotDistances[j];
            tInd=orderedRobotsIndexes[j];
            robotDistances[j]=robotDistances[j+1];
            orderedRobotsIndexes[j]=orderedRobotsIndexes[j+1];
            robotDistances[j+1]=tDist;
            orderedRobotsIndexes[j+1]=tInd;
          }
    }
  }

  Rectangles rectangles;

  static Random random;
  static final String _CNAME = "8:FindingTheGap";
  static final Color _CCOLOR = new Color(1.0f,0.67f,0.67f);
  static final int _NORTH = 0;
  static final int _SOUTH = 1;
  static final int _WEST = 2;
  static final int _EAST = 3;

  Vector emptyRecs;
  Robot[] robots;        /* robots */
  emptyRec[] AsignedEmptyRecs; //the emptyrec asssigned to robot
  int numRobots;         /* number of robots */
  int size;              /* board size */
  int destX[], destY[];  /* destinations of robots; -1 for grid walking mode */
  int method[];          /* which method am i moving in? */

  /*
   Method 1:
   Plaid, grid
   method 2:
   2 pieces starting together going oppsite directions, walk apart then come back togetehr
   */

  Robot[] closestEnemy;  /* the closest enemy of each of our Robots */
  int[] movesFollowed; /* how many moves the enemy has been chasing each Robot */
  int availableRobots;
  int direction[];       /* robot directions; one of _NORTH, ... */

  public IFCPlayer me;

  public Robot[] register(Rectangles rectangles) throws Exception
  {
      return null;
  }

  public Robot[] register(Rectangles rectangles, IFCPlayer myPlayer) throws Exception {
      me = myPlayer;
    this.rectangles = rectangles;
    this.numRobots = rectangles.numRobots();
    this.robots = new Robot[numRobots];
    this.size = rectangles.size();
    this.destX = new int[numRobots];
    this.destY = new int[numRobots];
    this.method = new int[numRobots];
    this.AsignedEmptyRecs= new emptyRec[numRobots];
    this.direction = new int[numRobots];
    this.random = java.security.SecureRandom.getInstance("SHA1PRNG");
    this.availableRobots=0;
    this.closestEnemy = new Robot[numRobots];
    this.movesFollowed = new int[numRobots];

    emptyRecs = new Vector(numRobots) ;
    /* determines the initial locations of the robots */
    int inc =  (size/numRobots);//*2
    int x = size/2;
    int y = size-1-inc;
    for (int i = 0; i < numRobots/2; i++) {
      robots[i] = new Robot(x, size - 1);
      x += inc;
      destX[i] = x-1 ;
      destY[i] = size/2-1;
      method[i]=1;
    }
    for (int i = numRobots/2; i < numRobots; i++) {
      robots[i] = new Robot(size-1, y);
      destX[i] = size/2-1;
      destY[i] = y + inc;
      y -= inc;
      method[i]=1;
    }

    /* if the number of robots are less than 3, then skip the plaid walking
       and do the grid walking */
    if (numRobots < 5)
      for (int i = 0; i < numRobots; i++)
	destX[i] = destY[i] = -1;

    return robots;
  }

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

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

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

  /* determine the next moves of robots */
  public char[] move() throws Exception {
    char[] moves = new char[numRobots];
    if(availableRobots>0){
      findEmptyRecs();
    }
    int i;
    for (i = 0; i < numRobots; i++)
      moves[i] = nextMove(i);
    i=0;
    //find a robot to do an empty recatangle
    if(availableRobots>0){
      findEmptyRecs();
    }
    while(availableRobots>0&&i<emptyRecs.size()){
      emptyRec ER=(emptyRec)emptyRecs.elementAt(i);

      boolean notAssigned=true;
      for(int j=0;j<numRobots&&notAssigned;j++)
        if(method[ER.orderedRobotsIndexes[j]]==3){
          int myRNum=ER.orderedRobotsIndexes[j];
          //assign this one
          method[myRNum] = 4;
          AsignedEmptyRecs[myRNum]=new emptyRec(ER);
          availableRobots--;
          notAssigned=false;
          moves[myRNum]=nextMove(myRNum);
        }
      if (notAssigned)
        System.out.print("BIG PROBLEM... MISCOUNTED\n");

      i++;
    }


//    for (int i = 0; i < numRobots; i++)
//      System.out.print(direction[i]) ;
    for (i = 0; i < numRobots; i++) {
      if(method[i]==3){ //get real direction if jus assigned an emptyRec
        moves[i] = nextMove(i);
        method[i]=2;
        availableRobots--;
      }
//      System.out.print(moves[i]) ;
      direction[i] = charDir(moves[i]);
//      System.out.print(direction[i]) ;
    }
//    System.out.print("\n");

    return moves;
  }

  /* determine the next move of the i-th robot */
  public char nextMove(int i) throws Exception {
    if (isBeingChased(i))
	return 'Y';

    char move = 'S';
    int x = robots[i].xpos();  /* current x-pos of the i-th robot */
    int y = robots[i].ypos();  /* current y-pos of the i-th robot */
    int destx = destX[i];      /* destination x-pos of the i-th robot */
    int desty = destY[i];      /* destination y-pos of the i-th robot */


    switch(method[i])
    {
    case 0:
      /* if grid walking mode */
      int validCont = validMoveDir(x, y, direction[i]);
      int rnd = random.nextInt(15);
      if (numRobots < 5)
        rnd = random.nextInt(10);
      if (validCont > 0) {
        int validRight = validMoveDir(x, y, nextDirection(direction[i]));
        int validLeft = validMoveDir(x, y, prevDirection(direction[i]));
        if (validRight > 0 && rnd < 3) {
          if (validLeft > 0 && rnd == 0)
            return dirChar(prevDirection(direction[i]));
          else
            return dirChar(nextDirection(direction[i]));
        }
        else
          return dirChar(direction[i]);
      }
      else if (validCont == 0) /* out of range */
        return dirChar(nextDirection(direction[i]));
      else if (validCont == _CFILLED)
        return dirChar(nextDirection(direction[i]));
      break;
    case 1://move to desitnation
      move=goToDest(x,y,destx,desty);
      if(Character.toUpperCase(move)=='Y'){
        /* now arrived at the destination; get new destination */
//  destX[i] = random.nextInt(size);
//  destY[i] = random.nextInt(size);
//  destY[i] = -1;
        method[i] = 3;
        availableRobots++;
        return nextMove(i);
      }
      return move;
    case 2:
      //circle border...
      method[i] = 4;
      emptyRec e=new emptyRec(numRobots);
      e.topLeftX = 0;
      e.topLeftY = 0;
      e.width = size;
      e.height = size;
      e.size = getRecSize(e.topLeftX, e.topLeftY, e.width, e.height);
      if(e.size<=0)
        method[i] = 5;
      AsignedEmptyRecs[i]=e;
      availableRobots--;
      return nextMove(i);

    case 3:
      //look for closest rec
      return 'Y'; //handled in the move function when returned '3'
    case 4:

      //on a mission to closest rec
      //move towards it, then aroound it...

//      System.out.print("robot " + i + " Heading for rec: "  + AsignedEmptyRecs[i].topLeftX + "," + AsignedEmptyRecs[i].topLeftY + " via " );
      char dir=AsignedEmptyRecs[i].getDirectionFrom(x,y);
      if(dir=='Y'){
        //walk the border... closest non-filled
        int mex=rectangles.indexOf(me);
        dir=AsignedEmptyRecs[i].getDirectionToNextBorder(x,y,rectangles.colors(),mex);
        if(dir=='B'){
          method[i] = 3;
          availableRobots++;
          return nextMove(i);
        }
      }
      else
      {
      }
      return dir;
      //check if still available.
      //if not then free
    case 5: //set smile
      switch(i){
        case 0:destX[i]=size/2+2;destY[i]=size/2-1;  break;
        case 1:destX[i]=size/2-2;destY[i]=size/2-1;  break;
        case 2:destX[i]=size/2;destY[i]=size/2; break;
        case 3:destX[i]=size/2;destY[i]=size/2+3; break;
        case 4:destX[i]=size/2-1;destY[i]=size/2+3; break;
        case 5:destX[i]=size/2+1;destY[i]=size/2+3; break;
        case 6:destX[i]=size/2-2;destY[i]=size/2+2; break;
        case 7:destX[i]=size/2+2;destY[i]=size/2+2; break;
        case 8:destX[i]=size/2-3;destY[i]=size/2+1; break;
        case 9:destX[i]=size/2+3;destY[i]=size/2+1; break;
        default:destX[i]=0;destY[i]=0;method[i]=1; break;
      }
      destx = destX[i];desty = destY[i];
      return goToDest(x,y,destx,desty);
    }
    //shouldn't be here...
    return '0';
  }

  /* checks if moving to the given direction from [x, y] is valid */
  public int validMoveDir(int x, int y, int dir) throws Exception {
    switch (dir) {
    case  _EAST: return validMove(x + 1, y);
    case  _WEST: return validMove(x - 1, y);
    case _SOUTH: return validMove(x, y + 1);
    case _NORTH: return validMove(x, y - 1);
    default: return 1;
    }
  }

  /* checks if moving to [x, y] is valid; returns _CFILLED (-1) if the
   * position is already filled with black paint, returns 0 if it's
   * out of range, and returns 1 if it's ok
   */
  public int validMove(int x, int y) throws Exception {
    int[][] col = rectangles.colors();
    if (((x >= 0) && (x <= size-1)) && ((y >= 0) && (y <= size-1))) {
      if (col[x][y] == _CFILLED)
	return _CFILLED;
      else
	return 1;
    }
    else
      return 0;
  }

  /* returns a random direction */
  public int randomDirection() {
    int dir = random.nextInt(4);
    switch (dir) {
    case 0: return _EAST;
    case 1: return _WEST;
    case 2: return _SOUTH;
    case 3: return _NORTH;
    default: return _SOUTH;
    }
  }

  /* returns the 'char' of the given direction */
  public char dirChar(int dir) {
    switch (dir) {
    case  _EAST: return 'E';
    case  _WEST: return 'W';
    case _SOUTH: return 'S';
    case _NORTH: return 'N';
    case _CSTAY: return 'Y';
    default: return 'S';
    }
  }

  /* inverse function of dirChar */
  public int charDir(char ch) {
    switch (ch) {
    case 'E': return _EAST;
    case 'W': return _WEST;
    case 'S': return _SOUTH;
    case 'N': return _NORTH;
    case 'Y': return _CSTAY;
    default: return _CSTAY;
    }
  }

  /* returns the next direction in grid walking */
  public int nextDirection(int dir) {
    switch (dir) {
      case  _EAST: return _SOUTH;
      case  _WEST: return _NORTH;
      case _SOUTH: return _WEST;
      case _NORTH: return _EAST;
    default: return _EAST;
    }
  }
  /* returns the previous direction in grid walking */
  public int prevDirection(int dir) {
  switch (dir) {
    case  _EAST: return _NORTH;
    case  _WEST: return _SOUTH;
    case _SOUTH: return _EAST;
    case _NORTH: return _WEST;
  default: return _EAST;
  }
}


  /* determine if the given direction (_NORTH, _SOUTH, ...) of [x, y]
   * is filled or not
   */
  public boolean filled(int x, int y, int direction) throws Exception {
    int[][] colors = rectangles.colors();

    switch (direction) {
    case _EAST:
      return (x + 1 < size) ? (colors[x + 1][y] == _CFILLED) : false;
    case _WEST:
      return (x - 1 >= 0) ? (colors[x - 1][y] == _CFILLED) : false;
    case _SOUTH:
      return (y + 1 < size) ? (colors[x][y + 1] == _CFILLED) : false;
    case _NORTH:
      return (y - 1 >= 0) ? (colors[x][y - 1] == _CFILLED) : false;
    default:
      return true;
    }
  }

  /* Manhattan distance between two points */
  public int distance(int x, int y, int dx, int dy) {
    return Math.abs(x - dx) + Math.abs(y - dy);
  }

  public char goToDest(int x, int y, int destx, int desty)  throws Exception{
    char move = 'Y';
    if (x > destx) /* destx <-- x */
      move = 'W';
    else if (x < destx) /* x --> destx */
      move = 'E';
    else if (x == destx) {
      /* now move along y-axis */
      if (y > desty) /* desty --> y */
        move = 'N';
      else if (desty > y)
        move = 'S';
      else if (y == desty) {
        move = 'Y';
      }
    }
    if(validMoveDir(x,y,charDir(move))!=0)
       return move;
    else
      return 'Y';

  }
  public void emptyRecSort(Vector vER){
    int i,j;
    emptyRec a,b;
    //bubble sort again
    for(i=vER.size()-1;i>0;i--)
       for(j=0;j<i;j++){
         a=(emptyRec)vER.elementAt(j);
         b=(emptyRec)vER.elementAt(j+1);
//took out sort by size
//         if (a.size < b.size) {
//added sort by distance
         if(a.closestAvailableDistance > b.closestAvailableDistance){
           vER.removeElementAt(j+1);
           vER.removeElementAt(j);
           vER.add(j,b);
           vER.add(j+1,a);
         }
       }
//     for(i=0;i<vER.size();i++)
//       System.out.print(((emptyRec)vER.elementAt(i)).size + " " );
  }
  int getMaxX(int tX,int tY) throws Exception{
    int tx2;
    for (tx2 = tX; tx2 < size && (rectangles.colors()[tx2][tY] != _CFILLED); tx2++)
      ;
    tx2--; //to fix the extra one incounting : endbound=tx2
    return tx2;
  }
  boolean checkBorders(int tx, int tx2, int ty) throws Exception{
//    System.out.print("cb:"+tx+","+tx2+","+ty+" ");
    return rectangles.colors()[tx][ty] != _CFILLED &&
        rectangles.colors()[tx2][ty] != _CFILLED;
  }

  boolean allEmpty(int tx, int tx2, int ty) throws Exception {
//    System.out.print("ae:"+tx+","+tx2+","+ty+" ");
    for (int t = tx; t <= tx2; t++)
      if (rectangles.colors()[t][ty] == _CFILLED) {
        return false;
      }
    return true;
  }

  int getRecSize(int tx, int ty, int w, int h) throws Exception {
//      System.out.print("r:"+tx+","+ty+","+w+" "+","+h+" ");
    if (w < 3 || h < 3)
      return 0;

    int i, j, cnt = 0;
    //count inside boxes....
    for (i = tx+1; i <= (tx + w - 2 < size ? tx + w - 2 : 0); i++)
      for (j = ty+1; j <= (ty + h - 2 < size ? ty + h - 2 : 0); j++)
        if (rectangles.colors()[i][j] != _CFILLED)
          cnt++;
    return cnt;
  }

  void findEmptyRecs() throws Exception {
    /*find empty Recs*/
    int[][] colors = rectangles.colors();
    int[] maxY = new int[size];
    for (int i = 0; i < size; i++)
      maxY[i] = -1;

    emptyRecs.clear();
    int tX, tY;
    int rX, rY;
    int rW, rH;
    for (tY = 0; tY < size; tY++) { //size
      for (tX = 0; tX < size; tX++) {
//        System.out.print("["+tX+","+tY+"]");
        if (colors[tX][tY] != _CFILLED) {
          //loop through existing roobts, make sure none are looking for this rec
          boolean skip=false;
          for(int j=0;j<numRobots;j++)
            if(method[j]==4){//if on a mission
              emptyRec ER = AsignedEmptyRecs[j];
              if(ER.topLeftX==tX&&ER.topLeftY==tY){
                for (int tx5 = ER.topLeftX; tx5 <= ER.topLeftX + ER.width - 1; tx5++)
                  maxY[tx5] = ER.topLeftY + ER.height - 2;
                tX = ER.topLeftX + ER.width- 2;
                skip=true;
              }
            }
          if (tY > maxY[tX]&&!skip) {
            boolean looking = true;
            int tx2 = 0, tx3 = 0, endX = 0;
            int ty2 = 0, ty3 = 0, endY = 0;
            //see how far you can go to the right,first line
            tx2 = getMaxX(tX, tY);
            if (tx2 - tX + 1 > 10) //mazimum width of 10
              tx2 = tX + 9;

              //tx2 contains the maximum size...
            for (tx3 = (tx2 - tX) + 1; tx3 >= 3 && ty3 == 0; tx3--) { //loop over all possible widths, start with max...
//              System.out.print("Tw:"+tx3+" ");
              boolean bordersOK;
              for (ty2 = tY + 1, bordersOK = true;
                   bordersOK && ty2 < size && (ty2 - tY) + 1 <= 10; ty2++) { //max height of 10
//                System.out.print("Ty2:"+ty2+" ");
                bordersOK = checkBorders(tX, tX + tx3 - 1, ty2); //checkif borders are ok
                if (allEmpty(tX, tX + tx3 - 1, ty2))
                  ty3 = ty2;
              }
            }
            tx3++;
//System.out.print(".");
            emptyRec e = new emptyRec(numRobots);
            e.topLeftX = tX;
            e.topLeftY = tY;
            e.width = (tx3);
            e.height = (ty3 - tY) + 1;
            e.size = getRecSize(e.topLeftX, e.topLeftY, e.width, e.height);
//System.out.print(e.size);
            if (e.width >= 3 && e.height >= 3 && e.size > 0) {
              e.getCloseRobots(robots);
              emptyRecs.add(e);
//              System.out.println("Added");
              int heightToUse = e.height;
              int widthToUse = e.width;
              //include only normal sized boxes
              /*              if(>8)
                          heightToUse=(heightToUse>16?8:heightToUse/2);
                        if(widthToUse>8)
                          widthToUse=(widthToUse>16?8:widthToUse/2);
               */
//              System.out.println("setting maxy to:" + (e.topLeftY + heightToUse-1) );
              for (tx2 = e.topLeftX; tx2 <= e.topLeftX + e.width - 1; tx2++)
                maxY[tx2] = e.topLeftY + heightToUse - 2;
              tX = e.topLeftX + widthToUse - 2; //resume search from end...
            }
          }
        }
      }
    }
    emptyRecSort(emptyRecs);

  }

//anti-chasing algorithm taken from Group1 and Group3.

//from Group 3
		public char getLastMove(Robot robot) throws Exception {
			MoveResult[] history = rectangles.history();
			MoveResult last = history[history.length - 1];
			return last.moves()[robot.playerIndex()][robot.ID()];
		}

		public boolean isBeingChased(int myRobot) throws Exception {			
			Robot closestEnemyBot = closestEnemyRobot(myRobot);
			
			if (robotsEqual(closestEnemyBot, closestEnemy[myRobot])) {
				if(getLastMove(robots[myRobot]) == getLastMove(closestEnemyBot))
					movesFollowed[myRobot]++;				
			} else {
				movesFollowed[myRobot] = 0;				
			}
			
			closestEnemy[myRobot] = closestEnemyBot;
			
			if (movesFollowed[myRobot] >= 6 &&
					distance(robots[myRobot].xpos(), robots[myRobot].ypos(), closestEnemyBot.xpos(), closestEnemyBot.ypos()) <= 2) {				
				return true;
			}
			
			return false;
		}
		
		private boolean robotsEqual(Robot r1, Robot r2) throws Exception {
			if (r1 == null) {
				return (r2 == null);
			}
			
			if (r2 == null) {
				return false;
			}
			
			return (r1.playerIndex() == r2.playerIndex() && r1.ID() == r2.ID());
		}
		
		/**
		 * Routine for determining the closest enemy robot was taken from
		 * OldGroup5Robot.java and refactored to save time. 
		 */
		private Robot closestEnemyRobot(int myRobot) throws Exception {
			Robot[][] allRobots = rectangles.allRobots();
			int closest = Integer.MAX_VALUE;
			Robot closestRobot = null;
			
			for (int i = 0; i < allRobots.length; i++) {
				if (i == rectangles.indexOf(me) ) {
					continue;
				}
				
				for (int j = 0; j < allRobots[i].length; j++) {
					Robot enemyRobot = allRobots[i][j];
					int dist = distance(robots[myRobot].xpos(), robots[myRobot].ypos(), enemyRobot.xpos(), enemyRobot.ypos());
					if (dist < closest) {
						closest = dist;
						closestRobot = enemyRobot;
					}
				}
			}
			
			return closestRobot;
		}
}


