/*
	Blocks
	Author: Faster Lu
		faster@pacific.net.sg
*/



import java.awt.*;
import java.applet.*;
import java.lang.*;

public class Blocks extends Applet implements Runnable	{
	Thread tr;
	Bar[] bar;
	Bar tmpbar;
	BControls btns;
	PuzzleSet puzzles;
	int[][] board;
	int[][] flag;
	static final int sz=15;
	static int bw;
	static int bh;

	boolean done=false;
	int timeused=0;
	int tx;
	int msght;

	int item=-1;

	public void init() {
		setBackground(Color.black);
		try	{
			setFont(new Font("Dialog",Font.BOLD,12));
		} catch (Exception e) {};

		setLayout(new BorderLayout());

		btns=new BControls(this);
		add("South",btns);

		bw=bounds().width/sz;
		bh=(bounds().height-btns.preferredSize().height-25)/sz;
		msght=bounds().height-btns.preferredSize().height-bh*sz-1;
		tx=bounds().width/2+12;

		board=new int[bw][bh];
		flag=new int[bw][bh];

		Bar.setBoard(this,board,flag,bw,bh,sz);
		bar=new Bar[12];
		puzzles=new PuzzleSet(this);

		newGame();
	}

	public void msg(String otext, Graphics g)	{
		if (g==null)
			g=getGraphics();

		if (g!=null)	{
			g.setColor(Color.cyan);
			g.fillRect(0,bh*sz+1,tx-100,msght);
			g.setColor(Color.red);
			g.drawString(otext,10,bh*sz+(msght+12)/2);
		}
	}

	public void drawTime(Graphics g)	{
		if (g==null)
			g=getGraphics();

		if (g!=null)	{
			g.setColor(Color.cyan);
			g.fillRect(tx,bh*sz+1,24,msght);
			g.setColor(Color.red);
			g.drawString(Integer.toString(timeused),tx,bh*sz+(msght+12)/2);
		}
	}

	public void paint(Graphics g)	{
		for (int i=0;i<12;i++)	{
			if (i==item)
				bar[i].draw(g,2);
			else
				bar[i].draw(g,1);
		}

		puzzles.drawShape(g);

		g.setColor(Color.green);
		g.drawLine(0,bh*sz,bounds().width,bh*sz);
		g.setColor(Color.cyan);
		g.fillRect(0,bh*sz+1,bounds().width,msght);

		g.setColor(Color.black);
		String s="Rotate: X/Z    Turn: Right Mouse";
		g.drawString(s,bounds().width-6*s.length()-40,bh*sz+(msght+12)/2);
		s="Time: ";
		g.drawString(s,tx-6*(s.length()+1)-40,bh*sz+(msght+12)/2);

		drawTime(g);
	}

	public void newGame()	{
		for (int i=0;i<bw;i++)
			for (int j=0;j<bh;j++)	{
				flag[i][j]=board[i][j]=-1;
			}

		puzzles.initGame();

		for (int i=0;i<12;i++)	{
			if (bar[i]==null)
				bar[i]=new Bar(i,-1,-1);
			bar[i].setInitPosition(-1,-1);
		}

		if (tmpbar==null)
			tmpbar=new Bar(bar[0]);

		item=-1;

		done=false;
		timeused=0;

		repaint();
	}

	public boolean check()	{
		for (int i=0;i<12;i++)	{
			if (flag[bar[i].x][bar[i].y]<0)
				return false;
			for (int j=0;j<4;j++)	{
				if (flag[bar[i].x+bar[i].offx[j]][bar[i].y+bar[i].offy[j]]<0)
					return false;
			}
		}

		done=true;
		msg("Congratulations!",null);
		return true;
	}

