//***********************************************************
//*
//* File:           Rectangles.java
//* Author:         Srikant Krishna
//* Contact:        srikant@cs.columbia.edu
//* Update:         10/16/2002
//*
//* Description:    Game model for Project 3, CS4444
//*                 Fall 2002
//*
//*
//***********************************************************


package Rectangles;

import ui.*;
import java.util.*;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.TableModel;
import java.text.NumberFormat;

public final class Rectangles implements IFCConstants, IFCModel {

    private Class[]                 _classlist;
    private Class[]                 _playerlist;
    private PlayerWrapper[]         _players;
    private int                     _rounds;
    private int                     _finish;
    private int                     _numrounds;
    private int                     _numplayers;
    private int                     _numrobots;
    private int                     _state;
    private int                     _lastchange;
    private transient IFCUI         _ui;
    private IFCConfiguration        _config;
    private static Random           _random;
    private JTextField              _input;
    private ControlPanel            _control;
    private ViewPanel               _view;
    private int                     _playerindex;
    private ArrayList               _history;
    private int                     _size;
    private boolean[]               _boot;
    private boolean                 _registered;
    private int[][]                 _board;
    private ArrayList[][]           _robots;
    private char[][]                _moves;
    private boolean[][]             _changed;
    private boolean[][]             _filled;
    private boolean                 _initialized;
    private HashMap                 _rectangles;
    private static final int        _CSELECTING             = 0;
    private static final int        _CMOVING                = 1;
    private static final int        _CWAITING               = 2;
    private static final int        _CDECISION              = 3;
    private static final int        _CFINISHED              = 4;
    private static final int[]      _CSTATES                = { _CSELECTING, _CMOVING, _CWAITING, _CDECISION, _CFINISHED };
    private static final String     _CNAME                  = "Rectangles";
    private static final String     _CPROPERTIES_FILE       = "gamemodel.properties";
    private static final int        _CMIN_ROUNDS            = 1;
    private static final int        _CMAX_ROUNDS            = 1000;
    private static final int        _CMIN_ROBOTS            = 1;
    private static final int        _CMAX_ROBOTS            = 200;
    private static final int        _CMIN_PLAYERS           = 1;
    private static final int        _CMAX_PLAYERS           = 30;
    private static final int        _CMIN_SIZE              = 8;
    private static final int        _CMAX_SIZE              = 250;
    private static final int        _CSINGLE_COLOR          = 1;
    private static final int        _CPLURALITY             = 2;
    private static final int        _CDECISION_TYPE         = _CPLURALITY;


    //********************************************
    //*
    //* Constructors
    //*
    //********************************************
    public Rectangles() throws Exception {
        create(createDefaultConfiguration());
    }

    public Rectangles(IFCConfiguration __config) throws Exception {
        create(__config);
    }

    public Rectangles(IFCTournament __tournament) throws Exception {
        run(__tournament);
    }
    
    //********************************************
    //*
    //* Constructor Delegates
    //*
    //********************************************
    public void run(IFCTournament __tournament) throws Exception {
        IFCGameRecord[] games;
        Robot[] initial;
        IFCPlayer player;
        HashMap hash;
        int _MAX;
        
        games = __tournament.games();
        if (games == null) {
            throw new Exception("Error:  Null game record list");
        }
        _MAX = games.length;
        _random = new Random();
        hash = new HashMap();

        for (int i=0; i < _MAX; i++) {
            System.err.println("[Game]: "+i+" Starting at " + System.currentTimeMillis()/60000.0);
            _playerlist = games[i].players();
            if (_playerlist == null) {
                throw new Exception("Error:  Null player list in game record: "+i);
            }
            _numplayers = _playerlist.length;
            _numrounds = games[i].numRounds();;
            _numrobots = games[i].numRobots();
            _size = games[i].size();
            _rounds = 0;
            _state = _CSELECTING;
            _boot = new boolean[_numplayers];
            _history = new ArrayList();
            _lastchange = 0;
            _board = new int[_size][_size];
            _robots = new ArrayList[_size][_size];
            _changed = new boolean[_size][_size];
            _filled = new boolean[_size][_size];
            _moves = new char[_numplayers][];
            _rectangles = new HashMap();
            _playerindex = 0;
            
            for (int j=0; j < _size; j++) {
                for (int k=0; k < _size; k++) {
                    _board[j][k] = _CEMPTY;
                    _robots[j][k] = new ArrayList();
                }
            }

            _players = new PlayerWrapper[_numplayers];
            for (int j=0; j < _numplayers; j++) {
                if (_playerlist[j] == null) {
                    throw new Exception("Error:  Null player, game record: "+i+", player: "+j);
                }
                player = (IFCPlayer) hash.get(_playerlist[j]);
                if (player != null) {
                    _players[j] = new PlayerWrapper(_playerlist[j]);
                    initial = _players[j].register(this, player);
                } else {
                    _players[j] = new PlayerWrapper(_playerlist[j]);
                    initial = _players[j].register(this);
                }
                if (_players[j].interactive()) {
                    throw new Exception("Error:  Interactive Players Are Not Permitted In Tournaments");
                }
                if (initial == null) {
                    _boot[j] = true;
                    continue;
                }
                for (int k=0; k < _numrobots; k++) {
                    initial[k].setColor(_players[j].color());
                    initial[k].setPlayerIndex(j);
                    initial[k].setID(k);
                    _robots[initial[k].xpos()][initial[k].ypos()].add(initial[k]);
                }
            }

            processMoves(null);
            while (step()) { }
            games[i].setScores(((MoveResult) _history.get(_history.size()-1)).scores());

            for (int j=0; j < _numplayers; j++) {
                if (_players[j].player() instanceof IFCPersistentPlayer) {
                    if (!games[i].batchComplete()) {
                        hash.put(_players[j].playerClass(), _players[j].player());
                    } else {
                        hash.put(_players[j].playerClass(), null);
                    }
                }
            }
        }
    }
       
