/*
 *  CPS108 Spring 1998
 *  Boggle: BoggleGame.java
 *  Brian Fan & Geoff Berry
 */

package boggle;
import java.lang.*;
import java.util.*;

/**
 *  BoggleGame - class that plays the game
 */

public class BoggleGame
{
    /**
     *  Constructs a BoggleGame
     *  @param validwords array of valid words (for example, a dictionary)
     *  !WARNING! this implmentation assumes that the array is sorted
     *  in alphabetical order
     */

    BoggleGame(String[] validwords)
    {
	setupBoard();
	myTimer.start();
	myPP = new PerfectPlayer(myBoard, validwords, this);
	myPP.start();
    }

    /**
     *  Adds a BoggleView to the BoggleGame
     *  @param view the BoggleView that should be added
     *  @return true if the BoggleView was successfully added,
     *  false if the game is full or another player already has
     *  the same name
     */
  
    public boolean addBoggleView(BoggleView view)
    {
	if ( myViews.size() >= BoggleConsts.MAX_NUM_PLAYERS )
	{
	    view.showMessage("BoggleView refused: game is currently full");
	    return false;
	}
	if ( myStarted )
	{
	    view.showMessage("BoggleView refused: game has already started");
	    return false;
	}
	int i;
	int j = myViews.size();
	for (i=0; i<j; i++)
	{
	    if ( ((BoggleView)myViews.elementAt(i)).getName() ==
		 view.getName() )
	    {
		view.showMessage("BoggleView refused: " +
				 "someone else already has that name");
		return false;
	    }
	}

	myViews.addElement(view);
	view.setup();
	view.startWaiting(BoggleConsts.TIME_WAITING);
	return true;
    }

    /**
     *  Notifies the BoggleView that all possible words have been found
     */

    public void notifyFoundAllWords()
    {
	myFoundAllWords = true;
    }

    /**
     *  Receive a tick and decide on appropriate action (if any)
     *  @param tick the tick count
     */

    public void tick(int count)
    {
	switch (count)
	{
	    case BoggleConsts.TIME_WAITING:
		countdownToStart(); break;
	    case (BoggleConsts.TIME_WAITING + BoggleConsts.TIME_COUNTDOWN):
		startPlay(); break;
	    case (BoggleConsts.TIME_WAITING + BoggleConsts.TIME_COUNTDOWN +
		  BoggleConsts.TIME_PLAYING):
		stopPlay(); break;
	}

	if ( count < ( BoggleConsts.TIME_WAITING +
		       BoggleConsts.TIME_COUNTDOWN +
		       BoggleConsts.TIME_PLAYING ) )
	{
	    int i;
	    int j = myViews.size();
	    for (i=0; i<j; i++)
	    {
		((BoggleView)myViews.elementAt(i)).tick(count);
	    }
	}
	else
 	    { myTimer.off(); }
    }

/*
 *  Private Functions
 */

/* *** Functions used by the ticker *** */

    /**
     *  Start the countdown to game time
     */

    private void countdownToStart()
    {
	int i;
	int j = myViews.size();
	myStarted = true;
	String[] players = new String[myViews.size()];
	for (i=0; i<j; i++)
	{
	    players[i] = ((BoggleView)myViews.elementAt(i)).getName();
	}
	for (i=0; i<j; i++)
	{
	    ((BoggleView)myViews.elementAt(i)).setOpponents(players);
	    ((BoggleView)myViews.elementAt(i)).startCountdown(
		BoggleConsts.TIME_COUNTDOWN);
	}
    }

    /**
     *  Start the game
     */

    public void startPlay()
    {
	int i;
	int j = myViews.size();
	for (i=0; i<j; i++)
	{
	    ((BoggleView)myViews.elementAt(i)).initializeBoard(myBoard);
	    ((BoggleView)myViews.elementAt(i)).startGame(
		BoggleConsts.TIME_PLAYING);
	}
    }

    /**
     *  Stop the game
     */

