//-------------------------------------------------------------------------
// Javvle (Java Scrabble) Version 1.0
// Date: November 26, 1998
// Version 2.0
//-------------------------------------------------------------------------
import java.awt.*;
import java.applet.*;
import java.util.StringTokenizer;
import java.net.*;
import java.io.*;
import java.util.*;
import java.awt.image.*;
import java.awt.event.*;


class LetterHolder extends Canvas implements MouseListener, MouseMotionListener
{
	Javvle applet;
	Vector v;
	Image offimg;
	Graphics offg;
	Letter selected;
	int selx = -1;
	int dx;

	public LetterHolder(Javvle j)
	{
		applet = j;
		v = new Vector();

		addMouseListener(this);
		addMouseMotionListener(this);

	}		

	public void initletters(String ls)
	{
		for(int i=0;i<ls.length();++i)
		{
			v.addElement(new Letter((Letter)applet.letterinfo.get(""+ls.charAt(i))));
		}
		lineupletters();
		deselect();
		repaint();
	}

	public void replaceletters(String s)
	{
		v.removeAllElements();
		addletters(s);
	}

	public void addletters(String ls)
	{
		for(int i=0;i<ls.length();++i)
		{
			v.addElement(new Letter((Letter)applet.letterinfo.get(""+ls.charAt(i))));
		}
		lineupletters();
		deselect();
		repaint();
	}

	public String gettraytiles()
	{
		StringBuffer sb = new StringBuffer("");
		for(int i=0;i<v.size();++i)
		{
			sb.append(((Letter)v.elementAt(i)).symbol);
		}
		return sb.toString();
	}

	public void lineupletters()
	{
		for(int i =0; i < v.size(); ++i)
			((Letter)v.elementAt(i)).rect.setLocation(i * 27,1);
	}

	public void update(Graphics  g)
	{	paint(g);
	}

	public void deselect()
	{
		selx = -1;
		selected = null;
		repaint();
	}

	public Letter getselected()
	{
		Letter tmp = selected;
		v.removeElementAt(v.size()-1);
		selected = null;
		applet.b.deselect();
		repaint();
		return tmp;
	}

	public void DropLetter(Letter l,int xpos)
	{
		if(v.size() > 7)
			return;

		v.addElement(l);
		selx = v.size()-1;
		selected = l;
		xpos-=15;
		if(xpos+30 > getSize().width)
			xpos = getSize().width - 30;
		if(xpos < 0)
			xpos = 0;

		l.rect.setLocation(xpos,1);
		repaint();
	}

	public void paint(Graphics g)
	{
		if(offimg == null)
		{	offimg = createImage(getSize().width,getSize().height);
			offg = offimg.getGraphics();
		}

		offg.setColor(Color.lightGray);
		offg.fillRect(0,0,getSize().width,getSize().height);
		offg.setColor(Color.black);
		offg.drawRect(0,0,getSize().width-1,getSize().height-1);

		for(int i =0; i < v.size(); ++i)
		{
			((Letter)v.elementAt(i)).draw(offg, this, (i == selx), true, false);
		}
		
		g.drawImage(offimg,0,0,this);
	}


	// MouseListener
	public void mouseEntered(MouseEvent me) {}
	public void mouseExited(MouseEvent me) {}
	public void mouseReleased(MouseEvent me) {}
	public void mouseClicked(MouseEvent me) {}
	public void mousePressed(MouseEvent me)
	{
		int x = me.getX();
		int y = me.getY();

		selected = null;
		for(int i =0; i < v.size(); ++i)
		{
			if( ((Letter)v.elementAt(i)).rect.contains(x,y))
			{	
				dx = x - ((Letter)v.elementAt(i)).rect.x;	
				
				selected = ((Letter)v.elementAt(i));
				Letter tmp = ((Letter)v.elementAt(v.size()-1));
				v.setElementAt(selected,v.size()-1);
				v.setElementAt(tmp,i);
				selx = v.size()-1;
				applet.b.deselect();
				repaint();
				break;
			}
		}

		if(selected == null)
		{
			if(applet.b.selected != null)
			{	DropLetter(applet.b.getselected(),x);
			}
		}
	
	}


	// MouseMotionListener
	public void mouseMoved(MouseEvent me) {}
	public void mouseDragged(MouseEvent me) 
	{
		int x = me.getX();
		int y = me.getY();

		if(selected != null)
		{	
			if(x-dx > 0 && x-dx+30 < getSize().width)
			{	
				selected.rect.setLocation(x-dx,1);
				repaint();
			}
		}
	}
}


class Position
{
	Image icon;

	//-----------------------------------------------------------
	// Apply all letter multipliers then apply word multipliers.
	//-----------------------------------------------------------
	// Position types
	static final int L1 = 0;	// Letter value
	static final int L2 = 1;	// Letter x 2
	static final int L3 = 2;	// Letter x 3
	static final int W2 = 3;	// Word x 2
	static final int W3 = 4;	// Word x 3

	static final int CT = 5;	// Center must be played at start of game.
								// Word x 2
	int neighbors;		
	// neighbors is a bit flag indicating on which sides an occupied
	// space has neighbors. It is used in rule algorithms.
	// -------
	// |  N  |
	// |W   E|
	// |  S  |
	// -------
	static final int NFLAG = 1;
	static final int SFLAG = 2;
	static final int EFLAG = 4;
	static final int WFLAG = 8;

	// Each position type signifies the possible neighbors a
	// position can have.
	int location;
	static final int LEFT		= 0;
	static final int TOPLEFT	= 1;
	static final int TOP		= 2;
	static final int TOPRIGHT	= 3;
	static final int RIGHT		= 4;
	static final int BOTTOMRIGHT= 5;
	static final int BOTTOM		= 6;
	static final int BOTTOMLEFT	= 7;
	static final int INNER		= 8;

	// Offsets to add to index while testing for neighbor existence.
	static final int N = -15;
	static final int S =  15;
	static final int E =   1;
	static final int W =  -1;

	// Offset arrays
	static final int[] NSEW = {N,S,E,W};
	static final int[] SE   = {S,E};
	static final int[] NE   = {N,E};
	static final int[] NW   = {N,W};
	static final int[] SW   = {S,W};
	static final int[] SEW  = {S,E,W};
	static final int[] NSW  = {N,S,W};
	static final int[] NEW  = {N,E,W};
	static final int[] NSE  = {N,S,E};

	boolean locked;		 // If locked you cannot change the position's letter.
	boolean occupied;	 // True if the position is occupied by a letter.
	Letter occupant;	 // Reference to the letter occupying the position.
	int scoretype;		 // Position's scoretype.
	int index;			 // Position's index (0-224)
	int[] neighborcheck; // Offsets to add to index while testing neighbors.

	public void setup(int loc, int[] dirs)
	{
		location = loc;
		neighborcheck = dirs;
	}

	public Position(int t,int ix, Image i)
	{
		icon = i;
		index = ix;
		locked = false;
		occupied = false;
		occupant = null;
		scoretype = t;
		neighborcheck = new int[4];
		
		// determine location type
		int max = 225;
		int n = ix % Board.ROWS;
		int m = ix / Board.ROWS;

		// Don't change the order of these tests.
		// Later tests assume former tests have failed.
		
		// Test for INNER (most prevalent case) first.
		if( (n > 0) && (n < (Board.ROWS-1)) && (m > 0) && (m < (max/Board.ROWS)-1) )
			setup(INNER, NSEW);

		// Specific positions tests.
		else if(ix == 0)
			setup(TOPLEFT, SE);
		else if(ix == max-Board.ROWS)
			setup(BOTTOMLEFT, NE);
		else if(ix == (max-1))
			setup(BOTTOMRIGHT, NW);
		else if(ix == Board.ROWS-1)
			setup(TOPRIGHT, SW);

		// Test value ranges last.
		else if(ix < (Board.ROWS-1))
			setup(TOP, SEW);
		else if(n == (Board.ROWS-1))
			setup(RIGHT, NSW);
		else if(ix > max-Board.ROWS)
			setup(BOTTOM, NEW);
		else
			setup(LEFT, NSE);
	}

	public void lock(boolean s)
	{
		locked = s;
	}

	//------------------------------------------------
	// Return true if Letter is set else return false.
	//------------------------------------------------
	public boolean setletter(Letter l)
	{
		if(!occupied && !locked)
		{	occupied = true;
			occupant = new Letter(l);
			return true;
		}
		else
			return false;
	}
}


class Board extends Canvas implements MouseListener
{
	Point[] cellorgs;
	int[] xs;
	int[] ys;
	Image offimg;
	Graphics offg;
	boolean checkpos = false;
	Letter selected;
	int selectedcell = -1;
	int inspectedcell = -1;
	Position[] positions;
	static final int ROWS = 15;
	static final int HORZ = 1;
	static final int VERT = 15;
	public StringBuffer pused;
	public int[] ipused = {-1,-1,-1,-1,-1,-1,-1,-1};

	Javvle applet;
	boolean sized = false;
	
	public void lockboard()
	{
		for(int i = 0; i < 225; ++i)
		{
			if(positions[i].occupied)
				positions[i].lock(true);
		}

	}