	public void rotate(int choice)	{
		Graphics g=getGraphics();

		if (item<0)	{
			msg("Please select an item first!",g);
			return;
		}

		tmpbar.copy(bar[item]);
		if (bar[item].rotate(choice))	{
			tmpbar.draw(g,0);
			bar[item].draw(g,2);
			if (bar[item].isFree(-1,-1))	{
				bar[item].put(-1,-1);
				msg("",g);
			} else
				msg("Overlapped",g);
		} else	{
			bar[item].copy(tmpbar);
			msg("Out of boundary!",g);
		}
	}

	public boolean keyDown(java.awt.Event evt, int c)	{
		if (c=='x' || c=='X')
			rotate(1);
		if (c=='z' || c=='Z')
			rotate(2);

		return false;
	}

	public boolean mouseDown(java.awt.Event evt, int x, int y) {
		Graphics g=null;
		int i,j;

		if ((evt.modifiers & Event.META_MASK)!=0)	{
			rotate(3);
			return false;
		}

		i=x/Bar.sz;
		j=y/Bar.sz;

		if (i<0 || i>=bw || j<0 || j>=bh || board[i][j]<0)	{
			return false;
		}

		if (item>=0)	{
			bar[item].dragflag=0;

			if (bar[item].isFree(-1,-1))	{
				g=getGraphics();
				bar[item].draw(g,1);
				item=-1;
			} else	{
				return false;
			}
		}

		if (g==null)
			g=getGraphics();

		item=board[i][j];
		bar[item].draw(g,2);
		bar[item].dragflag=0;

		return false;
	}

	public boolean mouseDrag(java.awt.Event evt, int x, int y) {
		int i,j;

		if ((evt.modifiers & Event.META_MASK)!=0)
			return false;

		i=x/Bar.sz;
		j=y/Bar.sz;

		if (item<0 || i<0 || i>=bw || j<0 || j>=bh || ++bar[item].dragflag==1)	{

			return false;
		}

		Graphics g=getGraphics();
		if (g==null)	{
			return false;
		}

		if (bar[item].isIn(i,j))	{
			bar[item].draw(g,0);
			bar[item].x=i;
			bar[item].y=j;
			bar[item].draw(g,2);
			if (bar[item].isFree(-1,-1))	{
				bar[item].put(-1,-1);
				msg("",g);
			} else
				msg("Overlapped",g);
		} else	{
			msg("Out of boundary!",g);
		}

		return false;
	}

	public boolean mouseUp(java.awt.Event evt, int x, int y) {
		if (bar[item].isFree(-1,-1))	{
			check();
		}
		return false;
	}

	public void start() {
		tr = new Thread(this);
		tr.start();
    	}

	public void stop() {
		tr.stop();
    	}

	public void run() {
		while (true) {
			try {Thread.currentThread().sleep(1000);} catch (InterruptedException e){}
			if (done==false)	{
				timeused++;
				drawTime(null);
			}
		}
	}
}


class Bar	{
	static Blocks blocksapp;
	static int [][] board=null;
	static int [][] flag=null;
	static int bw=0;
	static int bh=0;
	static int sz=0;

	static Color[] color;
	static final int cr[]={192,192,0,224,192,64,192,64,128,192,128,0};
	static final int cg[]={192,0,192,64,224,64,128,192,64,0,192,128};
	static final int cb[]={0,192,192,64,192,224,64,128,192,128,0,192};

	static final int shape_x[]={-2,-1,1,2, -1,1,2,-1, -1,1,2,0, -1,1,-1,0, -1,1,-1,1, -1,-1,1,-1, -1,-1,1,0, -1,-1,1,1, -1,-1,0,1, 0,-1,1,0, 0,0,1,2, -1,0,1,2};
	static final int shape_y[]={0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,1,1, 0,0,1,1, -1,0,0,1, -1,0,0,1, -1,0,0,1, -1,0,1,1, -1,0,0,1, -2,-1,0,0, 0,1,1,1};

	int id;
	int x,y;
	int offx[],offy[];
	int dragflag;

	Bar(Bar bar)	{
		offx=new int[4];
		offy=new int[4];

		copy(bar);
	}