    private void stopPlay()
    {
	int i;
	int j = myViews.size();
	myGuesses = new String[j][];
	for (i=0; i<j; i++)
	{
	    ((BoggleView)myViews.elementAt(i)).endGame();
	    myGuesses[i] = ((BoggleView)myViews.elementAt(i)).getGuesses();
	}

	for (i=0; i<j; i++)
	{
	    removeDuplicates(myGuesses[i]);
	}

	if ( !myFoundAllWords )
	{
	    for (i=0; i<j; i++)
	    {
		((BoggleView)myViews.elementAt(i)).showMessage(
			"Hang on, still trying to find all words" );
	    }
	    while (!myFoundAllWords)
	    {
		Thread.currentThread().yield();
	    }
	}

	myPossible = myPP.getPossibleWords();
	CalculateResults();
	ShowResults();
    }

/* *** Miscellaneous Functions *** */

    private void setupBoard()
    {
	char ch;
	int i;
	int row = 0;
	int col = 0;
	Random rgen = new Random();
	for (i=0; i<BoggleConsts.BOARD_SIZE; i++)
	{
	    int j = Math.abs(rgen.nextInt() % 26);
	    switch (j)
	    {
		case 0:  ch = 'a';  break;  case 1:  ch = 'b';  break;
		case 2:  ch = 'c';  break;  case 3:  ch = 'd';  break;
		case 4:  ch = 'e';  break;  case 5:  ch = 'f';  break;
		case 6:  ch = 'g';  break;  case 7:  ch = 'h';  break;
		case 8:  ch = 'i';  break;  case 9:  ch = 'j';  break;
		case 10: ch = 'k';  break;  case 11: ch = 'l';  break;
		case 12: ch = 'm';  break;  case 13: ch = 'n';  break;
		case 14: ch = 'o';  break;  case 15: ch = 'p';  break;
		case 16: ch = 'q';  break;  case 17: ch = 'r';  break;
		case 18: ch = 's';  break;  case 19: ch = 't';  break;
		case 20: ch = 'u';  break;  case 21: ch = 'v';  break;
		case 22: ch = 'w';  break;  case 23: ch = 'x';  break;
		case 24: ch = 'y';  break;  case 25: ch = 'z';  break;
		default: ch = ' ';  break;
	    }
	    if ( col >= BoggleConsts.BOARD_ROWS )
	    {
		col = 0;
		row++;
	    }
	    myBoard[row][col] = ch;
	    col++;
	}
    }

    private void CalculateResults()
    {
	int i;
	int j = myPossible.length;
	int count;
	Vector common = new Vector();

	// Find all the common words and stick them in a vector
	for (i=0; i<j; i++)
	{
	    count = 0;
	    int k;
	    int l = myViews.size();
	    for (k=0; k<l; k++)
	    {
	        int m;
		int n = myGuesses[k].length;
		for (m=0; m<n; m++)
		{
		    if (myGuesses[k][m].equals(myPossible[i]))
		    {
		        count++;
		    }
		}
	    }
	    if ( count > 1 )
	    {
	        common.addElement(myPossible[i]);
	    }
	}
	myCommon = new String[common.size()];
	common.copyInto(myCommon);

	// Find number of unique words for each player
	Vector possible = new Vector();
	for (i=0; i<myPossible.length; i++)
	    { possible.addElement(myPossible[i]); }
	int[] scores = new int[myViews.size()];
	j = myViews.size();
	for (i=0; i<j; i++)
	{
	    int k;
	    int l = myGuesses[i].length;
	    for (k=0; k<l; k++)
	    {
		if (!common.contains(myGuesses[i][k]) &&
		    possible.contains(myGuesses[i][k]))
		{
		    scores[i]++;
		}
	    }
	}

	// Find the high score
	int highscore = scores[0];
	for (i=1; i<j; i++)
	{
	    if (scores[i] > highscore)
	        { highscore = scores[i]; }
	}

// System.out.println("Highscore: " + highscore);

	// Find the people who had the high score
	Vector winners = new Vector();
	for (i=0; i<j; i++)
	{
// System.out.println("  Score " + i + ": " + scores[i]);
	    if (scores[i] == highscore)
	    {
	        winners.addElement(
		    ((BoggleView)myViews.elementAt(i)).getName() );
	    }
	}
	myWinners = new String[winners.size()];
	winners.copyInto(myWinners);

	FindMissed();
    }

