/************************************************************************
 * This library is free software; you can redistribute it and/or        *
 * modify it under the terms of the GNU Library General Public          *
 * License as published by the Free Software Foundation; either         *
 * version 2 of the License, or (at your option) any later version.     *
 *                                                                      *
 * This library is distributed in the hope that it will be useful,      *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of       *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    *
 * Library General Public License for more details.                     *
 *                                                                      *
 * You should have received a copy of the GNU Library General Public    *
 * License along with this library; if not, write to the                *
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,         *
 * Boston, MA  02111-1307, USA.                                         *
 *                                                                      *
 * The GNU Library General Public License file is included in           *
 * Online-Documentation                                                 *
 *                                                                      *
 * GNU's Homepage - http://www.gnu.org/                                 *
 *                                                                      *
 * Questions & comments referring to Hexi: please contact us:           *
 *                                                                      *
 * eMail:                                                               *
 *      ramsey@dbai.tuwien.ac.at                                        *
 * WWW:                                                                 *
 *      http://www.dbai.tuwien.ac.at/proj/ramsey/                       *
 *                                                                      *
 * (c) Wolfgang Slany 1987, Bidan Zhu 1996, Andreas Beer 1999           *
 * Hexi.java                   Version 2.6                   18.08.1999 *
 ***********************************************************************/

import java.io.*;
import java.awt.*;
import java.net.*;
import java.awt.image.*;
import java.applet.Applet;

/************************************************************************************
			 HEXI -- A Game Applet
In this game, a position is represented by a 15-byte array, each element with
value 0 indicating unoccupied, 1 indicating an edge drawn by the first player
and 2 for an edge of the second player. All the possible game  positions  are
read from files on the server side and saved in hash tables.  Array winner[]
holds who can win absolutely from that position, hashkey1 and hashkey2 hold the
packed key for each position.
*************************************************************************************/

public class Hexi extends Applet implements Runnable
{
	/** Play status WIN LOSS and PLAY **/
	static final byte WIN	= 1;
	static final byte LOSS	= 2;
	static final byte PLAY	= 3;

	/** Learn factor **/
	static final byte LEARN_FACTOR	= 14;

	/** Color for background and edges **/
	static final Color RED   = new Color(200, 10,   10);
	static final Color GREEN = new Color( 10, 200,  10);
	static final Color GRAY  = new Color(200, 200, 200);

	/** Flag for if print debug information**/
	static final boolean DebugFlag = false;

	/** Diameter for Ball_Image **/
	static final int diameter = 40;

 	/**	Hash table for game positions. The data are read from file on server.**/
	public byte winner[][]   = new byte[3][];
	public byte hashkey1[][] = new byte[3][4096];
 	public byte hashkey2[][] = new byte[3][8192];

	Thread animatorThread = null;
	LoadDataThread Load_Data_Thread = null;
 	HexiClient Hexi_Client = null;
	Image Ball_Image;
	MediaTracker tracker = new MediaTracker(this);
	Dimension offDimension = new Dimension();
	Image offImage;
	Graphics offGraphics;

	int  Key_To_Transfer[];
	int xoff, yoff;
	int node[][] = new int[6][2];
 	byte state, selected = -1, step;
	byte triangle_node1,triangle_node2,triangle_node3;
	byte HEXI=2,PLAYER=1,LEVEL=2,HExi,LEvel;
	byte position[]	= new byte[15];
	byte route[]	= new byte[15];
	int  to_learn[]	= new int[7];
	byte to_learn_Nr = 0;
	byte Computed_Move[] = new byte[15];
	byte Computed_Move_Nr;

	boolean ReadTreeReady1 = false,ReadTreeReady2 = false, Player_Moved=false;
	boolean ShowDialog = true, Allow_More = false, Allow_Shaking = true;
	boolean without_help, LoadImageReady, DemoRunning;

	/* If for some reason the Hexi-applet had to regenerate the database(es) - there was an error while downloading them from the server - then it is not
	allowed to transmit anything (learning info or highscore) back to the server. The learning info would be inconsistent with the old one, the highscore
	would be invalid, since HEXI had not played to its full potential. Therefore the flag "can_reply" has to be set. */
	boolean can_reply;

	HexiUtility Utility	= new HexiUtility(this);
	HexiConsole controlbox;
	HallofFame fame;
	Container con;
	long PlayTime, BeginTime;

