
import java.applet.*;
import java.awt.image.*;
import java.awt.event.*;
import java.awt.*;
//import java.util.PriorityQueue;
//import java.util.Random;
//import java.util.Stack;
//import java.util.concurrent.ArrayBlockingQueue;
import java.util.*;
import java.util.concurrent.*;
//import javax.sound.sampled.AudioFormat;
//import javax.sound.sampled.AudioSystem;
//import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.*;

/*
 * 		Tetris4k
 * 		
 * 		The programming techniques used do not represent best practice.
 * 		This was written with the goal of being under 4KB in size.
 * 
 * 		@author Elliot Walmsley, minus the MIDI things.
 * 
 * 		@date 30/01/10
 */

/*
	<APPLET CODE="G.class" width="480" HEIGHT="480">
	<PARAM NAME="cache_archive" VALUE="drts-4k.jar" />
	<PARAM NAME="java_arguments" VALUE="-Djnlp.packEnabled=true" />
	</APPLET>
 */

@SuppressWarnings("serial")
public class T extends Applet implements Runnable {
	
	/* 
	 * 
	 * GLOBAL VARS 
	 * 
	 */
	
	// Store keys pressed on the keyboard
	private boolean[] keys = new boolean[255];
	
	private static final int WIDTH = 10, HEIGHT = 20, BOXWIDTH = 15, MARGIN = 3, PADDING = 5, TOP = 40;
	private static final int SCOREBOXW = 80, RIGHTBOXW = 101, LEFT = 100; 
	
	// x starting pos of right box
	private final int RIGHTBOX = (PADDING * 2) + ((MARGIN + BOXWIDTH) * WIDTH);
	
	//private static final int BLACK = 0x000000, DARK = 0x224400, LIGHT = 0xFFFF00, DARKER = 0x222200;
	private static final int BLACK = 0x000000, WHITE = 0xFFFFFF, MIDDLEGROUND = 0xCCCCCC, FOREGROUND = 0x000000; //, DARKER = 0x222200;
	
	/* SOUND CONSTANTS */
	
	// Config for setting up midi player
	private static final String config = "\u0000\u0000\u0000\u0000\u0000\u0001\u0008\u0000\u0001\u004d\u0001\u003a\u0003\u002e\u0000\u0000\uc0ff\u0000\u0000\u0016\u004d\u0002\u0052\u0013\u001f\uc0ff\u0000\u0000\u0008\u0000\u0008\u0000\u0008\u0000\uc0ff\u0000\u0000\u0005\u003f\u0007\u0057\u0004\u004e\uc0ff\u0000\u0000\u0000\u0000\u0008\u0000\u0000\u0000\uc0ff\u0001\u0057\u0008\u0000\u0003\u004d\u0008\u0000\uc0ff\u0000\u0000\u0000\u0000\u0000\u0001\u0008\u0000\u0000\u0077\u0005\u0043\u0004\u0029\u0000\u0000\uc0ff\u0000\u0000\u0011\u004a\u0005\u0057\u0017\u0072\uc0ff\u0000\u0000\u0008\u0000\u0008\u0000\u0008\u0000\uc0ff\u0000\u0000\u0002\u0017\u0003\u0066\u0003\u0040\uc0ff\u0000\u0000\u0000\u0000\u0008\u0000\u0000\u0000\uc0ff\u0002\u001f\u0003\u006c\u0007\u0061\u0000\u0000\uc0ff\u0000\u0000\u0000\u0000\u0000\u000f\u0004\u000b\u0001\u002e\u0002\u002d\u0007\u0057\u0000\u0000\uc0ff\u0000\u0000\r\u0043\u0003\u0033\u0008\u0048\uc0ff\u0000\u0000\u0004\u0000\u0008\u0000\u0004\u0000\uc0ff\u0000\u0000\u0005\u0031\u0007\u0071\u0003\u006b\uc0ff\u0000\u0000\u0000\u0000\u0008\u0000\u0000\u0000\uc0ff\u0004\u0000\u0008\u0000\u0000\u0000\u0000\u0000\uc0ff\u0000\u0000\u0004\u006c\u0000\u0033\u0000\u0000\uc0ff\u0000\u0000\u0016\u000b\u0000\u002e\u000c\u004f\uc0ff\u0000\u0000\u0012\u0077\u0000\u000f\u0000\u0000\uc0ff\u0000\u0000\u0004\u006a\u0001\u0000\u0000\n\uc0ff\u0000\u0000\u0004\u0024\u0000\u0014\u0000\u0000\uc0ff\u0007\u0061\u0000\u0000\u0008\u0000\u0000\u0000\uc0ff\u0000\u0000\u0008\u0000\u0000\u0043\u0008\u0000\u0001\n\u0000\u0000\uc0ff\u0000\u0000\u0002\u0002\u0004\u0061\u0000\u0000\uc0ff\u0000\u0000\u0010\u0000\u0000\u0029\u0004\u0004\uc0ff\u0000\u0000\u0001\u0009\u0000\u001f\u0000\u0018\uc0ff\u0000\u0000\u0003\u007c\u0000\u000f\u0000\u0000\uc0ff\u0001\u000f\u0008\u0000\u0000\u0000\u0000\u0000\uc0ff\u0000\u0000\u0005\u005a\u0001\u0024\u0000\u0000\uc0ff\u0000\u0000\u0015\u003e\u0001\u001f\u0009\u006c\uc0ff\u0000\u0000\u002e\u0046\u0000\u007b\u0001\u0065\uc0ff\u0000\u0000\u0006\u0023\u0000\u0071\u0002\u0070\uc0ff\u0000\u0000\u0004\u0019\u0000\u000f\u0001\u0008\u0001\u0005\u0000\u0000\uc0ff\u0000\u0066\u0008\u0000\u0008\u0000\u0008\u0000\uc0ff\u0000\u0000\u0006\u0070\u0000\u0066\u0003\u001a\u0002\u0034\u0000\u0000\uc0ff\u0000\u0000\u0010\u0066\u0002\u0033\u0010\u0000\uc0ff\u0000\u0000\u0008\u0000\u0001\u004d\u000b\u001a\u0002\u0033\u0010\u0000\uc0ff\u0000\u0000\u0004\u0000\u0002\u0033\u0004\u0000\uc0ff\u0000\u0000\u0000\u0000\u0008\u0000\u0000\u0000\uc0ff\u0004\u0000\u0008\u0000\u0008\u0000\u0000\u0000\uc0ff\u0000\u0000\u0004\u0016\u0002\u0000\u0000\u0000\uc0ff\u0000\u0000\u0000\u0000\u0000\n\u000e\u0036\uc0ff\u0000\u0000\u0005\u0034\u0000\u0043\u0000\u0073\uc0ff\u0000\u0000\u0000\u001f\u0000\u0001\u0004\u0023\u0001\u0000\u0001\u0009\uc0ff\u0000\u0000\u0008\u0000\u0000\u0005\u0008\u0000\u0000\u000f\u0006\u0070\u0005\u001f\u0000\u0000\uc0ff\u0008\u0000\u0008\u0000\u0003\u0076\u0000\u0000\uc0ff";
	
