Thursday, June 23, 2011

Tetris from Scratch

Related Articles

Introduction

Have you ever tried to build a game from scratch ? If you are interested in game programming and don't know where to start, this tutorial is for you.

The Tetris Game

It is one of the simplest game and it is not very difficult to find the source code of some other programmers. However, most of them just provide the source code of the "completed product". On the contrary, I am going to provide a real step by step walk-through. This was actually how I built the game from scratch. The changes are incremental and it should be easy to follow.

Let's build the game of Tetris

You can build a game of tetris in a afternoon if you :
  1. know how to compile and run the sample in this series
  2. know where to find documentation of a Java API
  3. will compile and run every sample program listed below, one by one in the sequence. And make sure you understand each step before going further.


Steps by Steps Walk-through

Important :
  • MUST RUN EVERY SAMPLE TO SEE WHAT THE PROGRAM DOES
  • No screen shots will be provided because you are assumed to run EACH PROGRAM.
  • No detailed explanation will be provided. It is because the result of each running program, together with the source code, provide the best documentation and explanation.

A total of 21 steps will be listed. Each step will make small progress and finally a fully playable game will be produced.




Step 1 : draw a green background

/******************************************************************************
* File : Tetris1.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background

class Tetris1 extends javax.swing.JPanel
{
  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);
  }

  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris1 tetris = new Tetris1();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);
  } 
}

Step 2 : draw a red cell with black border

/******************************************************************************
* File : Tetris2.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border

class Tetris2 extends javax.swing.JPanel
{
  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);
    gr.setColor(java.awt.Color.BLACK);
    gr.fillRect(0,0,24,24);
    gr.setColor(java.awt.Color.RED);
    gr.fillRect(1,1,22,22);
  }

  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris2 tetris = new Tetris2();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);
  } 
}

Step 3 : create a drawCell method()

/******************************************************************************
* File : Tetris3.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()

class Tetris3 extends javax.swing.JPanel
{
  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);
  }

  public void drawCell(java.awt.Graphics gr, int x,int y)
  {
    gr.setColor(java.awt.Color.BLACK);
    gr.fillRect(x,y,24,24);
    gr.setColor(java.awt.Color.RED);
    gr.fillRect(x+1,y+1,22,22);
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);
    drawCell(gr,0,0);
  }

  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris3 tetris = new Tetris3();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);
  } 
}

Step 4 : draw a token

/******************************************************************************
* File : Tetris4.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token

class Tetris4 extends javax.swing.JPanel
{
  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);
  }

  public void drawCell(java.awt.Graphics gr, int x,int y)
  {
    gr.setColor(java.awt.Color.BLACK);
    gr.fillRect(x,y,24,24);
    gr.setColor(java.awt.Color.RED);
    gr.fillRect(x+1,y+1,22,22);
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);
    drawCell(gr,0,0);
    drawCell(gr,0,1*24);
    drawCell(gr,1*24,0);
    drawCell(gr,2*24,0);
  }

  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris4 tetris = new Tetris4();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);
  } 
}

Step 5 : move the factor 24 into drawCell()

/******************************************************************************
* File : Tetris5.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller

class Tetris5 extends javax.swing.JPanel
{
  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);
  }

  public void drawCell(java.awt.Graphics gr, int x,int y)
  {
    gr.setColor(java.awt.Color.BLACK);
    gr.fillRect(x*24,y*24,24,24);
    gr.setColor(java.awt.Color.RED);
    gr.fillRect(x*24+1,y*24+1,22,22);
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);
    drawCell(gr,0,0);
    drawCell(gr,0,1);
    drawCell(gr,1,0);
    drawCell(gr,2,0);
  }

  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris5 tetris = new Tetris5();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);
  } 
}

Step 6 : create a drawToken() method

/******************************************************************************
* File : Tetris1.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller
// Step 6 : create a drawToken() method

class Tetris6 extends javax.swing.JPanel
{
  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);
  }

  public void drawCell(java.awt.Graphics gr, int x,int y)
  {
    gr.setColor(java.awt.Color.BLACK);
    gr.fillRect(x*24,y*24,24,24);
    gr.setColor(java.awt.Color.RED);
    gr.fillRect(x*24+1,y*24+1,22,22);
  }

  public void drawToken(java.awt.Graphics gr, int x, int y)
  {
    drawCell(gr,x+0,y+0);
    drawCell(gr,x+0,y+1);
    drawCell(gr,x+1,y+0);  
    drawCell(gr,x+2,y+0);    
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);

    drawToken(gr,0,0);

    drawToken(gr,5,10);
  }

  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris6 tetris = new Tetris6();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);
  } 
}

Step 7 : create a eraseCell() method

/******************************************************************************
* File : Tetris7.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller
// Step 6 : create a drawToken() method
// Step 7 : create a eraseCell() method, create an occupied array

class Tetris7 extends javax.swing.JPanel
{
  int[][] occupied = new int[10][20];

  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);
  }

  public void drawCell(java.awt.Graphics gr, int x,int y)
  {
    occupied[x][y] = 1;
    gr.setColor(java.awt.Color.BLACK);
    gr.fillRect(x*24,y*24,24,24);
    gr.setColor(java.awt.Color.RED);
    gr.fillRect(x*24+1,y*24+1,22,22);
  }

  public void eraseCell(java.awt.Graphics gr, int x,int y)
  {
    occupied[x][y] = 0;
    gr.setColor(java.awt.Color.BLACK);
    gr.fillRect(x*24,y*24,24,24);
  }

  public void drawToken(java.awt.Graphics gr, int x, int y)
  {
    drawCell(gr,x+0,y+0);
    drawCell(gr,x+0,y+1);
    drawCell(gr,x+1,y+0);  
    drawCell(gr,x+2,y+0);    
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);

    drawToken(gr,0,0);

    drawToken(gr,5,10);

    eraseCell(gr,5,10);
  }

  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris7 tetris = new Tetris7();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);
  } 
}

Step 8

  • modify drawCell() so that it only fills the occupied array,
  • move the actual drawing code to paint()
/******************************************************************************
* File : Tetris8.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller
// Step 6 : create a drawToken() method
// Step 7 : create a eraseCell() method, create an occupied array
// Step 8 : modify drawCell() so that it only fills the occupied array,
//          move the actual drawing code to paint()

class Tetris8 extends javax.swing.JPanel
{
  int[][] occupied = new int[10][20];

  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);

    drawToken(0,0);

    drawToken(5,10);

    eraseCell(5,10);

  }

  public void drawCell(int x,int y)
  {
    occupied[x][y] = 1;
  }

  public void eraseCell(int x,int y)
  {
    occupied[x][y] = 0;
  }

  public void drawToken(int x, int y)
  {
    drawCell(x+0,y+0);
    drawCell(x+0,y+1);
    drawCell(x+1,y+0);  
    drawCell(x+2,y+0);    
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);

    for (int x=0;x<10;x++)
      for (int y=0;y<20;y++)
        if (occupied[x][y]==1)
        {
          // draw cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
          gr.setColor(java.awt.Color.RED);
          gr.fillRect(x*24+1,y*24+1,22,22);
        }
        else
        {
          // erase cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
        }
  }

  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris8 tetris = new Tetris8();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);
  } 
}

Step 9 : modify drawToken() to accept an array of relative position

/******************************************************************************
* File : Tetris9.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller
// Step 6 : create a drawToken() method
// Step 7 : create a eraseCell() method, create an occupied array
// Step 8 : modify drawCell() so that only fill the occupied array,
//          move the actual drawing code to paint()
// Step 9 : modify drawToken() to accept an array of relative position

class Tetris9 extends javax.swing.JPanel
{
  int[][] occupied = new int[10][20];

  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);

    // array of relative position
    int[] xArray = { 0,0,1,2 };   
    int[] yArray = { 0,1,0,0 };
    drawToken(0,0,xArray, yArray);

    drawToken(5,10,xArray, yArray);

    eraseCell(5,10);

  }

  public void drawCell(int x,int y)
  {
    occupied[x][y] = 1;
  }

  public void eraseCell(int x,int y)
  {
    occupied[x][y] = 0;
  }

  public void drawToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      drawCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);

    for (int x=0;x<10;x++)
      for (int y=0;y<20;y++)
        if (occupied[x][y]==1)
        {
          // draw cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
          gr.setColor(java.awt.Color.RED);
          gr.fillRect(x*24+1,y*24+1,22,22);
        }
        else
        {
          // erase cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
        }
  }

  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris9 tetris = new Tetris9();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);
  } 
}

Step 10 : define rotation array of the token

/******************************************************************************
* File : Tetris10.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller
// Step 6 : create a drawToken() method
// Step 7 : create a eraseCell() method, create an occupied array
// Step 8 : modify drawCell() so that only fill the occupied array,
//          move the actual drawing code to paint()
// Step 9 : modify drawToken() to accept an array of relative position
// Step 10 : define rotation array of the token

class Tetris10 extends javax.swing.JPanel
{
  int[][] occupied = new int[10][20];

  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);

    // array of relative position
    int[] xArray = { 0,0,1,2 };   
    int[] yArray = { 0,1,0,0 };

    drawToken(0,0,xArray, yArray);

    // first rotation
    xArray = new int[] { 0,0,0,1};
    yArray = new int[] { 0,1,2,2};
    drawToken(0,5,xArray, yArray);

    // second rotation
    xArray = new int[] { 2,0,1,2};
    yArray = new int[] { 0,1,1,1};
    drawToken(0,10,xArray, yArray);

    // third rotation
    xArray = new int[] { 0,1,1,1};
    yArray = new int[] { 0,0,1,2};
    drawToken(0,15,xArray, yArray);


  }

  public void drawCell(int x,int y)
  {
    occupied[x][y] = 1;
  }

  public void eraseCell(int x,int y)
  {
    occupied[x][y] = 0;
  }

  public void drawToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      drawCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);

    for (int x=0;x<10;x++)
      for (int y=0;y<20;y++)
        if (occupied[x][y]==1)
        {
          // draw cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
          gr.setColor(java.awt.Color.RED);
          gr.fillRect(x*24+1,y*24+1,22,22);
        }
        else
        {
          // erase cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
        }
  }

  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris10 tetris = new Tetris10();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);
  } 
}

Step 11 : define rotation array of the second token

/******************************************************************************
* File : Tetris11.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller
// Step 6 : create a drawToken() method
// Step 7 : create a eraseCell() method, create an occupied array
// Step 8 : modify drawCell() so that only fill the occupied array,
//          move the actual drawing code to paint()
// Step 9 : modify drawToken() to accept an array of relative position
// Step 10 : define rotation array of the first token
// Step 11 : define rotation array of the second token

class Tetris11 extends javax.swing.JPanel
{
  int[][] occupied = new int[10][20];

  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);
    
    // array of relative position
    int[] xArray = { 0,0,1,2 };   
    int[] yArray = { 0,1,0,0 };

    drawToken(0,0,xArray, yArray);

    // first rotation
    xArray = new int[] { 0,0,0,1};
    yArray = new int[] { 0,1,2,2};
    drawToken(0,5,xArray, yArray);

    // second rotation
    xArray = new int[] { 2,0,1,2};
    yArray = new int[] { 0,1,1,1};
    drawToken(0,10,xArray, yArray);

    // third rotation
    xArray = new int[] { 0,1,1,1};
    yArray = new int[] { 0,0,1,2};
    drawToken(0,15,xArray, yArray);

    // second token, rotation 0
    xArray = new int[] { 0,0,1,1};
    yArray = new int[] { 0,1,1,2};
    drawToken(5,0,xArray, yArray);

    // second token, rotation 1
    xArray = new int[] { 1,2,0,1};
    yArray = new int[] { 0,0,1,1};
    drawToken(5,5,xArray, yArray);

    // second token, rotation 2
    xArray = new int[] { 0,0,1,1};
    yArray = new int[] { 0,1,1,2};
    drawToken(5,10,xArray, yArray);

    // second token, rotation 3
    xArray = new int[] { 1,2,0,1};
    yArray = new int[] { 0,0,1,1};
    drawToken(5,15,xArray, yArray);

  }

  public void drawCell(int x,int y)
  {
    occupied[x][y] = 1;
  }

  public void eraseCell(int x,int y)
  {
    occupied[x][y] = 0;
  }

  public void drawToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      drawCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);

    for (int x=0;x<10;x++)
      for (int y=0;y<20;y++)
        if (occupied[x][y]==1)
        {
          // draw cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
          gr.setColor(java.awt.Color.RED);
          gr.fillRect(x*24+1,y*24+1,22,22);
        }
        else
        {
          // erase cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
        }
  }

  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris11 tetris = new Tetris11();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);
  } 
}

Step 12 : create global array to hold the rotation array

/******************************************************************************
* File : Tetris12.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller
// Step 6 : create a drawToken() method
// Step 7 : create a eraseCell() method, create an occupied array
// Step 8 : modify drawCell() so that only fill the occupied array,
//          move the actual drawing code to paint()
// Step 9 : modify drawToken() to accept an array of relative position
// Step 10 : define rotation array of the first token
// Step 11 : define rotation array of the second token
// Step 12 : create global array to hold the rotation array

class Tetris12 extends javax.swing.JPanel
{
  int[][] occupied = new int[10][20];

    // [two tokens] [ four rotations ] [ four cells]
    static int[][][] xRotationArray = {
       { {0,0,1,2}, {0,0,0,1}, {2,0,1,2}, {0,1,1,1} },  // token number 0
       { {0,0,1,1}, {1,2,0,1}, {0,0,1,1}, {1,2,0,1} }    // token number 1
    };

    static int[][][] yRotationArray = {
       { {0,1,0,0}, {0,1,2,2}, {0,1,1,1}, {0,0,1,2} },  // token number 0
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} }    // token number 1
    };


  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);

      
    // array of relative position
    int[] xArray = xRotationArray[0][0];  // [token 0] [ rotation 0]
    int[] yArray = yRotationArray[0][0];

    drawToken(0,0,xArray, yArray);

    // first rotation
    xArray = xRotationArray[0][1];  // [token 0] [ rotation 1]
    yArray = yRotationArray[0][1];
    drawToken(0,5,xArray, yArray);

    // second rotation
    xArray = xRotationArray[0][2];  // [token 0] [ rotation 2]
    yArray = yRotationArray[0][2];
    drawToken(0,10,xArray, yArray);

    // third rotation
    xArray = xRotationArray[0][3];  // [token 0] [ rotation 3]
    yArray = yRotationArray[0][3];
    drawToken(0,15,xArray, yArray);

    // second token, rotation 0
    xArray = xRotationArray[1][0];  // [token 1] [ rotation 0]
    yArray = yRotationArray[1][0];  
    drawToken(5,0,xArray, yArray);

    // second token, rotation 1
    xArray = xRotationArray[1][1];  // [token 1] [ rotation 1]
    yArray = yRotationArray[1][1];
    drawToken(5,5,xArray, yArray);

    // second token, rotation 2
    xArray = xRotationArray[1][2];  // [token 1] [ rotation 2]
    yArray = yRotationArray[1][2];
    drawToken(5,10,xArray, yArray);

    // second token, rotation 3
    xArray = xRotationArray[1][3];  // [token 1] [ rotation 3]
    yArray = yRotationArray[1][3];
    drawToken(5,15,xArray, yArray);

  }

  public void drawCell(int x,int y)
  {
    occupied[x][y] = 1;
  }

  public void eraseCell(int x,int y)
  {
    occupied[x][y] = 0;
  }

  public void drawToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      drawCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);

    for (int x=0;x<10;x++)
      for (int y=0;y<20;y++)
        if (occupied[x][y]==1)
        {
          // draw cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
          gr.setColor(java.awt.Color.RED);
          gr.fillRect(x*24+1,y*24+1,22,22);
        }
        else
        {
          // erase cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
        }
  }

  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris12 tetris = new Tetris12();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);
  } 
}

Step 13 : create rotation array for all the seven tokens

/******************************************************************************
* File : Tetris13.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller
// Step 6 : create a drawToken() method
// Step 7 : create a eraseCell() method, create an occupied array
// Step 8 : modify drawCell() so that only fill the occupied array,
//          move the actual drawing code to paint()
// Step 9 : modify drawToken() to accept an array of relative position
// Step 10 : define rotation array of the first token
// Step 11 : define rotation array of the second token
// Step 12 : create global array to hold the rotation array
// Step 13 : create rotation array for all the seven tokens

class Tetris13 extends javax.swing.JPanel
{
//  int[][] occupied = new int[10][20];
  int[][] occupied = new int[28][20];  // expand temporarily to show all tokens

    // [seven tokens] [ four rotations ] [ four cells]
    static int[][][] xRotationArray = {
       { {0,0,1,2}, {0,0,0,1}, {2,0,1,2}, {0,1,1,1} },  // token number 0
       { {0,0,1,1}, {1,2,0,1}, {0,0,1,1}, {1,2,0,1} },  // token number 1
       { {1,1,0,0}, {0,1,1,2}, {1,1,0,0}, {0,1,1,2} },  // token number 2
       { {0,1,2,2}, {0,1,0,0}, {0,0,1,2}, {1,1,0,1} },  // token number 3
       { {1,0,1,2}, {1,0,1,1}, {0,1,1,2}, {0,0,1,0} },  // token number 4
       { {0,1,0,1}, {0,1,0,1}, {0,1,0,1}, {0,1,0,1} },  // token number 5
       { {0,1,2,3}, {0,0,0,0}, {0,1,2,3}, {0,0,0,0} }   // token number 6
    };

    static int[][][] yRotationArray = {
       { {0,1,0,0}, {0,1,2,2}, {0,1,1,1}, {0,0,1,2} },  // token number 0
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 1
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 2
       { {0,0,0,1}, {0,0,1,2}, {0,1,1,1}, {0,1,2,2} },  // token number 3
       { {0,1,1,1}, {0,1,1,2}, {0,0,1,0}, {0,1,1,2} },  // token number 4
       { {0,0,1,1}, {0,0,1,1}, {0,0,1,1}, {0,0,1,1} },  // token number 5
       { {0,0,0,0}, {0,1,2,3}, {0,0,0,0}, {0,1,2,3} }   // token number 6
    };


  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(680,480));
    this.setBackground(java.awt.Color.GREEN);

    for (int tokenNumber=0;tokenNumber<7;tokenNumber++)
      for (int rotationNumber=0;rotationNumber<4;rotationNumber++)
      {
        int[] xArray = xRotationArray[tokenNumber][rotationNumber];
        int[] yArray = yRotationArray[tokenNumber][rotationNumber];
        int x = tokenNumber*4;
        int y = rotationNumber*5;
        drawToken(x,y,xArray,yArray);
      }
    
      

  }

  public void drawCell(int x,int y)
  {
    occupied[x][y] = 1;
  }

  public void eraseCell(int x,int y)
  {
    occupied[x][y] = 0;
  }

  public void drawToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      drawCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);

    for (int x=0;x<occupied.length;x++)
      for (int y=0;y<20;y++)
        if (occupied[x][y]==1)
        {
          // draw cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
          gr.setColor(java.awt.Color.RED);
          gr.fillRect(x*24+1,y*24+1,22,22);
        }
        else
        {
          // erase cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
        }
  }

  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris13 tetris = new Tetris13();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);
  } 
}

Step 14 : random token test

/******************************************************************************
* File : Tetris14.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller
// Step 6 : create a drawToken() method
// Step 7 : create a eraseCell() method, create an occupied array
// Step 8 : modify drawCell() so that only fill the occupied array,
//          move the actual drawing code to paint()
// Step 9 : modify drawToken() to accept an array of relative position
// Step 10 : define rotation array of the first token
// Step 11 : define rotation array of the second token
// Step 12 : create global array to hold the rotation array
// Step 13 : create rotation array for all the seven tokens
// Step 14 : random token test

class Tetris14 extends javax.swing.JPanel
{
  int[][] occupied = new int[10][20];

    // [seven tokens] [ four rotations ] [ four cells]
    static int[][][] xRotationArray = {
       { {0,0,1,2}, {0,0,0,1}, {2,0,1,2}, {0,1,1,1} },  // token number 0
       { {0,0,1,1}, {1,2,0,1}, {0,0,1,1}, {1,2,0,1} },  // token number 1
       { {1,1,0,0}, {0,1,1,2}, {1,1,0,0}, {0,1,1,2} },  // token number 2
       { {0,1,2,2}, {0,1,0,0}, {0,0,1,2}, {1,1,0,1} },  // token number 3
       { {1,0,1,2}, {1,0,1,1}, {0,1,1,2}, {0,0,1,0} },  // token number 4
       { {0,1,0,1}, {0,1,0,1}, {0,1,0,1}, {0,1,0,1} },  // token number 5
       { {0,1,2,3}, {0,0,0,0}, {0,1,2,3}, {0,0,0,0} }   // token number 6
    };

    static int[][][] yRotationArray = {
       { {0,1,0,0}, {0,1,2,2}, {0,1,1,1}, {0,0,1,2} },  // token number 0
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 1
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 2
       { {0,0,0,1}, {0,0,1,2}, {0,1,1,1}, {0,1,2,2} },  // token number 3
       { {0,1,1,1}, {0,1,1,2}, {0,0,1,0}, {0,1,1,2} },  // token number 4
       { {0,0,1,1}, {0,0,1,1}, {0,0,1,1}, {0,0,1,1} },  // token number 5
       { {0,0,0,0}, {0,1,2,3}, {0,0,0,0}, {0,1,2,3} }   // token number 6
    };


  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);          

  }

  public void drawCell(int x,int y)
  {
    occupied[x][y] = 1;
  }

  public void eraseCell(int x,int y)
  {
    occupied[x][y] = 0;
  }

  public void drawToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      drawCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);

    for (int x=0;x<occupied.length;x++)
      for (int y=0;y<occupied[0].length;y++)
        if (occupied[x][y]==1)
        {
          // draw cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
          gr.setColor(java.awt.Color.RED);
          gr.fillRect(x*24+1,y*24+1,22,22);
        }
        else
        {
          // erase cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
        }
  }

  public void randomTokenTest()
  {
    try { Thread.sleep(1000); } catch (Exception ignore) {}
    int x=(int) (7*Math.random());    // random x: 0 to 6
    int y=(int) (15*Math.random());   // random y: 0 to 14

    int tokenNumber = (int) (7*Math.random());
    int rotationNumber = (int) (4*Math.random());

    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];

    drawToken(x,y,xArray,yArray);
    repaint();
  }

  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris14 tetris = new Tetris14();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);

    // randomly draw 20 tokens, without overlap checking
    for (int i=0;i<20;i++)
      tetris.randomTokenTest();
  } 
}

Step 15 : add validation check

/******************************************************************************
* File : Tetris15.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller
// Step 6 : create a drawToken() method
// Step 7 : create a eraseCell() method, create an occupied array
// Step 8 : modify drawCell() so that only fill the occupied array,
//          move the actual drawing code to paint()
// Step 9 : modify drawToken() to accept an array of relative position
// Step 10 : define rotation array of the first token
// Step 11 : define rotation array of the second token
// Step 12 : create global array to hold the rotation array
// Step 13 : create rotation array for all the seven tokens
// Step 14 : random token test
// Step 15 : add validation check

class Tetris15 extends javax.swing.JPanel
{
  int[][] occupied = new int[10][20];

    // [seven tokens] [ four rotations ] [ four cells]
    static int[][][] xRotationArray = {
       { {0,0,1,2}, {0,0,0,1}, {2,0,1,2}, {0,1,1,1} },  // token number 0
       { {0,0,1,1}, {1,2,0,1}, {0,0,1,1}, {1,2,0,1} },  // token number 1
       { {1,1,0,0}, {0,1,1,2}, {1,1,0,0}, {0,1,1,2} },  // token number 2
       { {0,1,2,2}, {0,1,0,0}, {0,0,1,2}, {1,1,0,1} },  // token number 3
       { {1,0,1,2}, {1,0,1,1}, {0,1,1,2}, {0,0,1,0} },  // token number 4
       { {0,1,0,1}, {0,1,0,1}, {0,1,0,1}, {0,1,0,1} },  // token number 5
       { {0,1,2,3}, {0,0,0,0}, {0,1,2,3}, {0,0,0,0} }   // token number 6
    };

    static int[][][] yRotationArray = {
       { {0,1,0,0}, {0,1,2,2}, {0,1,1,1}, {0,0,1,2} },  // token number 0
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 1
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 2
       { {0,0,0,1}, {0,0,1,2}, {0,1,1,1}, {0,1,2,2} },  // token number 3
       { {0,1,1,1}, {0,1,1,2}, {0,0,1,0}, {0,1,1,2} },  // token number 4
       { {0,0,1,1}, {0,0,1,1}, {0,0,1,1}, {0,0,1,1} },  // token number 5
       { {0,0,0,0}, {0,1,2,3}, {0,0,0,0}, {0,1,2,3} }   // token number 6
    };


  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);          

  }

  public void drawCell(int x,int y)
  {
    occupied[x][y] = 1;
  }

  public void eraseCell(int x,int y)
  {
    occupied[x][y] = 0;
  }

  public void drawToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      drawCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);

    for (int x=0;x<occupied.length;x++)
      for (int y=0;y<occupied[0].length;y++)
        if (occupied[x][y]==1)
        {
          // draw cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
          gr.setColor(java.awt.Color.RED);
          gr.fillRect(x*24+1,y*24+1,22,22);
        }
        else
        {
          // erase cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
        }
  }

  public boolean isValidPosition(int x,int y, int tokenNumber, int rotationNumber)
  {
    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];
    
    for (int i=0;i<4;i++)  // loop over the four cells 
    {
      int xCell = x+xArray[i];
      int yCell = y+yArray[i];

      // range check
      if (xCell<0) return false;
      if (xCell>=10) return false;
      if (yCell<0) return false;
      if (yCell>=20) return false;

      // occupancy check
      if (occupied[xCell][yCell]==1) return false;
    }
    return true;
  }

  public void randomTokenTest()
  {
    try { Thread.sleep(1000); } catch (Exception ignore) {}

    int x,y,tokenNumber,rotationNumber;

    while (true)  // loop until position is valid
    {
      x=(int) (10*Math.random());    // random x: 0 to 9
      y=(int) (20*Math.random());    // random y: 0 to 19

      tokenNumber = (int) (7*Math.random());
      rotationNumber = (int) (4*Math.random());

      if (isValidPosition(x,y,tokenNumber,rotationNumber)) break;
    }


    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];

    drawToken(x,y,xArray,yArray);
    repaint();
  }

  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris15 tetris = new Tetris15();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);

    // randomly draw 10 tokens, with range check and occupancy check
    for (int i=0;i<10;i++)
      tetris.randomTokenTest();
  } 
}

Step 16 : add a falling token

/******************************************************************************
* File : Tetris16.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller
// Step 6 : create a drawToken() method
// Step 7 : create a eraseCell() method, create an occupied array
// Step 8 : modify drawCell() so that only fill the occupied array,
//          move the actual drawing code to paint()
// Step 9 : modify drawToken() to accept an array of relative position
// Step 10 : define rotation array of the first token
// Step 11 : define rotation array of the second token
// Step 12 : create global array to hold the rotation array
// Step 13 : create rotation array for all the seven tokens
// Step 14 : random token test
// Step 15 : add validation check
// Step 16 : add a falling token

class Tetris16 extends javax.swing.JPanel
{
  int[][] occupied = new int[10][20];

    // [seven tokens] [ four rotations ] [ four cells]
    static int[][][] xRotationArray = {
       { {0,0,1,2}, {0,0,0,1}, {2,0,1,2}, {0,1,1,1} },  // token number 0
       { {0,0,1,1}, {1,2,0,1}, {0,0,1,1}, {1,2,0,1} },  // token number 1
       { {1,1,0,0}, {0,1,1,2}, {1,1,0,0}, {0,1,1,2} },  // token number 2
       { {0,1,2,2}, {0,1,0,0}, {0,0,1,2}, {1,1,0,1} },  // token number 3
       { {1,0,1,2}, {1,0,1,1}, {0,1,1,2}, {0,0,1,0} },  // token number 4
       { {0,1,0,1}, {0,1,0,1}, {0,1,0,1}, {0,1,0,1} },  // token number 5
       { {0,1,2,3}, {0,0,0,0}, {0,1,2,3}, {0,0,0,0} }   // token number 6
    };

    static int[][][] yRotationArray = {
       { {0,1,0,0}, {0,1,2,2}, {0,1,1,1}, {0,0,1,2} },  // token number 0
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 1
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 2
       { {0,0,0,1}, {0,0,1,2}, {0,1,1,1}, {0,1,2,2} },  // token number 3
       { {0,1,1,1}, {0,1,1,2}, {0,0,1,0}, {0,1,1,2} },  // token number 4
       { {0,0,1,1}, {0,0,1,1}, {0,0,1,1}, {0,0,1,1} },  // token number 5
       { {0,0,0,0}, {0,1,2,3}, {0,0,0,0}, {0,1,2,3} }   // token number 6
    };


  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);          

  }

  public void drawCell(int x,int y)
  {
    occupied[x][y] = 1;
  }

  public void eraseCell(int x,int y)
  {
    occupied[x][y] = 0;
  }

  public void drawToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      drawCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void eraseToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      eraseCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);

    for (int x=0;x<occupied.length;x++)
      for (int y=0;y<occupied[0].length;y++)
        if (occupied[x][y]==1)
        {
          // draw cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
          gr.setColor(java.awt.Color.RED);
          gr.fillRect(x*24+1,y*24+1,22,22);
        }
        else
        {
          // erase cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
        }
  }

  public boolean isValidPosition(int x,int y, int tokenNumber, int rotationNumber)
  {
    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];
    
    for (int i=0;i<4;i++)  // loop over the four cells 
    {
      int xCell = x+xArray[i];
      int yCell = y+yArray[i];

      // range check
      if (xCell<0) return false;
      if (xCell>=10) return false;
      if (yCell<0) return false;
      if (yCell>=20) return false;

      // occupancy check
      if (occupied[xCell][yCell]==1) return false;
    }
    return true;
  }

  public void randomTokenTest()
  {
    try { Thread.sleep(1000); } catch (Exception ignore) {}

    int x,y,tokenNumber,rotationNumber;

    while (true)  // loop until position is valid
    {
      x=(int) (10*Math.random());    // random x: 0 to 9
      y=(int) (20*Math.random());    // random y: 0 to 19

      tokenNumber = (int) (7*Math.random());
      rotationNumber = (int) (4*Math.random());

      if (isValidPosition(x,y,tokenNumber,rotationNumber)) break;
    }


    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];

    drawToken(x,y,xArray,yArray);
    repaint();
  }

  public void addFallingToken()
  {
    int x=5,y=0;
    int tokenNumber, rotationNumber;

    while (true)  // loop until position is valid
    {

      tokenNumber = (int) (7*Math.random());
      rotationNumber = (int) (4*Math.random());

      if (isValidPosition(x,y,tokenNumber,rotationNumber)) break;
    }


    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];

    drawToken(x,y,xArray,yArray);
    repaint();

    int delay=500;  // mini second
    boolean reachFloor=false;
    while (!reachFloor)
    {
      try { Thread.sleep(delay); } catch (Exception ignore) {}
      eraseToken(x,y,xArray,yArray);
      y += 1;  // falling
      if (!isValidPosition(x,y,tokenNumber,rotationNumber)) // reached floor
      {
        reachFloor=true;
        y -= 1;  // restore position
      }
      drawToken(x,y,xArray,yArray);
      repaint();
    }

  }


  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris16 tetris = new Tetris16();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);

    try { Thread.sleep(1000); } catch (Exception ignore) {}
    tetris.addFallingToken();
  } 
}

Step 17 : game over check

/******************************************************************************
* File : Tetris17.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller
// Step 6 : create a drawToken() method
// Step 7 : create a eraseCell() method, create an occupied array
// Step 8 : modify drawCell() so that only fill the occupied array,
//          move the actual drawing code to paint()
// Step 9 : modify drawToken() to accept an array of relative position
// Step 10 : define rotation array of the first token
// Step 11 : define rotation array of the second token
// Step 12 : create global array to hold the rotation array
// Step 13 : create rotation array for all the seven tokens
// Step 14 : random token test
// Step 15 : add validation check
// Step 16 : add a falling token
// Step 17 : game over check

class Tetris17 extends javax.swing.JPanel
{
  int[][] occupied = new int[10][20];

    // [seven tokens] [ four rotations ] [ four cells]
    static int[][][] xRotationArray = {
       { {0,0,1,2}, {0,0,0,1}, {2,0,1,2}, {0,1,1,1} },  // token number 0
       { {0,0,1,1}, {1,2,0,1}, {0,0,1,1}, {1,2,0,1} },  // token number 1
       { {1,1,0,0}, {0,1,1,2}, {1,1,0,0}, {0,1,1,2} },  // token number 2
       { {0,1,2,2}, {0,1,0,0}, {0,0,1,2}, {1,1,0,1} },  // token number 3
       { {1,0,1,2}, {1,0,1,1}, {0,1,1,2}, {0,0,1,0} },  // token number 4
       { {0,1,0,1}, {0,1,0,1}, {0,1,0,1}, {0,1,0,1} },  // token number 5
       { {0,1,2,3}, {0,0,0,0}, {0,1,2,3}, {0,0,0,0} }   // token number 6
    };

    static int[][][] yRotationArray = {
       { {0,1,0,0}, {0,1,2,2}, {0,1,1,1}, {0,0,1,2} },  // token number 0
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 1
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 2
       { {0,0,0,1}, {0,0,1,2}, {0,1,1,1}, {0,1,2,2} },  // token number 3
       { {0,1,1,1}, {0,1,1,2}, {0,0,1,0}, {0,1,1,2} },  // token number 4
       { {0,0,1,1}, {0,0,1,1}, {0,0,1,1}, {0,0,1,1} },  // token number 5
       { {0,0,0,0}, {0,1,2,3}, {0,0,0,0}, {0,1,2,3} }   // token number 6
    };


  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);          

  }

  public void drawCell(int x,int y)
  {
    occupied[x][y] = 1;
  }

  public void eraseCell(int x,int y)
  {
    occupied[x][y] = 0;
  }

  public void drawToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      drawCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void eraseToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      eraseCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);

    for (int x=0;x<occupied.length;x++)
      for (int y=0;y<occupied[0].length;y++)
        if (occupied[x][y]==1)
        {
          // draw cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
          gr.setColor(java.awt.Color.RED);
          gr.fillRect(x*24+1,y*24+1,22,22);
        }
        else
        {
          // erase cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
        }
  }

  public boolean isValidPosition(int x,int y, int tokenNumber, int rotationNumber)
  {
    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];
    
    for (int i=0;i<4;i++)  // loop over the four cells 
    {
      int xCell = x+xArray[i];
      int yCell = y+yArray[i];

      // range check
      if (xCell<0) return false;
      if (xCell>=10) return false;
      if (yCell<0) return false;
      if (yCell>=20) return false;

      // occupancy check
      if (occupied[xCell][yCell]==1) return false;
    }
    return true;
  }

  public void randomTokenTest()
  {
    try { Thread.sleep(1000); } catch (Exception ignore) {}

    int x,y,tokenNumber,rotationNumber;

    while (true)  // loop until position is valid
    {
      x=(int) (10*Math.random());    // random x: 0 to 9
      y=(int) (20*Math.random());    // random y: 0 to 19

      tokenNumber = (int) (7*Math.random());
      rotationNumber = (int) (4*Math.random());

      if (isValidPosition(x,y,tokenNumber,rotationNumber)) break;
    }


    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];

    drawToken(x,y,xArray,yArray);
    repaint();
  }

  boolean gameOver=false;
  public void addFallingToken()
  {
    int x=5,y=0;
    int tokenNumber, rotationNumber;


      tokenNumber = (int) (7*Math.random());
      rotationNumber = (int) (4*Math.random());



    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];

    if (!isValidPosition(x,y,tokenNumber,rotationNumber)) 
    {
      gameOver=true;
      drawToken(x,y,xArray,yArray);
      repaint();
      return;
    }

    drawToken(x,y,xArray,yArray);
    repaint();

    int delay=100;  // mini second
    boolean reachFloor=false;
    while (!reachFloor)
    {
      try { Thread.sleep(delay); } catch (Exception ignore) {}
      eraseToken(x,y,xArray,yArray);
      y += 1;  // falling
      if (!isValidPosition(x,y,tokenNumber,rotationNumber)) // reached floor
      {
        reachFloor=true;
        y -= 1;  // restore position
      }
      drawToken(x,y,xArray,yArray);
      repaint();
    }

  }

  public void printGameOver()
  {
    javax.swing.JLabel gameOverLabel = new javax.swing.JLabel("GAME OVER");
    gameOverLabel.setBounds(300,300,100,30);
    add(gameOverLabel);
    repaint();
  }

  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris17 tetris = new Tetris17();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);

    try { Thread.sleep(1000); } catch (Exception ignore) {}

    tetris.gameOver=false;
    while (!tetris.gameOver)
      tetris.addFallingToken();

    tetris.printGameOver();
  } 
}

Step 18 : add keyboard control

Hint : try to play the game with the arrow keys and space bar. It is not complete but you can control the token to move around
/******************************************************************************
* File : Tetris18.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller
// Step 6 : create a drawToken() method
// Step 7 : create a eraseCell() method, create an occupied array
// Step 8 : modify drawCell() so that only fill the occupied array,
//          move the actual drawing code to paint()
// Step 9 : modify drawToken() to accept an array of relative position
// Step 10 : define rotation array of the first token
// Step 11 : define rotation array of the second token
// Step 12 : create global array to hold the rotation array
// Step 13 : create rotation array for all the seven tokens
// Step 14 : random token test
// Step 15 : add validation check
// Step 16 : add a falling token
// Step 17 : game over check
// Step 18 : add keyboard control

class Tetris18 extends javax.swing.JPanel 
implements java.awt.event.KeyListener
{
  int[][] occupied = new int[10][20];

    // [seven tokens] [ four rotations ] [ four cells]
    static int[][][] xRotationArray = {
       { {0,0,1,2}, {0,0,0,1}, {2,0,1,2}, {0,1,1,1} },  // token number 0
       { {0,0,1,1}, {1,2,0,1}, {0,0,1,1}, {1,2,0,1} },  // token number 1
       { {1,1,0,0}, {0,1,1,2}, {1,1,0,0}, {0,1,1,2} },  // token number 2
       { {0,1,2,2}, {0,1,0,0}, {0,0,1,2}, {1,1,0,1} },  // token number 3
       { {1,0,1,2}, {1,0,1,1}, {0,1,1,2}, {0,0,1,0} },  // token number 4
       { {0,1,0,1}, {0,1,0,1}, {0,1,0,1}, {0,1,0,1} },  // token number 5
       { {0,1,2,3}, {0,0,0,0}, {0,1,2,3}, {0,0,0,0} }   // token number 6
    };

    static int[][][] yRotationArray = {
       { {0,1,0,0}, {0,1,2,2}, {0,1,1,1}, {0,0,1,2} },  // token number 0
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 1
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 2
       { {0,0,0,1}, {0,0,1,2}, {0,1,1,1}, {0,1,2,2} },  // token number 3
       { {0,1,1,1}, {0,1,1,2}, {0,0,1,0}, {0,1,1,2} },  // token number 4
       { {0,0,1,1}, {0,0,1,1}, {0,0,1,1}, {0,0,1,1} },  // token number 5
       { {0,0,0,0}, {0,1,2,3}, {0,0,0,0}, {0,1,2,3} }   // token number 6
    };


  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);          

  }

  public void drawCell(int x,int y)
  {
    occupied[x][y] = 1;
  }

  public void eraseCell(int x,int y)
  {
    occupied[x][y] = 0;
  }

  public void drawToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      drawCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void eraseToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      eraseCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);

    for (int x=0;x<occupied.length;x++)
      for (int y=0;y<occupied[0].length;y++)
        if (occupied[x][y]==1)
        {
          // draw cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
          gr.setColor(java.awt.Color.RED);
          gr.fillRect(x*24+1,y*24+1,22,22);
        }
        else
        {
          // erase cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
        }
  }

  public boolean isValidPosition(int x,int y, int tokenNumber, int rotationNumber)
  {
    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];
    
    for (int i=0;i<4;i++)  // loop over the four cells 
    {
      int xCell = x+xArray[i];
      int yCell = y+yArray[i];

      // range check
      if (xCell<0) return false;
      if (xCell>=10) return false;
      if (yCell<0) return false;
      if (yCell>=20) return false;

      // occupancy check
      if (occupied[xCell][yCell]==1) return false;
    }
    return true;
  }

  public void randomTokenTest()
  {
    try { Thread.sleep(1000); } catch (Exception ignore) {}

    int x,y,tokenNumber,rotationNumber;

    while (true)  // loop until position is valid
    {
      x=(int) (10*Math.random());    // random x: 0 to 9
      y=(int) (20*Math.random());    // random y: 0 to 19

      tokenNumber = (int) (7*Math.random());
      rotationNumber = (int) (4*Math.random());

      if (isValidPosition(x,y,tokenNumber,rotationNumber)) break;
    }


    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];

    drawToken(x,y,xArray,yArray);
    repaint();
  }

  boolean gameOver=false;
  public void addFallingToken()
  {
    int x=5,y=0;
    int tokenNumber, rotationNumber;


      tokenNumber = (int) (7*Math.random());
      rotationNumber = (int) (4*Math.random());



    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];

    if (!isValidPosition(x,y,tokenNumber,rotationNumber)) 
    {
      gameOver=true;
      drawToken(x,y,xArray,yArray);
      repaint();
      return;
    }

    drawToken(x,y,xArray,yArray);
    repaint();

    int delay=50;  // mini second
    int frame=0;
    boolean reachFloor=false;
    while (!reachFloor)
    {
      try { Thread.sleep(delay); } catch (Exception ignore) {}
      eraseToken(x,y,xArray,yArray);

      // add keyboard control
      if (leftPressed && isValidPosition(x-1,y,tokenNumber,rotationNumber)) x -= 1;
      if (rightPressed && isValidPosition(x+1,y,tokenNumber,rotationNumber)) x += 1;
      if (downPressed && isValidPosition(x,y+1,tokenNumber,rotationNumber)) y += 1;
      if (spacePressed && isValidPosition(x,y,tokenNumber,(rotationNumber+1)%4)) 
      {
        rotationNumber = (rotationNumber+1)%4;
        xArray = xRotationArray[tokenNumber][rotationNumber];
        yArray = yRotationArray[tokenNumber][rotationNumber];
        spacePressed=false;  
      }

      if (frame % 30==0) y += 1;  // fall for every 30 frame
      if (!isValidPosition(x,y,tokenNumber,rotationNumber)) // reached floor
      {
        reachFloor=true;
        y -= 1;  // restore position
      }
      drawToken(x,y,xArray,yArray);
      repaint();
      frame++;
    }

  }

  public void printGameOver()
  {
    javax.swing.JLabel gameOverLabel = new javax.swing.JLabel("GAME OVER");
    gameOverLabel.setBounds(300,300,100,30);
    add(gameOverLabel);
    repaint();
  }


  boolean leftPressed=false;
  boolean rightPressed=false;
  boolean downPressed=false;
  boolean spacePressed=false;

  // must implements this method for KeyListener
  public void keyPressed(java.awt.event.KeyEvent event)
  {
//    System.out.println(event);
    if (event.getKeyCode()==37) // left arrow
    {
      leftPressed=true;
    }
    if (event.getKeyCode()==39) // right arrow
    {
      rightPressed=true;
    }
    if (event.getKeyCode()==40) // down arrow
    {
      downPressed=true;
    }
    if (event.getKeyCode()==32) // space
    {
      spacePressed=true;
    }

  }

  // must implements this method for KeyListener
  public void keyReleased(java.awt.event.KeyEvent event)
  {
//    System.out.println(event);

    if (event.getKeyCode()==37) // left arrow
    {
      leftPressed=false;
    }
    if (event.getKeyCode()==39) // right arrow
    {
      rightPressed=false;
    }
    if (event.getKeyCode()==40) // down arrow
    {
      downPressed=false;
    }
    if (event.getKeyCode()==32) // space
    {
      spacePressed=false;
    }

  }

  // must implements this method for KeyListener
  public void keyTyped(java.awt.event.KeyEvent event)
  {
//    System.out.println(event);
  }


  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris18 tetris = new Tetris18();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);

    try { Thread.sleep(1000); } catch (Exception ignore) {}

    window.addKeyListener(tetris);  // listen to keyboard event

    tetris.gameOver=false;
    while (!tetris.gameOver)
      tetris.addFallingToken();

    tetris.printGameOver();

  } 

}

Step 19 : erase filled rows

Hint : try your best to fill some rows
/******************************************************************************
* File : Tetris19.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller
// Step 6 : create a drawToken() method
// Step 7 : create a eraseCell() method, create an occupied array
// Step 8 : modify drawCell() so that only fill the occupied array,
//          move the actual drawing code to paint()
// Step 9 : modify drawToken() to accept an array of relative position
// Step 10 : define rotation array of the first token
// Step 11 : define rotation array of the second token
// Step 12 : create global array to hold the rotation array
// Step 13 : create rotation array for all the seven tokens
// Step 14 : random token test
// Step 15 : add validation check
// Step 16 : add a falling token
// Step 17 : game over check
// Step 18 : add keyboard control
// Step 19 : erase filled rows

class Tetris19 extends javax.swing.JPanel 
implements java.awt.event.KeyListener
{
  int[][] occupied = new int[10][20];

    // [seven tokens] [ four rotations ] [ four cells]
    static int[][][] xRotationArray = {
       { {0,0,1,2}, {0,0,0,1}, {2,0,1,2}, {0,1,1,1} },  // token number 0
       { {0,0,1,1}, {1,2,0,1}, {0,0,1,1}, {1,2,0,1} },  // token number 1
       { {1,1,0,0}, {0,1,1,2}, {1,1,0,0}, {0,1,1,2} },  // token number 2
       { {0,1,2,2}, {0,1,0,0}, {0,0,1,2}, {1,1,0,1} },  // token number 3
       { {1,0,1,2}, {1,0,1,1}, {0,1,1,2}, {0,0,1,0} },  // token number 4
       { {0,1,0,1}, {0,1,0,1}, {0,1,0,1}, {0,1,0,1} },  // token number 5
       { {0,1,2,3}, {0,0,0,0}, {0,1,2,3}, {0,0,0,0} }   // token number 6
    };

    static int[][][] yRotationArray = {
       { {0,1,0,0}, {0,1,2,2}, {0,1,1,1}, {0,0,1,2} },  // token number 0
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 1
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 2
       { {0,0,0,1}, {0,0,1,2}, {0,1,1,1}, {0,1,2,2} },  // token number 3
       { {0,1,1,1}, {0,1,1,2}, {0,0,1,0}, {0,1,1,2} },  // token number 4
       { {0,0,1,1}, {0,0,1,1}, {0,0,1,1}, {0,0,1,1} },  // token number 5
       { {0,0,0,0}, {0,1,2,3}, {0,0,0,0}, {0,1,2,3} }   // token number 6
    };


  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);          

  }

  public void drawCell(int x,int y)
  {
    occupied[x][y] = 1;
  }

  public void eraseCell(int x,int y)
  {
    occupied[x][y] = 0;
  }

  public void drawToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      drawCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void eraseToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      eraseCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);

    for (int x=0;x<occupied.length;x++)
      for (int y=0;y<occupied[0].length;y++)
        if (occupied[x][y]==1)
        {
          // draw cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
          gr.setColor(java.awt.Color.RED);
          gr.fillRect(x*24+1,y*24+1,22,22);
        }
        else
        {
          // erase cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
        }
  }

  public boolean isValidPosition(int x,int y, int tokenNumber, int rotationNumber)
  {
    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];
    
    for (int i=0;i<4;i++)  // loop over the four cells 
    {
      int xCell = x+xArray[i];
      int yCell = y+yArray[i];

      // range check
      if (xCell<0) return false;
      if (xCell>=10) return false;
      if (yCell<0) return false;
      if (yCell>=20) return false;

      // occupancy check
      if (occupied[xCell][yCell]==1) return false;
    }
    return true;
  }

  public void randomTokenTest()
  {
    try { Thread.sleep(1000); } catch (Exception ignore) {}

    int x,y,tokenNumber,rotationNumber;

    while (true)  // loop until position is valid
    {
      x=(int) (10*Math.random());    // random x: 0 to 9
      y=(int) (20*Math.random());    // random y: 0 to 19

      tokenNumber = (int) (7*Math.random());
      rotationNumber = (int) (4*Math.random());

      if (isValidPosition(x,y,tokenNumber,rotationNumber)) break;
    }


    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];

    drawToken(x,y,xArray,yArray);
    repaint();
  }


  public void clearCompleteRow(int[] completed)
  {
    // erase
    for (int i=0;i<completed.length;i++)
    {
      if (completed[i]==1)
      {
        for (int x=0;x<10;x++)
        {
          occupied[x][i]=0;
        }
      }
    }

    repaint();
  }

  public void checkRowCompletion()
  {
    int[] complete = new int[20];
    for (int y=0;y<20;y++)  // 20 rows
    {
      int filledCell = 0;
      for (int x=0;x<10;x++)  // 10 columns
      {
        if (occupied[x][y]==1) filledCell++;
        if (filledCell==10) // row completed 
        {
          complete[y]=1;
        }
      }
    }

    clearCompleteRow(complete);
   
  }

  boolean gameOver=false;
  public void addFallingToken()
  {
    int x=5,y=0;
    int tokenNumber, rotationNumber;


      tokenNumber = (int) (7*Math.random());
      rotationNumber = (int) (4*Math.random());



    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];

    if (!isValidPosition(x,y,tokenNumber,rotationNumber)) 
    {
      gameOver=true;
      drawToken(x,y,xArray,yArray);
      repaint();
      return;
    }

    drawToken(x,y,xArray,yArray);
    repaint();

    int delay=50;  // mini second
    int frame=0;
    boolean reachFloor=false;
    while (!reachFloor)
    {
      try { Thread.sleep(delay); } catch (Exception ignore) {}
      eraseToken(x,y,xArray,yArray);

      // add keyboard control
      if (leftPressed && isValidPosition(x-1,y,tokenNumber,rotationNumber)) x -= 1;
      if (rightPressed && isValidPosition(x+1,y,tokenNumber,rotationNumber)) x += 1;
      if (downPressed && isValidPosition(x,y+1,tokenNumber,rotationNumber)) y += 1;
      if (spacePressed && isValidPosition(x,y,tokenNumber,(rotationNumber+1)%4)) 
      {
        rotationNumber = (rotationNumber+1)%4;
        xArray = xRotationArray[tokenNumber][rotationNumber];
        yArray = yRotationArray[tokenNumber][rotationNumber];
        spacePressed=false;  
      }

      if (frame % 30==0) y += 1;  // fall for every 30 frame
      if (!isValidPosition(x,y,tokenNumber,rotationNumber)) // reached floor
      {
        reachFloor=true;
        y -= 1;  // restore position
      }
      drawToken(x,y,xArray,yArray);
      repaint();
      frame++;
    }

  }

  public void printGameOver()
  {
    javax.swing.JLabel gameOverLabel = new javax.swing.JLabel("GAME OVER");
    gameOverLabel.setBounds(300,300,100,30);
    add(gameOverLabel);
    repaint();
  }


  boolean leftPressed=false;
  boolean rightPressed=false;
  boolean downPressed=false;
  boolean spacePressed=false;

  // must implements this method for KeyListener
  public void keyPressed(java.awt.event.KeyEvent event)
  {
//    System.out.println(event);
    if (event.getKeyCode()==37) // left arrow
    {
      leftPressed=true;
    }
    if (event.getKeyCode()==39) // right arrow
    {
      rightPressed=true;
    }
    if (event.getKeyCode()==40) // down arrow
    {
      downPressed=true;
    }
    if (event.getKeyCode()==32) // space
    {
      spacePressed=true;
    }

  }

  // must implements this method for KeyListener
  public void keyReleased(java.awt.event.KeyEvent event)
  {
//    System.out.println(event);

    if (event.getKeyCode()==37) // left arrow
    {
      leftPressed=false;
    }
    if (event.getKeyCode()==39) // right arrow
    {
      rightPressed=false;
    }
    if (event.getKeyCode()==40) // down arrow
    {
      downPressed=false;
    }
    if (event.getKeyCode()==32) // space
    {
      spacePressed=false;
    }

  }

  // must implements this method for KeyListener
  public void keyTyped(java.awt.event.KeyEvent event)
  {
//    System.out.println(event);
  }


  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris19 tetris = new Tetris19();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);

    try { Thread.sleep(1000); } catch (Exception ignore) {}

    window.addKeyListener(tetris);  // listen to keyboard event

    tetris.gameOver=false;
    while (!tetris.gameOver)
    {
      tetris.addFallingToken();
      tetris.checkRowCompletion();
    }

    tetris.printGameOver();

  } 

}

Step 20 : shift down everything above a completed row

Hint : This step produce a really playable version, the next step will be the final polished version
/******************************************************************************
* File : Tetris20.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller
// Step 6 : create a drawToken() method
// Step 7 : create a eraseCell() method, create an occupied array
// Step 8 : modify drawCell() so that only fill the occupied array,
//          move the actual drawing code to paint()
// Step 9 : modify drawToken() to accept an array of relative position
// Step 10 : define rotation array of the first token
// Step 11 : define rotation array of the second token
// Step 12 : create global array to hold the rotation array
// Step 13 : create rotation array for all the seven tokens
// Step 14 : random token test
// Step 15 : add validation check
// Step 16 : add a falling token
// Step 17 : game over check
// Step 18 : add keyboard control
// Step 19 : erase filled rows
// Step 20 : shift down everything above a completed row

class Tetris20 extends javax.swing.JPanel 
implements java.awt.event.KeyListener
{
  int[][] occupied = new int[10][20];

    // [seven tokens] [ four rotations ] [ four cells]
    static int[][][] xRotationArray = {
       { {0,0,1,2}, {0,0,0,1}, {2,0,1,2}, {0,1,1,1} },  // token number 0
       { {0,0,1,1}, {1,2,0,1}, {0,0,1,1}, {1,2,0,1} },  // token number 1
       { {1,1,0,0}, {0,1,1,2}, {1,1,0,0}, {0,1,1,2} },  // token number 2
       { {0,1,2,2}, {0,1,0,0}, {0,0,1,2}, {1,1,0,1} },  // token number 3
       { {1,0,1,2}, {1,0,1,1}, {0,1,1,2}, {0,0,1,0} },  // token number 4
       { {0,1,0,1}, {0,1,0,1}, {0,1,0,1}, {0,1,0,1} },  // token number 5
       { {0,1,2,3}, {0,0,0,0}, {0,1,2,3}, {0,0,0,0} }   // token number 6
    };

    static int[][][] yRotationArray = {
       { {0,1,0,0}, {0,1,2,2}, {0,1,1,1}, {0,0,1,2} },  // token number 0
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 1
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 2
       { {0,0,0,1}, {0,0,1,2}, {0,1,1,1}, {0,1,2,2} },  // token number 3
       { {0,1,1,1}, {0,1,1,2}, {0,0,1,0}, {0,1,1,2} },  // token number 4
       { {0,0,1,1}, {0,0,1,1}, {0,0,1,1}, {0,0,1,1} },  // token number 5
       { {0,0,0,0}, {0,1,2,3}, {0,0,0,0}, {0,1,2,3} }   // token number 6
    };


  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);          

  }

  public void drawCell(int x,int y)
  {
    occupied[x][y] = 1;
  }

  public void eraseCell(int x,int y)
  {
    occupied[x][y] = 0;
  }

  public void drawToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      drawCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void eraseToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      eraseCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);

    for (int x=0;x<occupied.length;x++)
      for (int y=0;y<occupied[0].length;y++)
        if (occupied[x][y]==1)
        {
          // draw cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
          gr.setColor(java.awt.Color.RED);
          gr.fillRect(x*24+1,y*24+1,22,22);
        }
        else
        {
          // erase cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
        }
  }

  public boolean isValidPosition(int x,int y, int tokenNumber, int rotationNumber)
  {
    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];
    
    for (int i=0;i<4;i++)  // loop over the four cells 
    {
      int xCell = x+xArray[i];
      int yCell = y+yArray[i];

      // range check
      if (xCell<0) return false;
      if (xCell>=10) return false;
      if (yCell<0) return false;
      if (yCell>=20) return false;

      // occupancy check
      if (occupied[xCell][yCell]==1) return false;
    }
    return true;
  }

  public void randomTokenTest()
  {
    try { Thread.sleep(1000); } catch (Exception ignore) {}

    int x,y,tokenNumber,rotationNumber;

    while (true)  // loop until position is valid
    {
      x=(int) (10*Math.random());    // random x: 0 to 9
      y=(int) (20*Math.random());    // random y: 0 to 19

      tokenNumber = (int) (7*Math.random());
      rotationNumber = (int) (4*Math.random());

      if (isValidPosition(x,y,tokenNumber,rotationNumber)) break;
    }


    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];

    drawToken(x,y,xArray,yArray);
    repaint();
  }


  public void clearCompleteRow(int[] completed)
  {
    // erase
    for (int i=0;i<completed.length;i++)
    {
      if (completed[i]==1)
      {
        for (int x=0;x<10;x++)
        {
          occupied[x][i]=0;
        }
      }
    }

    repaint();
  }

  public void shiftDown(int[] completed)
  {
    for (int row=0;row<completed.length;row++)
    {
      if (completed[row]==1)
      {
        for (int y=row;y>=1;y--)
        {
          for (int x=0;x<10;x++)
          {
            occupied[x][y] = occupied[x][y-1];
          }
        }
      }
    }
  }

  public void checkRowCompletion()
  {
    int[] complete = new int[20];
    for (int y=0;y<20;y++)  // 20 rows
    {
      int filledCell = 0;
      for (int x=0;x<10;x++)  // 10 columns
      {
        if (occupied[x][y]==1) filledCell++;
        if (filledCell==10) // row completed 
        {
          complete[y]=1;
        }
      }
    }

    clearCompleteRow(complete);

    shiftDown(complete);   
  }

  boolean gameOver=false;
  public void addFallingToken()
  {
    int x=5,y=0;
    int tokenNumber, rotationNumber;


      tokenNumber = (int) (7*Math.random());
      rotationNumber = (int) (4*Math.random());



    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];

    if (!isValidPosition(x,y,tokenNumber,rotationNumber)) 
    {
      gameOver=true;
      drawToken(x,y,xArray,yArray);
      repaint();
      return;
    }

    drawToken(x,y,xArray,yArray);
    repaint();

    int delay=50;  // mini second
    int frame=0;
    boolean reachFloor=false;
    while (!reachFloor)
    {
      try { Thread.sleep(delay); } catch (Exception ignore) {}
      eraseToken(x,y,xArray,yArray);

      // add keyboard control
      if (leftPressed && isValidPosition(x-1,y,tokenNumber,rotationNumber)) x -= 1;
      if (rightPressed && isValidPosition(x+1,y,tokenNumber,rotationNumber)) x += 1;
      if (downPressed && isValidPosition(x,y+1,tokenNumber,rotationNumber)) y += 1;
      if (spacePressed && isValidPosition(x,y,tokenNumber,(rotationNumber+1)%4)) 
      {
        rotationNumber = (rotationNumber+1)%4;
        xArray = xRotationArray[tokenNumber][rotationNumber];
        yArray = yRotationArray[tokenNumber][rotationNumber];
        spacePressed=false;  
      }

      if (frame % 30==0) y += 1;  // fall for every 30 frame
      if (!isValidPosition(x,y,tokenNumber,rotationNumber)) // reached floor
      {
        reachFloor=true;
        y -= 1;  // restore position
      }
      drawToken(x,y,xArray,yArray);
      repaint();
      frame++;
    }

  }

  public void printGameOver()
  {
    javax.swing.JLabel gameOverLabel = new javax.swing.JLabel("GAME OVER");
    gameOverLabel.setBounds(300,300,100,30);
    add(gameOverLabel);
    repaint();
  }


  boolean leftPressed=false;
  boolean rightPressed=false;
  boolean downPressed=false;
  boolean spacePressed=false;

  // must implements this method for KeyListener
  public void keyPressed(java.awt.event.KeyEvent event)
  {
//    System.out.println(event);
    if (event.getKeyCode()==37) // left arrow
    {
      leftPressed=true;
    }
    if (event.getKeyCode()==39) // right arrow
    {
      rightPressed=true;
    }
    if (event.getKeyCode()==40) // down arrow
    {
      downPressed=true;
    }
    if (event.getKeyCode()==32) // space
    {
      spacePressed=true;
    }

  }

  // must implements this method for KeyListener
  public void keyReleased(java.awt.event.KeyEvent event)
  {
//    System.out.println(event);

    if (event.getKeyCode()==37) // left arrow
    {
      leftPressed=false;
    }
    if (event.getKeyCode()==39) // right arrow
    {
      rightPressed=false;
    }
    if (event.getKeyCode()==40) // down arrow
    {
      downPressed=false;
    }
    if (event.getKeyCode()==32) // space
    {
      spacePressed=false;
    }

  }

  // must implements this method for KeyListener
  public void keyTyped(java.awt.event.KeyEvent event)
  {
//    System.out.println(event);
  }


  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris20 tetris = new Tetris20();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);

    try { Thread.sleep(1000); } catch (Exception ignore) {}

    window.addKeyListener(tetris);  // listen to keyboard event

    tetris.gameOver=false;
    while (!tetris.gameOver)
    {
      tetris.addFallingToken();
      tetris.checkRowCompletion();
    }

    tetris.printGameOver();

  } 

}

Step 21: Congratulations

The long journey completed ! The game will have a maximum of 30 levels. The token will fall faster for each level. If you can finish level 30, then it will cycle back to level 0. However, the game becomes very difficult when it is near level 30.

Filling one row will awards you 10 bonus scores. If you fill more than one rows at the same time, each additional row will double the bonus. Due to the nature of the game, you may at most fill four rows at the same time. In this case, the score will be 10+20+40+80 = 150

/******************************************************************************
* File : Tetris21.java
* Author : http://java.macteki.com/
* Description :
*   Step by Step walk-through for building the tetris game
* Tested with : JDK 1.6
******************************************************************************/

