/**   Columns
 *    by Linh Yao Pham 2002
 *
 *    This is my first attempt at a Java applet -- basically a puzzle game where your goal
 *    is to create sets of 3 colors in a row while rearranging columns that fall from the sky
 *
 *    Special thanks to Eric Idema for sharing his Tetris code, which proved to be an excellent starting point
 *    for my own app
*/

import java.awt.*;
import java.applet.*;
import Col;     // My special class for the columns  see Col.java in same dir
import java.lang.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.*;

public class Columns extends Applet implements Runnable{

    byte[][] field;
    Col col, nextCol;
    Thread engine;
    String[] rawScores;
    int NUMSCORES = 10;
    String[][] topScores = new String[NUMSCORES][2];
    Panel namePanel;
    Label nameLabel;
    TextField nameTextField;
    Button submitPlayerName;
    String playerName = "No Name Given";
    boolean serverUp = true;

    // Use for double buffering. The "frozenguy_buffer" is where we paint the blocks that are
    // frozen on the screen, and as such, doesn't need to be updated every frame.
    Image buffer, frozenguy_buffer;   
    
    boolean gameOn, gameOver, update_field = true;   
    
    boolean poof = false, aniwait = false;   // This is flag we use to tell the game
    // that we're in "make the stuff disappear" mode
    
    private static final byte REMOVE_FLAG = 64;  // The byte we use to mark blocks
    // that are eventually removed
    
    int level=0, points=0;
    int count = 1;

    public void init() {
	this.setLayout(new BorderLayout());
	gameOn = false;
	gameOver = false;
	
	buffer = createImage(400,450);
	frozenguy_buffer = createImage(400,450);
	
	field = new byte[8][18];  // The field where all the blocks are falling
	engine = new Thread(this);
	try{
	    topScores = getScores();
	}
	catch(NullPointerException e)
	    {
		serverUp = false;
	    }
	
	if(serverUp){
	    nameLabel = new Label("Please enter your name:",Label.CENTER);
	    nameTextField = new TextField(15);
	    submitPlayerName = new Button("Submit") { 
		    public boolean action(Event event, Object object){
			playerName = nameTextField.getText();
			namePanel.hide();
			return true;
		    }
		};
	    namePanel = new Panel();
	    namePanel.add(nameLabel);
	    namePanel.add(nameTextField);
	    namePanel.add(submitPlayerName);
		add(namePanel,BorderLayout.CENTER);
	}
	
    }
    public void gameStart() {
	gameOver = false;

	// Clear out the field
	
	for (int i=0; i<8; i++) {
	    for (int j=0; j<18;j++) {
		field[i][j]=0;
	    }
	}
	
	
	update_field = true;   // This tells the update method that the field has actually changed.
	
	// Get the new shapes
	col = new Col();
	nextCol = new Col();
	
	repaint();  // Calls the update method 
	
	if (!gameOn) {
	    gameOn = true;
	    engine.start();
	}
	else {
	    //	    scorePanel.show();
	    count++;
	    init();
	    engine.resume();   // If we're starting a new game after playing one -- the engine's
	    // already started
	}
	
    }
    
    public void stop() {
	engine.suspend();
    }
    