	/************************************************************************************
	Initialization of the applet: start a thread to load the game tree, load image
 	and get controlbox ready for showing
	************************************************************************************/

	public void init()
	{
 		debug("Hexi init");
		winner[0]   = new byte[4096];
		winner[1]   = new byte[8192];
		winner[2]   = new byte[8192];
		Ball_Image = getImage(getCodeBase(), "ball.gif");
		tracker.addImage(Ball_Image, 1);
		try
		{
 			tracker.waitForID(1);
		}
		catch (InterruptedException e)
		{
			debug ("Error at loading image!");
		}
		Load_Data_Thread = new LoadDataThread(this);
		Load_Data_Thread.start();
		can_reply=true;
		con = getParent();
 		while (! (con instanceof Frame))
		{
 			try
			{
 				con = con.getParent ();
 			}
			catch (NullPointerException e)
			{
				System.out.println ("no Frame");
			}
		}
		controlbox = new HexiConsole(this,(Frame)con);
		controlbox.show();
		controlbox.reshape(520,150,250,400);
       		controlbox.hide();
		restartGame();
		debug("Init ready");
   	} // init

	/************************************************************************************
	Two ways to eliminate flashing -- overriding the update() and double buffering.
	Overriding the update() methode is necessary because it's the only way to prevent
	the entire background of the Component from being cleared every time the Component
	is drawn. Double buffering involves performing multiple graphics operations on an
	undisplayed graphics buffer, and then displaying the resulting image onscreen.
	Double buffering prevents incomplete images from being drawn to the screen.
	************************************************************************************/

	public void update (Graphics g)
	{
		Dimension d = size();
		if (offGraphics==null || d.width!=offDimension.width || d.height!=offDimension.height)
		{
			FixNodePosition();
  			offDimension = d;
			offImage = createImage(d.width, d.height);
			offGraphics  = offImage.getGraphics();
			offGraphics.setFont(new Font("TimesRoman",Font.BOLD,22));
			drawFrame(offGraphics,0,0,d.width,d.height,5);
		}
		paint_hexi(); // Call another user-defined paint procedure
	} // Update End

 	/************************************************************************************
	Even though our implementation of update() doesn't  call paint(), we must still
	implement a paint() method. paint() is called by   AWT when the Component first
	appearing onscreen, or the Component being uncovered by another window (an area
	that the Component displays is suddenly revealed after being hidden).   The AWT
	calls paint() directly, without calling update().An easy way to implement paint()
	is to simply have it call update().
 	*************************************************************************************/

	public void paint(Graphics g)
	{
		update (g);
	}

	/************************************************************************************
	Since the AWT orders drawing requests by making them run in a single thread, the
	drawing is actually asynchronic. But our animation needs it to be synchronic.
	Then, instead of using repaint() to request to be scheduled for drawing, we call
	our self-defined paint_hexi() method.
	************************************************************************************/

	public void paint_hexi()
	{
		int i;
		Dimension d = size();
		offGraphics.setColor(GRAY);
		offGraphics.fill3DRect(5,5,d.width-10,d.height-10,true);
		offGraphics.fill3DRect(10,10,70,33,true);
		offGraphics.setColor(GRAY.darker());
		offGraphics.drawString("HEXI",17,34);
		if (Allow_More && Player_Moved)
		{
			offGraphics.setColor(GRAY);
			offGraphics.fill3DRect(d.width-80,d.height-45,70,35,true);
			offGraphics.setColor(GRAY.darker());
			offGraphics.drawString("OK",d.width-65,d.height-21);
		}
		for (i=0; i<15; i++) drawLine (offGraphics,i);
		for (i=0; i<6; i++) offGraphics.drawImage(Ball_Image,node[i][0]-diameter/2,node[i][1]-diameter/2,diameter,diameter,this);
		if (getGraphics() != null) getGraphics().drawImage (offImage,0,0,this);
	}

