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();

  } 

}

11 comments:

  1. hi!
    I have one Question, what is the Files Tetris 1 to ... should I down load it? If yes where?

    Thank You!

    ReplyDelete
  2. hi Ivan,

    i tried your code and it is really great and ive modified it to have two tetris displays in one window. i need to know how i can display only the fallen blocks in the left and the falling blocks in the right

    ReplyDelete
    Replies
    1. To do so, you must have some ways to distinguish the fallen
      and the falling blocks.

      Note that currently the occupany array only has two possible values, 1 or 0.
      Looking at the paint() method,
      if occupied[x][y]==1, a red square would be drawn.
      if occupied[x][y]==0, nothing would be done

      Now you would need to define a new value, for example:
      if (occupied[x][y]==0) do nothing
      if (occupied[x][y]==1) draw only on the left
      if (occupied[x][y]==2) draw only on the right

      The logic of setting occupied[x][y] is the same,
      except that at the very end of addFallingToken(),
      set the occupied array to 2 before returning.

      for (int i=0;i<4;i++)
      occupied[(x+xArray[i])][(y+yArray[i])]=2;

      Delete
    2. Ivan, Thank you so much man you made my day.....

      Delete
  3. Ivan, is it possible to make the black area where the pieces move around larger, so that the pieces have more wiggle room?

    ReplyDelete
    Replies
    1. The power of open source program is that you may modify it to fit your needs.

      1. At the very beginning, instead of writing:
      int[][] occupied = new int[10][20];
      write :
      int DIMENSION_X=20, DIMENSION_Y=25;
      int[][] occupied = new int[DIMENSION_X][DIMENSION_Y];

      2. inside init(), make change to these lines:
      this.setPreferredSize(new java.awt.Dimension(640,DIMENSION_Y*24));
      scoreLabel.setBounds(DIMENSION_X*24+60,50,100,30);
      levelLabel.setBounds(DIMENSION_X*24+60,100,100,30);

      3. inside isValidPosition()
      if (xCell>=DIMENSION_X) return false; // was:xCell>=10
      if (yCell>=DIMENSION_Y) return false; // was:yCell>=20

      4. inside clearCompleteRow(), modify the inner for loop
      for (int x=0;x<DIMENSION_X;x++)

      5. inside shiftDown(), modify the inner for loop
      for (int x=0;x<DIMENSION_X;x++)

      6. inside checkRowCompletion(), make change to these lines:
      int[] complete = new int[DIMENSION_Y];
      for (int y=0;y<DIMENSION_Y;y++)
      for (int x=0;x<DIMENSION_X;x++)
      if (filledCell==DIMENSION_X)

      7. inside printGameOver()
      gameOverLabel.setBounds(DIMENSION_X*24+60,300,100,30);

      Delete
    2. This comment has been removed by the author.

      Delete
  4. In addition, how can you delete columns rather than rows?

    ReplyDelete
  5. Came across this code when I was trying to build my own tetris, it's a big help and thank you!! But I wanted to add in, as addition, that every shape has a different color, but I can't seem to figure out how to do it.. ?

    ReplyDelete
    Replies
    1. Have a look at:
      https://github.com/macteki/colorful-game

      Delete