/**
	Simple Othello Applet
	@Author: Faster Lu
 		 faster@pacific.net.sg
*/



import java.awt.*;
import java.applet.*;
import java.lang.*;
import java.util.*;

public class Othello extends Applet {
	OControls btncontrol;
	int xx,yy,sz,bx,by,r=0;
	int cside=1;
	OGame game=new OGame();
	int olddata[]=new int[64];
	int olddot=-1;
	boolean pass=false;

	public void init() {
		OGame.othelloapp=this;
		add(btncontrol=new OControls(this));
		System.arraycopy(game.data,0,olddata,0,64);
		setBackground(Color.lightGray);
		try	{
			setFont(new Font("Dialog",Font.BOLD,12));
		} catch (Exception e) {};
	}

	public void start() {
		btncontrol.enable();
	}

	public void stop() {
		btncontrol.disable();
	}

	public static void main(String args[]) {
		Frame f = new Frame("Othello");
		Othello	othello = new Othello();

		othello.init();
		othello.start();

		f.add("Center", othello);
		f.resize(500,300);
		f.show();
    	}

	private void drawText(Graphics g)	{
		String otext;
		int x=bx+40,y=by+40;

		g.setColor(Color.pink);
		g.fillRect(x,y,140,60);

		g.setColor(Color.red);
		g.drawString("Author: Faster Lu",bx+40,by+130);

		g.setColor(Color.black);
		if (game.turn==1)
			otext="Red";
		else
			otext="Blue";
		otext+="'s turn";
		g.drawString(otext,x,y+19);
//		otext=game.debuginf;
		g.drawString("Blue:",x,y+39);
		g.drawString(Integer.toString(game.s[0]),x+36,y+39);
//		otext="c0/c1: "+game.c[0]+"/"+game.c[1];
		g.drawString("Red:",x,y+59);
		g.drawString(Integer.toString(game.s[1]),x+36,y+59);
	}

	private void msg(String instr)	{
		Graphics g=getGraphics();

		g.setColor(Color.pink);
		g.fillRect(bx+40,by+8,140,20);
		g.setColor(Color.red);
		g.drawString(instr,bx+40,by+19);
	}

	private void showResult()	{
		if (game.s[0]>game.s[1])	{
			msg("Blue win!");
		} else if (game.s[0]<game.s[1])	{
			msg("Red Win!");
		} else
			msg("Even!");
	}

	private void drawDot(Graphics g,int k,int i,int j)	{
		if (k==-1)	{
			g.setColor(Color.yellow);
			g.fillRect(xx+sz*i+sz/2-r,yy+sz*j+sz/2-r,r+r+1,r+r+1);
		} else	{
			if (k==1)
				g.setColor(Color.red);
			else
				g.setColor(Color.blue);
			g.fillOval(xx+sz*i+sz/2-r,yy+sz*j+sz/2-r,r+r,r+r);
		}
	}

	private void drawSign(Graphics g)	{
		if (olddot>=0)
			drawDot(g,game.data[olddot],olddot/8,olddot%8);

		if (game.count>0)	{
			olddot=game.mv[game.count-1];
			if (olddot>=100)
				olddot-=100;
			g.setColor(Color.white);
			g.drawRect(xx+sz*(olddot/8)+sz/2-r/3,yy+sz*(olddot%8)+sz/2-r/3,r*2/3,r*2/3);
		}
	}

    	public void drawBoard(Graphics g) {
		int i,j,k;

		g.setColor(Color.white);
		g.drawLine(bx,by+135,bx+180,by+135);
		g.drawLine(bx+180,by,bx+180,by+135);
		g.setColor(Color.black);
		g.drawLine(bx,by,bx+180,by);
		g.drawLine(bx,by,bx,by+135);
		g.setColor(Color.pink);
		g.fillRect(bx+1,by+1,180-1,135-1);

		g.setColor(Color.black);
		g.fillRect(xx-8+5,yy-8+5,sz*8+16,sz*8+16);
		g.setColor(Color.yellow);
		g.fillRect(xx-8,yy-8,sz*8+16,sz*8+16);

		g.setColor(Color.black);
		g.drawRect(xx,yy,sz*8,sz*8);
		for (i=1;i<8;i++)	{
			g.drawLine(xx,yy+sz*i,xx+sz*8,yy+sz*i);
			g.drawLine(xx+sz*i,yy,xx+sz*i,yy+sz*8);
		}

		for (i=0;i<8;i++)
			for (j=0;j<8;j++)	{
				k=game.data[i*8+j];
				if (k!=-1)
					drawDot(g,k,i,j);
			}

		drawSign(g);
	}