    void create(IFCConfiguration __config) throws Exception {
        Robot[] initial;

        _config = __config;
        _classlist = _config.classList();
        _playerlist = _config.playerList();
        _numrounds = _config.numRounds();
        _numplayers = _config.numPlayers();
        _numrobots = _config.numRobots();
        _size = _config.size();
        _rounds = 0;
        _random = new Random();
        _state = _CSELECTING;
        _boot = new boolean[_numplayers];
        _history = new ArrayList();
        
        _board = new int[_size][_size];;
        _robots = new ArrayList[_size][_size];
        _changed = new boolean[_size][_size];
        _filled = new boolean[_size][_size];
        _rectangles = new HashMap();
        for (int i=0; i < _size; i++) {
            for (int j=0; j < _size; j++) {
                _board[i][j] = _CEMPTY;
                _robots[i][j] = new ArrayList();
            }
        }
        _players = new PlayerWrapper[_numplayers];
        for (int i=0; i < _numplayers; i++) {
            _players[i] = new PlayerWrapper(_playerlist[i]);
            initial = _players[i].register(this);   
            if (initial == null) {
                _boot[i] = true;
                continue;
            }
            for (int j=0; j < _numrobots; j++) {
                initial[j].setColor(_players[i].color());
                initial[j].setPlayerIndex(i);
                initial[j].setID(j);
                _robots[initial[j].xpos()][initial[j].ypos()].add(initial[j]);
            }
        }
        _moves = new char[_numplayers][];
        processMoves(null);

        _control = new ControlPanel();
        _view = new ViewPanel();
    }


    //********************************************
    //*
    //* Initial Configuration
    //*
    //********************************************
    public static IFCConfiguration createDefaultConfiguration() throws Exception {
        IFCConfiguration RET = new Configuration();
        String[] toks;
        Class[] classes;
        Class[] players;
        int _MAX;
        Properties properties;
        Random random = new Random();
        ParseValue pv;

        RET.setNumRoundsBounds(_CMIN_ROUNDS, _CMAX_ROUNDS);
        RET.setNumPlayersBounds(_CMIN_PLAYERS, _CMAX_PLAYERS);
        RET.setNumRobotsBounds(_CMIN_ROBOTS, _CMAX_ROBOTS);
        RET.setSizeBounds(_CMIN_SIZE, _CMAX_SIZE);

        properties = Util.gatherProperties(_CPROPERTIES_FILE);
        RET.setLogFile(properties.getProperty("LOG_FILE").trim());

        pv = ParseValue.parseIntegerValue(properties.getProperty("NUM_ROUNDS").trim(), _CMIN_ROUNDS, _CMAX_ROUNDS);
        if (!pv.isValid()) {
            throw new Exception("Properties parameter out of range, Number of Rounds");
        }
        RET.setNumRounds(((Integer) pv.value()).intValue());

        pv = ParseValue.parseIntegerValue(properties.getProperty("NUM_ROBOTS").trim(), _CMIN_ROBOTS, _CMAX_ROBOTS);
        if (!pv.isValid()) {
            throw new Exception("Properties parameter out of range, Number of Robots");
        }
        RET.setNumRobots(((Integer) pv.value()).intValue());

        pv = ParseValue.parseIntegerValue(properties.getProperty("SIZE").trim(), _CMIN_SIZE, _CMAX_SIZE);
        if (!pv.isValid()) {
            throw new Exception("Properties parameter out of range, Size");
        }
        RET.setSize(((Integer) pv.value()).intValue());

        toks = Util.split(",\t\n ", properties.getProperty("CLASS_LIST").trim());
        _MAX = toks.length;
        classes = new Class[_MAX];
        for (int i=0; i < _MAX; i++) {
            classes[i] = Class.forName(toks[i]);
        }
        RET.setClassList(classes);

        toks = Util.split(",\t\n ", properties.getProperty("PLAYER_LIST").trim());
        _MAX = toks.length;
        if (_MAX < _CMIN_PLAYERS || _MAX > _CMAX_PLAYERS) {
            throw new Exception("Properties parameter out of range, Number of Players (Player List)");
        }
        players = new Class[_MAX];
        for (int i=0; i < _MAX; i++) {
            players[i] = Class.forName(toks[i]);
        }
        RET.setPlayerList(players);

        return RET;
    }


    //********************************************
    //*
    //* Exposed Methods
    //*
    //********************************************
    public int maxRounds() throws Exception {
        return _numrounds;
    }

    public int rounds() throws Exception {
        return _rounds;
    }
    
    public int lastChange() throws Exception {
        return _lastchange;
    }

    public int numPlayers() throws Exception {
        return _numplayers;
    }
    
    public int size() throws Exception {
        return _size;
    }

    public int numRobots() throws Exception {
        return _numrobots;
    }
    
    public boolean[][] filled() throws Exception {
        boolean[][] RET = new boolean[_size][_size];
        
        for (int i=0; i < _size; i++) {
            System.arraycopy(_filled[i], 0, RET[i], 0, _size);
        }
        return RET;
    }
    
    public int[][] colors() throws Exception {
        int[][] RET = new int[_size][_size];
        
        for (int i=0; i < _size; i++) {
            System.arraycopy(_board[i], 0, RET[i], 0, _size);
        }
        return RET;
    }
    
    public Robot[][] allRobots() throws Exception {
        Robot[][] RET = new Robot[_numplayers][_numrobots];
        
        for (int i=0; i < _numplayers; i++) {
            for (int j=0; j < _numrobots; j++) {    
                RET[i][j] = _players[i].robot(j).copy();
            }
        }
        return RET;
    }

    public MoveResult[] history() throws Exception {
        int _MAX = _history.size();
        MoveResult[] RET = new MoveResult[_MAX];

        for (int i=0; i < _MAX; i++) {
            RET[i] = ((MoveResult) _history.get(i)).copy();
        }
        return RET;
    }

    public int indexOf(IFCPlayer __player) throws Exception {
        int _MAX = numPlayers();

        for (int i=0; i < _MAX; i++) {
            if (_players[i].player() == __player) {
                return i;
            }
        }
        throw new Exception("Player not found: "+__player);
    }

    public void print(String __str) throws Exception {
        if (_registered) {
              _ui.print(__str);
        }
    }

    public void println(String __str) throws Exception {
        if (_registered) {
              _ui.println(__str);
        }
    }

    public void println() throws Exception {
        if (_registered) {
              _ui.println();
        }
    }