	public void shaking()
	{
		int n1,n2;
		double xx1,yy1,xx2,yy2,dx,dy;
		byte  weight[] = new byte[6];
		weight = Utility.weigh(position);
		do
		{
			n1 = (int)(Math.random()*6);
			n2 = (int)(Math.random()*6);
		} while ( n1 == n2 || weight[n1] == weight[n2]);
		int x1 = node[n1][0];
		int y1 = node[n1][1];
		int x2 = node[n2][0];
		int y2 = node[n2][1];
		xx1 = x1;
		yy1 = y1;
		xx2 = x2;
		yy2 = y2;
		dx = (x2 - x1)/20.0;
		dy = (y2 - y1)/20.0;
		for (int i=1;i<20;i++)
		{
			long startTime = System.currentTimeMillis();
			xx1 += dx;
			yy1 += dy;
			xx2 -= dx;
			yy2 -= dy;
			node[n1][0] = (int)xx1;
			node[n1][1] = (int)yy1;
			node[n2][0] = (int)xx2;
			node[n2][1] = (int)yy2;
 			paint_hexi(); //Delay depending on how far we are behind
			try
			{
				startTime += 10*Math.abs(10-i);
				Thread.sleep(Math.max(0, startTime-System.currentTimeMillis()));
			}
			catch (InterruptedException e)
			{
				break;
			}
		}
 		node[n1][0] = x2;
		node[n1][1] = y2;
		node[n2][0] = x1;
		node[n2][1] = y1;
		paint_hexi();
	}

	/************************************************************************************
	Respond to user actions on controls.    The action() method is an speciall event
	handling method. Only basic control components -- Button, Checkbox, Choice, List,
	MenuItem, and TextField objects -- produce action events.    They do so when the
	user indicates somehow that the control should perform an action.    For example,
	when the user clicks a button, an action  event is generated. By implementing the
	action() method, you can react to user actions on controls without worrying about
	the low-level events, such as key presses and mouse clicks,that caused the action.
`	************************************************************************************/

	public boolean action(Event evt, Object obj)
	{
		if (obj.equals("Demo"))
		{
			controlbox.status_label.setText(" A demo run of play");
			controlbox.Button_NewGame.disable();
			controlbox.Button_Undo.disable();
			controlbox.Button_Hint.disable();
			controlbox.Button_PHint.disable();
			controlbox.Button_Demo.disable();
			InitializeVariable ();
			DemoRunning = true;
			RunDemo();
 			DemoRunning = false;
			controlbox.Button_NewGame.enable();
			controlbox.Button_Demo.enable();
		}
		else if (obj.equals("New Game"))
		{
			restartGame();
		}
		else if (obj.equals("Undo"))
		{
			if (!Allow_More)
			{
				if ( state == WIN )
				{
					animatorThread = null;
					position[route[step--]] = 0;
 					state = PLAY;
					controlbox.Button_Hint.enable(); controlbox.Button_PHint.enable();
				}
				else if (state == PLAY)
				{
					position[route[step--]] = 0; position[route[step--]] = 0;
					if (to_learn_Nr > 0) to_learn_Nr--;
					if (step < 1) controlbox.Button_Undo.disable();
					controlbox.Button_Hint.enable(); controlbox.Button_PHint.enable();
				}
				controlbox.status_label.setText((step+2)+" : your turn please!");
				paint_hexi();
			}
			without_help = false;
		}
		else if (obj.equals("Show Hint") || obj.equals("Play Hint"))
		{
			if ( state == PLAY )
			{
				controlbox.Button_Hint.disable(); controlbox.Button_PHint.disable();
				if (compute_best_move(PLAYER,HEXI) != -1)
				{
					byte MoveNr = 0;
					while (MoveNr < Computed_Move_Nr)
					{
						byte hint = Computed_Move[MoveNr];
						if (obj.equals("Play Hint")) playerMove(hint);
						else
						{
							position[hint] = PLAYER;
							StretchLine(hint);
							position[hint] = 0;
							selected = -1;
							try
							{
								Thread.sleep(300);
							}
							catch ( InterruptedException e) {};
						}
						MoveNr++;
					}
					if (obj.equals("Show Hint")) paint_hexi();
					controlbox.Button_Hint.enable();
					controlbox.Button_PHint.enable();
					if (obj.equals("Play Hint") && Allow_More)
					{
						Player_Moved = false;
						Hexi_Move();
					}
				}
				else controlbox.status_label.setText ("Sorry, you must give up !!");
			}
			without_help = false;
		}
		else if ( evt.target == controlbox.user )
		{
			PLAYER = 1;
			HEXI = 2;
			restartGame();
		}
		else if ( evt.target == controlbox.computer )
		{
			PLAYER = 2;
			HEXI = 1;
			restartGame();
 		}
 		else if ( evt.target == controlbox.allow_more )
		{
			if (ReadTreeReady1)
			{
				Allow_More = controlbox.allow_more.getState();
 				if (Allow_More)
				{
					controlbox.Button_Undo.hide();
					controlbox.Button_Demo.hide();
				}
				else
				{
					controlbox.Button_Undo.show();
					controlbox.Button_Demo.show();
				}
			}
			else
			{
				controlbox.status_label.setText ("Sorry, I am loading the data for multimove now!!");
				controlbox.allow_more.setState(false);
			}
		}
		else if ( evt.target == controlbox.allow_shaking )
		{
			Allow_Shaking = controlbox.allow_shaking.getState();
 		}
		return true;
	} /** Respond to mouseclick **/