	public void paintupdate()	{
		int i,j,k;
		Graphics g=getGraphics();

		if (r==0)
			return;

		for (i=0;i<8;i++)
			for (j=0;j<8;j++)	{
				k=game.data[i*8+j];
				if (k!=olddata[i*8+j])
					drawDot(g,k,i,j);
			}

		drawSign(g);

		System.arraycopy(game.data,0,olddata,0,64);

		drawText(g);
	}

	public void paint(Graphics g)	{
		if (r==0)	{
			xx=20;
			yy=20;
			sz=30;
			r=sz*2/5;
			bx=290;
			by=137;

			g.setColor(Color.lightGray);
			g.fillRect(0,0,size().width,size().height);
		}

		btncontrol.reshape(bx,yy-8,180,110);
		btncontrol.newbtn.reshape(15,10,70,25);
		btncontrol.levelbtn.reshape(95,10,70,25);
		btncontrol.passbtn.reshape(15,40,70,25);
		btncontrol.undobtn.reshape(95,40,70,25);
		btncontrol.sidebtn.reshape(15,70,150,25);

		drawBoard(g);
		drawText(g);
	}

	public boolean mouseUp(java.awt.Event evt, int x, int y) {
		int i,j;

		if (cside==game.turn || pass)
			return false;

		i=(x-xx)/sz;
		j=(y-yy)/sz;

		if (i<0 || i>7 || j<0 || j>7)
			return false;

		if (game.adjust(i,j,game.turn,0)==false)	{
			msg("Illegal move");
			return false;
		}

		msg("");

		addDot(i,j);

		return true;
	}

	public void newGame()	{
		if (game.turn==cside && !game.endofGame)	{
			game.cancel();
			return;
		}

		game.init();

		btncontrol.checkControl();
		paintupdate();

		if (game.turn==cside)
			addDot(5,4);
	}

	public void undo()	{
		game.undo();
		while (game.turn==cside)
			game.undo();

		btncontrol.checkControl();
		paintupdate();

		if (game.c[game.turn]==0)	{
			pass=true;
			promptPass();
		} else
			pass=false;
			msg("");
	}

	public void addDot(int i,int j)	{
		pass=game.addDot(i,j);

		game.debuginf="value="+game.judge();

		btncontrol.checkControl();
		paintupdate();

		if (game.endofGame)	{
			showResult();
			pass=false;
			return;
		}

		if (pass)
			promptPass();
		else if (cside==game.turn)
			think();
	}

	private void promptPass()	{
		if (game.turn==1)
			msg("Red must pass");
		else
			msg("Blue must pass");
		if (game.turn!=cside)
			btncontrol.passbtn.enable();
		else	{
			pass=false;
			game.turn=1-game.turn;
		}
	}

	public void resume()	{
		pass=false;
		btncontrol.passbtn.disable();
		game.turn=1-game.turn;
		paintupdate();
		msg("");
		if (cside==game.turn)
			think();
	}

	public void think()	{
		msg("Thinking...");
		btncontrol.undobtn.disable();
		btncontrol.levelbtn.disable();

		game.select();
	}

	public void thinkdone(int ret)	{
		int i,j;

		if (ret<0)	{
			game.turn=1-cside;
			newGame();
			return;
		}

		j=ret%8;
		i=ret/8;

		msg("");
		try	{
			play(getCodeBase(),"ding.au");
		} catch (Exception e) {};

		addDot(i,j);

		btncontrol.levelbtn.enable();
		btncontrol.undobtn.enable();
	}
}