    //********************************************
    //*
    //* IFCModel
    //*
    //********************************************
    public void register(IFCUI __ui) throws Exception {
        _ui = __ui;
        _ui.register(this);
        _registered = true;

        println("[Player Configuration]: ");
        for (int i=0; i < _numplayers; i++) {
            println("\t[Player" + i + "]: " + _players[i].name());
        }
        refresh();
    }

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

    public JPanel exportControlPanel() throws Exception {
        return _control;
    }

    public JPanel exportViewPanel() throws Exception {
        return _view;
    }

    public JButton[] exportTools() throws Exception {
        return _control.exportTools();
    }
    
    public JMenu exportMenu() throws Exception {
        return null;
    }

    public IFCConfiguration exportConfiguration() throws Exception {
        return _config;
    }
    
    
    private void refresh() throws Exception {
        if (_registered) {
            _ui.refresh();
        }
    } 
    //********************************************
    //*
    //* Private Methods
    //*
    //********************************************


    private void reset() throws Exception {
        if (_registered) {
            _ui.reset();
        }
    }
    
    private void processMoves(char[][] __moves) throws Exception {
        int xpos;
        int ypos;
        Robot swap;
        Robot robot;
        Robot[] robots;
        Corner[][] corners;
        Corner corner;
        int val = _size - 1;
        int _MAX;
        int _MAXJ;
        int _MAXK;
        int _MAXA;
        int _MAXB;
        int _MAXC;
        int _MAXD;
        ArrayList listb;
        ArrayList listc;
        ArrayList listd;
        boolean valid;
        double[] plurality;
        int[] winners;
        int pos;
        int count=0;

        _lastchange++;
        if (__moves != null) {
            for (int i=0; i < _numplayers; i++) {
                if (_boot[i]) {
                    continue;
                }
                if (__moves[i] == null) {
                    println("Error:  Player["+i+"]:  Invalid Move List, Ignored, Booted & Skipped");
                    _boot[i] = true;
                    continue;
                }
                robots = _players[i].robots();
                for (int j=0; j < _numrobots; j++) {
                    if (__moves[i][j] == _CSTAY) {
                        continue;
                    }
                    xpos = robots[j].xpos();
                    ypos = robots[j].ypos();
                    _changed[xpos][ypos] = true;
                    if ((__moves[i][j] == _CEAST && xpos == val) ||
                        (__moves[i][j] == _CWEST && xpos == 0) ||
                        (__moves[i][j] == _CNORTH && ypos == 0) ||
                        (__moves[i][j] == _CSOUTH && ypos == val)) {
                                println("\t\tPlayer["+_playerindex+"]:  Your Robot["+j+"] is about to fall off the edge!.");
                        continue;
                    }
                    _robots[xpos][ypos].remove(robots[j]);

                    switch (__moves[i][j]) {
                        case _CEAST:    xpos++;
                                        break;
                        case _CWEST:    xpos--;
                                        break;
                        case _CNORTH:   ypos--;
                                        break;
                        case _CSOUTH:   ypos++;
                                        break;
                        default:
			System.out.println(_players[i].name());
                        throw new Exception("Error:  Unknown Direction: "+__moves[i][j]);
                    }
                    robots[j].setXpos(xpos);
                    robots[j].setYpos(ypos);
                    _robots[xpos][ypos].add(robots[j]);
                    _changed[xpos][ypos] = true;
                }
            }
        }

        plurality = new double[_numplayers];
        for (int x=0; x < _size; x++) {
            for (int y=0; y < _size; y++) {
                _changed[x][y] = false;
                if (_robots[x][y].size() == 0 ||
                    _board[x][y] == _CFILLED)
                {
                    continue;
                }
                if (_robots[x][y].size() == 1) {
                    robot = (Robot) _robots[x][y].get(0);
                    if (_board[x][y] != robot.playerIndex()) {
                        _board[x][y] = robot.playerIndex();
                    }
                }
                if (_robots[x][y].size() > 1) {
                    _MAX = _robots[x][y].size();
                    for (int i=0; i < _numplayers; i++) {
                        plurality[i] = 0;
                    }
                    for (int i=0; i < _MAX; i++) {
                        plurality[((Robot) _robots[x][y].get(i)).playerIndex()]++;
                    }
                    
                    if (_CDECISION_TYPE == _CPLURALITY) {
                        winners = Util.maxIndex(plurality);
                        if (winners.length == 1) {
                            for (pos=0; pos < _MAX; pos++) {
                                if (winners[0] == ((Robot) _robots[x][y].get(pos)).playerIndex()) {
                                    break;
                                }
                            }
                            robot = (Robot) _robots[x][y].get(pos);
                            swap = (Robot) _robots[x][y].get(0);
                            _robots[x][y].set(pos, swap);
                            _robots[x][y].set(0, robot);
                            if (_board[x][y] != winners[0]) {
                                _board[x][y] = winners[0];
                                _changed[x][y] = true;
                            }
                        } else {
                            if (_board[x][y] != _CCONTESTED && _board[x][y] != _CEMPTY) {
                                _board[x][y] = _CCONTESTED;
                                _changed[x][y] = true;
                            }
                        }
                    } else {
                        count = 0;
                        for (int i=0; i < _numplayers; i++) {
                            if (plurality[i] > 0) {
                                count++;
                            }
                        }
                        winners = Util.maxIndex(plurality);
                        if (count > 1 && _board[x][y] != _CCONTESTED) {
                            _board[x][y] = _CCONTESTED;
                            _changed[x][y] = true;
                        }
                        else
                        if (count == 1 && _board[x][y] != winners[0]) {
                            _board[x][y] = winners[0];
                            _changed[x][y] = true;
                        }
                    }

                } else {
                    if (_board[x][y] == _CCONTESTED) {
                        _board[x][y] = _CEMPTY;
                        _changed[x][y] = true;
                    }
                }
            }
        }

        for (int x=0; x < _size; x++) {
            for (int y=0; y < _size; y++) {
                if (_board[x][y] == _CEMPTY ||
                    _board[x][y] == _CFILLED ||
                    _board[x][y] == _CCONTESTED) {
                    continue;
                }
                if (    x < _size - 2 && y < _size - 2 &&
                        _board[x+1][y] == _board[x][y] &&
                        _board[x+2][y] == _board[x][y] &&
                        _board[x][y+1] == _board[x][y] &&
                        _board[x][y+2] == _board[x][y]) {
                            corner = new Corner(x, y, _CSOUTHEAST);
                            if (!_players[_board[x][y]].containsCorner(corner)) {
                                _players[_board[x][y]].addCorner(corner);
                            }

                }
                if (    x < _size - 2 && y > 1 &&
                        _board[x+1][y] == _board[x][y] &&
                        _board[x+2][y] == _board[x][y] &&
                        _board[x][y-1] == _board[x][y] &&
                        _board[x][y-2] == _board[x][y]) {
                            corner = new Corner(x, y, _CNORTHEAST);
                            if (!_players[_board[x][y]].containsCorner(corner)) {
                                _players[_board[x][y]].addCorner(corner);
                            }
                }
                if (    x > 1 && y < _size - 2 &&
                        _board[x-1][y] == _board[x][y] &&
                        _board[x-2][y] == _board[x][y] &&
                        _board[x][y+1] == _board[x][y] &&
                        _board[x][y+2] == _board[x][y]) {
                            corner = new Corner(x, y, _CSOUTHWEST);
                            if (!_players[_board[x][y]].containsCorner(corner)) {
                                _players[_board[x][y]].addCorner(corner);
                            }
                        }
                if (    x > 1 && y > 1 &&
                        _board[x-1][y] == _board[x][y] &&
                        _board[x-2][y] == _board[x][y] &&
                        _board[x][y-1] == _board[x][y] &&
                        _board[x][y-2] == _board[x][y]) {
                            corner = new Corner(x, y, _CNORTHWEST);
                            if (!_players[_board[x][y]].containsCorner(corner)) {
                                _players[_board[x][y]].addCorner(corner);
                            }
                        }
            }
        }

        for (int i=0; i < _numplayers; i++) {
            corners = _players[i].corners();
            _MAXA = corners[0].length;

            for (int a=0; a < _MAXA; a++) {
                corners[1] = _players[i].getCorners(_CNORTHEAST, corners[0][a].xpos());

                if (corners[1] == null) {
                    continue;
                }
                _MAXB = corners[1].length;
                
                corners[2] = _players[i].getCorners(_CSOUTHWEST, corners[0][a].ypos());
                if (corners[2] == null) {
                    continue;
                }
                _MAXC = corners[2].length;

                for (int b=0; b < _MAXB; b++) {
                    for (int c=0; c < _MAXC; c++) {
                        corners[3] = _players[i].getCorners(_CNORTHWEST, corners[2][c].xpos());
                        if (corners[3] == null) {
                            continue;
                        }
                        _MAXD = corners[3].length;

                        for (int d=0; d < _MAXD; d++) {

                            if (corners[0][a].ypos() == corners[1][b].ypos() ||
                                corners[0][a].xpos() == corners[2][c].xpos() ||
                                corners[3][d].ypos() == corners[2][c].ypos() ||
                                corners[3][d].xpos() == corners[1][b].xpos()) {
                                continue;
                            }

                            if (corners[0][a].xpos() == corners[1][b].xpos() &&
                                corners[0][a].ypos() == corners[2][c].ypos() &&
                                corners[3][d].xpos() == corners[2][c].xpos() &&
                                corners[3][d].ypos() == corners[1][b].ypos()) {

                                    if (_rectangles.containsKey(new FilledRectangle(corners[0][a].xpos(), corners[2][c].xpos(), corners[0][a].ypos(), corners[1][b].ypos()))) {
                                        continue;
                                    }


                                    valid = true;
                                    pos = corners[0][a].ypos();
                                    _MAXJ = corners[2][c].xpos();
                                    for (int x = corners[0][a].xpos(); x < _MAXJ; x++) {
                                        if (_board[x][pos] != i) {
                                            valid = false;
                                            break;
                                        }
                                    }

                                    if (valid) {
                                        pos = corners[2][c].xpos();
                                        _MAXJ = corners[3][d].ypos();
                                        for (int y = corners[2][c].ypos(); y < _MAXJ; y++) {
                                            if (_board[pos][y] != i) {
                                                valid = false;
                                                break;
                                            }
                                        }
                                    }

                                    if (valid) {
                                        pos = corners[1][b].ypos();
                                        _MAXJ = corners[3][d].xpos();
                                        for (int x = corners[1][b].xpos(); x < _MAXJ; x++) {
                                            if (_board[x][pos] != i) {
                                                valid = false;
                                                break;
                                            }
                                        }
                                    }

                                    if (valid) {
                                        pos = corners[0][a].xpos();
                                        _MAXJ = corners[1][b].ypos();
                                        for (int y = corners[0][a].ypos(); y < _MAXJ; y++) {
                                            if (_board[pos][y] != i) {
                                                valid = false;
                                                break;
                                            }
                                        }
                                    }

                                    if (valid) {
                                        _MAXJ = corners[2][c].xpos();
                                        _MAXK = corners[1][b].ypos();
                                        for (int x = corners[0][a].xpos() + 1; x < _MAXJ; x++) {
                                            for (int y = corners[0][a].ypos() + 1; y < _MAXK; y++) {

                                                if (_board[x][y] != _CFILLED) {
                                                    _players[i].removeCorners(x, y);
                                                    _board[x][y] = _CFILLED;
                                                    _changed[x][y] = true;
                                                    _lastchange = 0;
                                                    count++;
                                                }
                                            }
                                        }
                                        _rectangles.put(new FilledRectangle(corners[0][a].xpos(), corners[2][c].xpos(), corners[0][a].ypos(), corners[1][b].ypos()), null);
                                        _players[i].setScore(count + _players[i].score());
                                        count = 0;
                                    }
                            }
                        }
                    }
                }
            }
        }
        for (int x=0; x < _size; x++) {
            for (int y=0; y < _size; y++) {
                _filled[x][y] = (_board[x][y] == _CFILLED);
            }
        }
    }