    private void FindMissed()
    {
	int i;
	int j = myPossible.length;
	Vector missed = new Vector();
	for (i=0; i<j; i++)
	{
	    int k;
	    int l = myViews.size();
	    boolean found = false;
	    for (k=0; k<l; k++)
	    {
	        int m;
		int n = myGuesses[k].length;
	        for (m=0; m<n; m++)
		{
		    if (myGuesses[k][m].equals(myPossible[i]))
		    {
		        found = true;
		    }
		}
	    }
	    if (!found)
	    {
	        missed.addElement(myPossible[i]);
	    }
	}
	myMissed = new String[missed.size()];
	missed.copyInto(myMissed);
    }

    private void ShowResults()
    {
	int i;
	int j = myViews.size();
	removeDuplicates(myCommon);
	removeDuplicates(myMissed);
	for (i=0; i<j; i++)
	{
	    ((BoggleView)myViews.elementAt(i)).showResults(
		myWinners, myCommon, myMissed );
	}
/*
 * DEBUG: prints out guesses made by each player
 *
for(i=0; i<j; i++)
{ System.out.println("Guesses: ");
int k; int l = myGuesses[i].length;
for (k=0; k<l; k++)
{ System.out.println("  " + myGuesses[i][k]); }
}
 *
 */
    }

    private void removeDuplicates(String[] array)
    {
        Vector temp = new Vector();
	int i;
	for (i=0; i<array.length; i++)
	{
	    if ( !temp.contains(array[i]) )
	    {
	        temp.addElement(array[i]);
	    }
	}
	array = new String[temp.size()];
	temp.copyInto(array);
    }


    private Vector myViews = new Vector();
    private String[][] myGuesses;
    private char[][] myBoard =
	new char[BoggleConsts.BOARD_ROWS][BoggleConsts.BOARD_COLS];
    private boolean myStarted = false;
    private Timer myTimer = new Timer(this);
    private String[] myWinners;
    private String[] myPossible;
    private String[] myCommon;
    private String[] myMissed;
    private boolean myFoundAllWords = false;
    private PerfectPlayer myPP;
}


class Timer extends Thread
{
    public Timer(BoggleGame game)
    {
	myGame = game;
    }

    public void off()
    {
	myOn = false;
    }

    public void run()
    {
	while ( myOn )
	{
	    myGame.tick(myCount);
	    myCount++;
	    try { Thread.sleep(1000); }
	    catch( InterruptedException e )  { }
	}
    }

    private int myCount = 0;
    private boolean myOn = true;
    private BoggleGame myGame;
}


class PerfectPlayer extends Thread
{
    public PerfectPlayer(char[][] board, String[] dictionary,
			 BoggleGame game)
    {
	myBoard = board;
	myValidwords = dictionary;
	myGame = game;
    }

    public void run()
    {
	FindAllWords();
	myGame.notifyFoundAllWords();
    }

    public String[] getPossibleWords()
    {
	String[] temp = new String[myWords.size()];
	myWords.copyInto(temp);
	return temp;
    }

    private void FindAllWords()
    {
	int i;
	int j = myValidwords.length;
	for (i=0; i<j; i++)
	{
	    int k;
	    for (k=0; k<BoggleConsts.BOARD_ROWS; k++)
	    {
		int l;
		for(l=0; l<BoggleConsts.BOARD_COLS; l++)
		{
		    if (myBoard[k][l] == myValidwords[i].charAt(0))
		    {
			Vector pos = new Vector();
			Position p = new Position(k, l);
			pos.addElement(p);
			if (SearchSurroundings(k, l, myValidwords[i], pos, 1))
			{
			    myWords.addElement(myValidwords[i]);
			}
		    }
		}
	    }
	    yield();
	}
    }