    public boolean keyDown(Event e, int keyCode){
	if (gameOver) return true;   // We don't care what key you hit when the game is over
	if (poof) return true;	     // Game is essentially paused in poof mode.
	
	if (keyCode == Event.LEFT || keyCode == 'j') {
	    col.moveLeft(field);
	    repaint();
	    return true;
	    
	}
	
	else if (keyCode == Event.RIGHT || keyCode == 'l') {
	    col.moveRight(field);
	    repaint();
	    return true;
	}
	
	else if (keyCode == Event.UP || keyCode == 'i') {
	    col.rotate();
	    repaint();
	    return true;
	}
	
	else if (keyCode == Event.DOWN || keyCode == 'k' || keyCode == ' ') {
	    while (col.haveRoom(field)) {
		col.drop();
	    }
	    nextPiece();		       
	    repaint();		
	    return true;
	}

	
	return false;
    }
    
    
    public void nextPiece()  {
	
	// This function will add the current shape to the field, make sure the game isn't over,
	// check for lines to remove, and then start the next piece.
	
	if (col.checkGameOver()) {  // Basically, not enough room for the entire column on the screen
	    gameOver = true;
	    repaint();
	    stop();
	} else {
	    col.freeze(field);  // Adds the current shape to the field
	    
	    col = nextCol;
	    
	    checkForLines();
	    
	    update_field = true;
	    
	}
    }		
    
    
    public void run() {
	
	if (!poof)
	    {
		
		// The game is proceeding normally...
		
		if (col.haveRoom(field))  { // Do we have room to fall?
		    col.drop();
		} else {  // Okay, we've hit something
		    nextPiece();
		}
	    } else {
		
		// The game is halted as we draw the lines that are disappearing. Kinda kludgy.
		
		if (aniwait) {  // We want to go one step with the squares-to-be-removed marked differently
		    aniwait = false;
		} else {
		    
				// Remove the lines that are gone, check for new ones
		    
		    removeGaps();
		    update_field = true;
		    poof = false;
		    checkForLines();				
		}
	    }
	repaint();
	try {
	    engine.sleep(750 - 40*level);
	} catch (Exception e) {};
	run();
    }
    
    
    public void paint(Graphics g) {
	if (!gameOn) {
	    g.setColor(Color.black);
	    g.fillRect(0,0,  200,450);
	    g.setColor(Color.red);
	    g.drawString("Columns", 50, 100);
	    g.drawString("Click to start",40,150);
	    if(serverUp){
		paintScores(g);
	    }

	    return;		
	}
	
	if (gameOver) {
	    g.setColor(Color.black);
	    g.fillRect(0,130, 200,60);
	    g.setColor(Color.red);
	    g.drawString("Game over, man!",60,170);
	    
	    if(serverUp){
		if(points > Integer.parseInt(topScores[NUMSCORES-1][1])){
		    columnsClient cs = new columnsClient();
		    while(!cs.sendScore(playerName,points)){
			//wait till the server finishes updating
		    }
		    
		    g.drawString("Hey man!  You're in the top ten scores!",60,190);
		    g.drawString("Just click to see your place on the scores list",60,210);
		g.drawString("Level: " + level, 205,90);
		g.drawString("Points: " + points, 205, 110);
		g.drawString("Next:",205,130);
		paintScores(g);
		}
	    }
	    return;
	}
	
	// First, paint the background playing field into the current graphics context.
	g.drawImage(frozenguy_buffer,0,0,this);
	
	// Next, draw the active column
	col.paint(g);
	
	// Finally, draw a few informative things to the side
	
	g.setColor(Color.black);
	g.drawString("Level: " + level, 205,90);
	g.drawString("Points: " + points, 205, 110);
	g.drawString("Next:",205,130);
	nextCol.paintNext(g);
	
	if(serverUp){
	    paintScores(g);
	}
    }
    

    public void paintScores(Graphics g2)
    {
	Color oldColor = g2.getColor();
	g2.setColor(Color.red);
	g2.drawString("Top Scores",205,300);
	g2.setColor(Color.blue);
	int j = 310;
	for(int i = 0; i < NUMSCORES ; i++){
	    g2.drawString(topScores[i][0],200,j);
	    g2.drawString(topScores[i][1],350,j);
	    j+=10;
	}
	g2.setColor(oldColor);
	    
	

    }

    public boolean mouseDown (Event e, int x, int y) {
	if (!gameOn || gameOver) {
	    points = 0; level = 0;
	    gameStart();
	    return true;
	}
	return false;
	
    }
    
    public void update(Graphics g) {
	Graphics gamegraphics = buffer.getGraphics();
	Graphics fieldgraphics = frozenguy_buffer.getGraphics();
	
	// First, if necessary, we update the "field" -- the blocks that have
	// landed. We do this into the frozenguy_buffer.
	
	if (update_field) {
	    paint_field(fieldgraphics);
	    update_field = false;
	}
	
	// Next, we paint into the "gamegraphics" 
	paint (gamegraphics);
	// Once we've painted to the buffer, we copy that into the applet graphics
	g.drawImage(buffer, 0,0, this);
    }
    
    
    
    // This just draws the blocks that are frozen -- not any column that's currently in play
    
    public void paint_field (Graphics g) {
	g.setColor(Color.black);
	g.fillRect(0,0, 200,450);
	
	for (int i = 0; i < 8; i++) {
	    for (int j = 0; j < 18; j++) {
		if (field[i][j] != 0) {
		    
		    // Special case for blocks that are going away
		    
		    if ((field[i][j]& REMOVE_FLAG) == REMOVE_FLAG) {  
			g.setColor((col.findColor((byte)(field[i][j] & 15))).darker());
			g.fillRect(i*25,j*25,25,25);
			g.setColor(Color.red);
			g.drawLine(i*25,j*25,i*25+25,j*25+25);
			g.drawLine(i*25+25,j*25,i*25,j*25+25);
		    } else {
			g.setColor(col.findColor(field[i][j]));
			g.fillRect(i*25,j*25,25,25);
			g.fill3DRect(i*25+4,j*25+4,17,17,true);
		    }
		    
		}
	    }
	}
    }
    
    
    