    private char[] parseMove(String __str) throws Exception {
        try{
            if (__str == null) {
                return null;
            }

            StringTokenizer st = new StringTokenizer(__str, " \t\n;");
            char[] RET = new char[_numrobots];
            int count = 0;

            while (st.hasMoreElements()) {
                RET[count] = Character.toUpperCase(st.nextToken().charAt(0));
                count++;
            }
            return RET;
        } catch (Exception EXC) {
            println("Error:  "+EXC.getMessage());
            return null;
        }
    }

    //********************************************
    //*
    //* State Transition
    //*
    //********************************************
    private boolean step() throws Exception {

        MoveResult result;
        char[] pmove = null;
        double[] scores;
        int[] winners = null;
        int _MAX;
        int _MAXJ;
        long stime;
        long etime;
        StringBuffer SB = new StringBuffer();

        switch (_state) {
            case _CWAITING: {
                println("Please Make A Move, "+_players[_playerindex].name());
                return false;
            }

            case _CSELECTING:   {
                    SB.append("\t[Round ");
                    SB.append(Integer.toString(_rounds));
                    SB.append("]\n\t\t[Selecting]");
                    SB.append("\t\t\t[Moves]: ");
                    _playerindex = 0;
                    _state = _CMOVING;
                    for (int i=0; i < _numplayers; i++) {
                        _moves[i] = null;
                    }
                    println(new String(SB));
                    break;
            }

            case _CMOVING:  {
                if (_playerindex >= _numplayers) {
                    SB.append("\t\t[Updating]\n");
                    for (int j=0; j < _numplayers; j++) {
                        if (_boot[j]) {
                            _moves[j] = null;
                        }
                    }
                    processMoves(_moves);
                    scores = new double[_numplayers];
                    SB.append("\t\t[Robot Positions]: \n");
                    for (int i=0; i < _numplayers; i++) {
                        scores[i] = _players[i].score();
                        for (int j=0; j < _numrobots; j++) {
                            SB.append("\t\t\tPlayer[");
                            SB.append(Integer.toString(i));
                            SB.append("]: ");
                            SB.append(_players[i].robot(j).toString());
                            SB.append("\n");
                        }
                    }
                    SB.append("\t\t[Scores]:\n");
                    for (int i=0; i < _numplayers; i++) {
                        SB.append("\t\t\tPlayer[");
                        SB.append(Integer.toString(i));
                        SB.append("]: ");
                        SB.append(Integer.toString((int) _players[i].score()));
                        SB.append("\n");
                    }
                    result = new MoveResult();
                    result.setMoves(_moves);
                    result.setScores(scores);
                    _history.add(result);
                    _state = _CSELECTING;
                    _rounds++;
                    println(new String(SB));
                    break;
                }
                if (_boot[_playerindex]) {
                    _playerindex++;
                    break;
                }
                if (!_players[_playerindex].interactive()) {
                    stime = System.currentTimeMillis();
                    pmove = _players[_playerindex].move();
                    etime = System.currentTimeMillis();
                    if (pmove != null) {
                        _moves[_playerindex] = new char[_numrobots];
                        SB.append("\t\t\tPlayer[");
                        SB.append(Integer.toString(_playerindex));
                        SB.append("]: \n");
                        for (int i=0; i < _numrobots; i++) {
                            SB.append("\t\t\t\t[");
                            SB.append(pmove[i]);
                            SB.append("]\n");
                        }
                        System.arraycopy(pmove, 0, _moves[_playerindex], 0, _numrobots);
                    } else {
                        SB.append("\t\t\tPlayer[");
                        SB.append(Integer.toString(_playerindex));
                        SB.append("] has made an invalid move (Exception).\n");
                        SB.append("\t\t\tPlayer[");
                        SB.append(Integer.toString(_playerindex));
                        SB.append("] has been given the boot.\n");
                        _players[_playerindex].setScore(0);
                        _boot[_playerindex] = true;
                    }
                    _playerindex++;;
                } else {
                    _state = _CWAITING;
                    break;
                }
                println(new String(SB));
                break;
            }

            case _CDECISION:  {
                _MAX = numPlayers();
                scores = new double[_MAX];
                for (int i=0; i < _MAX; i++) {
                    scores[i] = _players[i].score();
                }
                winners = Util.maxIndex(scores);
                if (winners.length == 1) {
                    SB.append("\t[Final Result]: The Winner Is:  Player[");
                    SB.append(winners[0]);
                    SB.append("]\n");
                } else {
                    SB.append("\t[Final Result]: ");
                    SB.append(winners.length);
                    SB.append(" of the Champions are Evenly Matched!\n");
                    _MAX = winners.length;
                    for (int i=0; i < _MAX; i++) {
                        SB.append("\t\t[Tied]:  [");
                        SB.append(winners[i]);
                        SB.append("] ");
                        SB.append(_players[winners[i]].name());
                    }
                }
                for (int i=0; i < _MAX; i++) {
                    _players[i].gameOver();
                }
                _state = _CFINISHED;
                refresh();
                println(new String(SB));
                break;
            }
        }
	/* amg2006 -- added if condition for when the stop button is hit */
        if (_lastchange >= _numrounds && _state < _CDECISION) {
            _state = _CDECISION;
            step();
        };
        return (_state != _CFINISHED);
    }
    