	public boolean mouseUp(Event evt, int x, int y)
	{
  		if ( state == PLAY )
		{
			byte i, k;

			for (i=0;i<6;i++) if ( x>node[i][0]-diameter/2 && x<node[i][0]+diameter/2 && y>node[i][1]-diameter/2 && y<node[i][1]+diameter/2 )
			{
       				if ( selected == -1 )
				{
					selected = i;
					if (animatorThread == null)
					{
						animatorThread = new Thread(this);
						animatorThread.start();
					}
				}
				else
				{
					if ( i != selected )
					{
						k  = Utility.pt_to_edge[selected][i];
 						if ( position[k] == 0  )
						{
							playerMove(k);
							return true;
						}
						else
						{
							controlbox.status_label.setText("Sorry ! That's an illegal stroke ");
						}
					}
					animatorThread = null; selected = -1;
				}
				return true;
			}
			for ( i=14;i>-1;i-- )
			{
				int t;
				int x1=	node[Utility.ptc1[i]][0];
				int y1= node[Utility.ptc1[i]][1];
				int x2= node[Utility.ptc2[i]][0];
				int y2= node[Utility.ptc2[i]][1];
				if (Math.abs(x2-x1) < Math.abs(y2-y1))
				{
					if (y >=Math.min(y1, y2) && y <= Math.max(y1, y2))
					{
						t = x1 + (x2 - x1) * (y - y1) / (y2 - y1);
						if (t - 4 <= x && x <= t + 4)
						{
							if ( position[i] == 0 )	playerMove (i);
							return true;
						}
					}
				}
				else
				{
					if (x >= Math.min(x1, x2) && x <= Math.max(x1, x2))
					{
						t = y1 + (y2 - y1) * (x - x1) / (x2 - x1);
						if (t - 4 <= y && y <= t + 4)
						{
							if ( position[i] == 0 ) playerMove (i);
							return true;
						}
					}
				}
			}
			if (Allow_More && x > size().width-80 && x < size().width-10 && y > size().height-45 && y < size().height-10)
			{
				if (Player_Moved)
				{
					Player_Moved = false;
					paint_hexi();
					Hexi_Move();
				}
			}
		}  // if state == PLAY
		else if (controlbox.isShowing())
		{
			restartGame();
			return true;
		}
		if (!controlbox.isShowing())
		{
			controlbox.show();
		}
		return true;
	} // mouseUp

	public void StretchLine (byte edge)
	{
		byte p1 = Utility.ptc1[edge];
		byte p2 = Utility.ptc2[edge];
		int dx,dy,ddx,ddy;
		int x1 = node[p1][0];
		int y1 = node[p1][1];
		int x2 = node[p2][0];
		int y2 = node[p2][1];
		dx = (node[p2][0]-node[p1][0])/25;
		dy = (node[p2][1]-node[p1][1])/25;
		node[p1][0] += dx;
		node[p1][1] += dy;
		node[p2][0] -= dx;
		node[p2][1] -= dy;

		paint_hexi();
		try
		{
			Thread.sleep(300);
		}
		catch ( InterruptedException e ) {};
		node[p1][0] = x1;
		node[p1][1] = y1;
		node[p2][0] = x2;
		node[p2][1] = y2;
		paint_hexi();
	}

