A friend once told me that whenever he learned a new programming language, he would challenge himself to write a Tetris game using that language. So in this tradition, I took it upon myself to do the very same when I first learned to program using the Java language. My first attempt, although a complete game, was very basic and very ugly. As time went on and I gained greater experience in Java design and development, I knew that I could separate the game model from the GUI (similar to Swing components) and create a Tetris bean. So I set about doing just that.
In this article, I'll guide you through the construction and implementation of Tetris bean. You can see the TetrisBean in use. Just click Start and use the arrow keys on your keyboard to maneuver the tetris pieces. Pressing the up-arrow changes the orientation of the piece; pressing the left- and right-arrows moves the piece sideways; and pressing the down-arrow moves the piece to the bottom. Give it a try!
Tetris has several components that can be expressed as Java objects:
- The Tetris pieces
- The Tetris board that holds the pieces
- The game that controls the pieces on the board, manages scoring, and so on.
We'll look at each of these elements more closely.
The Tetris pieces: Class TetrisPiece
The key elements of a Tetris piece, shown in Figure 1, are as follows:
- Each piece is made up of exactly four blocks.
- Each block in the piece has an (x,y) coordinate within the Tetris board.
- Each piece has a rotation factor of either 0, 2, or 4.
- Each piece can be either an L, J, S, O, I, Z, or T shape.
Figure 1. Four elements are needed for each Tetris piece: blocks, an (x,y) coordinate, a rotation factor, and its shape.
The first two items can be implemented using a very simple system. By choosing a centre block for each piece and storing its (x,y) position, you can store the rest of the blocks in the piece as coordinates relative around it. This allows you to describe any style of piece by simply storing its shape as blocks relative around a centre piece. The centre point can be stored as a java.awt.Point and the relative coordinates in a java.awt.Point array. To make it easier for coordinate calculations, you can also store the centre piece as a block with relative coordinates (0,0).
This system also makes things easier when it comes to calculating the rotation of the pieces. Using simple matrix manipulation, you can rotate a piece 90 degrees clockwise by simply swapping the y-coordinate with the x-coordinate and the x-coordinate with the negative of the y-coordinate. Since we happen to be using relative coordinates around a centre point, we can do the same here:
temp = x; x = -y; y = temp; |
You can also rotate a piece 90 degrees counterclockwise by applying three clockwise rotations. (Try it out for yourself if you don't believe me!)
Finally, not all the pieces have the same amount of rotation factor, so some checks will need to be implemented in the rotation method to compensate for this.
Because we use the same TetrisPiece class to represent all the types of pieces, we need a way to distinguish among them. To do this, we use some static int constants to represent the different types and have a local variable to store the piece's type. Here is an example of one of the constants:
public static final int L_PIECE = 0; |
Because we'll be moving the pieces around the Tetris board, we need to provide a move method that allows us to do this. Notice that some moves may be illegal (such as an attempt to move right when you are already as far right as possible), so we'll need to validate all move requests. We'll do this against the Tetris board, which we'll store a reference to. So from this the constructor of our class will take two parameters: first the type of piece created, and second a reference to the board. In the constructor, we'll call a private utility method named initalizeBlocks(), which will set the values of the piece's relative coordinates to what the respective piece type is.
A simple approach of checking move legality is to remove the piece from the board, then move it in the desired direction and see if it fits. If it does, then put the piece into the new board position. Otherwise, undo the move and put it back where it was. Depending on the result, the returned value will either be true (if the move was fine) or false (if it wasn't).
One type of move that needs a bit more consideration is that of a piece fall. A fall means the piece will immediately drop to its lowest position in the board. To do this we need a while loop that will continue moving the piece down until it can't move any further. The piece will then be placed at that position.
To differentiate the various moves that can be applied to a piece we'll use some extra static int constants, as shown in the following example:
public static final int LEFT = 10; |
The willFit() method for checking if a piece fits will be implemented in the TetrisBoard class later.
Finally, to wrap up the TetrisPiece class we have a few getters and setters for variables such as the centre point and the relative coordinates, and a static method called getRandomPiece() that will return a TetrisPiece instance of random type.
The complete source, which includes the TetrisPiece class, is available for download in Resources.
The Tetris board: Class TetrisBoard
A Tetris board can be thought of as a 2D grid containing empty blocks and coloured blocks. Because the various types of Tetris pieces are differentiated by int constants, all we need to do is define a value for an empty block and we can store the board as a 2D int array. Using this approach, an empty block in the board will be represented by:
public static final int EMPTY_BLOCK = -1; |
To keep things flexible, we'll allow the size of the board to be variable but define this in the constructor. So the constructor will accept two ints representing the number of rows and columns. It will then call a resetBoard() method that will default all values in the 2D array to the empty block.
Because pieces are added to and removed from the board, we provide addPiece()
and removePiece() methods. The addPiece() method works by taking a TetrisPiece() and setting the values of all the positions on the board it occupies to that of its type. The removePiece() method is similar except the board values are set to that of the empty block.
To keep users updated when something happens on the board, a BoardEvent will be fired when pieces are added or moved. For classes to listen to this event, we need a BoardListener interface, whose boardChange() method gets called when the event is fired. These events can be used by a Tetris board GUI to be notified when the screen needs updating. To store the listeners, we'll use a java.util.Vector and provide relevant methods for adding and removing the listeners and firing events.
Occasionally, it might be inappropriate to fire BoardEvents when you're adding and removing pieces; for example, when a piece fall is requested (remember this move uses a while loop to drop the piece). In this case, an event is only required when the piece hits the bottom. To facilitate this, we'll create the addPiece() method so it takes a boolean parameter, which will cause events to be fired only if the value is true.
One important factor in the game of Tetris is that when a line is completed it disappears and all rows above it drop down. To do this, we'll provide a removeRow() method that accepts the index of the row to remove as
a parameter. After the row has been removed, a BoardEvent will get fired.
In addition to the necessary getters and setters to access private variables, we need one more method: the
willFit() method discussed earlier. This method takes TetrisPiece as a parameter and returns a boolean value to determine whether the piece fits in this board. By fitting I mean that the piece is within the boundaries of the board and the blocks on the board corresponding to where the piece would fit are set to the empty block value. If this is the case, then a value of true is returned.
That completes the TetrisBoard class. Again, the complete source, which includes this class is available for download in Resources.
The Tetris game model: Class TetrisGame
Now that we've created the two main components used in a Tetris game, all we have to do is put them together with a little game logic and we're away.
A nice way to control the flow of play is to embed it in an inner class extending java.lang.Thread. One advantage of this approach is that we can add a thread sleep call to control the speed of play. Another is that because the main application thread is now freed up we shouldn't have any painting problems when a GUI is attached, which can happen sometimes if the main thread gets tied up and doesn't have enough time to paint.
The logic in the thread will be implemented within a while loop inside the run() method. The loop will continually create pieces and drop them within the game board until no more pieces can fit. At this point a local boolean variable called fPlaying will be set to false, ending the loop and firing a GameEvent to signify the end of the game.
Within the while loop is an if statement that checks on the value of the boolean fPaused. If this is set to true, the loop will continue running but all the game logic will be bypassed, giving the impression of a pause. When the boolean is changed back to false the game will continue.
Because we're concerned only with one piece dropping at a time, we'll create a variable called fCurrPiece that will store a reference to it. If this variable is set to null, it means the previous piece could not move down any further and has been left in its final position on the board. At this point we need to create a new piece and position it at the centre of the top of the board. For all the time that the fCurrPiece variable is non-null, all we have to do is drop it by one position and sleep the thread for a given amount of time.
When a piece cannot move any further, we need to determine whether we have any completed lines. An easy way to do this is to use a pair of nested for loops where the outer one works up through the row indexes and the inner one checks across them. If we find a completed line, we can call the removeRow() method implemented in the TetrisBoard class and pass it the index of the completed line. Because all rows above it will now be shifted down by one, we'll need to check that row again. To encourage the completion of multiple lines at once we'll store a count of the lines completed and award a high score increment respectively.
Another major element of Tetris game play is the fact that as more lines are completed the pieces fall faster. This feature can be implemented by checking the number of lines completed so far and decrementing the thread sleep delay accordingly.
That's all there is to the GameThread inner class, but there is another inner class we'll need to implement. Because there will be a load of events fired that in turn will require listeners to be stored, it would be neat for us to keep them all in one place. We'll do this using the EventHandler inner class.
This class will store references to listeners that are interested in the events we fire. In addition to providing add and remove methods for the listeners, there'll also be utility methods for firing the events.
This class takes care of the following types of events:
-
GameEvent, which gets fired whenever a game starts or stops. It has a value to signify a gameSTARTorEND.
-
BoardEvent, which gets fired whenever something has changed on the board. In theEventHandlerclass the add/remove listener calls are passed through to theTetrisBoardclass.
-
ScoreEvent, which gets fired whenever the score changes.
There are many other types of events that could get fired, but to keep things simple I've stuck to the ones above. One other obvious event we could implement is LineEvent, which would get fired when a line or multiple lines are completed and potentially could be used to trigger an on-screen animation.
Now that we've finished the inner classes, we need to describe the rest of the TetrisGame class. As with all Java beans, we need a zero argument constructor. In this constructor we'll create an instance of the EventHandler class and an instance of the TetrisBoard class. The TetrisBoard class will have a default size of 10x20.
To control the state of the game we'll use some start, stop, and pause methods. The startGame()
method will reset all game variables and fire both a ScoreEvent (as it has now been reset to zero) and a GameEvent (with a parameter type of START). It will also create and kick off a GameThread. The stopGame() method will change the fPlaying variable to false, causing the GameThread to end and fire a GameEvent with parameter type END. The setPause() method will only pause a game if one is actually being played.
Apart from all the necessary getters and setters, there's only one more method to implement, that is the
move() method. This method takes as a parameter the direction of the move, which is a constant from the TetrisPiece class. Assuming that a game is in play and not paused, the move() method attempts to make the move. If the move is a drop or a fall request and it is unsuccessful, then fCurrPiece is set to null and the piece is left in its current board position. The GameThread then takes care of creating a new piece for us.
And that's it for the TetrisGame. Again, this class is available as part of the complete source for download in Resources.
Now for completeness sake we could create a BeanInfo class and jar these classes up with an appropriate manifest file, but for now we don't need to. What we do need, though, is a simple GUI to test our bean out, and one is provided in Resources. This is a very simple class that uses an inner class to draw the Tetris board and contains some logic for coordinating key presses. The GUI itself is displayed in a javax.swing.JFrame, although it could alternatively be a java.applet.Applet.
To test out the Tetris bean, unzip the source code, keeping the directory structure intact. (This is necessary as I have placed the bean classes in a TetrisBean package so they need to be in a TetrisBean directory.) Add the path where you unzipped the source files to your java classpath and compile the files. Now all you have to do is run "java Scottris" and you're on your way!
My own little disclaimer: I understand that there are many ways this Tetris bean could be implemented, but what I've given you here is a very simple approach that I hope will stoke your creative fire. Please feel free to improve on it.
| Name | Size | Download method |
|---|---|---|
| j-tetris.zip | 10KB | HTTP |
Information about download methods
- Check out JavaGaming.org for more game developer resources.
-
Robocode wraps up Java gaming and Java education in one great program. And don't miss Sing Li's excellent guide "Rock 'em, sock 'em Robocode" (developerWorks, January 2002).
- You'll find hundreds of articles about every aspect of Java programming in the IBM developerWorks
Java technology zone.

Scott Clee currently works as an FV Tester for IBM's CICS product. He's been a Java programmer for four years and enjoys dabbling in fun projects involving Java. While his name is in lights, he'd like to say hello to his parents, Stephen and Susan, and his brothers, Craig and Christian. Contact Scott at Scott_Clee@uk.ibm.com.
Comments (Undergoing maintenance)