	private int step;
	private PriorityQueue<Integer> cmds = new PriorityQueue<Integer>();
	
	private static final int SAMPLE_RATE = 44100;
	private static final int STEPS_PER_SEC = 80; // 64

	private static final int CHANNELS = 8;
	private static final int FILTER_RES = 0;
	private static final int FILTER_LP = 1;
	private static final int FILTER_BP = 2;
	private static final int FILTER_HP = 3;
	private static final int TIME = 4;
	private static final int FREQ = 5;
	private static final int PHASE = 6;
	private static final int OSC_FB = 7;
	private static final int FILTER_Z1 = 8;
	private static final int FILTER_Z2 = 9;
	private static final int OSC_ENV = 10;
	private static final int FB_ENV = 11;
	private static final int FREQ_ENV = 12;
	private static final int FILT_ENV = 13;
	private static final int NOISE_ENV = 14;
	private final float[][][] envelopes = new float[CHANNELS][5][];
	private final float[][] channels = new float[CHANNELS][16];
	
	public final void start() {
		new Thread(this).start();
	}
	
	public final void run() {

		// Turn on key events
		enableEvents(AWTEvent.KEY_EVENT_MASK);
		
		setSize(500, 445);
		
		/* 
		 * 
		 * Local vars 
		 * 
		 */

		// Starting position of new blocks
		int xPiece = WIDTH / 2 - 1, yPiece = 0;

		int[][] grid = new int[WIDTH][HEIGHT]; 

		final boolean O = false;
		final boolean I = true;
		
		boolean[][] currentPiece = {
				{ O, O, O, O },
				{ O, O, O, O },
				{ O, O, O, O },
				{ O, O, O, O },
		};

		boolean newPiece = true;

		int score = 0;
		int level = 0;
		int levelInc = 0;
		
		boolean justEnded = false;
		
		final boolean[][][] pieces = { 
				
			// 7 shapes
			
			// L BLOCK
			{ 
				{ I, O, O, O },
				{ I, O, O, O },
				{ I, I, O, O },
				{ O, O, O, O }
			},
			// REVERSE L BLOCK
			{ 
				{ O, I, O, O },
				{ O, I, O, O },
				{ I, I, O, O },
				{ O, O, O, O }
			},
			// LINE PIECE!
			{ 
				{ I, O, O, O },
				{ I, O, O, O },
				{ I, O, O, O },
				{ I, O, O, O }
			},
			// T BLOCK
			{ 
				{ O, I, O, O },
				{ I, I, I, O },
				{ O, O, O, O },
				{ O, O, O, O }
			},
			// SQUIGGLY
			{ 
				{ I, I, O, O },
				{ O, I, I, O },
				{ O, O, O, O },
				{ O, O, O, O }
			},
			// REVERSE SQUIGGLY
			{ 
				{ O, I, I, O },
				{ I, I, O, O },
				{ O, O, O, O },
				{ O, O, O, O }
			},
			// SQUARE
			{ 
				{ I, I, O, O },
				{ I, I, O, O },
				{ O, O, O, O },
				{ O, O, O, O }
			}
		};
		
		long lastTime_yLevel = 0;
		long lastTime_rotate = 0;
		long lastTime_move = 0;
		long lastTime_music = 0;

		// Colours of blocks
		final int[] colours = {
			0xFFFFCC, // Pastel Greenish
			0xFF9900, // Orangey
			0x00CCFF, // Light blue
			
			0xFF0000,
			0x00FF00,
			0x0000FF,
			
			0xFFFF00,
			0x00FFFF,
			
			0xFF00FF
		};
		
		// Names of the pieces
		final String[] pieceNames = { "L BLOCK", "R. L BLOCK", "LINE PIECE!", "T BLOCK", "SQUIGGLY", "R. SQUIGGLY", " SQUARE" };
		
		Random r = new Random();
		
		int nextPieceNum = r.nextInt(7);
		int curPieceNum = nextPieceNum;
		
		float downSpeed = 1;
		
		Stack<Point> fillStack = new Stack<Point>();
		ArrayBlockingQueue<Integer> yQueue = new ArrayBlockingQueue<Integer>(HEIGHT);
		
		// Set up the graphics stuff, double-buffering.
		final BufferedImage screen = new BufferedImage(500, 470, BufferedImage.TYPE_INT_RGB);
		Graphics g = screen.getGraphics();
		final Graphics appletGraphics = (Graphics) getGraphics();
		
		String courierNew = "Courier New";
		
		Font font = new Font(courierNew, Font.PLAIN, 12);
		Font fontMedium = new Font(courierNew, Font.PLAIN, 28);
		g.setFont(font);
		//g.setFont(new Font("", Font.PLAIN, 18));
		
		boolean gameOver = true;
		
		boolean[][] nextPiece = new boolean[4][4];
		
		//float xOffset = 0.0f, yOffset = 0.0f;
		
		/* SOUND */
		
		boolean playMusic = true;

		final String tetrisSong = "\uc080hc\u001fc`\u0011ad\u000fcf!da\u0010`c\u000f\\a!a\\\u0010da\u0010dh cf\u0010ad\u0010c`0ad\u0010fc hc da a\\ a\\?\u0011f] ai\u0010dm\u0010d\u0008d\u0008ck\u0010ai\u0010_h0d\\\u0010h_\u0010a\u0008_\u0008f]\u0010\\d\u0010`c `c\u0010da\u0010cf hc ad \\a \\a?\u0001hc `c\u0010da\u0010fc da\u0010`c\u0010a\\ a\\\u0010ad\u0010hd fc\u0010ad\u0010`c0ad\u0010cf ch ad a\\ \\a?\u0011]f ia\u0010md\u0010d\u0008d\u0008kc\u0010ai\u0010h_0\\d\u0010_h\u0010a\u0008_\u0008]f\u0010d\\\u0010c` `c\u0010ad\u0010fc ch ad \\a a\\?\u0001a\u0010h\u0010a\u0010h\u0010a\u0010h\u0010a\u0010h\u0010`\u0010h\u0010`\u0010h\u0010`\u0010h\u0010`\u0010h\u0010a\u0010h\u0010a\u0010h\u0010a\u0010h\u0010a\u0010h\u0010`\u0010h\u0010`\u0010h\u0010`\u0010h\u0010`\u0010h\uc081D\u000fP\u0010D\u0011P\u000fD\u0010P\u0011D\u0010P\u000fI\u0011U\u0010I\u0010U\u0010I\u0010U\u0010I\u0010U\u0010H\u0010T\u0010H\u0010T\u0010D\u0010P\u0010D\u0010P\u0010I\u0010U\u0010I\u0010U\u0010I\u0010U\u0010K\u0010L\u0010N\u0010B B B\u0008B\u0008I\u0010E\u0010@\u0010L L @\u0008A\u0008B\u0010C W W P T\u0010I\u0010P\u0010I\u0010P\u0010I?\u0001D\u0010P\u0010D\u0010P\u0010D\u0010P\u0010D\u0010P\u0010I\u0010U\u0010I\u0010U\u0010I\u0010U\u0010I\u0010U\u0010H\u0010T\u0010H\u0010T\u0010D\u0010P\u0010D\u0010P\u0010I\u0010U\u0010I\u0010U\u0010I\u0010U\u0010K\u0010L\u0010N\u0010B B B\u0008B\u0008I\u0010E\u0010@\u0010L L @\u0008A\u0008B\u0010C W W P T\u0010I\u0010P\u0010I\u0010P\u0010I\uc082\u000fH!H\u001fH\u0008H\u0019H H H H\u0010H\u0010H H H H\u0008H\u0018H H H H\u0010H\u0010H H H H\u0008H\u0018H H H H\u0010H\u0010H H H H\u0008H\u0018H H H H\u0010H\u0010H H H H\u0008H\u0018H H H H\u0010H\u0010H H H H\u0008H\u0018H H H H\u0010H\u0010H H H H\u0008H\u0018H H H H\u0010H\u0010H H H H\u0008H\u0018H H H H\u0010H\u0010H H H H\u0008H\u0018H H H H\u0010H\u0010H H H H\u0008H\u0018H H H H\u0010H\u0010H";

		final String hitBottom = "\uc086\u005E"; 
		final String clearRow = "\u0086\u0067"; // \u0086\u0067\u0086\u006b\u0086\u0010\u0086\u0067\u0086\u0069  // "\uc086\u005A\uc086\u005E\uc086\u005F"; // "\u0084\u005D";
		final String fail = "\uc084\u004C"; //\u004C\uc084\u0035\uc084\u0021";
		final String rotate = "\uc086\u004E";
		
		AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true,true);
		SourceDataLine dest = null;
		try {
			dest = AudioSystem.getSourceDataLine(format, null);
			dest.open(format, 4096);
		} catch (Exception e) {}
		dest.start();

