/*
 * TetriX.java
 *
 * Created on 4 février 2002, 14:49
 */

package TetriX;

import java.awt.*;

/**
 * @author Olivier Zitvogel - http://www.zitvogel.com - All rights reserved
 */
public class TetriX extends java.applet.Applet
{
    
    //each block is a 16 bit short where each 4 bits represents a 4 cols row
    private short[] blocks =
    {0x0660,0x4460,0x2260,0x04E0,0x4444,0x4620,0x2640,(short)(Math.random()*100)};
    
    //18 rows of 10 cols, we will use only the 10 first bits of the 16 bit short
    private short[] board = new short[21];
    
    //the current block
    private short current=blocks[1];
    
    //the next block
    private short next;
    
    //the current block position (upper left corner)
    private byte XOffset=6;
    private byte YOffset=0;
    
    private long sleepms = 500;
    
    private Thread runner;
    
    //double buff
    private Image offImg;
    private Graphics offG;
    private Graphics screenG;
    
    //ccw rotation (newX = oldY, newY = width-1-oldX, on a short where each 4 bits sequence represents a row, the position is calculated like this X=i%4 and Y=i/4)
    private short rotate(short block)
    {
        short newBlock = 0;
        for(int i=15;i>=0;i--)
            newBlock = (short)(newBlock|(block>>>i&0x0001)<<(((3-i%4)<<2)+(i>>>2)));
        return newBlock;
    }
    
    /* moves
     * 0 = down, 1 = right, 2 = left , 3 rotate returns false when the piece cannot move anymore or rotate*/
    private  boolean move(int direction)
    {
        byte newX=XOffset, newY=YOffset;
        short block = current;
        
        switch(direction)
        {
            case 0:
                newY++;
                break;
            case 1:
                newX--;
                break;
            case 2:
                newX++;
                break;
            case 3:
                block = rotate(current);
                break;
        }
        
        deleteCurrentBlock();
        if(fits(newX,newY,block))
        {
            YOffset = newY;
            XOffset = newX;
            current = block;
            insertCurrentBlock();
            return true;
        }
        else
        {
            insertCurrentBlock();
            return false;
        }
    }
    
    private void insertCurrentBlock()
    {
        board[YOffset+3]    |= (short)((current&0x000F)<<XOffset);
        board[YOffset+2]    |= (short)((current>>>4&0x000F)<<XOffset);
        board[YOffset+1]    |= (short)((current>>>8&0x000F)<<XOffset);
        board[YOffset]      |= (short)((current>>>12&0x000F)<<XOffset);
        render();
    }
    
    private void deleteCurrentBlock()
    {
        board[YOffset+3]    ^= (short)((current&0x000F)<<XOffset);
        board[YOffset+2]    ^= (short)((current>>>4&0x000F)<<XOffset);
        board[YOffset+1]    ^= (short)((current>>>8&0x000F)<<XOffset);
        board[YOffset]      ^= (short)((current>>>12&0x000F)<<XOffset);
    }
    
    /* returns true if the shape fits */
    private boolean fits(byte newX, byte newY, short block)
    {
        if((board[newY+3]&(short)((block&0x000F)<<newX))==0)
            if((board[newY+2]&(short)((block>>>4&0x000F)<<newX))==0)
                if((board[newY+1]&(short)((block>>>8&0x000F)<<newX))==0)
                    if((board[newY]&(short)((block>>>12&0x000F)<<newX))==0)
                        return true;
        
        return false;
    }
    
    private void checkFullLines()
    {
        byte count=0;
        
        for(int i=17;i>=1;)
        {
            if(board[i]==(short)0xFFFF)
            {
                count++;
                for(int j=i;j>=1;j--)
                    board[j]=board[j-1];
            }
            else
                i--;
        }
        //System.out.println(count +" line(s)");
        render();
    }
    
    private void render()
    {
        Dimension d = getSize();
        offG.setColor(getBackground());
        offG.fillRect(0,0,d.width,d.height);
        offG.setColor(getForeground());
        
        if(runner==null)
        {
            offG.setColor(Color.red);
            offG.fillRect(0,0,150,50);
            offG.setColor(Color.white);
            offG.drawString("PERDU GLANDU!!!",10,20);
        }
        else
        {
            for(int y=0;y<18;y++)
                for(int x=3;x<13;x++)
                    if((board[y]<<x&0x8000)==0x8000)
                        offG.fillRect(x*10,y*10,10,10);
                    else
                        offG.drawRect(x*10,y*10,10,10);
        }
        
        paint(offG);
        screenG.drawImage(offImg,0,0,null); 
    }    
    
    private void startGame()
    {
        runner = new Thread()
        {
            public void run()
            {
                Thread thisThread = Thread.currentThread();
                while(runner==thisThread)
                {
                    current = blocks[(int)(Math.random()*blocks.length)];
                    XOffset=6;
                    YOffset=0;
                    insertCurrentBlock();
                    try
                    {
                        this.sleep(sleepms);
                    }
                    catch(InterruptedException e)
                    {}
                    while(move(0))
                    {
                        try
                        {
                            this.sleep(sleepms);
                        }
                        catch(InterruptedException e)
                        {}
                    }
                    checkFullLines();
                    if(YOffset==0)
                        stopGame();
                }
            }
        };
        
        initBoard();
        runner.start();
    }
    
    private void initBoard()
    {
        for(int i=0;i<18;i++)
            board[i]=(short)0xE007;
        
        board[18]=(short)0xFFFF;
        board[19]=(short)0xFFFF;
        board[20]=(short)0xFFFF;
        
    }
    
    private void stopGame()
    {
        runner = null;        
        render();
    }
    
    public void init()
    {
        initComponents();
        offImg = createImage(this.getSize().width,this.getSize().height);
        offG = offImg.getGraphics();
        screenG = getGraphics();
    }
    
    public void start()
    {
        startGame();
    }
    
    private void initComponents()
    {
        
        setLayout(new java.awt.BorderLayout());
        
        addKeyListener(new java.awt.event.KeyAdapter()
        {
            public void keyPressed(java.awt.event.KeyEvent evt)
            {
                formKeyPressed(evt);
            }
        });
        
    }
    
    private void formKeyPressed(java.awt.event.KeyEvent evt)
    {
        switch(evt.getKeyCode())
        {
            case java.awt.event.KeyEvent.VK_DOWN:
                move(0);
                break;
            case java.awt.event.KeyEvent.VK_RIGHT:
                move(1);
                break;
            case java.awt.event.KeyEvent.VK_LEFT:
                move(2);
                break;
            case java.awt.event.KeyEvent.VK_SPACE:
                move(3);
        }
    }    
}