	//------------------------------------------------------
	// Send this function a Vector of occupied positions
	// representing a word and it will return the score 
	// with letter and word multipliers applied.
	//------------------------------------------------------
	public int calc_word_score(Vector wordpositions)
	{
		int score = 0;
		int mux = 1;
		for(int i=0; i<wordpositions.size(); ++i)
		{
			Position P = (Position)(wordpositions.elementAt(i));
			Letter L = P.occupant;

			if(P.locked)
			{
				score += L.value;
			}
			else
			{
				switch(P.scoretype)
				{
					case Position.L1:		// Normal.
						score += L.value;
						break;
					case Position.L2:		// Letter x 2.
						score += 2*L.value;
						break;
					case Position.L3:		// Letter x 3.
						score += 3*L.value;
						break;
					case Position.W2:		// Word x 2.
						score += L.value;
						mux *= 2; 			
						break;
					case Position.CT:		// Center: Word x 2.
						score += L.value;
						mux *= 2; 			
						break;
					case Position.W3:		// Word x 3.
						score += L.value;
						mux *= 3; 			
						break;
				}
			}
		}

		return (score * mux);
	}

	
	Vector find_horizontal_word(int sp)
	{
		int minpos = (sp/Board.ROWS)*Board.ROWS-1;
		int maxpos = ((sp/ROWS)+1)*Board.ROWS;
		Vector vv = new Vector(16);

		int i = sp-1;
		while(i > minpos && positions[i].occupied)
			--i;
		
		++i;
		while(i < maxpos && positions[i].occupied)
		{
			vv.addElement(positions[i]);
			++i;
		}
		return vv;
	}

	Vector find_vertical_word(int sp)
	{	
		int minpos = -1;
		int maxpos = 225;
		Vector vv = new Vector(16);

		int i = sp-Board.ROWS;
		while(i > minpos && positions[i].occupied)
			i-=Board.ROWS;

		i+=Board.ROWS;
		while(i < maxpos && positions[i].occupied)
		{
			vv.addElement(positions[i]);
			i+=Board.ROWS;
		}
		return vv;
	}


	public int score_calc(int dir, int pos1)
	{
		Vector w;
		Vector q;
		int turnscore = 0;
		Position p;
		int horz_score = 0;
		int horz_size = 0;
		int vert_score = 0;
		int vert_size = 0;

		//---------------------------------------------------
		// dir = Board.HORZ or Board.VERT.
		// pos1 = Position of first unlocked letter in word.
		//---------------------------------------------------
		if(dir == Board.HORZ)
		{	
			w = find_horizontal_word(pos1);
			horz_size = w.size();
			if(horz_size > 0)
			{	
				horz_score = calc_word_score(w);
				for(int u=0;u<horz_size;++u)
				{	p = (Position)(w.elementAt(u));
					if(!p.locked)
					{
						q = find_vertical_word(p.index);
						vert_size = q.size();
						if(vert_size > 1)
						{	
							vert_score += calc_word_score(q);
						}
					}
				}
			}	

			//----------------------------------------------------
			// This code adjusts for defaulting single letter
			// plays to HORZ orientation.
			//----------------------------------------------------
			if(vert_score > 0 && horz_size < 2)
				turnscore = vert_score;
			else
				turnscore = vert_score + horz_score;

		}
		else // (dir == Board.VERT)
		{
			w = find_vertical_word(pos1);
			if(w.size() > 0)
			{	
				turnscore += calc_word_score(w);
				for(int u=0;u<w.size();++u)
				{	p = (Position)(w.elementAt(u));
					if(!p.locked)
					{
						q = find_horizontal_word(p.index);
						if(q.size() > 1)
						{	
							turnscore += calc_word_score(q);
						}
					}
				}
			}
		}
		return turnscore + ((scrabble_bonus) ? 50 : 0);
	}

	
	//------------------------------------------------------------
	// Letters must all be either horizontal or vertical.
	//
	// Find the first letter.
	// If no more return.
	// Else find the second letter.
	// If second.index - first.index < ROWS then word is HORZ.
	// Else word is vertical.
	//
	// If horizontal there must be letters in every position 
	// between first.index and last.index when counting by 1.
	//
	// If vertical there must be letters in every position between
	// first.index and last.index when counting by ROWS.
	//
	//------------------------------------------------------------

	// Used to communicate whether 7 letters were used or not to score_calc.
	public boolean scrabble_bonus;

	// Used to communicate the number of letters used during a turn.
	public int nused; 


	public int contiguity_check()
	{
		// This hold the positions used during this turn.
		int[] used = {-1,-1,-1,-1,-1,-1,-1,-1};
		
		// Create an ordered array of the position indexes used.
		nused = 0;
		pused = new StringBuffer("");
		for( int i=0; i<225; ++i)
		{	
			if(positions[i].occupied && !positions[i].locked)
			{	used[nused++] = i;
				pused.append(""+i+",");
			}
		}
		

		// Used in score_calc.
		scrabble_bonus = (nused == 7);
		
		// Determine if word is horizontal or vertical or one letter.
		int d = 0;
		int dir = 0;
		if(nused > 1)
		{	d = used[nused-1] - used[0];
			dir = (d < ROWS) ? HORZ : VERT;
		}
		else
			return HORZ; // Only one letter used, default to HORZ.
		
		//--------------------------------------------------------
		// Search from the first position until you fall off the 
		// board or find an empty position. 
		//--------------------------------------------------------
		// If you have not found all of the used positions then
		// they do not lie in a horizontal or vertical line
		//--------------------------------------------------------
		int found = 0;
		int max = (dir == VERT) ? 225 : ((used[0]/ROWS)+1)*ROWS;
		for(int xx=used[0]; xx<max; xx+=dir)
		{
			if(!positions[xx].occupied)
				break;

			for(int z=0; z<nused && found<nused; ++z)
			{	if(xx == used[z])
					++found;
			}
		}
		return (found == nused) ? dir : -1;
	}



	//--------------------------------------------------------------------
	//
	// New Letters must have a connection to the existing word structure.
	//
	//--------------------------------------------------------------------
	public int connected_check(int turncount)
	{
		if(turncount == 1)
		{
			// Return position of first unlocked letter.
			for(int i = 0; i < 225; ++i)
			{
				if(!positions[i].locked && positions[i].occupant != null)
					return i;
			}
			return -1;
		}

		boolean allconnected = false;
		int firstfound = -1;

		found_connection:
		// Walk through all positions....
		for(int i = 0; i < 225; ++i)
		{
			// If an unlocked tile is in this position...
			if(!positions[i].locked && positions[i].occupant != null)
			{
				// Save position of first letter so we can return it.
				if(firstfound == -1)
					firstfound = i;

				int nl = positions[i].neighborcheck.length;
				
				// Test each neighbor position to see if a neighbor exists.
				for(int j=0; j<nl; ++j)
				{
					int np = i + positions[i].neighborcheck[j];

					//---------------------------------------------------
					// If the word adjoins a locked position then it is 
					// connected to the existing word structure.
					//---------------------------------------------------
					if(positions[np].occupied && positions[np].locked)
					{	allconnected = true;
						break found_connection;
					}
				}
			}
		}
		return (allconnected) ? firstfound : -1;
	}


	//-------------------------------------------------------------------------
	//
	// Remember that a list of the latest used positions is going to be 
	// on the end of this string in the future. This will allow the board
	// to show the latest added word.
	//
	//-------------------------------------------------------------------------


	public void string2board(String s)
	{
		char[] bstate = new char[225];
		s.getChars(0,bstate.length,bstate,0);
		for(int i=0;i<225;++i)
		{	
			if(bstate[i] != '0')
			{
				positions[i].occupant = new Letter((Letter)applet.letterinfo.get(""+bstate[i]));
				positions[i].occupied = true;
				positions[i].lock(true);
			}
			else
			{	positions[i].occupant = null;
				positions[i].occupied = false;
				positions[i].lock(false);
			}
		}
		
		for(int kk=0;kk<8;++kk)
			ipused[kk] = -1;

		int j = s.lastIndexOf('|');
		StringTokenizer st = new StringTokenizer(s.substring(j+1),",");
		int m=0;
		while(st.hasMoreTokens())
		{
			String ii = st.nextToken();
			try
			{
				ipused[m++] = Integer.parseInt(ii);
			}
			catch(NumberFormatException nfe)
			{
				applet.ShowMsg("NumberFormatException at string2board.");
			}
		}
		repaint();
	}
	
	public boolean in_ipused(int t)
	{
		for(int kk=0;kk<8;++kk)
			if(t == ipused[kk])
				return true;

		return false;
	}

	public String board2string()
	{
		
		StringBuffer sb = new StringBuffer();
		for(int i = 0; i < 225; ++i)
		{
			if(positions[i].occupant != null)
				sb.append(positions[i].occupant.symbol);
			else
				sb.append("0");
		}

		return sb.toString();
	}

	public Board(Javvle j)
	{
		addMouseListener(this);
		applet = j;
		positions = new Position[225];
		for(int i = 0; i < 225; ++i)
		{
			switch(i)
			{
				// Intentional fallthrough below.
				case 0:	
				case 7:	
				case 14:
				case 105:
				case 119:
				case 210:
				case 217:
				case 224:
					positions[i] = new Position(Position.W3,i,applet.icons[7]);
					break;
				// Intentional fallthrough below. 
				case 16:
				case 32:
				case 48:
				case 64:
				case 28:
				case 42:
				case 56:
				case 70:
				case 154:
				case 168:
				case 182:
				case 196:
				case 160:
				case 176:
				case 192:
				case 208:
					positions[i] = new Position(Position.W2,i,applet.icons[6]);
					break;
				case 112:
					positions[i] = new Position(Position.CT,i,applet.icons[2]);
					break;
				// Intentional fallthrough below. 
				case 3:
				case 11:
				case 36:
				case 38:
				case 45:
				case 52:
				case 59:
				case 92:
				case 96:
				case 98:
				case 102:
				case 108:
				case 116:
				case 122:
				case 126:
				case 128:
				case 132:
				case 165:
				case 172:
				case 179:
				case 186:
				case 188:
				case 213:
				case 221:
					positions[i] = new Position(Position.L2,i,applet.icons[4]);
					break;
				// Intentional fallthrough below. 
				case 20:
				case 24:
				case 80:
				case 84:
				case 140:
				case 144:
				case 76:
				case 88:
				case 136:
				case 148:
				case 200:
				case 204:
					positions[i] = new Position(Position.L3,i,applet.icons[5]);
					break;
				default:
					positions[i] = new Position(Position.L1,i,null);
					break;
			}
		}

	}