		// calculate out buffer size and allocate
		int samplesPerStep = SAMPLE_RATE/STEPS_PER_SEC;
		byte[] outBuffer = new byte[samplesPerStep*2];
		
		// load in the synth config. The config is just a bunch of 14bit fixed point floats.
		String[] fields = config.split("\uc0ff"); // uc0ff is used as a field delimiter
		int field = 0;
		for(int i1 = 0; i1 < channels.length; i1++){
			for(int j = 0; j < 5; j++){
				String env = fields[field++];
				envelopes[i1][j] = new float[env.length()/2];
				for(int k = 0; k < env.length()/2; k++){
					envelopes[i1][j][k] = (env.charAt(k*2) << 7 | env.charAt(k*2+1))/1024.0f;
				}
			} 
			String filter = fields[field++];
			for(int j = 0; j < 4; j++){
				channels[i1][j] = (filter.charAt(j*2) << 7 | filter.charAt(j*2+1))/1024.0f;
			}
			channels[i1][TIME] = 100.0f;
		}
		
		int rand = 0;
		long sleepTime = 0;
		boolean playedEnding = true;

		// Colour of blocks
		int blockColour = 1;
		int nextColour = colours[r.nextInt(colours.length)];
		
		final String name = "Elliot Walmsley";
		final String game = "Tetris4k";

		/* 
		 * 
		 * 
		 * MAIN LOOP 
		 * 
		 * 
		 */
		