    private final class FilledRectangle {
        
        public int _xa;
        public int _xb;
        public int _ya;
        public int _yb;
        
        public FilledRectangle(int __xa, int __xb, int __ya, int __yb) {
            _xa = __xa;
            _xb = __xb;
            _ya = __ya;
            _yb = __yb;

        }
    
        public int hashCode() {
            return _xa + _xb + _ya + _yb;
        }

        public boolean equals(Object __obj) {
            try {
                FilledRectangle _rect = (FilledRectangle) __obj;
                
                if (_xa == _rect._xa &&
                    _xb == _rect._xb &&
                    _ya == _rect._ya &&
                    _yb == _rect._yb) {
                    
                    return true;
                }
                return false;

            } catch (Exception EXC) {
                return false;
            }
        }
    }

    //********************************************
    //*
    //* View Panel
    //*
    //********************************************
    private final class ViewPanel extends JPanel implements Serializable {
        double              _ratio;
        final int           _CWIDTH                         = 600;
        final int           _CHEIGHT                        = 600;
        final Font          _CVIEW_FONT                     = new Font("Courier", Font.BOLD, 35);
        final Font          _CROBOT_FONT                    = new Font("Courier", Font.BOLD, (_size > 50) ? (_size > 60) ? 8 : 9 : 10);
        final int           _CHOFFSET                       = _CROBOT_FONT.getSize() / 3;
        final int           _CVOFFSET                       = _CROBOT_FONT.getSize();
        final float         _CALPHA                         = 0.9f;
        final Color         _CWHITE                         = new Color(1.0f, 1.0f, 1.0f);
        final Color         _CBLACK                         = new Color(0.0f, 0.0f, 0.0f);
        final int           _COUTLINE_THICKNESS             = 2;