	public void calcsize()
	{
		//----------------------------------------------
		// Calculate cellorgs[], xs[], and ys[] once.
		//----------------------------------------------
		cellorgs = new Point[225];
		xs = new int[15];
		ys = new int[15];
		
		for(int i = 0; i < 15; i++)
		{	
			xs[i] = i*25;
			ys[i] = i*25;
		}
		//----------------------------------------------
		// Cause numbering of cellorgs to be...
		//  0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
		// 15,16,17,...
		//----------------------------------------------
		for(int i = 0; i < 15; i++)
		{	
			for(int k = 0; k < 15; k++)
			{	
				cellorgs[i*15 + k] = new Point(xs[k],ys[i]);
			}
		}

		sized = true;
	}

	public void update(Graphics  g)
	{	paint(g);
	}

	public void paint(Graphics  g)
	{
		if(!sized)
			calcsize();

		if(offimg == null)
		{	offimg = createImage(376,376);
			offg = offimg.getGraphics();
		}

		int cw = xs[1] - xs[0];
		int ch = ys[1] - ys[0];

		Color c;
		String lbl;

		boolean ff = true;

		// Board background color.
		offg.setColor(new Color(220,220,220));
		offg.fillRect(0,0,376,376);

		// Draw the lines on the board.
		offg.setColor(Color.darkGray);
		for(int i = 1; i < 15; i++)
		{
			offg.drawLine(xs[i],0,xs[i],375);
			offg.drawLine(0,ys[i],375,ys[i]);

		}
		offg.setColor(Color.black);
		offg.drawRect(0,0,375,375);

		// Draw the positions.
		for(int i = 0; i < 225; ++i)
		{
			if(positions[i].icon != null) 
				offg.drawImage(positions[i].icon,cellorgs[i].x-1,cellorgs[i].y-1,this);
		}

		// Draw the letters last.
		for(int i = 0; i < 225; ++i)
		{
			if( positions[i].occupied && (!(i == inspectedcell && checkpos)) )
			{
				positions[i].occupant.draw(offg,cellorgs[i].x,cellorgs[i].y,this,(i==selectedcell),positions[i].locked,applet.b.in_ipused(i));
			}
		}
		g.drawImage(offimg,0,0,this);

	}

	public int getcell(int x, int y)
	{
		int xcell = -1;
		int ycell = -1;

		for(int i=0;i<14;i++)
		{	
			if(x >= xs[i] && x < xs[i+1])
			{	
				xcell = i;
				break;
			}
			if(xcell < 0)
				xcell = 14;
		}
		
		for(int i=0;i<14;i++)
		{
			if(y >= ys[i] && y < ys[i+1])
			{	
				ycell = i;
				break;
			}
			if(ycell < 0)
				ycell = 14;
		}
		return ycell * 15 + xcell;
	}

	public Letter getselected()
	{
		Letter tmp = selected;
		selected = null;
		positions[selectedcell].occupant = null;
		positions[selectedcell].occupied = false;
		repaint(cellorgs[selectedcell].x,cellorgs[selectedcell].y,30,30);
		selectedcell = -1;
		return tmp;
	}



	// MouseListener
	public void mouseEntered(MouseEvent me) {}
	public void mouseExited(MouseEvent me) {}
	public void mouseClicked(MouseEvent me) {}
	
	public void mouseReleased(MouseEvent me)
	{	
		int x = me.getX();
		int y = me.getY();
		int tmp = getcell(x,y);

		if(checkpos == true)
			repaint(cellorgs[tmp].x,cellorgs[tmp].y,30,30);
		checkpos = false;
	}
	
	public void mousePressed(MouseEvent evt)
	{
		int x = evt.getX();
		int y = evt.getY();
		boolean pflag = false;
		boolean rightclick = ((evt.getModifiers() & Event.META_MASK) == Event.META_MASK);
		int cellno = getcell(x,y);
		inspectedcell = -1;
				
		if(!rightclick)
		{	
			if(applet.currentplayer != applet.playernumber)
			{	
				applet.winding.play();
				return;
			}

			if(!positions[cellno].occupied)
			{
				
				if(applet.lh.selected != null)
				{
					// Move letter from letterholder.
					positions[cellno].setletter(applet.lh.getselected());
					selected = positions[cellno].occupant;
				}
				else if(selected != null)
				{
					// Move letter from another board space.
					Letter tmp = getselected();
					positions[cellno].setletter(tmp);
				}
	
			}

		}
		else
		{
			inspectedcell = cellno;
			checkpos = true;
			pflag = true;
		}

		if(!positions[cellno].locked)
		{	
			selected = positions[cellno].occupant;
			selectedcell = cellno;
			applet.lh.deselect();
			pflag = true;
		}

		if(pflag)
			repaint();
	}

	public void deselect()
	{
		if(selected != null)
		{
			int tmp = selectedcell;
			selectedcell = -1;
			selected = null;
			repaint(cellorgs[tmp].x,cellorgs[tmp].y,30,30);
		}		

	}
}


class Letter
{
	char symbol;
	int value;
	int freq;
	Rectangle rect;
	Image img;

	public Letter(int v,char s, int f, Image i)
	{	
		value = v;
		symbol = s;
		freq = f;
		img = i;
		rect = new Rectangle(0,0,img.getWidth(Javvle.javvle),img.getHeight(Javvle.javvle));
	}

	public Letter(Letter l)
	{
		value = l.value;
		symbol = l.symbol;
		freq = l.freq;
		img = l.img;
		rect = new Rectangle(l.rect.x,l.rect.y,l.rect.width,l.rect.height);
	}

	public String toString()
	{
		return "" + symbol;
	}

	public void draw(Graphics offg,int x, int y, ImageObserver lh, boolean hilight, boolean locked, boolean used)
	{
		rect.setLocation(x, y);
		draw(offg, lh, hilight, locked, used);
	}
	
	public void draw(Graphics offg,int x, int y, ImageObserver lh)
	{
		rect.setLocation(x, y);
		draw(offg, lh, false, true, false);
	}

	public void draw(Graphics offg, ImageObserver lh, boolean hilight, boolean locked, boolean used)
	{
		int imx = rect.x;
		int imy = rect.y;
		int imsize = 30;

		if(hilight)
		{	
			offg.setColor(Color.green);
			offg.fillRect(imx,imy,23,23);
		}
		else if(used)
		{
			offg.setColor(Color.yellow);
			offg.fillRect(imx,imy,23,23);
		}
		else
			offg.drawImage(img,imx,imy,lh);

		
		offg.setColor(Color.black);
		offg.drawRect(imx,imy,24,24);
		
		if(!locked)
		{
			offg.setColor(Color.yellow);
			offg.drawRect(imx+1,imy+1,22,22);
			offg.drawRect(imx+2,imy+2,20,20);
		}


		if(symbol != ' ')
		{
			offg.setFont(Javvle.letterfont);
			if(Javvle.fm_letterfont == null)
				Javvle.fm_letterfont = offg.getFontMetrics();
			String ltr = ""+symbol;
			int sw = Javvle.fm_letterfont.stringWidth(ltr);
			int sh = Javvle.fm_letterfont.getAscent();
			int xo = (imsize - sw)/3-3;
			int yo = (imsize - sh)+1;
			offg.setColor(Color.black);
			offg.drawString(ltr, imx+xo, imy+imsize-yo);

			offg.setFont(Javvle.scorefont);
			if(Javvle.fm_scorefont == null)
				Javvle.fm_scorefont = offg.getFontMetrics();
			String scr = "" + value;
			offg.drawString(scr, imx+imsize-(2+Javvle.fm_scorefont.stringWidth(scr))-5, imy+7*imsize/8-3);
		}
	}
}


//-----------------------------------------------------------------------------
// Message Box Functionality
// Dependent classes
// class MsgBox extends Dialog
// class TextCanvas extends Canvas
// class picture extends Canvas
//-----------------------------------------------------------------------------
class MsgBox extends Dialog implements ActionListener, WindowListener
{
	// WindowListener
	public void windowActivated(WindowEvent we) {} 
	public void windowClosed(WindowEvent we) {}
	public void windowClosing(WindowEvent we)
	{
		dispose();
	}
	public void windowDeactivated(WindowEvent we) {} 
	public void windowDeiconified(WindowEvent we) {} 
	public void windowIconified(WindowEvent we) {}
	public void windowOpened(WindowEvent we) {} 

	Javvle applet;
	picture pic;

	public MsgBox(Frame app, Javvle p, String m, int w)
	{	
		super(app,"Message Box",true);
		super.setBounds(0,0,w,150);
		super.setResizable(false);

		addWindowListener(this);

		setLayout(new BorderLayout(3,3));
		applet = p;
		Button Ok = new Button("Ok");
		Ok.addActionListener(this);
		pic = new picture(applet.icons[3]);
		pic.setSize(50,50);
		add("West",pic);
		add("Center",new TextCanvas(m));
		add("South",Ok);
	}