    public int checkForLines() {
	
	/** A few notes about the checkForLines method...


	 *   It works by going through the entire grid and simply checking for 


	 *   3 of the same color in a row. This has the side effect of


	 *   counting 4 in a row as 2 sets of lines, and 5 in a row as 3 sets of lines.


	 *   Basically, it figures out the bonus points for us!


	 *  (In other words: It's not a bug, it's a feature!)


	 *


	 *   Second,	I go about this by doing 3 sets of loops through the whole matrix.


	 *   Once for vertical, once for horizontal, and once for both diagonal.


	 *   I did this so that I don't have to worry about going out of bounds in the array.


	 *   The other way, of course, is to just go through the whole thing once and use a whole


	 *   bunch of ifs to avoid going out of bounds.


	 *   I figured in the end, there wasn't much of a difference in efficiency. And, although


	 *   the code is bigger this way, it's probably easier for me to read. 


	 */


	
	
	int linescaught = 0;
	
	// CHECK VERTICAL
	
	
	// So, why all the & 15 stuff? Because when we find a match, we need to 
	// mark it. But at the same time, we can't change its color value yet,
	// since it may be used in other lines. So we add a REMOVE_FLAG, but
	// then only compare the first 4 bits for color values.
	
	for (int i=0; i<8; i++) {
	    for (int j=1; j< 17; j++) {
		if ((field[i][j] != 0) &&
		    ((field[i][j] & 15) == (field[i][j-1] & 15)) && 
		    ((field[i][j] & 15) == (field[i][j+1] & 15))) {
		    field[i][j] |= REMOVE_FLAG;
		    field[i][j+1] |= REMOVE_FLAG;
		    field[i][j-1] |= REMOVE_FLAG;
		    linescaught++;	
		}
	    }
	}
	
	// CHECK HORIZONTAL
	
	for (int i=1; i<7; i++) {
	    for (int j=0; j< 18; j++) {
		if ((field[i][j] != 0) &&
		    ((field[i][j] & 15) == (field[i+1][j] & 15)) && 
		    ((field[i][j] & 15) == (field[i-1][j] & 15))) {
		    field[i][j] |= REMOVE_FLAG;
		    field[i+1][j] |= REMOVE_FLAG;
		    field[i-1][j] |= REMOVE_FLAG;
		    linescaught++;	
		}
	    }
	}
	
	// CHECK DIAGONALS / and 
				    
	for (int i=1; i<7; i++) {
	    for (int j=1; j< 17; j++) {
		if ((field[i][j] != 0) &&
		    ((field[i][j] & 15) == (field[i-1][j+1] & 15)) && 
		    ((field[i][j] & 15) == (field[i+1][j-1] & 15))) {
		    field[i][j] |= REMOVE_FLAG;
		    field[i-1][j+1] |= REMOVE_FLAG;
		    field[i+1][j-1] |= REMOVE_FLAG;
		    linescaught++;	
		}
		
		if ((field[i][j] != 0) &&
		    ((field[i][j] & 15) == (field[i-1][j-1] & 15)) && 
		    ((field[i][j] & 15) == (field[i+1][j+1] & 15))) {
		    field[i][j] |= REMOVE_FLAG;
		    field[i-1][j-1] |= REMOVE_FLAG;
		    field[i+1][j+1] |= REMOVE_FLAG;
		    linescaught++;
		}
		
	    }
	}
	
	
	// Did we find something? Cool. Set the "poof" and "aniwait" values to be true


	// this essentially pauses the game while we show the blocks disappearing


	
	if (linescaught > 0) {
	    poof = true; aniwait = true;
	} else {
	    // This might seem like an odd place to put the code to call the next piece.
	    // but I did it here to make sure that the next piece doesn't appear until after
	    // we're done removing blocks from the scene. I found that playability was better
	    // this way.
	    
	    nextCol = new Col();
	}
	
	
	points += linescaught;
	level = (points > 100) ? 10 : points/10;   //Don't go past level 10.
	
	return linescaught;
	
    }
    
    
    /*  RemoveGaps goes through the entire grid, removes any block with the REMOVE_FLAG
     *    and then moves down the rest of the grid accordingly
     */
    
    public void removeGaps () {
	for (int i=0; i<8; i++) {
	    for (int j=0; j< 18; j++) {  // From top to bottom in each column...
		if ((field[i][j]& REMOVE_FLAG) == REMOVE_FLAG) {
		    for (int k = j; k > 0; k--) {
			field[i][k] = field[i][k-1];
		    }
		    field[i][0] = 0;
		}
	    }
	}
    }
    
    public int getPoints()
    {
	return points;
    }

    public String[][] getScores()
    {
	columnsClient cc = new columnsClient();
	columnsServer cs = new columnsServer();
	String scoresString = cc.getTopScores();
	StringTokenizer strtok = new StringTokenizer(scoresString,"&");
	String[] scores = new String[NUMSCORES];
	String[][] scoreLine = new String[NUMSCORES][2];
	try{
	    for(int i = 0; i < NUMSCORES ; i++){
		scores[i] = strtok.nextToken();
		scores[i] += "|";
		scoreLine[i][0] = cs.getPlayer(scores[i]);
		scoreLine[i][1] = cs.getScore(scores[i]);
		if(scoreLine[i][1].equals("-1"))
		   scoreLine[i][1] = "0";
	    }
	}
	catch(NullPointerException e){
	}
	    
	return scoreLine;
	
    }

}



