class OGame	{
	static Othello othelloapp;
	int data[];
	int turn,count;
	boolean endofGame;
	String debuginf="";

	static OSearch thinker=null;
	static int mode=0;

	static int mv[]=new int[60];
	static int s[]=new int[2];
	static int c[]=new int[2];
	static int b[]=new int[2];

	static final int dx[]={1,1,0,-1,-1,-1,0,1};
	static final int dy[]={0,1,1,1,0,-1,-1,-1};
	static final int cx[]={0,7,7,0};
	static final int cy[]={0,0,7,7};

	static final int stage0[]={40,38,36};
	static final int stage1[]={52,50,48};
	static final int depth1[]={3,3,4};
	static final int depth2[]={4,5,5};

	static final int winvalue=20000;
	static final int s_dot[]={2,4};
	static final int s_brd[]={6,0};
	static final int s_option[]={20,15};
	static final int s_00[]={800,700};
	static final int s_11[]={-400,-350};
	static final int s_10[]={-300,-250};

	OGame()	{
		data=new int[64];
		init();
	}

	public void copy(OGame value)	{
		System.arraycopy(value.data,0,data,0,64);
		count=value.count;
		endofGame=value.endofGame;
	}

	public void init()	{
		int i,j;

		for (i=0;i<8;i++)
			for (j=0;j<8;j++)
				data[i*8+j]=-1;

		data[3*8+3]=1;
		data[4*8+4]=1;
		data[3*8+4]=0;
		data[4*8+3]=0;

		turn=0;
		count=0;
		endofGame=false;

		s[0]=s[1]=2;
		c[0]=c[1]=4;
	}

	public void cancel()	{
		if (thinker!=null)	{
			thinker.stop();
			thinkdone(-1);
		}
	}

	private boolean adjLine(int x,int y,int side,int d,int update)	{
		int i,j;

		i=x+dx[d];
		j=y+dy[d];
		if (i>=0 && j>=0 && i<8 && j<8 && data[i*8+j]!=1-side)
			return false;

		i+=dx[d];
		j+=dy[d];
		while (i>=0 && j>=0 && i<8 && j<8)	{
			if (data[i*8+j]==-1)
				return false;
			if (data[i*8+j]==side)	{
				if (update==0)
					return true;
				i=x+dx[d];
				j=y+dy[d];
				while (data[i*8+j]!=side)	{
					data[i*8+j]=side;
					i+=dx[d];
					j+=dy[d];
				}
				return true;
			}
			i+=dx[d];
			j+=dy[d];
		}
		return false;
	}

	public boolean adjust(int x,int y,int side,int update)	{
		int d;
		boolean ret=false;

		if (data[x*8+y]!=-1)
			return false;

		for (d=0;d<8;d++)	{
			if (adjLine(x,y,side,d,update))	{
				if (update==0)
					return true;
				else
					ret=true;
			}
		}

		if (update!=0 && ret)	{
			data[x*8+y]=side;
			count++;
		}

		return ret;
	}

	public boolean addDot(int x,int y)	{
		boolean ret=false;

		adjust(x,y,turn,1);
		judge();

		if (turn==0)
			mv[count-1]=8*x+y;
		else
			mv[count-1]=100+8*x+y;

		if (!endofGame)	{
			turn=1-turn;
			if (c[turn]==0)
				ret=true;
		}

		return ret;
	}

	public void undo()	{
		int i=count-1,j,x,y;

		if (i<0)
			return;

		init();
		for (j=0;j<i;j++)	{
			if (mv[j]<100)	{
				y=mv[j]%8;
				x=mv[j]/8;
				adjust(x,y,0,1);
			} else	{
				y=(mv[j]-100)%8;
				x=(mv[j]-100)/8;
				adjust(x,y,1,1);
			}
		}

		if (mv[i]<100)
			turn=0;
		else
			turn=1;

		judge();
	}