    private boolean SearchSurroundings(int row, int col, String word,
				Vector pos, int found)
    {
	if (found == word.length())
	    { return true; }

// check the horizontal and vertical directions

	if ( (row > 0) &&
	     (notInVector(new Position(row-1, col), pos)) &&
	     (word.charAt(found) == myBoard[row-1][col]) )
	{
	    Position NP = new Position(row-1, col);
	    pos.addElement(NP);
	    if ( SearchSurroundings(row-1, col, word, pos, found+1) )
		{ return true; }
	    else
	        { pos.removeElementAt(pos.size() - 1); }
	}

	if ( (row < BoggleConsts.BOARD_ROWS - 1) &&
	     (notInVector(new Position(row+1, col), pos)) &&
	     (word.charAt(found) == myBoard[row+1][col]) )
	{
	    Position NP = new Position(row+1, col);
	    pos.addElement(NP);
	    if ( SearchSurroundings(row+1, col, word, pos, found+1) )
		{ return true; }
	    else
		{ pos.removeElementAt(pos.size() - 1); }
	}

	if ( (col > 0) &&
	     (notInVector(new Position(row, col-1), pos)) &&
	     (word.charAt(found) == myBoard[row][col-1]) )
	{
	    Position NP = new Position(row, col-1);
	    pos.addElement(NP);
	    if ( SearchSurroundings(row, col-1, word, pos, found+1) )
		{ return true; }
	    else
	        { pos.removeElementAt(pos.size() - 1); }
	}

	if ( (col < BoggleConsts.BOARD_COLS - 1) &&
	     (notInVector(new Position(row, col+1), pos)) &&
	     (word.charAt(found) == myBoard[row][col+1]) )
	{
	    Position NP = new Position(row, col+1);
	    pos.addElement(NP);
	    if ( SearchSurroundings(row, col+1, word, pos, found+1) )
		{ return true; }
	    else
		{ pos.removeElementAt(pos.size() - 1); }
	}

// Now check the diagonal directions

	if ( (row > 0) && (col > 0) &&
	     (notInVector(new Position(row-1, col-1), pos)) &&
	     (word.charAt(found) == myBoard[row-1][col-1]) )
	{
	    Position NP = new Position(row-1, col-1);
	    pos.addElement(NP);
	    if ( SearchSurroundings(row-1, col-1, word, pos, found+1) )
		{ return true; }
	    else
	        { pos.removeElementAt(pos.size() - 1); }
	}

	if ( (row < BoggleConsts.BOARD_ROWS - 1) &&
	     (col < BoggleConsts.BOARD_COLS - 1) &&
	     (notInVector(new Position(row+1, col+1), pos)) &&
	     (word.charAt(found) == myBoard[row+1][col+1]) )
	{
	    Position NP = new Position(row+1, col+1);
	    pos.addElement(NP);
	    if ( SearchSurroundings(row+1, col+1, word, pos, found+1) )
		{ return true; }
	    else
		{ pos.removeElementAt(pos.size() - 1); }
	}

	if ( (col > 0) && (row < BoggleConsts.BOARD_ROWS -1) &&
	     (notInVector(new Position(row+1, col-1), pos)) &&
	     (word.charAt(found) == myBoard[row+1][col-1]) )
	{
	    Position NP = new Position(row+1, col-1);
	    pos.addElement(NP);
	    if ( SearchSurroundings(row+1, col-1, word, pos, found+1) )
		{ return true; }
	    else
	        { pos.removeElementAt(pos.size() - 1); }
	}

	if ( (col < BoggleConsts.BOARD_COLS - 1) && (row > 0) &&
	     (notInVector(new Position(row-1, col+1), pos)) &&
	     (word.charAt(found) == myBoard[row-1][col+1]) )
	{
	    Position NP = new Position(row-1, col+1);
	    pos.addElement(NP);
	    if ( SearchSurroundings(row-1, col+1, word, pos, found+1) )
		{ return true; }
	    else
		{ pos.removeElementAt(pos.size() - 1); }
	}

// If everything above fails, then there were no matching chars surrounding
// this position, so the search fails
	return false;
    }

    private boolean notInVector(Position p, Vector pos)
    {
	int i;
	int j = pos.size();
	for (i=0; i<j; i++)
	{
	    if ( ((Position)pos.elementAt(i)).equals(p) )
	    {
		return false;
	    }
	}
	return true;
    }

    private Vector myWords = new Vector();
    private char[][] myBoard = new char[1][1];
    private String[] myValidwords = new String[1];
    private BoggleGame myGame;
}

class Position
{
    public Position(int r, int c)
    {
	row = r;
	col = c;
    }

    public boolean equals(Position p)
    {
	return (p.row == row && p.col == col);
    }

    public int row;
    public int col;
}