		while (true) {
			
			/* WAIT FOR INPUT BEFORE STARTING */
				
			for (int i = 0; i < 255; i++) {
				if (keys[i]) {
					
					// Reset Game
					
//					System.out.println("RESET GAME");
					
					gameOver = false;
					
					xPiece = WIDTH / 2 - 2;
					yPiece = 0;
	
					grid = new int[WIDTH][HEIGHT]; 
					
//					for (int i2 = HEIGHT - 1; i2 > 6; i2--) {
//						for (int i1 = 0; i1 < WIDTH; i1++) {
//							grid[i1][i2] = true;
//						}
//					}
					
					newPiece = true;
					nextPieceNum = r.nextInt(7);
					curPieceNum = nextPieceNum;
	
					score = 0;
					level = 0;
					levelInc = 0;
					
					sleepTime = 0;
					
					// Grab an audio line to write to.
					format = new AudioFormat(SAMPLE_RATE, 16, 1, true,true);
					dest = null;
					try {
						dest = AudioSystem.getSourceDataLine(format, null);
						dest.open(format, 4096);
					} catch (Exception e) {}
					dest.start();

					// calculate out buffer size and allocate
					samplesPerStep = SAMPLE_RATE/STEPS_PER_SEC;
					outBuffer = new byte[samplesPerStep*2];
					
					// load in the synth config. The config is just a bunch of 14bit fixed point floats.
					fields = config.split("\uc0ff"); // uc0ff is used as a field delimiter
					field = 0;
					for(int i1 = 0; i1 < channels.length; i1++){
						for(int j = 0; j < 5; j++){
							String env = fields[field++];
							envelopes[i1][j] = new float[env.length()/2];
							for(int k = 0; k < env.length()/2; k++){
								envelopes[i1][j][k] = (env.charAt(k*2) << 7 | env.charAt(k*2+1))/1024.0f;
							}
						} 
						String filter = fields[field++];
						for(int j = 0; j < 4; j++){
							channels[i1][j] = (filter.charAt(j*2) << 7 | filter.charAt(j*2+1))/1024.0f;
						}
						channels[i1][TIME] = 100.0f;
					}
					rand = 0;
				}
			}
			
			/* 
			 * 
			 * 
			 * MAIN Gameplay loop
			 * 
			 * 
			 */
			
			
			while (!gameOver) {
				
				/* SOUND */
				
				if (cmds.isEmpty() && playedEnding) {
					play(tetrisSong);
				}
				
				// main synth loop.
				// Step the sequencer, there are approximately 64 steps per second.
				synchronized(this){
					
					// process any new commands that have been queued up, this has to be synchronized because it will probably be from another thread.
					// the queue is continuously polled until we processed all commands for this step.
					while(!cmds.isEmpty() && (cmds.peek() >> 12) == step){
						int cmd = cmds.poll();
						// the step is the most significant 20bits the next 6 bits are for the channel and the final 6 the note value.
						int chan = (cmd >> 8) & 0x07; 
						channels[chan][FREQ] = 440.0f*(float)Math.pow(2, ((cmd & 0x3F) - 33)/12.0);
						channels[chan][TIME] = 0.0f;
					}
				}
				if (playMusic)
					step++;
				
				// Step the synthesizer by the number of samples in 1 step.
				for(int i = 0; i < outBuffer.length/2; i++){
					float out = 0.0f;
					for(int j = 0; j < channels.length; j++){
						// Each channel is basically just a self-fm modulated sine wave and noise through a filter.
						// All the parameters (fm amount, filter cutoff, frequency etc. are linear envelopes.
						float chanOut = 0.0f;
						float[] channel = channels[j];
						channel[OSC_ENV] = evalEnv(envelopes[j][0], channel[TIME], channel[OSC_ENV]);
						channel[FB_ENV] = evalEnv(envelopes[j][1], channel[TIME], channel[FB_ENV]);
						channel[FREQ_ENV] = evalEnv(envelopes[j][2], channel[TIME], channel[FREQ_ENV]);
						channel[FILT_ENV] = evalEnv(envelopes[j][3], channel[TIME], channel[FILT_ENV]);
						channel[NOISE_ENV] = evalEnv(envelopes[j][4], channel[TIME], channel[NOISE_ENV]);
						rand = (rand*16598013 + 2820163)%16777216;
						float noise = (rand/(float)16777216 - 0.5f)*channel[NOISE_ENV];
						
						// Self modulated sine wave, produces something close to a saw wave with the amount of feedback
						// controlling the band limiting.
						float osc = ((float)Math.sin(channel[PHASE]*Math.PI*2.0 + channel[OSC_FB]*channel[FB_ENV])); 
						// Low pass filter the feedback (important for good results).
						channel[OSC_FB] = (osc + channel[OSC_FB])*0.5f;
						osc *= channel[OSC_ENV];
						
						// State variable filter has high pass, low pass and band pass available simultaneously.
						// We just have a fixed combination of these for each channel.
						float f = channel[FILT_ENV];
						float lp = channel[FILTER_Z2] + f*channel[FILTER_Z1];
						float hp = (osc + noise) - lp - channel[FILTER_RES]*channel[FILTER_Z1];
						float bp = channel[FILTER_Z1] + f*hp;
						chanOut = channel[FILTER_LP]*lp + channel[FILTER_BP]*bp + channel[FILTER_HP]*hp;
						channel[FILTER_Z1] = bp;
						channel[FILTER_Z2] = lp;
						channel[TIME] += 1.0f/SAMPLE_RATE;
						
						// increment  oscillator phase and wrap
						channel[PHASE] += (channel[FREQ]*channel[FREQ_ENV])/SAMPLE_RATE;
						if(channel[PHASE] > 1.0f){ channel[PHASE] -= 1.0f; }
						
						// Hack, sound effects aren't loud enough, should fix in evelopes.
						if (j >= 5) chanOut*=2.0f;
						
						out += chanOut;
					}
					short outS = (short)(out*5000);
					outBuffer[i*2 + 1] = (byte)(outS & 0xFF);
					outBuffer[i*2] = (byte)(outS >> 8);
				}

				dest.write(outBuffer, 0, outBuffer.length);
				
				if (!playedEnding && sleepTime < System.currentTimeMillis() - 2500) {
					playedEnding = true;
					gameOver = true;
//					System.out.println("Stop ending");
				}
				
				if (playedEnding) {
					
					//System.out.println("logic");
					
					/* LOGIC */
					
					// Set up new piece
					if (newPiece) {
						
						// Make currentPiece the block of nextPieceNum
						for (int y = 0; y < 4; y++) {
							for (int x = 0; x < 4; x++) {
								currentPiece[x][y] = pieces[nextPieceNum][y][x]; // Flip
							}
						}
						
						// Set current piece
						curPieceNum = nextPieceNum;
						// Calculate next piece number
						nextPieceNum = r.nextInt(7);
						
						// New Block colours
						blockColour = nextColour;
						nextColour = colours[r.nextInt(colours.length)];
						
						// Set up the nextPiece grid
						nextPiece = new boolean[4][4];
						for (int y = 0; y < 4; y++) {
							for (int x = 0; x < 4; x++) {
								nextPiece[x][y] = pieces[nextPieceNum][y][x]; // Flip
							}
						}
						
						yPiece = 0;
						xPiece = WIDTH/2 - 1;
						
						// Check for gameover if top row has a piece on it
						for (int x = 0; x < WIDTH; x++) {
							if (grid[x][0] != 0) {
								justEnded = true;
								playedEnding = false;
							}
						}
						
						// Check for gameover if new piece is placed on a grid piece
						for (int y = 0; y < 4; y++) {
							for (int x = 0; x < 4; x++) {
								if (currentPiece[x][y])
									if (grid[xPiece + x][yPiece + y] != 0) {

										justEnded = true;
										playedEnding = false;
									}
							}
						}
						
						// If it's not gameover, draw piece to grid
						for (int y = 0; y < 4; y++) {
							for (int x = 0; x < 4; x++) {
								if (currentPiece[x][y])
									grid[xPiece + x][yPiece + y] = blockColour; //currentPiece[x][y];
							}
						}
						
						newPiece = false;
					}
					
					/* 
					 * 
					 * Keyboard entry
					 * 
					 */
					
					// Music On/Off
					
					if (keys[77] && System.currentTimeMillis() - lastTime_music > 300) {
						playMusic = !playMusic;
						lastTime_music = System.currentTimeMillis();
					}
					
					/* ROTATE BLOCK */
					
					if ((keys[87] || keys[38] || keys[32]) && System.currentTimeMillis() - lastTime_rotate > 150) {
						
						lastTime_rotate = System.currentTimeMillis();
						
						// Remove piece from grid
						for (int y = 0; y < 4; y++) {
							for (int x = 0; x < 4; x++) {
								if (currentPiece[x][y])
									grid[xPiece + x][yPiece + y] = 0;
							}
						}
						
						boolean[][] temp = new boolean[4][4];
						
						// Save this incase we dont want to rotate it (collision with wall)
						boolean[][] saveCurPiece = currentPiece.clone();
						
						// Rotate piece 90 degrees clockwise and put into temp
						for (int y = 0; y < 4; y++) {
							for (int x = 0; x < 4; x++) {
								temp[x][y] = currentPiece[y][3-x];
							}
						}
						
						// Move it to the lowest-left corner
						
						// Shift left
						int shift = -1;
						int space = 0;
						while (shift == -1 && space < 4) { // Get the amount of space to shift
							for (int i = 0; i < 4; i++) {
								if (temp[space][i]) {
									shift = space;
									continue;
								}
							}
							space++;
						}
		//				System.out.println("Shift amount: " + shift);
						boolean[][] temp2 = new boolean[4][4];
						// if (shift > 0)
						// Shift into temp array
						for (int y = 0; y < 4; y++) {
							for (int x = 0; x < 4 - shift; x++) {
								temp2[x][y] = temp[x + shift][y];
							}
						}
						// Put temp2 in temp
						temp = temp2.clone();
						
						// Check if it would go out of bounds before replacing
						
						// Check if it would go out the right side
						boolean switchPieces = true;
						for (int y = 0; y < 4; y++) {
							for (int x = 0; x < 4; x++) {
								if (temp[x][y]) {
									if (x + xPiece > WIDTH - 1) {
										switchPieces = false;
									}
								}
							}
						}
						
						// Check if it would go below
						for (int y = 0; y < 4; y++) {
							for (int x = 0; x < 4; x++) {
								if (temp[x][y]) {
									if (y + yPiece > HEIGHT - 1) {
										switchPieces = false;
									}
								}
							}
						}
						
						// Check if it would intersect other blocks
						if (switchPieces) {
							for (int y = 0; y < 4; y++) {
								for (int x = 0; x < 4; x++) {
									if (temp[x][y]) {
										if (grid[xPiece + x][yPiece + y] != 0) {
											switchPieces = false;
										}
									}
								}
							}
						}
						
						// Maybe switch pieces
						currentPiece = saveCurPiece.clone();
						if (switchPieces) {
							play(rotate);
							currentPiece = temp.clone();
							
						}
						
						// Place current piece
						for (int y = 0; y < 4; y++) {
							for (int x = 0; x < 4; x++) {
								if (currentPiece[x][y])
									grid[xPiece + x][yPiece + y] = blockColour; //currentPiece[x][y];
							}
						}
					}
					
					downSpeed = 1.0f;
					if (keys[83] || keys[40]) { // Down
						downSpeed = 0.1f;
					}
					
					/* MOVE LEFT */
					
					if ((keys[65] || keys[37]) && System.currentTimeMillis() - lastTime_move > 100) {
						
						lastTime_move = System.currentTimeMillis();
						
						// Remove piece from grid
						for (int y = 0; y < 4; y++) {
							for (int x = 0; x < 4; x++) {
								if (currentPiece[x][y])
									grid[xPiece + x][yPiece + y] = 0; //false;
							}
						}
						
						// Check if all rows have space to move left
						boolean canMoveLeft = true;
						for (int y = 0; y < 4; y++) {
							// Get the furthest left block
							int x = 0;
							int leftBlockX = -1;
							while (x < 4 && leftBlockX == -1) {
								if (currentPiece[x][y]) {
									leftBlockX = x;
								}
								x++;
							}
							
							if (leftBlockX != -1) {
								// Check grid for piece left of furthest left piece
								if (xPiece + leftBlockX - 1 >= 0) {
									if (grid[xPiece + leftBlockX - 1][yPiece + y] != 0) {
										canMoveLeft = false;
									}
								} else {
									canMoveLeft = false;
								}
							}
						}
						
						if (canMoveLeft) { xPiece--; }
						
						// Place current piece
						for (int y = 0; y < 4; y++) {
							for (int x = 0; x < 4; x++) {
								if (currentPiece[x][y])
									grid[xPiece + x][yPiece + y] = blockColour; //currentPiece[x][y];
							}
						}
						
					}
					
					
					/* MOVE RIGHT */
					
					if ((keys[68] || keys[39]) && System.currentTimeMillis() - lastTime_move > 100) {
						
						lastTime_move = System.currentTimeMillis();
						
						// Remove piece from grid
						for (int y = 0; y < 4; y++) {
							for (int x = 0; x < 4; x++) {
								if (currentPiece[x][y])
									grid[xPiece + x][yPiece + y] = 0; //false;
							}
						}
						
						// Check if all rows have space to move right
						boolean canMoveRight = true;
						for (int y = 0; y < 4; y++) {
							// Get the furthest right block
							int x = 3;
							int rightBlockX = -1;
							while (x >= 0 && rightBlockX == -1) {
								if (currentPiece[x][y]) {
									rightBlockX = x;
								}
								x--;
							}
							
							if (rightBlockX != -1) {
								// Check grid for piece right of furthest right piece
								if (xPiece + rightBlockX + 1 < WIDTH) {
									if (grid[xPiece + rightBlockX + 1][yPiece + y] != 0) {
										canMoveRight = false;
									}
								} else {
									canMoveRight = false;
								}
							}
						}
						
						if (canMoveRight) { xPiece++; }
						
						// Place current piece
						for (int y = 0; y < 4; y++) {
							for (int x = 0; x < 4; x++) {
								if (currentPiece[x][y])
									grid[xPiece + x][yPiece + y] = blockColour; //currentPiece[x][y];
							}
						}
					}
		
					
					/* Move DOWN periodically */
					
					if (System.currentTimeMillis() - lastTime_yLevel > ((500.0f - ((float) (level) * 40.0f)) * downSpeed) && yQueue.isEmpty()) {
						
						// Remove piece from grid
						for (int y = 0; y < 4; y++) {
							for (int x = 0; x < 4; x++) {
								if (currentPiece[x][y])
									grid[xPiece + x][yPiece + y] = 0; //false;
							}
						}
						
						boolean canMoveDown = true;
						
						// Check each column in the currentPiece to see if
						// the block can move down
						
						//for (int col = 0; col < 4; col++) {
						int col = 0;
						while (canMoveDown && col < 4) {
							
							// Find bottom
							int y = 3;
							int bot = -1;
							while (y >= 0 && bot == -1) {
								if (currentPiece[col][y]) {
									bot = y;
								}
								y--;
							}
		//					System.out.println(col + ": " + bot);
							
							if (bot != -1) {
								// Find the next block down
								
								/*
								    0: 3
									1: -1
									2: -1
									3: 0
								 */
								
								if (bot + yPiece + 1 < HEIGHT) {
		//							System.out.println("bot + yPiece + 1: " + (bot + yPiece + 1) + " col: " + col);
									if (grid[xPiece + col][bot + yPiece + 1] != 0) {
										canMoveDown = false;
		//								System.out.println("block found below col " + col + " at y: " + (bot + yPiece + 1));
										//break;
									}
								} else {
									canMoveDown = false;
		//							System.out.println("Hit bottom");
									//break;
								}
							}
							
							col++;
						}
						
						if (canMoveDown) {
							
							yPiece++;
							
							for (int y = 0; y < 4; y++) {
								for (int x = 0; x < 4; x++) {
									if (currentPiece[x][y]) // If there is a block
										grid[xPiece + x][yPiece + y] = blockColour; //currentPiece[x][y]; // Paste the block on the grid
								}
							}
							
						} else {
	
							// Increment score when a piece hits the ground
							score++;
							
							// Inc level every 15 pieces
							levelInc++;
							if (levelInc > 15) {
								level++;
								levelInc = 0;
							}
							
							// Transfer the currentPiece to the grid
							for (int y = 0; y < 4; y++) {
								for (int x = 0; x < 4; x++) {
									if (currentPiece[x][y]) // If there is a block
										grid[xPiece + x][yPiece + y] = blockColour; //currentPiece[x][y]; // Paste the block on the grid
								}
							}
							
							// Check for full lines, and remove
							boolean removed = false;
							for (int y = 0; y < HEIGHT; y++) {
								boolean filled = true;
								for (int x = 0; x < WIDTH; x++) {
									if (grid[x][y] == 0) {
										filled = false;
									}
								}
								
								if (filled) {
									removed = true;
									
									score += 10;
	//								for (int y2 = y; y2 > 0; y2--) {
	//									for (int x = 0; x < WIDTH; x++) {
	//										grid[x][y2] = grid[x][y2 - 1];
	//									}
	//								}
									for (int x = 0; x < WIDTH; x++) {
										fillStack.add(new Point(x, y));
									}
									yQueue.add(y);
	//								System.out.println("Added: " + y);
								}
							}
							if (removed) {
								play(clearRow);
							} else {
								play(hitBottom);
							}
							
							newPiece = true;
						}
						
						lastTime_yLevel = System.currentTimeMillis();
					}
					
					if (!fillStack.isEmpty()) {
						Point p = fillStack.pop();
						grid[p.x][p.y] = 0;
						
						if (fillStack.isEmpty()) {
							
							// Remove piece from grid
							for (int y = 0; y < 4; y++) {
								for (int x = 0; x < 4; x++) {
									if (currentPiece[x][y])
										grid[xPiece + x][yPiece + y] = 0;
								}
							}
							
							// Clear the rows after animation
							while (!yQueue.isEmpty()) {
								int y3 = yQueue.poll();
								for (int y2 = y3; y2 > 0; y2--) {
									for (int x = 0; x < WIDTH; x++) {
										grid[x][y2] = grid[x][y2 - 1];
									}
								}
	//							System.out.println("Clearing: " + y3);
							}
							
							// Transfer the currentPiece to the grid
							for (int y = 0; y < 4; y++) {
								for (int x = 0; x < 4; x++) {
									if (currentPiece[x][y]) // If there is a block
										grid[xPiece + x][yPiece + y] = blockColour; //currentPiece[x][y]; // Paste the block on the grid
								}
							}
							
						}
						
//						beforeY = p.y;
					}
					
	//				for (int y2 = y; y2 > 0; y2--) {
		//				for (int x = 0; x < WIDTH; x++) {
		//					grid[x][y2] = grid[x][y2 - 1];
		//				}
		//			}
				}
				
				/* DRAW */
				
				// Clear
	            g.setColor(new Color(WHITE)); // Black background
	            g.fillRect(0, 0, 800, 600);
				
//	            // Draw moving Background
//	            yOffset += 0.8f; //r.nextFloat() - 0.1f;
//	            xOffset += 0.8f; //r.nextFloat() - 0.1f;
//	            g.setColor(new Color(DARKER));
//	            for (int y = 0; y < 10; y++) {
//	            	for (int x = 0; x < 5; x++) {
//	            		g.drawOval((int) ((x * 50) + xOffset), (int) ((y * 50) + yOffset), 25, 25);
//	            	}
//	            }
	            
				// Draw Background
				g.setColor(new Color(MIDDLEGROUND));
				for (int y = 0; y < HEIGHT; y++) {
					for (int x = 0; x < WIDTH; x++) {
						g.drawRect(LEFT + PADDING + ((MARGIN + BOXWIDTH) * x), TOP + PADDING + ((MARGIN + BOXWIDTH) * y), BOXWIDTH, BOXWIDTH);
					}
				}
				
				// Draw Blocks
				
				for (int y = 0; y < HEIGHT; y++) {
					for (int x = 0; x < WIDTH; x++) {
						if (grid[x][y] != 0) {
//							g.setColor(new Color(DARKER));
//							g.fillRect(LEFT + PADDING + ((MARGIN + BOXWIDTH) * x) + 1, TOP + PADDING + ((MARGIN + BOXWIDTH) * y) + 1, BOXWIDTH - 1, BOXWIDTH - 1);
//							g.setColor(new Color(LIGHT));
//							g.fillRect(LEFT + PADDING + ((MARGIN + BOXWIDTH) * x) + 3, TOP + PADDING + ((MARGIN + BOXWIDTH) * y) + 3, BOXWIDTH - 5, BOXWIDTH - 5);
							
							g.setColor(new Color(BLACK));
							g.fillRect(LEFT + PADDING + ((MARGIN + BOXWIDTH) * x) - 2, TOP + PADDING + ((MARGIN + BOXWIDTH) * y) - 2, BOXWIDTH + 5, BOXWIDTH + 5);
						
							g.setColor(new Color(WHITE));
							g.fillRect(LEFT + PADDING + ((MARGIN + BOXWIDTH) * x), TOP + PADDING + ((MARGIN + BOXWIDTH) * y), BOXWIDTH + 1, BOXWIDTH + 1);
						
							g.setColor(new Color(grid[x][y]));
							g.fillRect(LEFT + PADDING + ((MARGIN + BOXWIDTH) * x) + 1, TOP + PADDING + ((MARGIN + BOXWIDTH) * y) + 1, BOXWIDTH - 1, BOXWIDTH - 1);
						}
					}
				}
				
				// Draw rightbox
				g.setColor(new Color(MIDDLEGROUND));
				g.drawRect(LEFT + RIGHTBOX, TOP + PADDING, RIGHTBOXW, ((MARGIN + BOXWIDTH) * HEIGHT) - MARGIN);
				g.setColor(new Color(WHITE));
				// Bottom and top cut off bits
				g.drawLine(LEFT + RIGHTBOX + 4, TOP + PADDING, LEFT + RIGHTBOX + RIGHTBOXW - 4, TOP + PADDING);
				g.drawLine(LEFT + RIGHTBOX + 4, TOP + PADDING + ((MARGIN + BOXWIDTH) * HEIGHT) - MARGIN, 
						LEFT + RIGHTBOX + RIGHTBOXW - 4, TOP + PADDING + ((MARGIN + BOXWIDTH) * HEIGHT) - MARGIN);
				
				
				// Draw next block
				for (int y = 0; y < 4; y++) {
					for (int x = 0; x < 4; x++) {
						g.setColor(new Color(MIDDLEGROUND));
						g.drawRect(LEFT + RIGHTBOX + ((MARGIN + BOXWIDTH) * x) + 16, TOP + (PADDING * 31) + ((MARGIN + BOXWIDTH) * y), BOXWIDTH, BOXWIDTH);
						if (nextPiece[x][y]) {
							g.setColor(new Color(BLACK));
							g.fillRect(LEFT + RIGHTBOX + ((MARGIN + BOXWIDTH) * x) + 14, TOP + (PADDING * 31) + ((MARGIN + BOXWIDTH) * y) - 2, BOXWIDTH + 5, BOXWIDTH + 5);
						
							g.setColor(new Color(WHITE));
							g.fillRect(LEFT + RIGHTBOX + ((MARGIN + BOXWIDTH) * x) + 16, TOP + (PADDING * 31) + ((MARGIN + BOXWIDTH) * y) + 0, BOXWIDTH + 1, BOXWIDTH + 1);
						
							g.setColor(new Color(nextColour));
							g.fillRect(LEFT + RIGHTBOX + ((MARGIN + BOXWIDTH) * x) + 17, TOP + (PADDING * 31) + ((MARGIN + BOXWIDTH) * y) + 1, BOXWIDTH - 1, BOXWIDTH - 1);
						}
					}
				}
				g.setColor(new Color(MIDDLEGROUND));
				g.drawRect(LEFT + RIGHTBOX + 10, TOP + (PADDING * 30) - 1, SCOREBOXW + 1, SCOREBOXW + 1);
				
				// Draw Music ON/OFF

				final int l = 100;
				final int t = 18;
				final int m = 1;
				
				g.drawString("M", l - 13, t + 10);
				if (playMusic)
					g.setColor(new Color(BLACK));
				final int[] soundXPoints = { l + (0 * m), l + (5 * m), l + (5 * m), l + (0 * m) };
				final int[] soundYPoints = { t + (3 * m) + 1, t + (0 * m) + 1, t + (9 * m) + 1, t + (6 * m) + 1 };
				g.drawPolygon(soundXPoints, soundYPoints, soundXPoints.length);
				g.drawArc(l - 3, t - 2, 14, 15, -45, 90);
				g.drawArc(l + 1, t - 5, 15, 21, -45, 90);
				
				
				g.setColor(new Color(FOREGROUND));
				// Score and Level
				g.drawString("Score", LEFT + RIGHTBOX + 32, TOP + (PADDING * 19) + 2);
				g.drawString("Level", LEFT + RIGHTBOX + 32, TOP + (PADDING * 50) + 10);
				
				g.setFont(fontMedium); //new Font("Courier New", Font.BOLD, 32));
				
				g.drawString(String.valueOf(level), (int) (LEFT + RIGHTBOX + 50 - (20 * ((float) Integer.toString(level).length()/ (float) 2))), TOP + (PADDING * 57) + 12);
				g.drawString(String.valueOf(score),(int) (LEFT + RIGHTBOX + 50 - (20 * ((float) Integer.toString(score).length()/ (float) 2))), TOP + (PADDING * 26) + 4);
				g.setFont(font);
				
				String piece = pieceNames[curPieceNum];
				
				g.drawString(String.valueOf(piece), LEFT + RIGHTBOX + 40 - ((piece.length() * 5)/2),
						TOP + (PADDING * 6) + 8);
	
				// Title

				g.setColor(new Color(FOREGROUND));
				g.drawString(game + " by " + name, LEFT + 46, 27);
				
				appletGraphics.drawImage(screen, 0, 0, null);
				
				
				
	
//				sleeptime = (int) ((float)(1 * 1000)/(float)(60)); // 60 FPS is target
//				try { Thread.sleep(sleeptime); } catch (Exception e) { }
				
				if (justEnded) {
				
					cmds.clear();
//					dest.stop();
//					dest.close();
					
					play(fail);
					
					playedEnding = false;
					
					justEnded = false;
	
					sleepTime = System.currentTimeMillis();
					
//					System.out.println("just Ended");
				}
				
				if (!isActive()) {
					dest.stop();
					dest.close();
					return;
				}
				
				
			} // Game loop
			
            g.setColor(new Color(WHITE)); // Black background
            g.fillRect(0, 0, 500, 470);
            
			// Draw Background Blocks
			g.setColor(new Color(MIDDLEGROUND));
			for (int y = 0; y < HEIGHT; y++) {
				for (int x = 0; x < WIDTH; x++) {
					g.drawRect(LEFT + PADDING + ((MARGIN + BOXWIDTH) * x), TOP + PADDING + ((MARGIN + BOXWIDTH) * y), BOXWIDTH, BOXWIDTH);
				}
			}
			
			// Draw rightbox
			g.setColor(new Color(MIDDLEGROUND));
			g.drawRect(LEFT + RIGHTBOX, TOP + PADDING, RIGHTBOXW, ((MARGIN + BOXWIDTH) * HEIGHT) - MARGIN);
			g.setColor(new Color(WHITE));
			// Bottom and top cut off bits
			g.drawLine(LEFT + RIGHTBOX + 4, TOP + PADDING, LEFT + RIGHTBOX + RIGHTBOXW - 4, TOP + PADDING);
			g.drawLine(LEFT + RIGHTBOX + 4, TOP + PADDING + ((MARGIN + BOXWIDTH) * HEIGHT) - MARGIN, 
					LEFT + RIGHTBOX + RIGHTBOXW - 4, TOP + PADDING + ((MARGIN + BOXWIDTH) * HEIGHT) - MARGIN);
            
			g.setColor(new Color(FOREGROUND));
			g.drawString("by " + name, LEFT + 82, 147);
			g.setFont(new Font(courierNew, Font.BOLD, 64));
			g.drawString(game, 80, 127);
			g.setFont(font);
			g.drawString("www.crustycabbage.com", LEFT + 68, 326);
			g.setFont(fontMedium);
			g.drawString("PRESS ANY KEY TO PLAY", 68, 238);
			g.setFont(font);
			
			appletGraphics.drawImage(screen, 0, 0, null);
			
			try {
				Thread.sleep(16);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			
		} // Main Loop

	}