	public void playerMove (byte edge)
	{
		byte k;

		if ( step == -1 || step == 0 )
		{
 			controlbox.Button_Undo.enable();
 			controlbox.allow_more.hide();
			controlbox.allow_shaking.hide();
		}
		else
		{
			long t = System.currentTimeMillis() - BeginTime;
			PlayTime += t;
			debug("Time "+t+"   Till now : "+PlayTime);
		}
		if (Allow_More) Player_Moved = true;
		if (animatorThread != null) animatorThread = null;
		if (selected != -1) selected = -1;
		position[edge] = PLAYER;
		route[++step]  = edge;
 		StretchLine(edge);
		if ( (k = Utility.is_triangle(position,edge,PLAYER)) != -1)
		{
			Player_Moved = false;
			triangle_node1 = Utility.ptc1[edge];
			triangle_node2 = Utility.ptc2[edge];
			triangle_node3 = k;
			controlbox.Button_Hint.disable(); controlbox.Button_PHint.disable();
			state = WIN;
 			controlbox.status_label.setText(" Sorry! You lose! Again?");
			if (animatorThread == null)
			{
				animatorThread = new Thread(this);
				animatorThread.start();
			}
			paint_hexi();
		}
		else if (!Allow_More) Hexi_Move();
		else controlbox.status_label.setText(" Click OK if you are ready!");
	}

	public void Hexi_Move()
	{
		byte k = compute_best_move(HEXI,PLAYER);
		if (k != -1)
		{
			byte MoveNr = 0;
			while (MoveNr < Computed_Move_Nr)
			{
				k = Computed_Move[MoveNr];
				position[k] = HEXI; StretchLine(k);
				route[++step] = k;
 				MoveNr++;
			}
			for (k=0;k<15;k++) if (position[k] == 0 && Utility.is_triangle(position,k,PLAYER) == -1) break;
 			if (k == 15)
			{
				controlbox.status_label.setText(" Sorry, you must give up!");
				if (without_help && HEXI == 1 && LEVEL == 2) learn(true);
			}
			else
			{
				controlbox.status_label.setText((step+2)+" : your turn please!");
				if (Allow_Shaking)
				{
					byte times = (byte)(Math.random()*10);
					if (times <= 6) times = 1;
					else if (times > 6 && times < 9) times = 2;
					else times = 3;
					for( int i=0;i<times;i++)
					{
						try
						{
							Thread.sleep(300);
						}
						catch ( InterruptedException e) {};
						shaking();
					}
				}
			}
			paint_hexi();
		}
		else
		{
			state = LOSS;
			controlbox.Button_Undo.disable();
 			controlbox.Button_Hint.disable();
 			controlbox.Button_PHint.disable();
			controlbox.status_label.setText(" You win! Play again ?");
			if (without_help && Allow_Shaking && can_reply) // Highscore only accepted without help (Undo or Hints), with activated shaking and if
			{						// the full database was available.
				debug("Time "+PlayTime);
				fame = new HallofFame(this,(Frame)con,PlayTime);
				fame.reshape(200,350,400,300);
				fame.show();
			}
			if (without_help && HEXI == 1 && LEVEL == 2) learn(false);
		}
		debug("computer ready");
		BeginTime = System.currentTimeMillis();
	}

	void Find_best (byte pos[], byte me)
	{
		byte n[] = new byte[15];
		byte k,kk;
		byte who;

		if (Allow_More) who = me;
		else who = 0;
		for (k=0;k<15;k++) n[k] = pos[k];
		k = 0;
		do
		{
			if (n[k] == 0 && Utility.is_triangle(n,k,me) == -1)
			{
				n[k] = me;
 				if (Utility.whowins(n,who) == me)
				{
					Computed_Move[Computed_Move_Nr++] = k;
					if (Allow_More && me == HEXI) Find_best (n,me);
					return;
				}
				else n[k] = 0; // This move is not good, try the next one.
			}
		} while (++k<15);
	}

	/****************  Calculate the best move for Computer *****************/