// Step 1 : draw a green background
// Step 2 : draw a red cell with black border
// Step 3 : create a drawCell method()
// Step 4 : draw a token
// Step 5 : move the factor 24 into drawCell(), remove "*24" from caller
// Step 6 : create a drawToken() method
// Step 7 : create a eraseCell() method, create an occupied array
// Step 8 : modify drawCell() so that only fill the occupied array,
//          move the actual drawing code to paint()
// Step 9 : modify drawToken() to accept an array of relative position
// Step 10 : define rotation array of the first token
// Step 11 : define rotation array of the second token
// Step 12 : create global array to hold the rotation array
// Step 13 : create rotation array for all the seven tokens
// Step 14 : random token test
// Step 15 : add validation check
// Step 16 : add a falling token
// Step 17 : game over check
// Step 18 : add keyboard control
// Step 19 : erase filled rows
// Step 20 : shift down everything above a completed row
// Step 21 : add blinking effect, add score, add level

class Tetris21 extends javax.swing.JPanel 
implements java.awt.event.KeyListener
{
  int[][] occupied = new int[10][20];

    // [seven tokens] [ four rotations ] [ four cells]
    static int[][][] xRotationArray = {
       { {0,0,1,2}, {0,0,0,1}, {2,0,1,2}, {0,1,1,1} },  // token number 0
       { {0,0,1,1}, {1,2,0,1}, {0,0,1,1}, {1,2,0,1} },  // token number 1
       { {1,1,0,0}, {0,1,1,2}, {1,1,0,0}, {0,1,1,2} },  // token number 2
       { {0,1,2,2}, {0,1,0,0}, {0,0,1,2}, {1,1,0,1} },  // token number 3
       { {1,0,1,2}, {1,0,1,1}, {0,1,1,2}, {0,0,1,0} },  // token number 4
       { {0,1,0,1}, {0,1,0,1}, {0,1,0,1}, {0,1,0,1} },  // token number 5
       { {0,1,2,3}, {0,0,0,0}, {0,1,2,3}, {0,0,0,0} }   // token number 6
    };

    static int[][][] yRotationArray = {
       { {0,1,0,0}, {0,1,2,2}, {0,1,1,1}, {0,0,1,2} },  // token number 0
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 1
       { {0,1,1,2}, {0,0,1,1}, {0,1,1,2}, {0,0,1,1} },  // token number 2
       { {0,0,0,1}, {0,0,1,2}, {0,1,1,1}, {0,1,2,2} },  // token number 3
       { {0,1,1,1}, {0,1,1,2}, {0,0,1,0}, {0,1,1,2} },  // token number 4
       { {0,0,1,1}, {0,0,1,1}, {0,0,1,1}, {0,0,1,1} },  // token number 5
       { {0,0,0,0}, {0,1,2,3}, {0,0,0,0}, {0,1,2,3} }   // token number 6
    };


  int score=0;  // score
  int lineCompleted = 0;   // number of lines completed
  int level=0;

  javax.swing.JLabel scoreLabel = new javax.swing.JLabel("SCORE : 0");
  javax.swing.JLabel levelLabel = new javax.swing.JLabel("LEVEL : 0");

  public void init()
  {
    this.setPreferredSize(new java.awt.Dimension(640,480));
    this.setBackground(java.awt.Color.GREEN);          

    this.setLayout(null);    // absolute coordinate system
   
    scoreLabel.setBounds(300,50,100,30);  // x,y,w,h (in pixels)
    this.add(scoreLabel);

    levelLabel.setBounds(300,100,100,30);
    this.add(levelLabel);

  }

  public void drawCell(int x,int y)
  {
    occupied[x][y] = 1;
  }

  public void eraseCell(int x,int y)
  {
    occupied[x][y] = 0;
  }

  public void drawToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      drawCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void eraseToken(int x, int y, int[] xArray, int[] yArray)
  {
    for (int i=0;i<4;i++)
    {
      eraseCell(x+xArray[i],y+yArray[i]);
    }
  }

  public void paint(java.awt.Graphics gr)
  {
    super.paint(gr);

    for (int x=0;x<occupied.length;x++)
      for (int y=0;y<occupied[0].length;y++)
        if (occupied[x][y]==1)
        {
          // draw cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
          gr.setColor(java.awt.Color.RED);
          gr.fillRect(x*24+1,y*24+1,22,22);
        }
        else
        {
          // erase cell
          gr.setColor(java.awt.Color.BLACK);
          gr.fillRect(x*24,y*24,24,24);
        }
  }

  public boolean isValidPosition(int x,int y, int tokenNumber, int rotationNumber)
  {
    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];
    
    for (int i=0;i<4;i++)  // loop over the four cells 
    {
      int xCell = x+xArray[i];
      int yCell = y+yArray[i];

      // range check
      if (xCell<0) return false;
      if (xCell>=10) return false;
      if (yCell<0) return false;
      if (yCell>=20) return false;

      // occupancy check
      if (occupied[xCell][yCell]==1) return false;
    }
    return true;
  }

  public void randomTokenTest()
  {
    try { Thread.sleep(1000); } catch (Exception ignore) {}

    int x,y,tokenNumber,rotationNumber;

    while (true)  // loop until position is valid
    {
      x=(int) (10*Math.random());    // random x: 0 to 9
      y=(int) (20*Math.random());    // random y: 0 to 19

      tokenNumber = (int) (7*Math.random());
      rotationNumber = (int) (4*Math.random());

      if (isValidPosition(x,y,tokenNumber,rotationNumber)) break;
    }


    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];

    drawToken(x,y,xArray,yArray);
    repaint();
  }



  public void clearCompleteRow(int[] completed)
  {
    // must loop for odd number of times.
    // toggle sequence : 0,1,0,1,0
    for (int blinking=0;blinking<5;blinking++)
    {
      for (int i=0;i<completed.length;i++)
      {
        if (completed[i]==1)
        {
          for (int x=0;x<10;x++)
          {
            // toggle the occupancy array
            occupied[x][i]=1-occupied[x][i];
          }
        }
      }
      repaint();
      try { Thread.sleep(100); } catch (Exception ignore) {}
    }
  }

  public void shiftDown(int[] completed)
  {
    for (int row=0;row<completed.length;row++)
    {
      if (completed[row]==1)
      {
        for (int y=row;y>=1;y--)
        {
          for (int x=0;x<10;x++)
          {
            occupied[x][y] = occupied[x][y-1];
          }
        }
      }
    }
  }

  public void checkRowCompletion()
  {
    int[] complete = new int[20];
    for (int y=0;y<20;y++)  // 20 rows
    {
      int filledCell = 0;
      for (int x=0;x<10;x++)  // 10 columns
      {
        if (occupied[x][y]==1) filledCell++;
        if (filledCell==10) // row completed 
        {
          complete[y]=1;
        }
      }
    }

    clearCompleteRow(complete);

    shiftDown(complete);   

    addScore(complete);
  }

  void addScore(int[] complete)
  {
    int bonus=10;  // score for the first completed line
    for (int row=0;row<complete.length;row++)
    {
      if (complete[row]==1)
      {
        lineCompleted += 1;
        score+=bonus;
        bonus*=2;  // double the bonus for every additional line
      }
    }

    // advance level for every 3 completed lines
    level = lineCompleted/3;  
    if (level>30) { lineCompleted=0; level=0; }  // MAX LEVEL

    scoreLabel.setText("SCORE : "+score);
    levelLabel.setText("LEVEL : "+level);
  }

  boolean gameOver=false;
  public void addFallingToken()
  {
    int x=5,y=0;
    int tokenNumber, rotationNumber;


      tokenNumber = (int) (7*Math.random());
      rotationNumber = (int) (4*Math.random());



    int[] xArray = xRotationArray[tokenNumber][rotationNumber];
    int[] yArray = yRotationArray[tokenNumber][rotationNumber];

    if (!isValidPosition(x,y,tokenNumber,rotationNumber)) 
    {
      gameOver=true;
      drawToken(x,y,xArray,yArray);
      repaint();
      return;
    }

    drawToken(x,y,xArray,yArray);
    repaint();

    int delay=50;  // mini second
    int frame=0;
    boolean reachFloor=false;
    while (!reachFloor)
    {
      try { Thread.sleep(delay); } catch (Exception ignore) {}
      eraseToken(x,y,xArray,yArray);

      // add keyboard control
      if (leftPressed && isValidPosition(x-1,y,tokenNumber,rotationNumber)) x -= 1;
      if (rightPressed && isValidPosition(x+1,y,tokenNumber,rotationNumber)) x += 1;
      if (downPressed && isValidPosition(x,y+1,tokenNumber,rotationNumber)) y += 1;
      if (spacePressed && isValidPosition(x,y,tokenNumber,(rotationNumber+1)%4)) 
      {
        rotationNumber = (rotationNumber+1)%4;
        xArray = xRotationArray[tokenNumber][rotationNumber];
        yArray = yRotationArray[tokenNumber][rotationNumber];
        spacePressed=false;  
      }

      int f=31-level;   // fall for every 31 frames, this value is decreased when level up
      if (frame % f==0) y += 1;  
      if (!isValidPosition(x,y,tokenNumber,rotationNumber)) // reached floor
      {
        reachFloor=true;
        y -= 1;  // restore position
      }
      drawToken(x,y,xArray,yArray);
      repaint();
      frame++;
    }

  }

  public void printGameOver()
  {
    javax.swing.JLabel gameOverLabel = new javax.swing.JLabel("GAME OVER");
    gameOverLabel.setBounds(300,300,100,30);
    add(gameOverLabel);
    repaint();
  }


  boolean leftPressed=false;
  boolean rightPressed=false;
  boolean downPressed=false;
  boolean spacePressed=false;

  // must implements this method for KeyListener
  public void keyPressed(java.awt.event.KeyEvent event)
  {
//    System.out.println(event);
    if (event.getKeyCode()==37) // left arrow
    {
      leftPressed=true;
    }
    if (event.getKeyCode()==39) // right arrow
    {
      rightPressed=true;
    }
    if (event.getKeyCode()==40) // down arrow
    {
      downPressed=true;
    }
    if (event.getKeyCode()==32) // space
    {
      spacePressed=true;
    }

  }

  // must implements this method for KeyListener
  public void keyReleased(java.awt.event.KeyEvent event)
  {
//    System.out.println(event);

    if (event.getKeyCode()==37) // left arrow
    {
      leftPressed=false;
    }
    if (event.getKeyCode()==39) // right arrow
    {
      rightPressed=false;
    }
    if (event.getKeyCode()==40) // down arrow
    {
      downPressed=false;
    }
    if (event.getKeyCode()==32) // space
    {
      spacePressed=false;
    }

  }

  // must implements this method for KeyListener
  public void keyTyped(java.awt.event.KeyEvent event)
  {
//    System.out.println(event);
  }


  public static void main(String[] args) throws Exception
  {
    javax.swing.JFrame window = new javax.swing.JFrame("Macteki Tetris");
    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);

    Tetris21 tetris = new Tetris21();
    tetris.init();

    window.add(tetris);
    window.pack();
    window.setVisible(true);

    try { Thread.sleep(1000); } catch (Exception ignore) {}

    window.addKeyListener(tetris);  // listen to keyboard event

    tetris.gameOver=false;
    while (!tetris.gameOver)
    {
      tetris.addFallingToken();
      tetris.checkRowCompletion();
    }

    tetris.printGameOver();

  } 

}