        //********************************************
        //*
        //* Constructor
        //*
        //********************************************
        public ViewPanel() throws Exception {
            super();
            setLayout(new BorderLayout());
            setBorder(BorderFactory.createCompoundBorder(BorderFactory.createRaisedBevelBorder(),
                                                         BorderFactory.createLoweredBevelBorder()));
            setPreferredSize(new Dimension(_CWIDTH, _CHEIGHT));
            setMinimumSize(new Dimension(_CWIDTH, _CHEIGHT));
            setFont(_CVIEW_FONT);
        }

        //********************************************
        //*
        //* paint() Override
        //*
        //********************************************
        public void paint(Graphics __g) {
            try {
                super.paint(__g);
                
                if (_board == null || _players == null) {
                    return;
                }

                int width = getWidth();
                int height = getHeight();
                _ratio = (width < height ? (double) width / (double) _size : (double) height / (double) _size);
                __g.setFont(_CROBOT_FONT);
                
                for (int x=0; x < _size; x++) {
                    for (int y=0; y < _size; y++) {
                        if (_initialized && !_changed[x][y]) {
                            continue;
                        }
                        if (_board[x][y] == _CEMPTY || _board[x][y] == _CCONTESTED) {
                            __g.setColor(_CWHITE);
                        }
                        else
                        if (_board[x][y] == _CFILLED) { 
                            __g.setColor(_CBLACK);
                        }
                        else {
                            __g.setColor(_players[_board[x][y]].color());
                        }
                        __g.fillRect((int) (x * _ratio), (int) (y * _ratio), (int) _ratio, (int) _ratio);
                    }
                }

                for (int x=0; x < _size; x++) {
                    for (int y=0; y < _size; y++) {
                        if (_initialized && !_changed[x][y]) {
                            continue;
                        }
                        if (_robots[x][y].size() > 0) {
                            __g.setColor(_CBLACK);
                            __g.fillRect((int) (x * _ratio), (int) (y * _ratio), (int) _ratio, (int) _ratio);
                            __g.setColor(((Robot) _robots[x][y].get(0)).color());
                            __g.fillRect((int) (x * _ratio) + _COUTLINE_THICKNESS, (int) (y * _ratio) + _COUTLINE_THICKNESS, (int) _ratio - _COUTLINE_THICKNESS * 2, (int) _ratio - _COUTLINE_THICKNESS * 2);
                            __g.setColor(_CBLACK);
                            __g.drawString(Integer.toString(((Robot) _robots[x][y].get(0)).ID()), (int) (x * _ratio) + _CHOFFSET, (int) (y * _ratio) + _CVOFFSET);
                            continue;
                        }
                    }
                }

            } catch (Exception EXC) {
                EXC.printStackTrace();
            }
        }        
    }    
    
    //********************************************
    //*
    //* Control Panel
    //*
    //********************************************
    private final class ControlPanel extends JPanel implements ActionListener, ItemListener, Serializable {
        JTabbedPane  _tab;
        JPanel       _conf;
        JPanel       _info;
        final        int         _CWIDTH = 300;
        final        int         _CHEIGHT = 350;
        final        int         _CPANEL_WIDTH = _CWIDTH;
        final        int         _CPANEL_HEIGHT = 21;
        final        int         _CPLAYER_NAME_LENGTH = 20;
        final        ImageIcon   _CSTEP_ICON  = new ImageIcon("Images/marble_step.gif");
        final        ImageIcon   _CSTOP_ICON  = new ImageIcon("Images/marble_stop.gif");
        final        ImageIcon   _CPLAY_ICON  = new ImageIcon("Images/marble_play.gif");
        final        ImageIcon   _CRESET_ICON = new ImageIcon("Images/marble_reset.gif");
        final        Color       _CDISABLED_FIELD_COLOR = new Color(1.0f, 1.0f, 1.0f);
        final Font   _CCONTROL_FONT  = new Font("Courier", Font.BOLD, 12);
        final Font   _CCOMBO_FONT = new Font("Courier", Font.BOLD, 10);

        JTextField   _rounds;
        JTextField   _lastchangefield;
        JTextField[] _scores;
        JTextField   _numplayersfield;
        JTextField   _numrobotsfield;
        JTextField   _numroundsfield;
        JTextField   _sizefield;
        JComboBox[]  _classes;
        JPanel       _infobox;
        JPanel       _confbox;
        JButton      _play;
        JButton      _step;
        JButton      _reset;        
	/* amg2006 stop button */
	JButton      _stop;

        NumberFormat _nf;

        //********************************************
        //*
        //* Constructor
        //*
        //********************************************
        public ControlPanel() throws Exception {
            super();

            SlotPanel       slot;
            JPanel          box;
            JLabel          label;
            int             _MAX;
            StringBuffer    SB;
            String          name;

            setLayout(new BorderLayout());
            setBorder(BorderFactory.createCompoundBorder(BorderFactory.createRaisedBevelBorder(),
                                                         BorderFactory.createLoweredBevelBorder()));
            setPreferredSize(new Dimension(_CWIDTH, _CHEIGHT));
            setMinimumSize(new Dimension(_CWIDTH, _CHEIGHT));
            setFont(_CCONTROL_FONT);

            _info = new JPanel();
            _info.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createRaisedBevelBorder(),
                                                         BorderFactory.createLoweredBevelBorder()));
            _info.setLayout(new BorderLayout());
            _info.setPreferredSize(new Dimension(_CWIDTH, _CHEIGHT));
            _info.setMinimumSize(new Dimension(_CWIDTH, _CHEIGHT));
            _info.setFont(_CCONTROL_FONT);