	public byte compute_best_move (byte me, byte he)
 	{
		int  evaluation[] = new int[15];
		byte alternative[] = new byte[15];
		byte n[] = new byte[15];
		int  w,Sum,key;
		byte k;
		byte who;

		Computed_Move_Nr = 0;
		if (step == -1)
		{
			Computed_Move[Computed_Move_Nr++] =  (byte)(Math.random()*15);
			return Computed_Move_Nr;
		}
		if (LEVEL == 2 || me==PLAYER && !DemoRunning)
		{
			//  Level Expert
			Find_best(position,me);
			if (Computed_Move_Nr != 0)
			{
				debug("Found best");
				return Computed_Move_Nr;
			}
			for (k=0;k<15;k++) n[k] = position[k];
			if (Allow_More) who = me;
			else who = 0;
			Sum = 0;
			k = 0;
			do
			{
				evaluation[k] = 0;
				if (n[k] == 0 && Utility.is_triangle(n,k,me) == -1)
				{
					n[k] = me;
 					if (Allow_More) w = winner[1][Utility.hash(n,me)];
					else
					{
						key = Utility.hash(n,(byte)0);
						w = winner[0][key];
					}
					w += Utility.Heuristic(n,me,he);
					if (w < 1) evaluation[k] = 1;
					else evaluation[k] = w;
					Sum += evaluation[k];
					n[k] = 0;
				}
			} while (++k < 15);
			if (Sum != 0)
			{
				k = Utility.max_random(evaluation);
				Computed_Move[Computed_Move_Nr++] = k;
				if (me == HEXI && !DemoRunning)
				{
					// Remember this unsure draw.
					for (byte i=0;i<15;i++) n[i] = position[i];
					n[k] = HEXI;
					key = Utility.hash(n,who);
					to_learn[to_learn_Nr++] = key;
					debug("Remember Step: "+step+" "+k);
				}
				return Computed_Move_Nr;
			}
		}
		else
		{
			// Level beginner
			debug("Level beginner");
			int PossibleMove_Nr = 0;
			for (k=0;k<15;k++) if (position[k] == 0 && Utility.is_triangle(position,k,me) == -1) alternative[PossibleMove_Nr++] = k;
			if (PossibleMove_Nr != 0)
			{
				Computed_Move[Computed_Move_Nr++] = alternative[(int)(Math.random()*PossibleMove_Nr)];
				return Computed_Move_Nr;
			}
		}
		return -1; // No more moves possible, must give up.
	} // compute_best_move

	public void restartGame()
	{
		byte k;
		debug("New Game");
		animatorThread = null; without_help = true;
		InitializeVariable (); state = PLAY;
  		controlbox.Button_Undo.disable();
 		controlbox.Button_Hint.enable();
		controlbox.Button_PHint.enable();
		controlbox.allow_more.show();
		controlbox.allow_shaking.show();
		controlbox.validate();
		if ( HEXI == 1 )
		{
			k = compute_best_move(HEXI,PLAYER);
			position[k] = HEXI;
			route[++step] = k;
			controlbox.status_label.setText("I moved first. Your turn!");
		}
		else
		{
			controlbox.status_label.setText("Your first move please!");
		}
		repaint ();
		PlayTime = 0;  // Reset playtime to 0
		BeginTime = System.currentTimeMillis();
	}

	public void drawLine(Graphics g, int edge)
	{
		Color color;
		byte MoveNr = 0;
		if (position[edge] == HEXI) color = GREEN;
		else if (position[edge] == PLAYER) color = RED;
		else color = GRAY;
		Color brighter = color.brighter();
		Color darker   = color.darker();
		int x1 = node[Utility.ptc1[edge]][0];
		int y1 = node[Utility.ptc1[edge]][1];
		int x2 = node[Utility.ptc2[edge]][0];
		int y2 = node[Utility.ptc2[edge]][1];
		if (Math.abs(x2-x1) < Math.abs(y2-y1))
		{
			if ( Math.abs(x2-x1) == xoff )
			{
				g.setColor(darker);
			        g.drawLine(x1-5, y1, x2-5, y2);
			        g.drawLine(x1+5, y1, x2+5, y2);
			}
		        g.setColor(darker);
		        g.drawLine(x1-4, y1, x2-4, y2);
			g.drawLine(x1+4, y1, x2+4, y2);
			g.setColor(color);
			g.drawLine(x1-3, y1, x2-3, y2);
			g.drawLine(x1+3, y1, x2+3, y2);
			g.drawLine(x1-2, y1, x2-2, y2);
			g.drawLine(x1+2, y1, x2+2, y2);
			g.setColor(brighter);
			g.drawLine(x1-1, y1, x2-1, y2);
			g.drawLine(x1+1, y1, x2+1, y2);
			g.setColor(brighter.brighter());
			g.drawLine(x1, y1, x2, y2);
		}
		else
		{
			if ( Math.abs(x2-x1) == xoff && Math.abs(y2-y1) != 0 )
			{
				g.setColor(darker);
				g.drawLine(x1, y1-5, x2, y2-5);
				g.drawLine(x1, y1+5, x2, y2+5);
				g.setColor(color);
				g.drawLine(x1, y1-4, x2, y2-4);
				g.drawLine(x1, y1+4, x2, y2+4);
			}
			else
			{
				g.setColor(darker);
				g.drawLine(x1, y1-4, x2, y2-4);
				g.drawLine(x1, y1+4, x2, y2+4);
			}
		        g.setColor(color);
			g.drawLine(x1, y1-3, x2, y2-3);
			g.drawLine(x1, y1-2, x2, y2-2);
			g.drawLine(x1, y1+2, x2, y2+2);
			g.drawLine(x1, y1+3, x2, y2+3);
			g.setColor(brighter);
			g.drawLine(x1, y1-1, x2, y2-1);
			g.drawLine(x1, y1, x2, y2);
			g.drawLine(x1, y1+1, x2, y2+1);
		}
	}