	public void actionPerformed(ActionEvent e) 
	{
		applet.msgbox = null;
		dispose();
	}
}

class TextCanvas extends Canvas
{
	String s;
	Color bkgnd = null;
	boolean outline = false;
	int leftmarg = 10;

	public TextCanvas(String m)
	{	s = m;
	}

	public TextCanvas(String m, boolean ol, Color c, int lmarg)
	{
		s = m;
		outline = ol;
		bkgnd = c;
		leftmarg = lmarg;
	}

	public void update(Graphics g)
	{	paint(g);
	}

	public void paint(Graphics g)
	{
		int lm = leftmarg;
		if(bkgnd != null)
		{
			g.setColor(bkgnd);
			g.fillRect(0,0,getSize().width,getSize().height);
		}
		g.setColor(Color.black);
		if(outline)
		{
			g.drawRect(0,0,getSize().width-1,getSize().height-1);
		}
		int lines = 0;

		g.setFont(new Font("Helvetica",Font.BOLD,12));
		FontMetrics fm = g.getFontMetrics();

		StringTokenizer st = new StringTokenizer(s,"\n");
		while(st.hasMoreTokens()) 
		{	st.nextToken();
			++lines;
		}

		int yy = (getSize().height - (lines-1) * fm.getHeight())/2;
		st = new StringTokenizer(s,"\n");
		while(st.hasMoreTokens())
		{	
			String tmp = st.nextToken();
			
			if(lines == 1)
				lm = (getSize().width - fm.stringWidth(tmp))/2;

			g.drawString(tmp,lm,yy);
			yy += fm.getHeight();
		}
	}
}


class picture extends Canvas
{
	Image img;
	int h,w;
	public picture(Image i)
	{	
		img = i;
		h = img.getHeight(this);
		w = img.getWidth(this);
	}

	public void paint(Graphics g)
	{
		g.drawImage(img,(getSize().width-w)/2,(getSize().height-h)/2,this);
	}
}


class lettercanvas extends Canvas
{
	Letter lettertoshow;
	Javvle applet;

	public lettercanvas(String s, Javvle j)
	{	applet = j;
		lettertoshow = new Letter((Letter)applet.letterinfo.get(s));
	}
	public void paint(Graphics g)
	{
		lettertoshow.draw(g,0,0,this);
	}
	public String getletter()
	{
		return ""+lettertoshow.symbol;
	}
}

class LetterExchange extends Dialog implements ActionListener, WindowListener
{
	Javvle applet;
	lettercanvas[] letters;
	Checkbox[] checks;
	Button ok;
	Button cancel;
	int nletters;
	int[] xhold = {-1,-1,-1,-1,-1,-1,-1,-1};
	String[] labs;


	// WindowListener
	public void windowActivated(WindowEvent we) {} 
	public void windowClosed(WindowEvent we) {}
	public void windowClosing(WindowEvent we)
	{
		dispose();
	}
	public void windowDeactivated(WindowEvent we) {} 
	public void windowDeiconified(WindowEvent we) {} 
	public void windowIconified(WindowEvent we) {}
	public void windowOpened(WindowEvent we) {} 



	public LetterExchange(Frame app, Javvle j)
	{	
		super(app,"Exchange Letters and/or Skip Turn",true);
		super.setBounds(0,0,255,260);
		super.setResizable(false);

		addWindowListener(this);

		applet = j;
		setLayout(new BorderLayout(3,3));
		Panel p = new Panel();
		String tiles = applet.lh.gettraytiles();
		nletters = tiles.length();
		p.setLayout(new GridLayout(2,7,3,3));
		checks = new Checkbox[nletters];
		labs = new String[nletters];
		lettercanvas lc;
		//--------------------------------------------------------
		// The blank labels are for spacing.
		//--------------------------------------------------------
		for(int i=0;i<7;++i)
		{
			if(i < nletters)
			{	lc = new lettercanvas(""+tiles.charAt(i), applet);
				labs[i] = new String(lc.getletter());
				lc.setSize(25,25);
				p.add(lc);
			}
			else
				p.add(new Label(""));
		}
		for(int i=0;i<7;++i)
		{	
			if(i < nletters)
			{	checks[i] = new Checkbox(labs[i]);
				p.add(checks[i]);
			}
			else
				p.add(new Label(""));
		}

		ok = new Button("Exchange/Skip");
		ok.addActionListener(this);
		cancel = new Button("Cancel");
		cancel.addActionListener(this);

		Panel q = new Panel();
		q.setLayout(new GridLayout(1,2,1,1));
		q.add(ok);
		q.add(cancel);
		
		add("North",p);
		if(nletters > 0)
			add("Center",
			     new TextCanvas(
				 "Score received for a skipped turn is 0.\n" +
				 " \n" +
				 "To skip your turn press Exchange/Skip.\n" +
				 " \n" +
				 "To skip your turn and exchange letters\n" +
				 "click the checkboxes of the letters\n" + 
				 "you want to exchange and then click the\n" + 
				 "Exchange/Skip button.",
				 true,Color.white,10));
		else
			add("Center",
			     new TextCanvas(
				 "Pressing Exchange will skip your turn.\n" +
				 " \n" +
				 "Score for a skipped turn is 0.\n" +
				 " \n" +
				 "There are no letters to exchange.",
				 true,Color.white,10));
		add("South",q);

	}

	public void actionPerformed(ActionEvent evt)
	{	
		String arg = evt.getActionCommand();

		if("Exchange/Skip".equals(arg))
		{	
			StringBuffer sbremv = new StringBuffer("");
			StringBuffer sbkeep = new StringBuffer("");
			for(int c = 0; c < nletters; ++c)
			{
				if(checks[c].getState())
					sbremv.append(labs[c]);
				else
					sbkeep.append(labs[c]);
			}

			// Exchanging letters if any selected.
			if(sbremv.length() > 0) 
			{
				Vector v = 
				applet.CallCGI(applet.cgiscript +
				        "?COMMAND=EXCHANGELETTERS" +
						"&RETURNLETTERS=" + sbremv.toString() +
				        "&GAMENAME=" + applet.gamename);

				if(v.size() > 0)
					applet.lh.replaceletters(sbkeep.toString() + (String)v.elementAt(0));
			}

			// Skip turn.
			applet.sendscore("",0);
			applet.lex = null;
			dispose();
			return;
		}

		if("Cancel".equals(arg))
		{	
			applet.lex = null;
			dispose();
		}
	}
}


class ChallengeBox extends Dialog implements ActionListener, WindowListener
{
	Javvle applet;
	lettercanvas[] letters;
	Button ok;
	Button cancel;

	
	// WindowListener
	public void windowActivated(WindowEvent we) {} 
	public void windowClosed(WindowEvent we) {}
	public void windowClosing(WindowEvent we)
	{
		dispose();
	}
	public void windowDeactivated(WindowEvent we) {} 
	public void windowDeiconified(WindowEvent we) {} 
	public void windowIconified(WindowEvent we) {}
	public void windowOpened(WindowEvent we) {} 


	public ChallengeBox(Frame app, Javvle j)
	{	
		super(app,"Your last play has been challenged.",true);
		super.setBounds(0,0,250,230);
		super.setResizable(false);

		addWindowListener(this);

		applet = j;
		setLayout(new BorderLayout(3,3));
		ok = new Button("Yes");
		ok.addActionListener(this);
		cancel = new Button("No");
		cancel.addActionListener(this);

		Panel q = new Panel();
		q.setLayout(new GridLayout(1,2,1,1));
		q.add(ok);
		q.add(cancel);
		
		add("Center",
		     new TextCanvas(
			 "Player " + applet.challenger + " has challenged your last play !\n" +
			 " \n" +
			 "Are you positive that all of the words\n" + 
			 "formed by your last play are in the\nofficial Javvle dictionary?\n" +
			 " \n" +
			 "Press Start Chat to discuss your\ndecision or . . . press Yes or No.",
			 true,Color.yellow,10));
		add("South",q);

		applet.challengedbeep.play();
	}

	public void actionPerformed(ActionEvent evt)
	{	
		String arg = evt.getActionCommand();

		if("Yes".equals(arg))
		{	
			// PLAYERNUMBER must be set to challenger.
			
			applet.CallCGI(applet.cgiscript +
			"?COMMAND=CHALLENGE" +
			"&GAMENAME=" + applet.gamename +
			"&ACTION=CONTINUE" +
			"&LASTLETTERS=0" +
			"&CURRENTPLAYERNUMBER=" + applet.currentplayer +
			"&PLAYERNUMBER=" + applet.challenger +
			"&NPLAYERS=" + applet.nplayers);
			
			applet.polling = true;
			applet.lex = null;
			dispose();
			return;
		}

		if("No".equals(arg))
		{	

			// PLAYERNUMBER must be set to challenger.

			Vector v = applet.CallCGI(applet.cgiscript +
			"?COMMAND=CHALLENGE" +
			"&GAMENAME=" + applet.gamename +
			"&ACTION=UNDO" +
			"&LASTLETTERS=" + applet.lastrcvdletters +
			"&CURRENTPLAYERNUMBER=" + applet.currentplayer +
			"&PLAYERNUMBER=" + applet.challenger +
			"&NPLAYERS=" + applet.nplayers);

			applet.lh.replaceletters(applet.prelastletters + (String)v.elementAt(0));

			applet.polling = true;
			applet.lex = null;
			dispose();
		}
	}
}