	Bar(int id,int posx,int posy)	{
		this.id=id;

		dragflag=0;

		offx=new int[4];
		offy=new int[4];

		System.arraycopy(shape_x,id*4,offx,0,4);
		System.arraycopy(shape_y,id*4,offy,0,4);
	}

	public void copy(Bar bar)	{
		id=bar.id;

		x=bar.x;
		y=bar.y;
		dragflag=bar.dragflag;

		System.arraycopy(bar.offx,0,offx,0,4);
		System.arraycopy(bar.offy,0,offy,0,4);
	}

	static public void setBoard(Blocks app,int[][] barray,int[][] farray,int width,int height,int size)	{

		blocksapp=app;
		board=barray;
		flag=farray;
		bw=width;
		bh=height;
		sz=size;

		if (color==null)	{
			color=new Color[12];
			for (int i=0;i<12;i++)
				color[i]=new Color(cr[i],cg[i],cb[i]);
		}
	}

	private void drawUnit(Graphics g,int mode,int xx,int yy)	{
		if (mode==0)	{
			if (board[xx][yy]<0)
				g.setColor(Color.black);
			else if (board[xx][yy]==id)	{
				g.setColor(Color.black);
				board[xx][yy]=-1;
			} else
				g.setColor(color[board[xx][yy]]);

			g.fillRect(xx*sz+1,yy*sz+1,sz-2,sz-2);
		} else	{
			if (mode==1)
				g.setColor(color[id]);
			else
				g.setColor(Color.red);
			g.fillRect(xx*sz+1,yy*sz+1,sz-2,sz-2);
		}
	}

	public synchronized void draw(Graphics g,int mode)	{
		drawUnit(g,mode,x,y);
		for (int i=0;i<4;i++)
			drawUnit(g,mode,x+offx[i],y+offy[i]);
	}

	public void setInitPosition(int posx,int posy)	{
		x=posx;
		y=posy;

		while (x==-1)	{
			x=(int)(Math.random()*bw);
			y=(int)(Math.random()*bh);
			if (isIn(x,y)==false || isFree(x,y)==false)
				x=-1;
		}

		put(-1,-1);
	}

	public boolean isIn(int posx,int posy)	{
		int xx,yy;

		if (posx==-1)	{
			posx=x;
			posy=y;
		}

		if (posx<0 || posx>=bw || posy<0 || posy>=bh )
			return false;

		for (int i=0;i<4;i++)	{
			xx=posx+offx[i];
			yy=posy+offy[i];

			if (xx<0 || xx>=bw || yy<0 || yy>=bh )
				return false;
		}
		return true;
	}

	public boolean isFree(int posx,int posy)	{
		int xx,yy;

		if (posx==-1)	{
			posx=x;
			posy=y;
		}

		if (board[posx][posy]>=0 && board[posx][posy]!=id)
			return false;

		for (int i=0;i<4;i++)	{
			xx=posx+offx[i];
			yy=posy+offy[i];

			if (board[xx][yy]>=0 && board[xx][yy]!=id)
				return false;
		}
		return true;
	}

	public void put(int posx,int posy)	{
		if (posx!=-1)	{
			x=posx;
			y=posy;
		}

		board[x][y]=id;

		for (int i=0;i<4;i++)
			board[x+offx[i]][y+offy[i]]=id;
	}

	public boolean rotate(int choice)	{
		for (int i=0;i<4;i++)	{
			int tmp=offx[i];

			if (choice==1)	{
				offx[i]=-offy[i];
				offy[i]=tmp;
			} else	if (choice==2)	{
				offx[i]=offy[i];
				offy[i]=-tmp;
			} else
				offx[i]=-offx[i];
		}

		return isIn(-1,-1);
	}
}



class BControls extends Panel	{
	Blocks blocksapp;