Monday, June 20, 2011

Square Root of Two

The Easy Way


double x=Math.pow(2,0.5);
System.out.println(x);


Newton's Method

class NewtonRootTwo
{
  public static void main(String[] args) throws Exception
  {
    double guess=1;

    for (int i=0;i<100;i++)  // maximum 100 passes, more than enough
    {
      System.out.println("guess["+i+"]="+guess);
      double nextGuess = (guess+2/guess)/2;
      if (nextGuess==guess) break;  // cannot improve precision further
      guess = nextGuess; 
    }
  }
}

Result

java NewtonRootTwo
guess[0]=1.0
guess[1]=1.5
guess[2]=1.4166666666666665
guess[3]=1.4142156862745097
guess[4]=1.4142135623746899
guess[5]=1.414213562373095



Higher Precision

Since the double data type provides very limited precision, we must use the BigDecimal class to get higher precision.
import java.math.BigDecimal;

class HighPrecisionRootTwo
{
  public static void main(String[] args) throws Exception
  {
    int precision=60;    // 60 decimal places

    BigDecimal guess=new BigDecimal("1.0");
    BigDecimal TWO = new BigDecimal("2.0");

    for (int i=0;i<100;i++)  // maximum 100 passes, adjust if necessary
    {
      System.out.println("guess["+i+"]="+guess);
      // nextGuess = ( guess + 2/guess ) / 2
      BigDecimal nextGuess = guess.add(
        TWO.divide(guess,precision,BigDecimal.ROUND_DOWN)).
        divide(TWO,precision,BigDecimal.ROUND_DOWN);

      if (nextGuess.compareTo(guess)==0) break;
      guess = nextGuess; 
    }
  }
}
java HighPrecisionRootTwo
guess[0]=1.0
guess[1]=1.500000000000000000000000000000000000000000000000000000000000
guess[2]=1.416666666666666666666666666666666666666666666666666666666666
guess[3]=1.414215686274509803921568627450980392156862745098039215686274
guess[4]=1.414213562374689910626295578890134910116559622115744044584905
guess[5]=1.414213562373095048801689623502530243614981925776197428498289
guess[6]=1.414213562373095048801688724209698078569671875377234001561013
guess[7]=1.414213562373095048801688724209698078569671875376948073176679