//-----------------------------------------------------------------------------
//
// Applet class
//
//-----------------------------------------------------------------------------
public class Javvle extends Applet implements Runnable, ActionListener
{

	boolean challengeinprogress;
	ChallengeBox cbox;
	boolean skipturnflag = false;
	String lastrcvdletters;
	String prelastletters;
	int nplayers;
	String gamename;
	String username;
	String cgiscript = "/~rjwsoft/newscrabble/scrabble_cgi/Javvle.pl";
	boolean gamestarted = false;
	int playernumber;
	int currentplayer;
	int timeleft = 180;
	boolean timing = false;
	boolean polling = false;
	boolean msgff = true;
	int challenged;
	int challenger;

	LetterExchange lex;
	GameStarter gamestarter;
	ChatBox chatbox;
	static boolean debug = false;
	private Thread thread = null;
	int turncount = 1;
	Hashtable letterinfo;
	Board b;
	LetterHolder lh;
	Image[] icons;
	Image iconstrip;
	static Javvle javvle;
	static Font letterfont;
	static Font scorefont;
	static FontMetrics fm_letterfont;
	static FontMetrics fm_scorefont;

	Button submit;
	Label txtscore;
	Button startgame;
	Button startchat;
	Button exchange;
	Button challenge;
	Label status1;
	Label status2;
	Label timer;
	TextArea ta;
	String cgi_url;
	Frame frame = null;
	AudioClip winding;
	AudioClip chirp;
	AudioClip coocoo;
	AudioClip challengedbeep;
	AudioClip tada;

	Label sc1,sc2,sc3,sc4;
	boolean oktochallenge = false;
	int challengetime = 30;
	MsgBox msgbox = null;

	public void paint(Graphics g)
	{
		g.setColor(Color.black);
		g.drawRect(0,0,getSize().width-1,getSize().height-1);
	}

	public void ShowMsg(String msg, int w)
	{
		msgbox = new MsgBox(frame, this, msg, w);
		winding.play();
		msgbox.show();
	}
	public void ShowMsg(String msg)
	{
		MsgBox msgbox = new MsgBox(frame,this, msg, 400);
		winding.play();
		msgbox.show();
	}

	public void init()
	{  
		Component comp = this;
		while(comp != null)
		{	if(comp instanceof Frame)
			{	frame = (Frame)comp;
				break;
			}
			comp = comp.getParent();
		}

		debug = param2bool("DEBUGFLAG");
		
		winding = getAudioClip(getCodeBase(),"winding.au");
		chirp = getAudioClip(getCodeBase(),"chirp1.au");
		coocoo = getAudioClip(getCodeBase(),"coocooclock.au");
		challengedbeep = getAudioClip(getCodeBase(),"challenged.au");
		tada = getAudioClip(getCodeBase(),"tada.au");
		
		letterfont = new Font("Helvetica",Font.BOLD,17);
		scorefont  = new Font("Helvetica",Font.PLAIN,10);

		javvle = this;

		MediaTracker mt = new MediaTracker(this);
		iconstrip = getImage(getCodeBase(),"scrabble.gif");
		mt.addImage(iconstrip,0);
		try
		{
			mt.waitForID(0);
		}
		catch(InterruptedException ie)
		{
			System.out.println(ie.toString());
		}
		icons = new Image[8];

		icons[0] = GetStripImage(iconstrip, new Rectangle(140,0,24,24), this);
		icons[1] = GetStripImage(iconstrip, new Rectangle(164,0,24,24), this);
		icons[2] = GetStripImage(iconstrip, new Rectangle(112,0,28,28), this);
		icons[3] = GetStripImage(iconstrip, new Rectangle(188,0,38,30), this);
		icons[4] = GetStripImage(iconstrip, new Rectangle(0,0,28,28), this);		// double letter
		icons[5] = GetStripImage(iconstrip, new Rectangle(28,0,28,28), this);	// triple letter
		icons[6] = GetStripImage(iconstrip, new Rectangle(56,0,28,28), this);	// double word
		icons[7] = GetStripImage(iconstrip, new Rectangle(84,0,28,28), this);	// triple word

		setBackground(Color.lightGray);

		letterinfo = new Hashtable();

		// Letter(value, symbol, frequency)
		letterinfo.put("A",new Letter(1,'A',9,icons[0]));
		letterinfo.put("B",new Letter(3,'B',2,icons[1]));
		letterinfo.put("C",new Letter(3,'C',2,icons[0]));
		letterinfo.put("D",new Letter(2,'D',4,icons[1]));
		letterinfo.put("E",new Letter(1,'E',12,icons[0]));
		letterinfo.put("F",new Letter(4,'F',2,icons[1]));
		letterinfo.put("G",new Letter(2,'G',3,icons[0]));
		letterinfo.put("H",new Letter(4,'H',2,icons[1]));
		letterinfo.put("I",new Letter(1,'I',9,icons[0]));
		letterinfo.put("J",new Letter(8,'J',1,icons[1]));
		letterinfo.put("K",new Letter(5,'K',1,icons[0]));
		letterinfo.put("L",new Letter(1,'L',4,icons[1]));
		letterinfo.put("M",new Letter(3,'M',2,icons[0]));
		letterinfo.put("N",new Letter(1,'N',6,icons[1]));
		letterinfo.put("O",new Letter(1,'O',8,icons[0]));
		letterinfo.put("P",new Letter(3,'P',2,icons[1]));
		letterinfo.put("Q",new Letter(10,'Q',1,icons[0]));
		letterinfo.put("R",new Letter(1,'R',6,icons[1]));
		letterinfo.put("S",new Letter(1,'S',4,icons[0]));
		letterinfo.put("T",new Letter(1,'T',6,icons[1]));
		letterinfo.put("U",new Letter(1,'U',4,icons[0]));
		letterinfo.put("V",new Letter(4,'V',2,icons[1]));
		letterinfo.put("W",new Letter(4,'W',2,icons[0]));
		letterinfo.put("X",new Letter(8,'X',1,icons[1]));
		letterinfo.put("Y",new Letter(4,'Y',2,icons[0]));
		letterinfo.put("Z",new Letter(10,'Z',1,icons[1]));
		letterinfo.put(" ",new Letter(0,' ',2,icons[0])); // Blank

		//-------------------------------------------------
		//
		// User interface elements.
		//
		//-------------------------------------------------
		int hh = getSize().height;
		int cols = 18;
		b = new Board(this);
		lh = new LetterHolder(this);

		ta = new TextArea(7,cols);
		ta.setFont(new Font("Courier",Font.PLAIN,11));
		
		GridBagLayout gridbag = new GridBagLayout();
		GridBagConstraints c = new GridBagConstraints();
		setLayout(gridbag);

		c.gridx = 0;
		c.gridwidth = 2;
		c.gridheight = 1;
		c.weightx = 1.0;
		c.weighty = 1.0;
		c.anchor = GridBagConstraints.NORTHWEST;
		c.gridwidth = GridBagConstraints.RELATIVE;
		c.insets = new Insets(5,5,5,0);
		
		gridbag.setConstraints(b,c);
		add(b);
		b.setSize(376,376);

		c.gridx = 0;
		c.gridwidth = GridBagConstraints.RELATIVE;
		c.gridheight = 1;
		c.weightx = 1.0;
		c.weighty = 0.5;
		c.anchor = GridBagConstraints.SOUTH;

		gridbag.setConstraints(lh,c);
		add(lh);
		lh.setSize(10*hh/16-10,1*hh/16);
		
		Panel q = new Panel();
		q.setLayout(new GridLayout(9,1,2,2));

		txtscore = new Label("Your last score: 0",Label.CENTER);
		q.add(txtscore);
		txtscore.setBackground(Color.white);

		status2 = new Label("",Label.CENTER);
		q.add(status2);
		status2.setBackground(Color.white);
		
		status1 = new Label("",Label.CENTER);
		q.add(status1);
		status1.setBackground(Color.white);
		
		timer   = new Label("",Label.CENTER);
		q.add(timer);
		timer.setBackground(Color.white);

		submit = new Button("Done with turn");
		submit.addActionListener(this);
		q.add(submit);
		submit.setEnabled(false);
		
		startgame = new Button("Find/Start Game...");
		startgame.addActionListener(this);
		q.add(startgame);

		exchange = new Button("Letter Exchange...");
		exchange.addActionListener(this);
		q.add(exchange);
		exchange.setEnabled(false);

		challenge = new Button("Challenge...");
		challenge.addActionListener(this);
		q.add(challenge);
		challenge.setEnabled(false);

		startchat = new Button("Start Chat...");
		startchat.addActionListener(this);
		q.add(startchat);
		startchat.setEnabled(false);
		
		Panel p = new Panel();
		p.setLayout(new BorderLayout(2,2));

		Font f = new Font("Courier",Font.PLAIN,12);
		sc1 = new Label("P1",Label.CENTER);
		sc1.setFont(f);
		sc1.setBackground(Color.green);
		sc2 = new Label("P2",Label.CENTER);
		sc2.setFont(f);
		sc2.setBackground(Color.lightGray);
		sc3 = new Label("P3",Label.CENTER);
		sc3.setFont(f);
		sc3.setBackground(Color.lightGray);
		sc4 = new Label("P4",Label.CENTER);
		sc4.setFont(f);
		sc4.setBackground(Color.lightGray);
		Panel pp = new Panel();
		pp.setLayout(new FlowLayout(FlowLayout.LEFT,1,0));
		pp.add(sc1);
		pp.add(sc2);
		pp.add(sc3);
		pp.add(sc4);
						
		p.add("North",pp);
		p.add("Center",ta);
		ta.setEditable(false);	
		p.add("South",q);

		c.gridx = 1;
		c.gridy = 0;
		c.gridwidth = GridBagConstraints.REMAINDER;
		c.gridheight = 2;
		c.weightx = 1.0;
		c.weighty = 1.0;
		c.anchor = GridBagConstraints.NORTHEAST;
		c.fill = GridBagConstraints.BOTH;
		c.insets = new Insets(5,5,40,5);

		gridbag.setConstraints(p,c);
		add(p);

		thread = new Thread(this);
		thread.start();
	}