	BControls(Blocks app)	{
		blocksapp=app;
		setLayout(new GridLayout(1,4));
		add(new Button("New"));
		add(new Button("Rotate +"));
		add(new Button("Rotate -"));
		add(new Button("Turn"));
	}

	public boolean action(Event ev, Object arg) {
		if (ev.target instanceof Button) {
			String label = (String) arg;

			if (label.equals("New"))	{
				blocksapp.newGame();
			} else	if (label.equals("Rotate +"))	{
				blocksapp.rotate(1);
			} else	if (label.equals("Rotate -"))	{
				blocksapp.rotate(2);
			} else	if (label.equals("Turn"))	{
				blocksapp.rotate(3);
			}
		}
		return false;
	}

}



class PuzzleSet	{
	static final int sum=14;
	static final int id[]={ 0, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 6, 7, 7, 7, 8,  8, 8, 9, 9, 9,10,10,10,11,11,11,12,12,13,13,-1};
	static final int xx[]={ 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 8, 0, 0, 0, 0,  0, 4, 0, 0, 8, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4,-1};
	static final int yy[]={ 0, 0, 0, 0, 3, 0, 3, 0, 3, 0, 3, 3, 0, 3, 6, 0,  3, 6, 0, 3, 6, 0, 3, 6, 0, 3, 6, 0, 6, 0, 3,-1};
	static final int ww[]={12,15,20,16, 4,16, 4,12, 8,12, 4, 4, 4,12, 4, 4, 12, 4, 4,12, 4, 4, 8, 8, 4,12, 4, 4,12, 8,12,-1};
	static final int hh[]={ 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,  3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, 3, 3, 3,-1};

	Blocks blocksapp;
	int sz,bw,bh;
	int gameindex=-1;

	PuzzleSet(Blocks app)	{
		blocksapp=app;
		sz=app.sz;
		bw=app.bw;
		bh=app.bh;
	}

	public void initGame()	{
		int i,j,x,y,orgx,orgy,maxh=0,maxw=0;

		gameindex++;
		if (gameindex>=sum)
			gameindex=0;

		for (i=0;id[i]!=gameindex;i++);

		for (j=i;id[j]==gameindex;j++)	{
			if (xx[j]+ww[j]>maxw)
				maxw=xx[j]+ww[j];
			if (yy[j]+hh[j]>maxh)
				maxh=yy[j]+hh[j];
		}
		orgx=(bw-maxw)/2;
		orgy=(bh-maxh)/2;

		for (j=i;id[j]==gameindex;j++)	{
			for (x=0;x<ww[j];x++)
				for (y=0;y<hh[j];y++)
					blocksapp.flag[orgx+xx[j]+x][orgy+yy[j]+y]=1;
		}
	}

	public void drawShape(Graphics g)	{
		int i,j,k,x1,x2,orgx,orgy,maxh=0,maxw=0;

		for (i=0;id[i]!=gameindex;i++);

		for (j=i;id[j]==gameindex;j++)	{
			if (xx[j]+ww[j]>maxw)
				maxw=xx[j]+ww[j];
			if (yy[j]+hh[j]>maxh)
				maxh=yy[j]+hh[j];
		}
		orgx=(bw-maxw)/2;
		orgy=(bh-maxh)/2;

		for (j=i;id[j]==gameindex;j++)	{
			g.setColor(Color.white);
			g.drawRect((orgx+xx[j])*sz,(orgy+yy[j])*sz,ww[j]*sz,hh[j]*sz);
			for (k=i;k<j;k++)	{
				if (yy[k]+hh[k]==yy[j])	{
					x1=Math.max(xx[k],xx[j]);
					x2=Math.min(xx[k]+ww[k],xx[j]+ww[j]);
					if (x1<x2)	{
						g.setColor(Color.black);
						g.drawLine((orgx+x1)*sz+1,(orgy+yy[j])*sz,(orgx+x2)*sz-1,(orgy+yy[j])*sz);
					}
				}
			}
		}
	}
}