Computing Root Two to Million of Digits

The built in BigDecimal is just not fast enough. You will need to implement a faster BigDecimal class. "apfloat for java" may be a good choice.

Wednesday, June 15, 2011

Arithmetic Evaluator (BNF tokenizer)

Previous Work

This is the third article about arithmetic evaluator. Readers are suggested to read the whole series :

To summarize the previous work, let's consider the following arithmetic expression :
31*(2+4)+16

To evaluate the expression, we need three steps :
  1. Tokenize the expression
  2. Convert the expression into postfix form
  3. Evaluate the postfix expression

Tokenization

The second and the third step have been discussed in the preceding articles. We are going to write a tokenizer in this article.

Informally, we may define a token as an atomic piece of information. This definition highly depends on the context. For the arithmetic expression, a token can be a number, an operator, or a bracket sign. We may manually "tokenize" the expression by adding spaces to separate every token. Hence the tokenized expression become :
31 * ( 2 + 4 ) + 16

This is of course not very desirable because we are forcing the user to add spaces in the expression. As a user, it is reasonable to assume the following are all identical expressions :
31 * ( 2 + 4 ) + 16
31*(2+4)+16
31 * (2+4)         + 16
Hence we must implement a tokenizer such that no matter which of the above is input by the user, repeatedly calling the nextToken() function will return the token in the following order :
"31"   "*"   "("   "2"   "+"   "4"   ")"   "+"   "16"