	public void sendscore(String wp, int sc)
	{
		Vector v = 
			CallCGI(cgiscript +
			"?COMMAND=SENDSCORE" +
			"&PLAYERNUMBER=" + playernumber +
			"&BOARDSTRING=" + b.board2string() +
			"&WORDPOSITIONS=" + wp +
			"&SCORE=" + sc +
			"&TRAYTILES=" + lh.gettraytiles() +
			"&NPLAYERS=" + nplayers +
			"&GAMENAME=" + gamename);

		if(v.size() > 0)
		{
			String answer = (String)v.elementAt(0);
			if("GAMEOVER".equals(answer))
			{
				; // Do nothing...
			}
			else
			{
				prelastletters = lh.gettraytiles();
				lh.addletters((String)v.elementAt(0));
				lastrcvdletters = (String)v.elementAt(0);
			}
		}
		++turncount;
		submit.setEnabled(false);
		exchange.setEnabled(false);
		challenge.setEnabled(false);
		b.lockboard();
		b.deselect();
		polling = true;
	}


	public void actionPerformed(ActionEvent evt)
	{	
		String arg = evt.getActionCommand();
		Object target = evt.getSource();

		//-----------------------------------------
		// Make message box pseudo-modal.
		//-----------------------------------------
		if(msgbox != null)
		{
			winding.play();
			return;
		}

		if(target == challenge && oktochallenge)
		{
			CallCGI(cgiscript +
			"?COMMAND=CHALLENGE" +
			"&GAMENAME=" + gamename +
			"&ACTION=START" +
			"&LASTLETTERS=0" +
			"&CURRENTPLAYERNUMBER=" + currentplayer +
			"&PLAYERNUMBER=" + playernumber +
			"&NPLAYERS=" + nplayers);

			polling = true;
			return;

		}

		if(target == exchange && lex == null)
		{
			lex = new LetterExchange(frame,this);
			lex.show();
			return;
		}

		if(target == startchat && chatbox == null)
		{	
			/*
			if(!gamestarted)
			{	
				ShowMsg("Game must be started to start chat.");
				return;
			}
			*/
			
			chatbox = new ChatBox(frame,this);
			chatbox.show();
			return;
		}

		if(target == startgame && gamestarter == null)
		{
			gamestarter = new GameStarter(frame,this);
			gamestarter.show();
			return;

		}

		if(target == submit)
		{
			if(!gamestarted)
			{	ShowMsg("There is no game in progress.");
				return;
			}

			if(playernumber != currentplayer)
			{
				ShowMsg("It is not your turn.");
				return;
			}
			
			if(turncount == 1 && !b.positions[112].occupied)
			{
				ShowMsg("The center position must be used on the first play.");
				return;
			}
			
			int dir; // Needed for scoring algorithm.
			if( (dir = b.contiguity_check()) == -1)			
			{
				ShowMsg("Letters must be in a horizontal or vertical line\n" + 
					    "and must be adjoining each other.");
				return;
			}

			if(turncount == 1 && !b.positions[112].locked && b.nused < 2)
			{
				ShowMsg("At least two letters must be used on the first play.\n");
				return;
			}
			
			int pos1; // Needed for scoring algorithm.
			if( (pos1 = b.connected_check(turncount)) == -1)			
			{
				ShowMsg("When adding a word to the board it must be\n" + 
					    "connected to the word(s) already present.");
				return;
			}

			int uscore = b.score_calc(dir,pos1);
			txtscore.setText("Last Score: "+uscore);
			String pu = b.pused.toString();
			pu = pu.substring(0,pu.length()-1);

			sendscore(pu, uscore);
			return;
		}
	}

	//----------------------------------------------
	// Left pad number to fw spaces
	// Right pad number with post spaces.
	//----------------------------------------------
	public String spacepad(int fw,String s,int post)
	{
		if(s.length() >= fw)
			return ""+s+" ";

		StringBuffer sb = new StringBuffer("");
		for(int y=0;y<(fw - s.length());++y)
		{
			sb.append(" ");
		}
		sb.append(s);
		for(int y=0;y<post;++y)
		{
			sb.append(" ");
		}
		return sb.toString();
	}

	public void string2scores(String s)
	{
		int[] scores = {0,0,0,0,0};
		StringTokenizer st = new StringTokenizer(s,"X");
		int k = 1;
		StringBuffer sb = new StringBuffer("");
		while(st.hasMoreTokens())
		{
			s = st.nextToken();
			try
			{
				scores[k]+=Integer.parseInt(s);
			}
			catch(NumberFormatException e)
			{	
				ShowMsg("NumberFormatException while creating score string.");
			}
			if(k >= nplayers)
			{	
				sb.append(spacepad(3,s,1)+"\n");
				k = 1;
			}
			else
			{	
				sb.append(spacepad(3,s,1));
				++k;
			}
			
		}
		
		if(!sb.toString().endsWith("\n"))
			sb.append("\n");

		ta.setText(sb.toString());		

		if(nplayers == 2)
		{	
			ta.append("--- ---\n");
			ta.append(spacepad(3,""+scores[1],1)+
			              spacepad(3,""+scores[2],1));
		}
		if(nplayers == 3)
		{	
			ta.append("--- --- ---\n");
			ta.append(spacepad(3,""+scores[1],1)+
			              spacepad(3,""+scores[2],1)+
			              spacepad(3,""+scores[3],1));
		}
		if(nplayers == 4)
		{	
			ta.append("--- --- --- ---\n");
			ta.append(spacepad(3,""+scores[1],1)+
			              spacepad(3,""+scores[2],1)+
						  spacepad(3,""+scores[3],1)+
			              spacepad(3,""+scores[4],1));
		}
	}

	public Image GetStripImage(Image imageStrip, Rectangle r, Component com)
	{	int[] pixarray = new int[r.height * r.width];
		PixelGrabber pg = new PixelGrabber(imageStrip,r.x,r.y,r.width,r.height,pixarray,0,r.width);
		try
		{	pg.grabPixels();
		}
		catch(InterruptedException e)
		{
		}
		return createImage(new MemoryImageSource(r.width,r.height,pixarray,0,r.width));
	}

	String param2string(String paramname, String defstring)
	{	return (getParameter(paramname) == null) ? defstring : getParameter(paramname);
	}

	boolean param2bool(String paramname)
	{	return (atoi(getParameter(paramname)) == 1);
	}

	public int atoi(String num)
	{	int intv = 0;
		try
		{	intv = Integer.parseInt(num);
		}
		catch ( NumberFormatException nfe )
		{	intv = 0;
		}
		return intv;
	}

	//------------------------------------------------------------------------
	// Return a vector of String objects
	// Each element is a line from the return information.
	//------------------------------------------------------------------------
	public Vector CallCGI(String s)
	{
		Vector v = new Vector();
		boolean errflag = false;
		String urls = getDocumentBase().getProtocol() + 
			          "://" + 
					  getDocumentBase().getHost() + 
					  s;

		if(Javvle.debug == true)
		{	
			System.out.println(getDocumentBase().getProtocol());
			System.out.println(getDocumentBase().getHost());
		}

		URL url = null;
		try
		{	url = new URL(urls);
		}
		catch(MalformedURLException m)
		{	System.out.println("Malformed URL: " + urls);
			errflag = true;
			v.addElement(new String("Malformed URL ERROR"));
		}
		
		if(!errflag)
		{
			BufferedReader dis = null;
			String inputLine;
			try
			{	
				if(Javvle.debug == true)
				{	System.out.println("URL = " + urls);
				}

				dis = new BufferedReader(new InputStreamReader(url.openStream()));
				while ((inputLine = dis.readLine()) != null)
				{   
					if(Javvle.debug == true)
					{	System.out.println("inputLine:"+inputLine);
					}
					v.addElement(new String(inputLine));
				}
				dis.close();
			}
			catch(IOException e)
			{
				System.out.println("IOException occurred in CallCGI().");
				v.addElement(new String("IOException ERROR"));
			}
		}

		if(v.size() > 0)
		{
			String answer = (String)v.elementAt(0);
			if ("ERROR".equals(answer))
			{	
				if(v.size() > 1)
				{	
					ShowMsg("Game Terminated: " + (String)v.elementAt(1),400);
					System.out.println("Game Terminated: " + (String)v.elementAt(1));
					if(thread != null)
						thread.stop();
					if(chatbox.runner != null)
						chatbox.runner.stop();
					System.gc();
					System.exit(1);
				}
			}
		}
		return v;
	}

