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 :- know how to compile and run the sample in this series
- know where to find documentation of a Java API
- 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(); } }