	public void RunDemo()
	{
		byte first=-1, second=-1;
		byte me = 1, he = 2 ;
//		byte level = LEVEL ;
		byte SecondPlayerLevel = 2;
		int i;
		Graphics gs = getGraphics();
		animatorThread = null;
		paint_hexi();

		/* In approximately 6 out of 10 cases the second player will play like a beginner (i. e. just trying to avoid triangles): */

		if (Math.random()>0.4) SecondPlayerLevel =1;
		//controlbox.status_label.setText(" "+(step+1)+((me==PLAYER)?" Red":" Green")+"'move");
		controlbox.status_label.setText("Demo is running!");
		while  (compute_best_move(me,he) != -1 )
		{
			position[(first=Computed_Move[0])] = me;
			route[++step] = first;
			StretchLine(first);
			try
			{
  				Thread.sleep(500);
			}
			catch ( InterruptedException e) {};
  			LEVEL = SecondPlayerLevel;
			if (compute_best_move(he,me) != -1)
			{
				position[(second=Computed_Move[0])] = he;
 				route[++step] = second;
				StretchLine(second);
				try
				{
  					Thread.sleep(500);
				}
				catch ( InterruptedException e) {};
				//controlbox.status_label.setText(" "+(step+1)+((he==PLAYER)?" Red":" Green")+"'move");
			}
			else
			{
				// second player can't move
				second = -1;
				break;
			}
			LEVEL = 2;
		}
		if ( second != -1 )
		{

			/* If the first player was unable to move, the value of second
			will still be unequal -1 (from the previous calculation). If Red
			moved first (PLAYER==me==1) then "Red gives up." has to be displayed,
			otherwise "Green gives up." */

			controlbox.status_label.setText(" "+(step+2)+((me==PLAYER)?" Red":" Green")+" gives up.");
			state = WIN ;
		}
		else
		{

			/* second==-1 means that the first player was able to move and
			the second one was not able to. If the first player was Red (PLAYER==me==1)
			that means that Green has to give up, otherwise Red. */

			controlbox.status_label.setText(" "+(step+2)+((me==PLAYER)?" Green":" Red")+" gives up.");
			state = LOSS;
		}
		LEVEL = 2;
	}

	public void InitializeVariable ()
	{
		for (byte i=0;i<15;i++ )
		{
			position[i] = 0 ; route[i] = -1;
 		}
		selected = -1;
		step = -1;
		Player_Moved = false;
		to_learn_Nr = 0;
		Computed_Move_Nr = 0;
	}

	public void learn (boolean positiv)
	{
		if (can_reply && Allow_Shaking)	// only allowed to learn if the full database was available and Shaking enabled
		{
			debug("I am Learning!");
			Key_To_Transfer = new int[to_learn_Nr];
			for (int i=0;i<to_learn_Nr;i++)
			{
				int key = to_learn[i];
				byte w = winner[Allow_More?1:0][key];
				if (positiv)
				{
					Key_To_Transfer[i] = key;
					if (w < 126-LEARN_FACTOR) w += LEARN_FACTOR;
 					else w = 126;
					debug (" Increase winner data. "+key+" to "+w);
				}
				else
				{
					Key_To_Transfer[i] = -key;
					if (w > -128+LEARN_FACTOR) w -= LEARN_FACTOR;
					else w = -128;
					debug (" decrease winner data in "+key+" to "+w);
				}
				winner[Allow_More?1:0][key] = w;
				debug("Writing to "+key);
			}
			if (Hexi_Client == null || !Hexi_Client.isAlive())
			{
				Hexi_Client = new HexiClient(this);
				Hexi_Client.start();
			}
		}
	} // learn

 	void debug(String t)
	{
		if (DebugFlag) System.out.println (t);
	}