	public void run()
	{
		int counter = 0;

		while (true)
		{
			try
			{
				if(polling && counter >= 10)
				{
					counter = 0;

					if(!gamestarted)
					{
						//--------------------------------------------------------------
						//
						// Poll letter turn to get initial letters.
						//
						//--------------------------------------------------------------
						Vector v =
						CallCGI(cgiscript + 
						       "?COMMAND=POLLLETTERTURN" + 
						       "&GAMENAME=" + gamename);

						if(v.size() > 0)
						{	
							int letterturn = 5;

							String answer = (String)v.elementAt(0);
							if("STATUS".equals(answer))
							{	if(v.size() > 1)
									ShowMsg("Status Message: " + (String)v.elementAt(1),300);
							}
							else 
							{
								try
								{
									letterturn = Integer.parseInt((String)v.elementAt(0));
								}
								catch(NumberFormatException nfe)
								{
									ShowMsg("Number Format Exception\nduring game start.",300);
									break;
								}
							}
							
							if(playernumber == letterturn)
							{
								v =	CallCGI(cgiscript + 
						                    "?COMMAND=INITLETTERS" + 
						                    "&GAMENAME=" + gamename +
										    "&PLAYERNUMBER=" + playernumber);

								if(v.size() > 0)
								{	
									answer = (String)v.elementAt(0);
									if("STATUS".equals(answer))
									{	
										if(v.size() > 1)
											ShowMsg("Status Message: " + (String)v.elementAt(1),300);
									}
									else 
									{
										lh.initletters((String)v.elementAt(0));
										gamestarted = true;
										currentplayer = 1;
									}
								}
							}
						}
					}


					if(gamestarted)
					{

						Vector v =
						CallCGI(cgiscript + 
						       "?COMMAND=POLLTURN" + 
							   "&GAMENAME=" + gamename +
						       "&CURRENTPLAYERNUMBER=" + currentplayer);

						if(v.size() > 0)
						{	
							
							int challengeflag = 0;

							String answer = (String)v.elementAt(0);
							if ("STATUS".equals(answer))
							{	; // Do nothing...
							}
							else if ("GAMESTATE".equals(answer))
							{	
								if(v.size() > 3)
								{	
									b.string2board((String)v.elementAt(1));
									string2scores((String)v.elementAt(2));
									try
									{
										currentplayer = Integer.parseInt((String)v.elementAt(3));
									}
									catch(NumberFormatException nfe)
									{
										ShowMsg("Number Format Exception\nduring gamestate read.",300);
										break;
									}

									if(currentplayer > 100 && currentplayer < 110)
									{	currentplayer -= 100;
										v = CallCGI(cgiscript + "?COMMAND=GETFINALS&GAMENAME=" + gamename);
										String finalscores;
										if(v.size() > 0)
										{
											String ans = (String)v.elementAt(0);
											if("GAME-TIED".equals(ans))
											{	if(v.size() > 2)
												{	finalscores = 
													(String)v.elementAt(1) + "\n" +
													(String)v.elementAt(2);
													ShowMsg("Game Over\n" + finalscores);
												}
											}
											else
											{	finalscores = (String)v.elementAt(0);
												ShowMsg("Game Over, Winner is Player " + currentplayer + "\n" +
											            "Final Scores: " + finalscores);
											}
										}
										tada.play();
										break;
									}
									
									if(currentplayer > 200)
									{
										polling = true;

										challengeflag = currentplayer;
										int h = currentplayer/100;
										int t = currentplayer - (h * 100);
										challenger = t/10;
										challenged = t%10;
										
										if(challenged == nplayers)
											currentplayer = 1;
										else
											currentplayer = challenged + 1;

									}

									if(challengeflag > 200 && challengeflag < 300 && challengeinprogress == false)
									{	// Challenge just started.

										challengeinprogress = true;
										oktochallenge = false;
										challenge.setEnabled(false);


										if(playernumber != challenged)
										{
											ShowMsg("Player " + challenger + " has challenged Player " + challenged + "'s last play");

										}
										else
										{
											cbox = new ChallengeBox(frame,this);
											cbox.show();
										}

									}
									else if(challengeflag > 300 && challengeflag < 400 && challengeinprogress)
									{	
										// Challenged words are OK
										if(playernumber == challenger)
										{
											skipturnflag = true;

											CallCGI(cgiscript + 
										   "?COMMAND=SETTURN" +
										   "&GAMENAME=" + gamename +
										   "&CURRENTPLAYERNUMBER=" + currentplayer);
										}
										challengeinprogress = false;
									}
									else if(challengeflag > 400 && challengeinprogress)
									{	
										// Challenged words are not OK
										if(playernumber == challenged)
										{
											CallCGI(cgiscript + 
										   "?COMMAND=SETTURN" +
										   "&GAMENAME=" + gamename +
										   "&CURRENTPLAYERNUMBER=" + currentplayer);
										}
										challengeinprogress = false;
									}
								}
								
								//---------------------------------------------------------------------
								// Don't turn on challenge for the person who just sent the play.
								//---------------------------------------------------------------------
								int prevplayer = (currentplayer == 1) ? nplayers : currentplayer - 1;
								if(playernumber != prevplayer)
								{
									oktochallenge = true;
									challengetime = 30;
									challenge.setEnabled(true);
								}

								if(challengeflag > 300 && !challengeinprogress)
								{
									// force a game state reload.
									currentplayer = 0;
								}
							}
							else 
							{
								if(challengeinprogress)
								{
									challengeinprogress = false;
									polling = true;

								}

								try
								{
									currentplayer = Integer.parseInt((String)v.elementAt(0));
								}
								catch(NumberFormatException nfe)
								{
									ShowMsg("Number Format Exception\nduring current player assign.",300);
									break;
								}
							}
							
							sc1.setBackground((currentplayer == 1) ?  Color.green: Color.lightGray);
							sc2.setBackground((currentplayer == 2) ?  Color.green: Color.lightGray);
							sc3.setBackground((currentplayer == 3) ?  Color.green: Color.lightGray);
							sc4.setBackground((currentplayer == 4) ?  Color.green: Color.lightGray);

							if(challengeinprogress)
							{
								status1.setText("");
								status1.setBackground(Color.white);
								if(msgff)
									status1.setText("Challenge occurred.");
								else
									status1.setText("Challenge ongoing...");
								msgff = !msgff;
							}
							else if(playernumber == currentplayer)
							{
								if(skipturnflag)
								{	skipturnflag = false;
									sendscore("",0);
								}
								else
								{	winding.play();
									timeleft = 180;
									timing = true;
									timer.setBackground(Color.green);
									status1.setBackground(Color.green);
									status1.setText("Its your turn.");
									submit.setEnabled(true);
									exchange.setEnabled(true);
									polling = false;
								}
							}
							else
							{
								timing = false;
								status1.setText("");
								status1.setBackground(Color.white);
								if(msgff)
									status1.setText("Waiting for turn.");
								else
									status1.setText("Still Waiting...");
								msgff = !msgff;
							}
						}
					}
				} // End of if(polling && counter >= 10)

				Thread.sleep(1000);
				++counter;

				//----------------------------------------------------------------------------
				// Can only challenge within 30 seconds of getting the game state.
				//----------------------------------------------------------------------------
				if(oktochallenge)
				{
					--challengetime;
					if(challengetime <= 0)
					{	challenge.setEnabled(false);
						oktochallenge = false;	
					}
				}
				
				if(timing)
				{
					String ss;
					int m = timeleft/60;
					int s = timeleft%60;
					if(s < 10)
						ss = "0" + s;
					else
						ss = "" + s;
					
					if(timeleft <= 0)
					{	status1.setBackground(Color.red);
						status1.setText("Turn over!");
						timer.setBackground(Color.red);
						timer.setText("Time is up!");
						coocoo.play();
					}
					else
					{	coocoo.stop();
						timer.setText("Time Left: " + m + ":" + ss);
					}
					
					if(!challengeinprogress)
						--timeleft;
				}
				else
				{
					timer.setBackground(Color.white);
					timer.setText("");
					coocoo.stop();
				}
			}
			catch (InterruptedException e)
			{
				System.out.println("Interrupted Exception...");
			}
		}
	}
}


class GameStarter extends Dialog implements ActionListener, WindowListener
{
	// WindowListener
	public void windowActivated(WindowEvent we) {} 
	public void windowClosed(WindowEvent we) {}
	public void windowClosing(WindowEvent we)
	{
		dispose();
	}
	public void windowDeactivated(WindowEvent we) {} 
	public void windowDeiconified(WindowEvent we) {} 
	public void windowIconified(WindowEvent we) {}
	public void windowOpened(WindowEvent we) {} 

	List games;
	Button start;
	Button join;
	Button scan;
	TextField gamename;
	TextField uname;
	Javvle applet;
	Checkbox c2;
	Checkbox c3;
	Checkbox c4;
		