            _reset = new JButton(_CRESET_ICON);
            _reset.addActionListener(this);
            _step = new JButton(_CSTEP_ICON);
            _step.addActionListener(this);
            _play = new JButton(_CPLAY_ICON);
            _play.addActionListener(this);

	    /* amg2006 adding stop button */
	    _stop = new JButton(_CSTOP_ICON);
	    _stop.addActionListener(this);

            box = new JPanel();
            box.setLayout(new BoxLayout(box, BoxLayout.Y_AXIS));
            slot = new SlotPanel(_CPANEL_WIDTH, _CPANEL_HEIGHT);
            _input = new JTextField();
            _input.setFont(_CCONTROL_FONT);
            _input.addActionListener(this);
            label = new JLabel("Input:    ");
            label.setFont(_CCONTROL_FONT);
            slot.add(label, _input);
            box.add(slot);

            slot = new SlotPanel(_CPANEL_WIDTH, _CPANEL_HEIGHT);
            _rounds = new JTextField();
            _rounds.setEditable(false);
            _rounds.setFont(_CCONTROL_FONT);
            label = new JLabel("Rounds:   ");
            label.setFont(_CCONTROL_FONT);
            slot.add(label, _rounds);
            box.add(slot);

            slot = new SlotPanel(_CPANEL_WIDTH, _CPANEL_HEIGHT);
            _lastchangefield = new JTextField();
            _lastchangefield.setEditable(false);
            _lastchangefield.setFont(_CCONTROL_FONT);
            label = new JLabel("LastChange: ");
            label.setFont(_CCONTROL_FONT);
            slot.add(label, _lastchangefield);
            box.add(slot);

            _MAX = numPlayers();
            _scores = new JTextField[_MAX];
            for (int i=0; i < _MAX; i++) {
                _scores[i] = new JTextField();
                _scores[i].setEditable(false);
                _scores[i].setFont(_CCONTROL_FONT);
                slot = new SlotPanel(_CPANEL_WIDTH, _CPANEL_HEIGHT);
                label = new JLabel(Util.adjustString(_players[i].name(), _CPLAYER_NAME_LENGTH));
                label.setForeground(_players[i].color());
                label.setFont(_CCONTROL_FONT);
                slot.add(label, _scores[i]);
                box.add(slot);
            }
            _info.add(box, BorderLayout.CENTER);
            _infobox = box;