BNF

BNF stands for "Backus Normal Form", which is named after its inventor Backus Naur. It is used for defining the syntax of a programming language.

For example, consider the following simple expression :
3+4
The above expression is the sum of two terms. We may make a simple definition for expression :
<expression> :=  <term> + <term>

The symbol ":=" means "is defined as".

The above definition has an obvious problems. It only works for expression with exactly two terms. In the real world, an expression can contain one term only. Hence we may redefine the expression as :
<expression> :=  <term>  
OR  
<expression> :=  <term> + <term>
In BNF notation, we use a vertical stroke to represent "OR". Hence we should rewrite the above as :
<expression> :=  <term>  |  <term> + <term>

With a recursive definition, we may extend the above definition such that it defines expression with indefinite number of terms :
<expression> :=  <term>  |  <term> + <expression>

Up to now we didn't define what is a term. Assume we will only handle positive integer in our expressions. We may define term as :

<term> := <positive_integer>
<positive_integer> := <digit> | <digit> <positive_integer>
<digit> := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

We just defined term as a positive integer, and defined positive integer as "either a single digit" or "a single digit followed by a positive integer". This is another recursive definition.

We can extends our definition of expression to include subtraction of terms.

<expression> :=  <term>  |  <term> + <expression> | <term> - <expression>