	public GameStarter(Frame app, Javvle pp)
	{  
		super(app,"Select or Start a New Game",false);
		super.setResizable(false);	
		super.setBounds(0,0,280,300);	

		addWindowListener(this);

		applet = pp;
		setLayout(new BorderLayout());

		Panel p = new Panel();
		p.setLayout(new BorderLayout());
		p.add("West",new Label("Game Name ",Label.RIGHT));
		gamename = new TextField();
		p.add("Center",gamename);

		Panel w = new Panel();
		w.setLayout(new BorderLayout());
		w.add("West",new Label("  User Name ",Label.RIGHT));
		uname = new TextField();
		w.add("Center",uname);

		Panel q = new Panel();
		q.setLayout(new GridLayout(5,1,0,2));

		start = new Button("Start New Game");
		start.addActionListener(this);
		join = new Button("Join Game");
		join.addActionListener(this);
		scan = new Button("Get Game List");
		scan.addActionListener(this);

		Panel z = new Panel();
		z.setLayout(new FlowLayout(FlowLayout.CENTER,5,0));
		CheckboxGroup cbg = new CheckboxGroup();
		c2 = new Checkbox("2",cbg,true);
		c3 = new Checkbox("3",cbg,false);
		c4 = new Checkbox("4",cbg,false);
		z.add(new Label("Players",Label.CENTER));
		z.add(c2);
		z.add(c3);
		z.add(c4);

		Panel k = new Panel();
		k.setLayout(new GridLayout(1,2,2,2));
		k.add(scan);
		k.add(join);

		q.add(k);
		q.add(w);
		q.add(start);
		q.add(p);
		q.add(z);

		games = new List();
		add("Center",games);
		games.setFont(new Font("Courier",Font.PLAIN,11));
		add("South",q);
	}


	public void actionPerformed(ActionEvent evt)
	{	
		String arg = evt.getActionCommand();
		Object target = evt.getSource();

		if(target == join)
		{	
			applet.username = uname.getText();
			if(applet.username.length() < 5 || applet.username.length() > 15)
			{
				applet.ShowMsg("User Name must be between\n5 and 15 characters long.",300);
				return;
			}

			applet.gamename = games.getSelectedItem();
			if(applet.gamename == null)
			{
				applet.ShowMsg("You must select a game to Join.\nOr start a new game.",300);
				return;
			}
			
			StringTokenizer st = new StringTokenizer(applet.gamename,"|");
			if(st.hasMoreTokens())
				st.nextToken(); // Skip first string its the display version.
			if(st.hasMoreTokens())
				applet.gamename = st.nextToken(); // gamename.
			if(st.hasMoreTokens())
			{	
				try
				{
					applet.nplayers = Integer.parseInt(st.nextToken()); // nplayers.
				}
				catch(NumberFormatException nfe)
				{
					applet.ShowMsg("Number Format Exception\ngetting game from list.",300);
					return;	
				}
			}

			Vector v = 
			applet.CallCGI(applet.cgiscript + 
			               "?COMMAND=JOINGAME" +
						   "&GAMENAME=" + applet.gamename +
						   "&USERNAME=" + applet.username +
						   "&NPLAYERS=" + applet.nplayers );


			String answer = (String)v.elementAt(0);
			if("STATUS".equals(answer))
			{
				applet.ShowMsg("Status Message: " + (String)v.elementAt(1),300);
			}
			else if ("POLLLETTERTURN".equals(answer))
			{
				try
				{
					if(v.size() > 0)
					{	applet.ShowMsg("Game ready. Waiting for players...",300);
						applet.playernumber = Integer.parseInt((String)v.elementAt(1));
						applet.status2.setText("You are player " + applet.playernumber);
						applet.startgame.setEnabled(false);
						applet.startchat.setEnabled(true);
						applet.polling = true;
					}
				}
				catch(NumberFormatException nfe)
				{
					applet.ShowMsg("Number Format Exception\ngetting game from list.",300);
					return;	
				}
				dispose();
			}
		}

		if(target == scan)
		{
			games.removeAll();
			Vector v = applet.CallCGI(applet.cgiscript + "?COMMAND=GETGAMELIST");
			String gname="";
			String nplayers="";
			for(int i=0; i<v.size(); ++i)
			{
				StringBuffer sb = new StringBuffer("");
				StringTokenizer st = new StringTokenizer((String)v.elementAt(i),"|");
				if(st.hasMoreTokens())
				{
					// Space pad the name on the right.
					gname = st.nextToken();
					sb.append(gname);
					for(int k=0;k<16-gname.length();++k)
					{	sb.append(" ");
					}
					sb.append(" (");
				}
				if(st.hasMoreTokens())
				{	nplayers = st.nextToken();
					sb.append(nplayers + " players, ");
				}
				if(st.hasMoreTokens())
				{	sb.append(st.nextToken() + " needed)");
				}
				sb.append("  |"+gname+"|"+nplayers);
				games.addItem(sb.toString());
			}
			return;
		}

		if(target == start)
		{	
			applet.username = uname.getText();
			if(applet.username.length() < 5 || applet.username.length() > 15)
			{
				applet.ShowMsg("User Name must be between\n5 and 15 characters long.",300);
				return;
			}
			applet.gamename = gamename.getText();
			if(applet.gamename.length() < 5 || applet.gamename.length() > 15)
			{
				applet.ShowMsg("Game Name must be between\n5 and 15 characters long.",300);
				return;
			}
			applet.nplayers = c2.getState() ? 2 : (c3.getState() ? 3 : 4);
			
			Vector v = 
			applet.CallCGI(applet.cgiscript + 
			               "?COMMAND=STARTGAME" +
						   "&GAMENAME=" + applet.gamename +
						   "&USERNAME=" + applet.username +
						   "&NPLAYERS=" + applet.nplayers );

			String answer = (String)v.elementAt(0);
			if("STATUS".equals(answer))
			{
				applet.ShowMsg("Status Message: " + (String)v.elementAt(1),300);
			}
			else if ("POLLLETTERTURN".equals(answer))
			{
				applet.ShowMsg("Game ready. Waiting for players...",300);
				applet.polling = true;
				applet.playernumber = 1;
				applet.status2.setText("You are player 1");
				applet.startgame.setEnabled(false);
				applet.startchat.setEnabled(true);
				dispose();
			}
		}
	}
}


class ChatBox extends Dialog implements Runnable, ActionListener, WindowListener, KeyListener
{
	// WindowListener
	public void windowActivated(WindowEvent we) {} 
	public void windowClosed(WindowEvent we) {}
	public void windowClosing(WindowEvent we)
	{
		dispose();
	}
	public void windowDeactivated(WindowEvent we) {} 
	public void windowDeiconified(WindowEvent we) {} 
	public void windowIconified(WindowEvent we) {}
	public void windowOpened(WindowEvent we) {}     

	Button StopChat,SendChat,ClearChat;
	TextField Text2Send;
	TextArea Text2Show;
	Javvle applet;
	Thread runner;
	String scanfile;
	String sendfile;
	int cycles = 0;
	String timelastwritten = "0";

	public ChatBox(Frame app, Javvle p)
	{  
		super(app,"Chat Box",false);
		super.setResizable(false);	
		super.setBounds(0,0,400,200);

		addWindowListener(this);
      
		applet = p;
		setLayout(new BorderLayout());
		StopChat = new Button("Quit Chat");
		StopChat.addActionListener(this);
		SendChat = new Button("Send Chat");
		SendChat.addActionListener(this);
		ClearChat = new Button("Clear Chat");
		ClearChat.addActionListener(this);
		Text2Send = new TextField("Type here and press 'Enter' to send message.",50);
		Text2Send.addKeyListener(this);
		Text2Show = new TextArea(30,50);
		Text2Show.setEditable(false);	
		Text2Show.setBackground(Color.white);	

		add("North",Text2Send);
		add("Center",Text2Show);

		Panel pp = new Panel();
		pp.setLayout(new GridLayout(1,3));
		pp.add(StopChat);
		pp.add(SendChat);
		pp.add(ClearChat);
		add("South",pp);
      
		if(runner == null)
		{	
			runner = new Thread(this);
			runner.start();
		}
	}

	public void sendchat()
	{
		String words = applet.username + " sent at: " + (new Date().toString()) + "%0A" + Text2Send.getText() + "%0A%0A";
		Text2Send.selectAll();
		Text2Send.requestFocus();
		Vector v = 
		applet.CallCGI(applet.cgiscript + 
			       "?COMMAND=WRITECHAT" +
				   "&GAMENAME=" + applet.gamename +
				   "&MESSAGE=" + words);
	}


	public void keyReleased(KeyEvent ke){}
	public void keyTyped(KeyEvent ke) {}
	public void keyPressed(KeyEvent ke)
	{
		if(ke.getKeyCode() == KeyEvent.VK_ENTER)
			sendchat();
	}

	public void actionPerformed(ActionEvent evt)
	{	
		String arg = evt.getActionCommand();
		Object target = evt.getSource();

		if(target == StopChat)
			runner = null;
		else if(target == SendChat)
			sendchat();
		else if(target == ClearChat)
			Text2Show.setText("");
	}

	public void run()
	{  
		Text2Send.requestFocus();
		Text2Send.selectAll();
		
		while(runner != null)
		{   
			try
			{	Thread.sleep(100);
			}
			catch(InterruptedException e)
			{
			}

			++cycles;
			if(cycles < 100)
			{   continue;
			}
			cycles = 0;

			// Pass to here every 10 seconds.
			Vector v = 
			applet.CallCGI(applet.cgiscript + 
					   "?COMMAND=READCHAT" +
					   "&GAMENAME=" + applet.gamename +
			           "&TIMELASTWRITTEN=" + timelastwritten);
			
			String answer = (String)v.elementAt(0);
			if("STATUS".equals(answer))
			{
				; // Do nothing...
			}
			else if ("LASTREAD".equals(answer))
			{
				applet.chirp.play();
				timelastwritten = (String)v.elementAt(1);
				Text2Show.setText("");
				
				for(int z=0; z<v.size(); ++z)
				{
					if(z > 1)
						Text2Show.append((String)v.elementAt(z)+"\n");
				}
				Text2Show.append("\n");
			}
		}
		applet.chatbox = null;
		dispose();
	}
}