	public void FixNodePosition()
	{
		int margin = 130;
		xoff = (size().width-margin) / 3;
		yoff = (size().height-margin) / 2;
		margin /= 2;
		node[0][0] = xoff+margin;
		node[0][1] = margin;
		node[1][0] = 2*xoff+margin;
		node[1][1] = margin;
		node[2][0] = 3*xoff+margin;
		node[2][1] = margin+yoff;
		node[3][0] = 2*xoff+margin;
		node[3][1] = margin+2*yoff;
		node[4][0] = xoff+margin;
		node[4][1] = margin+2*yoff;
		node[5][0] = margin;
		node[5][1] = margin+yoff;
	}

	/** Draws a thickness thick frame from the specified x and y with width w and height h
	**************************************************************************************/

   	void drawFrame(Graphics g,int x,int y,int w,int h,int thickness)
	{
		for(int i=0;i<thickness;i++)
		{
			g.setColor(GRAY.darker());
			g.drawLine(x+i, y+i, x+w-i-1, y+i);
			g.drawLine(x+i, y+i, x+i, y+h-i-1);
			g.setColor(GRAY);
			g.drawLine(x+w-i-1, y+i, x+w-i-1, y+h-i-1);
			g.drawLine(x+i, y+h-i-1, x+w-i-1, y+h-i-1);
		}
	}

	public void initWinner(int who)
	{
		byte ss[] = new byte[15];
		long startTime;
		if (who == 1)
		{
			for ( int i=0;i<8192;i++)
			{
				winner[1][i] = 127;
				winner[2][i] = 127;
			}
			startTime = System.currentTimeMillis();
			Utility.Tree_Expand1(ss,(byte)1,(byte)2,(byte)0);
			System.out.println("Time to generate database for Ramsey-6 is : "+(System.currentTimeMillis()-startTime));
		}
		else
		{
			for (int i=0;i<4096;i++) winner[0][i] = 127;
		        startTime = System.currentTimeMillis();
			Utility.Tree_Expand2(ss,(byte)1,(byte)2,(byte)0);
 			System.out.println("Time to generate database for HEXI is : "+(System.currentTimeMillis()-startTime));
		}
		can_reply=false;	// Since the full database is not available, highscore and learning info will not be transmitted.
	}

	public void run()
	{
		int x1=0,x2=0,x3=0,y1=0,y2=0,y3=0;
		int select=0;
		int xx=-1,yy=-1;

       		if ( state != WIN && selected == -1 ) return;
		//Remember the starting time.
		long startTime = System.currentTimeMillis();
		if ( state == WIN )
		{
			x1 = node[triangle_node1][0];
			y1 = node[triangle_node1][1];
			x2 = node[triangle_node2][0];
			y2 = node[triangle_node2][1];
			x3 = node[triangle_node3][0];
			y3 = node[triangle_node3][1];
		}
		else
		{
			select  = selected;
			x1 = node[select][0];
			y1 = node[select][1];
		}
		debug("RUN");
		//Animation loop.
		while (Thread.currentThread() == animatorThread)
		{
			if (xx == -1)
			{
				if (yy == -1) xx = 1;
				else yy = -1;
			}
			else
			{
				if (yy == -1) yy = 1;
				else xx = -1;
			}
			if ( state == WIN )
			{
				node[triangle_node1][0] = x1 + xx;
				node[triangle_node1][1] = y1 + yy;
				node[triangle_node2][0] = x2 + yy;
				node[triangle_node2][1] = y2 + xx;
				node[triangle_node3][0] = x3 - xx;
				node[triangle_node3][1] = y3 - yy;
			}
			else
			{
				node[select][0] = x1 + xx;
				node[select][1] = y1 + yy;
			}
			//Display it.
			paint_hexi();
			//Delay depending on how far we are behind.
			try
			{
				startTime += 200;
				Thread.sleep(Math.max(0, startTime-System.currentTimeMillis()));
			}
			catch (InterruptedException e)
			{
				break;
			}
		}
		if ( x2 != 0 )
		{
			node[triangle_node1][0] = x1;
			node[triangle_node1][1] = y1;
			node[triangle_node2][0] = x2;
			node[triangle_node2][1] = y2;
			node[triangle_node3][0] = x3;
			node[triangle_node3][1] = y3;
		}
		else
		{
			node[select][0] = x1;
			node[select][1] = y1;
		}
		paint_hexi();
    	}

	public void stop()
	{
		if (animatorThread != null) animatorThread = null;
		selected = -1;
		controlbox.hide();
		debug ("Hexi stopped");
	}

} // Public class Hexi