	public int judge()	{
		int i,j,k,n,stage,ret=0;

		s[0]=s[1]=c[0]=c[1]=b[0]=b[1]=0;
		if (count==60)
			endofGame=true;
		if (count>stage0[mode])
			stage=1;
		else
			stage=0;

		for (i=0;i<8;i++)
			for (j=0;j<8;j++)	{
				if ((k=data[i*8+j])!=-1)	{
					s[k]++;
					if (i==0 || j==0 || i==7 || j==7)
						b[k]++;
				} else	if (!endofGame)	{
					if (adjust(i,j,0,0))	{
						c[0]++;
						if ( i%7==0 && j%7==0 && turn==0)
							ret+=s_00[stage];
					}
					if (adjust(i,j,1,0))	{
						c[1]++;
						if ( i%7==0 && j%7==0 && turn==1)
							ret-=s_00[stage];
					}
				}
			}

		if (c[0]+c[1]==0)
			endofGame=true;

		if (endofGame)	{
			if (s[0]>s[1])
				ret=winvalue+s[0]-s[1];
			else if (s[0]<s[1])
				ret=-winvalue+s[0]-s[1];
			else
				ret=0;

			return ret;
		}

		ret+=(s[0]-s[1])*s_dot[stage]+(c[0]-c[1])*s_option[stage]+(b[0]-b[1])*s_brd[stage];

		for (i=0;i<4;i++)	{
			j=data[cx[i]*8+cy[i]];
			if (j!=-1)	{
				if (j==0)
					ret+=s_00[stage];
				else
					ret-=s_00[stage];
			} else	{
				j=data[(cx[i]+dx[i+i+1])*8+cy[i]+dy[i+i+1]];
				if (j!=-1)	{
					ret+=(s_11[stage]*(1-j-j));
				}

				j=data[(cx[i]+dx[i+i+1])*8+cy[i]+dy[i+i+1]];
				if (j!=-1)	{
					ret+=(s_11[stage]*(1-j-j));
				}

				j=data[(cx[i]+dx[i+i+1])*8+cy[i]];
				if (j!=-1)	{
					n=2;
					while (n<=6 && data[(cx[i]+dx[i+i+1]*n)*8+cy[i]]==j)
						n++;

					if (n<=2)
						ret+=(s_10[stage]*(1-j-j));
					else if (n<=5)
						ret+=(s_10[stage]*(1-j-j)/2);
				}

				j=data[cx[i]*8+cy[i]+dy[i+i+1]];
				if (j!=-1)	{
					n=2;
					while (n<=6 && data[(cx[i]+dx[i+i+1]*n)*8+cy[i]]==j)
						n++;

					if (n<=2)
						ret+=(s_10[stage]*(1-j-j));
					else if (n<=5)
						ret+=(s_10[stage]*(1-j-j)/2);
				}
			}
		}

		return ret;
	}

	public void select()	{
		int n,ret;

		if (count<stage0[mode])
			n=depth1[mode];
		else if (count<stage1[mode])
			n=depth2[mode];
		else
			n=100;

		thinker=new OSearch(this,n);

		thinker.start();
	}

	public void thinkdone(int ret)	{
		othelloapp.thinkdone(ret);
	}
}



class OSearch extends Thread	{
	OGame initgame;
	static final int maxdepth=15;
	static final int initvalue=30000;
	OGame[] gm;
	int value[]=new int[maxdepth+1];
	int cursor[]=new int[maxdepth];
	int ndepth,depth,side;

	public OSearch(OGame initgame,int depth)	{
		this.initgame=initgame;
		this.ndepth=depth;
	}

	private void setInitValue(int layer)	{
		if ( (layer % 2)==side )
			value[layer]=-initvalue;
		else
			value[layer]=initvalue;
	}

	private int getNext(int layer)	{
		int ret;

		for (ret=cursor[layer]+1;ret<64;ret++)	{
			if (gm[layer].adjust(ret/8,ret%8,gm[layer].turn,0))
				return ret;
		}

		return -1;
	}