	/*
	 * Each character of cmd is a 7 bit command. If bit 7 is set then it is a note, otherwise
	 * a delay. If the 8th bit is set it is a channel switch command and all following commands
	 * are assumed to operate on that channel.
	 */
	public final synchronized void play(String cmd){
		int step = this.step;
		int chan = 0;
		for(int i = 0; i < cmd.length(); i++){
			int c = cmd.charAt(i);
			if (c >= 0x80){
				// channel switch
				chan = c & 0x07;
				step = this.step;
			} else if (c >= 0x40){
				// note on
				cmds.add((step << 12) | (chan << 8) | c);
			} else {
				// delay;
				step += c;
			}
		}
	}
	
	/*
	 * Evaluate the envelope at time t with last value. The env are time/value pairs
	 * and the envelope linearly interpolates between them.
	 */
	public final float evalEnv(float[] env, float t, float last){
		int s1 = 1;
		while(s1 < env.length/2-1 && env[s1*2] < t) s1++;
		int s0 = s1-1;
		s0 *= 2;
		s1 *= 2;
		float dt = (t - env[s0])/(env[s1] - env[s0]);
		if (dt < 0) dt = 0;
		if (dt > 1) dt = 1;
		return (env[s0+1]*(1-dt) + (env[s1+1])*dt)*0.1f + last*0.9f;
	}
	
	public final void processEvent(AWTEvent e) {

		/* KEYBOARD */
		
//		KeyEvent ke = (KeyEvent) e;
//		
//		if (ke.getID() == Event.KEY_PRESS) {
//			int key = ke.getKeyCode();
//			if (key >= 0 && key < 256 && !keys[key]) {
//				keys[key] = true;
//			}
//			
//		} else if (e.getID() == Event.KEY_RELEASE) {
//			int key = ke.getKeyCode();
//			if (key >= 0 && key < 256 && keys[key])
//				keys[key] = false;
//		}
		
		boolean down = false;
		
		if (((KeyEvent) e).getKeyCode() < 256)
			switch (e.getID()) {
			
				case KeyEvent.KEY_PRESSED:
					down = true;
				case KeyEvent.KEY_RELEASED:
					keys[((KeyEvent) e).getKeyCode()] = down;
					
			}
		
	}
	
}
	