            _conf = new JPanel();
            _conf.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createRaisedBevelBorder(),
                                                         BorderFactory.createLoweredBevelBorder()));
            _conf.setLayout(new BorderLayout());
            _conf.setPreferredSize(new Dimension(_CWIDTH, _CHEIGHT));
            _conf.setMinimumSize(new Dimension(_CWIDTH, _CHEIGHT));
            _conf.setFont(_CCONTROL_FONT);
            
            box = new JPanel();
            box.setLayout(new BoxLayout(box, BoxLayout.Y_AXIS));            
            slot = new SlotPanel(_CPANEL_WIDTH, _CPANEL_HEIGHT);
            _numplayersfield = new JTextField();
            _numplayersfield.setFont(_CCONTROL_FONT);
            _numplayersfield.setText(Integer.toString(_MAX));
            _numplayersfield.addActionListener(this);
            label = new JLabel("NumPlayers:  ");
            label.setFont(_CCONTROL_FONT);
            slot.add(label, _numplayersfield);
            box.add(slot);
            
            slot = new SlotPanel(_CPANEL_WIDTH, _CPANEL_HEIGHT);
            _numrobotsfield = new JTextField();
            _numrobotsfield.setFont(_CCONTROL_FONT);
            _numrobotsfield.setText(Integer.toString(_numrobots));
            _numrobotsfield.addActionListener(this);
            label = new JLabel("NumRobots:   ");
            label.setFont(_CCONTROL_FONT);
            slot.add(label, _numrobotsfield);
            box.add(slot);
            
            slot = new SlotPanel(_CPANEL_WIDTH, _CPANEL_HEIGHT);
            _numroundsfield = new JTextField();
            _numroundsfield.setFont(_CCONTROL_FONT);
            _numroundsfield.setText(Integer.toString(_numrounds));
            _numroundsfield.addActionListener(this);
            label = new JLabel("NumRounds:   ");
            label.setFont(_CCONTROL_FONT);
            slot.add(label, _numroundsfield);
            box.add(slot);

            slot = new SlotPanel(_CPANEL_WIDTH, _CPANEL_HEIGHT);
            _sizefield = new JTextField();
            _sizefield.setFont(_CCONTROL_FONT);
            _sizefield.setText(Integer.toString(_size));
            _sizefield.addActionListener(this);
            label = new JLabel("Size:        ");
            label.setFont(_CCONTROL_FONT);
            slot.add(label, _sizefield);
            box.add(slot);
            
            _classes = new JComboBox[_MAX];
            for (int i=0; i < _MAX; i++) {               
                _classes[i] = new JComboBox(_classlist);
                _classes[i].setSelectedItem(_players[i].playerClass());
                _classes[i].addItemListener(this);
                _classes[i].setFont(_CCOMBO_FONT);
                slot = new SlotPanel(_CPANEL_WIDTH, _CPANEL_HEIGHT);
                label = new JLabel("["+i+"]:  ");
                label.setFont(_CCONTROL_FONT);
                slot.add(label, _classes[i]);
                box.add(slot);                                    
            }                        
            _conf.add(box, BorderLayout.CENTER);
            _confbox = box;

            _tab = new JTabbedPane();
            _tab.add("Information", _info);                  
            _tab.add("Configuration", _conf);
            add(_tab, BorderLayout.CENTER);            
            
            _nf = NumberFormat.getInstance();
            _nf.setMinimumFractionDigits(2);
            _nf.setMaximumFractionDigits(2);

        }        

        //********************************************
        //*
        //* ActionListener Interface
        //*
        //********************************************
        public void actionPerformed(ActionEvent __event) {
            Object source = __event.getSource();
            ParseValue pv = null;
            JComboBox[] tmp;
            Class[] tmpcls;
            char[] moves;
            int prev;
            int curr;
            int _MAX;
            SlotPanel slot;
            JLabel label;
            MoveResult result;
            double[] scores;

            try {
                if (source == _step) {
                    step();
                    this.refresh();
                    Rectangles.this.refresh();
                    return;
                }
                if (source == _play) {
		    new StopListener(this).start();
                    return;
                }
                if (source == _reset) {
                    reset();
                    return;
                }
		if (source == _stop) {
		    _state = _CFINISHED;
		}

                if (source == _input) {
                    if (_state == _CSELECTING) {
                        step();
                    }
                    if (_state == _CFINISHED) {
                        return;
                    }
                    if ((moves = parseMove(_input.getText())) != null) {
                        _state = _CMOVING;
                        println("\t\t\tPlayer["+_playerindex+"]:");
                        for (int i=0; i < _numrobots; i++) {
                            println("\t\t\t\t["+moves[i]+"]");
                        }
                        _moves[_playerindex++] = moves;
                        step();
                        this.refresh();
                        Rectangles.this.refresh();
                    } else {
                        println("Invalid Input");
                    }
                    return;
                }
                if (source == _numrobotsfield) {
                    pv = ParseValue.parseIntegerValue(_numrobotsfield.getText(), _CMIN_ROBOTS, _CMAX_ROBOTS);
                    if (pv.isValid()) {
                        _config.setNumRobots(((Integer) pv.value()).intValue());
                        _ui.configure(_config);
                    } else {
                        println("Invalid Input");
                    }
                }
                if (source == _numroundsfield) {
                    pv = ParseValue.parseIntegerValue(_numroundsfield.getText(), _CMIN_ROUNDS, _CMAX_ROUNDS);
                    if (pv.isValid()) {
                        _config.setNumRounds(((Integer) pv.value()).intValue());
                        _ui.configure(_config);
                    } else {
                        println("Invalid Input");
                    }
                }
                if (source == _sizefield) {
                    pv = ParseValue.parseIntegerValue(_sizefield.getText(), _CMIN_SIZE, _CMAX_SIZE);
                    if (pv.isValid()) {
                        _config.setSize(((Integer) pv.value()).intValue());
                        _ui.configure(_config);
                    } else {
                        println("Invalid Input");
                    }
                }
                if (source == _numplayersfield) {
                    pv = ParseValue.parseIntegerValue(_numplayersfield.getText(), _CMIN_PLAYERS, _CMAX_PLAYERS);
                    if (pv.isValid()) {
                        prev = _config.numPlayers();
                        curr = ((Integer) pv.value()).intValue();
                        if (prev == curr) {
                            return;
                        }
                        if (curr > prev) {
                            tmp = _classes;
                            _classes = new JComboBox[curr];
                            System.arraycopy(tmp, 0, _classes, 0, prev);
                            tmpcls = new Class[curr];
                            System.arraycopy(_config.playerList(), 0, tmpcls, 0, prev);
                            for (int i=prev; i < curr; i++) {
                                _classes[i] = new JComboBox(_classlist);
                                _classes[i].addItemListener(this);
                                _classes[i].setFont(_CCOMBO_FONT);
                                slot = new SlotPanel(_CPANEL_WIDTH, _CPANEL_HEIGHT);
                                label = new JLabel("["+i+"]:  ");
                                label.setFont(_CCONTROL_FONT);
                                slot.add(label, _classes[i]);
                                _confbox.add(slot);
                                tmpcls[i] = (Class) _classes[i].getSelectedItem();
                            }
                            _config.setPlayerList(tmpcls);
                        }
                        if (curr < prev) {
                            tmp = new JComboBox[curr];
                            System.arraycopy(_classes, 0, tmp, 0, curr);
                            tmpcls = new Class[curr];
                            System.arraycopy(_config.playerList(), 0, tmpcls, 0,  curr);
                            for (int i=curr; i < prev; i++) {
                                _confbox.remove(_confbox.getComponents().length - 1);
                            }
                            _classes = tmp;
                            _config.setPlayerList(tmpcls);
                        }
                        _ui.configure(_config);
                        repaint();
                        Rectangles.this.refresh();
                    } else {
                        println("Invalid Input");
                    }
                }                                                                                
            } catch (Exception EXC) {
                System.out.println(EXC.getMessage());
                EXC.printStackTrace();
            }
        }        
        
        //********************************************
        //*
        //* ItemListener Interface
        //*
        //********************************************            
        public void itemStateChanged(ItemEvent __event) {
            Object source = __event.getSource();
            int _MAX = _classes.length;
            
            try {
                for (int i=0; i < _MAX; i++) {
                    if (source == _classes[i]) {
                        _config.setPlayer(i, (Class) _classes[i].getSelectedItem());
                        _ui.configure(_config);
                    }                                
                }                        
            } catch (Exception EXC) {
                System.out.println(EXC.getMessage());
                EXC.printStackTrace();
            }
        }
         
        //********************************************
        //*
        //* Score Updater
        //*
        //********************************************
        public void refresh() throws Exception {
            int _MAX = numPlayers();

            for (int i=0; i < _MAX; i++) {
                _scores[i].setText(_nf.format(_players[i].score()));
            }
            _rounds.setText(Integer.toString(rounds()));
            _lastchangefield.setText(Integer.toString(lastChange())  + ", Limit("+_numrounds+")" );
        }                            

        
        //********************************************
        //*
        //* Action Tool Exporter
        //*
        //********************************************
        public JButton[] exportTools() {
	    /* amg2006 changed to 4 */
            JButton[] ret = new JButton[4];
            ret[0] = _reset;
            ret[1] = _step;
            ret[2] = _play;
	    ret[3] = _stop;
            return ret;
        }
    }

    class StopListener extends Thread {
	private ControlPanel controlPanel;	
	public StopListener(ControlPanel cp) { 
	    controlPanel=cp;
	} 
	public void run () { 
	try {
	    while (GUI._rectangles.step()) {
		GUI._rectangles.refresh();
		controlPanel.refresh();
	    }		
	}
	catch (Exception e) {
	    System.out.println("unexpected exception caught in run");
                e.printStackTrace();
	}
    }

}
}