	public void run()	{
		int ret=-1,level=0,k;
		boolean back;

		gm=new OGame[maxdepth+1];
		for (k=0;k<maxdepth+1;k++)	{
			gm[k]=new OGame();
		}

		gm[0].copy(initgame);
		gm[0].turn=side=initgame.turn;
		setInitValue(0);
		cursor[0]=-1;
		depth=Math.min(ndepth,60-gm[0].count);

		while (level>=0)	{
			back=true;
			k=getNext(level);
			if (k>=0)	{
				gm[level+1].copy(gm[level]);
				gm[level+1].adjust(k/8,k%8,gm[level].turn,1);
				gm[level+1].turn=1-gm[level].turn;
				cursor[level]=k;
				if (++level<depth)	{
					cursor[level]=-1;
					setInitValue(level);
					back=false;
				} else	{
					value[level]=gm[level].judge();
				}
			} else	{
				if (cursor[level]<0)	{
					if (cursor[level-1]<0)	{
						level--;
						gm[level].endofGame=true;
						value[level]=gm[level].judge();
					} else	{
						gm[level+1].copy(gm[level]);
						gm[level+1].turn=1-gm[level].turn;
						if (++level<depth)	{
							cursor[level]=-1;
							setInitValue(level);
							back=false;
						} else	{
							value[level]=gm[level].judge();
						}
					}
				}
			}

			while (back)	{
				if ( level>0 && (level%2)==side )	{
					if (value[level]<value[level-1])	{
						value[level-1]=value[level];
						if (level==1)
							ret=cursor[0];
						if (level>=2 && value[level]<value[level-2])
							level--;
					}
				} else	if (level>0) {
					if (value[level]>value[level-1])	{
						value[level-1]=value[level];
						if (level==1)
							ret=cursor[0];
						if (level>=2 && value[level]>value[level-2])
							level--;
					}
				}
				level--;
				if (level<0 || cursor[level]>=0)
					back=false;
			}
		}

		initgame.thinkdone(ret);
		return;
	}
}



class OControls extends Panel {
	Othello othelloapp;
	Button newbtn,sidebtn,undobtn,levelbtn,passbtn;

	public OControls(Othello app) {
		this.othelloapp = app;

		add(newbtn=new Button("New"));
		add(levelbtn=new Button("Novice"));
		add(passbtn=new Button("Pass"));
		add(undobtn=new Button("Undo"));
		add(sidebtn=new Button("Computer: Red"));

//		setBackground(Color.lightGray);
		setBackground(Color.blue);

		checkControl();
	}

	public void checkControl()	{
		if (othelloapp.game.count!=0)	{
			if (othelloapp.game.count==1 && othelloapp.cside==0)
				sidebtn.enable();
			else
				sidebtn.disable();

			levelbtn.enable();
			undobtn.enable();
			passbtn.disable();
		} else	{
			sidebtn.enable();
			levelbtn.enable();
			undobtn.disable();
			passbtn.disable();
		}
	}

	public boolean action(Event ev, Object arg) {
		if (ev.target instanceof Button) {
			String label = (String) arg;

			if (label.equals("New"))	{
				othelloapp.newGame();
			} else	if (label.equals("Undo"))	{
				othelloapp.undo();
			} else	if (label.equals("Pass"))	{
				othelloapp.resume();
			} else	if (label.equals("Computer: Red"))	{
				othelloapp.cside=0;
				sidebtn.setLabel("Computer: Blue");
				othelloapp.addDot(5,4);
			} else if (label.equals("Computer: Blue"))	{
				othelloapp.cside=-1;
				sidebtn.setLabel("Computer: None");
				if (othelloapp.game.count!=0)
					sidebtn.disable();
			} else	if (label.equals("Computer: None"))	{
				othelloapp.cside=1;
				sidebtn.setLabel("Computer: Red");
			} else	if (label.equals("Novice"))	{
				othelloapp.game.mode=1;
				levelbtn.setLabel("Expert");
			} else	if (label.equals("Expert"))	{
				othelloapp.game.mode=2;
				levelbtn.setLabel("Master");
			} else	if (label.equals("Master"))	{
				othelloapp.game.mode=0;
				levelbtn.setLabel("Novice");
			}

			return true;
		}
		return false;
	}
}