Or in a more elegant way :
<expression> :=  <term>  |  <term> <additive_operator> <expression> 
<additive_operator> := + | -

We will not handle multiplication and division at the moment. You will see that it is easy to extend the BNF later.

Let's repeat the whole definition of arithmetic expression as follows :
<expression> :=  <term>  |  <term> <additive_operator> <expression> 
<additive_operator> := + | -
<term> := <positive_integer>
<positive_integer> := <digit> | <digit> <positive_integer>
<digit> := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

BNF Parser

Now we may write a parser for the above BNF. It is very easy to implement such a parser using a top down approach. Let's write the skeleton of a simple parser for the first rule :

We may write :
/********************************************************
<expression> :=  <term>  |  <term> <additive_operator> <expression> 
********************************************************/
  void readExpression()
  {
    readTerm();
    if (isAdditiveOperator(peek())
    {
      readAdditiveOperator();
      readExpression();
    }
  }

In order to parse an expression, we must have a function to read the expression character by character. We will name this function as next(). We also need a function to "look" at the next available character, without consuming it, this function is named peek().

For example, if the expression is "3+5", repeatedly calling next() will return "3","+","5", EOL. (End of Line mark)

Whereas repeatedly calling peek() will return "3","3","3","3","3",...

Let's write the skeleton for every BNF rule :
/********************************************************
<expression> :=  <term>  |  <term> <additive_operator> <expression> 
********************************************************/
  void readExpression()
  {
    readTerm();
    if (isAdditiveOperator(peek())
    {
      readAdditiveOperator();
      readExpression();
    }
  }

/********************************************************
<additive_operator> := + | -
********************************************************/
  void readAdditiveOperator()
  {
    token=next();    // read next character (must be a plus or minus sign)
    outputToken();  
  }

/********************************************************
<term> := <positive_integer>
********************************************************/
  void readTerm()
  {
    readPositiveInteger();
  }

/********************************************************
<positive_integer> := <digit> | <digit> <positive_integer>
********************************************************/
  void readPositiveInteger()
  {
    if (isDigit(peek())   // read all digits
    {
      token += next();       // append digit to token   
      readPositiveInteger();
    }
    outputToken();       // no more digits, so output the token
                         // and then reset the token to empty String
  }

/********************************************************
<digit> := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
********************************************************/
  boolean isDigit(String ch)
  {
    String digits="0123456789";
    return (digits.indexOf(ch)>=0);  
  }
  


Multiplication and Division

It is easy to extend our BNF definition to include multiplication. We have defined expression as :
<expression> := <term> | <term> <additive_operator> <expression>

And we have defined term as positive_integer. However, we know that a term can actually be a product one or more factors. Hence we may redefine as follows :
<factor> := <number>
<term> := <factor> | <factor> * <term>
We may extend the multiplicative operator to include division as well :
<factor> := <number>
<term> := <factor> | <factor> <multiplicative_operator> <term>
<multiplicative_operator> := * | /

Factors are not always simple numbers. Some factors are rather complex, for example, consider the following expression :
(3+4)*5*7 + 2*3 + 1

There are three terms in the expression, the first term is (3+4)*5*7, the second term is 2*3 and the third term is 1. The first term contains three factors, (3+4), 5 and 7. Hence a factor can be a bracket expression as well as a simple number.

We now extend the definition of factor such that either it is a number, or it is a bracket expression such as (3+4).
<factor> := <number> | ( <expression> )

And of course we need to define what is a <number>. The full definition is as follows :

<expression> := <term> | <term> <additive_operator> <expression>
<additive_operator> := + | -
<term> := <factor> | <factor> <multiplicative_operator> <term>
<multiplicative_operator> := * | /
<factor> := <number> | <signed_number> | ( <expression> )
<signed_number> := <additive_operator> <number>
<number> := <positive_integer> | <positive_integer> . <positive_integer>
<positive_integer> := <digit> | <digit> <positive_integer>
<digit> := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9


Putting it altogether

We can now write an arithmetic evaluator to evaluate the value of a rather complex expression.
We will use the following as an example in our sample code.
String expr="((-3.14*(2+(-4)))-7)/3+7-2*2*2+9";
You may try to calculate the expression in an Excel spread sheet and you will see the answer is 7.76. We are going to compute this answer with our own evaluator.

We will need four Java source files to achieve the purpose :
  1. CharReader.java
  2. It scans the input String character by character, providing the function next() and peek()
  3. ArithmeticBNF.java
  4. It parses the BNF grammer of arithmetic expression and tokenize it. Spaces are added between each token so that :
    (3.14+41)*2/5 would become "( 3.14 + 41 ) * 2 / 5"
    
  5. PostfixConverter.java
  6. It converts the tokenized expression into postfix form. Hence the expression would become :
    3.14 41 + 2 * 5 /
    
  7. PostfixEvaluator.java
  8. It evaluates the postfix expression. The result of the above postfix would be 17.656

PostfixEvaluator.java and PostfixConverter.java can be found in the following links :

Arithmetic Evaluator(Postfix Evaluator)
Arithmetic Evaluator(Infix to Postfix)

The other two files is listed at the end of this article.

To test, you will need all four Java files in the same folder.
javac *.java
java ArithmeticBNF
The output would be :
((-3.14*(2+(-4)))-7)/3+7-2*2*2+9=7.76

If you wish to compute the other expression, find the following line in main() and change it as needed.
String expr="((-3.14*(2+(-4)))-7)/3+7-2*2*2+9";

/******************************************************************************
* File : CharReader.java
* Author : http://java.macteki.com/
* Description :
*   Scan an input String character by character.
* Tested with : JDK 1.6
******************************************************************************/

class CharReader
{
  private String text;
  private char[] charArray;
  private int charPointer=0;
  public static final String EOL="\n";  // End of Line mark

  public CharReader(String s)
  {
    this.text=s;
    this.charArray = s.toCharArray();
    this.charPointer = 0;
  }

  // read next character, the character is "consumed" and the pointer is advanced
  public String next()
  {
    if (charPointer>=charArray.length) return EOL;
    return ""+this.charArray[charPointer++];
  }

  // "look" at the next character, the character is not "consumed".
  // hence the pointer is not advanced.
  public String peek()
  {
    if (charPointer>=charArray.length) return EOL;
    return ""+this.charArray[charPointer];
  }

  // testing 
  public static void main(String[] args) throws Exception
  {
    String s="apple orange";
    CharReader charReader=new CharReader(s);
    String ch="";
    while ((ch=charReader.next())!=EOL)
    {
      System.out.print("["+ch+"] ");
    }
  }
}

/******************************************************************************
* File : ArithmeticBNF.java
* Author : http://java.macteki.com/
* Description :
*   Parse and tokenize an arithmetic expression according to the BNF syntax.
*   the expression is defined in main() :
*       String expr="((-3.14*(2+(-4)))-7)/3+7-2*2*2+9";
*   The tokenized expression will be :
*      "( ( -3.14 * ( 2 + ( -4 ) ) ) -7 ) / 3 + 7 - 2 * 2 * 2 + 9"
* Tested with : JDK 1.6
******************************************************************************/

/****************************************************************************** 
BNF Definition :
<expression> := <term> | <term> <additive_operator> <expression>
<additive_operator> := + | -
<term> := <factor> | <factor> <multiplicative_operator> <term>
<multiplicative_operator> := * | /
<factor> := <number> | <signed_number> | ( <expression> )
<signed_number> := <additive_operator> <number>
<number> := <positive_integer> | <positive_integer> . <positive_integer>
<positive_integer> := <digit> | <digit> <positive_integer>
<digit> := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
******************************************************************************/

class ArithmeticBNF
{
  CharReader reader;
  String token="";
  String tokenList="";

  public ArithmeticBNF(String expr)
  {
    reader = new CharReader(expr);
  }

  void outputToken()
  {
    System.out.println(token);
    tokenList+=token+" ";
    token="";
    skipBlanks();
  }

  void skipBlanks()
  {
    while (peek().equals(" "))
    {
      next();
    }
  }

  String next()
  {
    return reader.next();
  }
  
  String peek()
  {
    return reader.peek();
  }

  // <expression> := <term> | <term> <additive_operator> <expression>
  void expression()
  {
    term();
    if (isAdditiveOperator(peek()))
    {
      additiveOperator();
      expression();
    }
  }


  // <additive_operator> := + | -
  void additiveOperator()
  {
    if (isAdditiveOperator(peek()))
    {
      token=next();
      outputToken();
    }
    else
    {
      throw new RuntimeException("Syntax error : expecting operator");
    }
  }

  // <multiplicative_operator> := * | /
  void multiplicativeOperator()
  {
    if (isMultiplicativeOperator(peek()))
    {
      token=next();
      outputToken();
    }
    else
    {
      throw new RuntimeException("Syntax error : expecting operator");
    }
  }


  // <number> := <positive_integer> | <positive_integer> . <positive_integer>
  void number()
  {
    positiveInteger();
    if (peek().equals("."))
    {
      decimalPoint();
      positiveInteger();
    }
  }

  void decimalPoint()
  {
    token+=next();
  }

  void sign()
  {
    token+=next();
  }

  // <positive_integer> := <digit> | <digit> <positive_integer>
  void positiveInteger()
  {
    if (isDigit(peek()))
    {
      token+=next();      
      positiveInteger();
    }    
  }

  // <factor> := <number> | <signed_number> | ( <expression> )
  // <signed_number> := <additive_operator> <number>
  void factor()
  {
    if (peek().equals("(")) 
    { 
      bracketExpression();  
    }
    else if (isDigit(peek()))
    {
      number();
      outputToken();
    }
    else if (isAdditiveOperator(peek()))
    {
      sign();
      number();
      outputToken();
    }
    else
      throw new RuntimeException("Unexpected character "+peek());
  }

  void bracketExpression()
  {
    leftBracket();

    expression();

    rightBracket();
  }

  void leftBracket()
  {
    if (peek().equals("("))
    {
      token=next();
      outputToken();
    }
    else
    {
      throw new RuntimeException("Expecting left bracket");
    }
  }

  void rightBracket()
  {
    if (peek().equals(")"))
    {
      token=next();
      outputToken();
    }
    else
    {
      throw new RuntimeException("Expecting right bracket");
    }
  }
  

  // <term> := <factor> | <factor> <multiplicative_operator> <term>
  void term()
  {
    factor();

    if (isMultiplicativeOperator(peek()))
    {
      multiplicativeOperator();
      term();
    }
  }

  // <digit> := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
  boolean isDigit(String ch)
  {
    String digits="0123456789";
    return (digits.indexOf(ch)>=0);
  }

  // <additive_operator> := + | -
  boolean isAdditiveOperator(String ch)
  {
    String operators="+-";
    return (operators.indexOf(ch)>=0);
  }

  // <multiplicative_operator> := * | /
  boolean isMultiplicativeOperator(String ch)
  {
    String operators="*/";
    return (operators.indexOf(ch)>=0);
  }

  
  public static void main(String[] args) throws Exception
  {
    String expr="((-3.14*(2+(-4)))-7)/3+7-2*2*2+9";
    ArithmeticBNF bnf=new ArithmeticBNF(expr);

    bnf.expression();
    String postfix=PostfixConverter.convert(bnf.tokenList); 
    double value=PostfixEvaluator.evaluate(postfix);
    
    System.out.println(expr+"="+value);
  }
}