Making Tetris
Lines and Removing Lines
So now pieces for the most part have motion and collide right. I had created some tests during this development that "precreate" the DeadGrid with blocks. For example, the game starts with 4 lines on the field etc. This was handy for testing and I kept the tests commented so I could turn them on or off.
So I could pile up lines but now how was I going to remove them? A player completes a line only when it hits the DeadGrid and when they drop a piece etc. Ok. So I created this method called copyToGrid(). It's very long and does many things. But in psuedocode, here's the gist of the Tetris line removing algorithm.
- Method is: void copyToGrid()
- First, get a temp copy of the current piece to test with
- Then, remember which rows the piece is in (which rows are affected)
- Mark filled rows (done rows) into doneRows array
- Now we remove rows if there are any to be done
- Copy rows to new array, skipping rows that are done, this creates a grid without completed rows, called trimmedGrid
- Copy trimmedGrid to new grid called compressedGrid (not compressed yet, to be compressed). Compressed meaning, empty lines removed, game field remove of empty space.
- Look for nonEmpty lines and remember them
- if empty, check that the row is one that we cleared (in rowsAffected). There are lots of empty rows at the top of the screen usually, we just need the empties that we just cleared.
- Loop through all nonEmptyRowNums and emptyRows.
- If nonEmptyRowNum < emptyRowNum then add 1 to "need to fall" for that row.
- Loop through non empty rows
- There is one row below us, add 1 to "need to fall" for this row. When this is done, we'll have a hashmap called "needToFall" that has a line number and a number of lines to fall. After this it's easy.
- Loop through our needToFall map and make them fall by rows stored. Making them fall is accomplished by copying the row above and deleting it.
- compressedGrid is now flattened and removed of done rows
So essentially it goes like this. Let's say a straight IPiece drops into a slot that clears 2 lines at once near the bottom of the screen. But there is a pile of random crap on top and on one line at the bottom. No blocks should remain on the play field except for the garbage remaining. copyToGrid() would mark the last four rows affected and as done. doneRows is length of four. The DeadGrid is copied to a new grid called trimmedGrid, skipping the last four rows. So trimmedGrid is now a floating line 5. An copy of trimmedGrid is created called compressedGrid but it's not compressed yet. We loop through all the lines and remember lines that aren't done. We also remember which rows are empty around the rows affected. This is because those rows don't need to fall. They are the reason other lines are falling.
Then create a needToFall hashmap that is a map of gaps pretty much. With this needToFall map, we just need to loop through our grid and make each line that is above the needToFall line, fall by the distance stored in the map. This whole alogithm is complex because of how lines can be cleared. Take the following example:
Here's a deadGrid state. There's a potential (if you drop a long piece) to clear two lines at once. But there's a lot of garbage around the lines. This is a tricky use case to handle which is why the algorithm was hard to figure out and develop.

If you drop an IPiece in this grid, it looks like this:

First, any row number that is less another row means above. 5 is greatest so it's on the bottom. If nonEmptyRowNum < emptyRowNum then add 1 to "need to fall" for that row. You can see that row 1 needs to fall 2 lines. Programically, 1 < 2 && 1 < 4. Row 3 < 4 only.

The needToFall map would contain the following values:

With the needToFall map in hand, it's easy to loop through the grid and make the falling happen.

Then the piece blocks are copied to the DeadGrid.

Then a new piece is created and the game continues.
The Options Menu

To create a pause type in-game options menu, first I created a new game state called MenuState. In this class I handled key events and drawing to navigate through a menu. This wasn't too bad but I spent a lot of time adding a bit of polish to it. I wanted to overlay the menu on top of a blurred or faded game screen so the player could see the game behind the options. So I figured out how to make a screenshot in processing and had to add a get() method to my PlayState object. The get() method returns an array of pixels and then MenuState can ask PlayState for its screenshot. Then it's just a matter of blurring the image, dimming it with a color trick and displaying this image first before painting the menu on top of it. It turned out really neat and I was excited to get that polish working.
Navigation of the menu is handled by capturing the keystrokes in the main loop and delegating them to the current game state (just like the main game state). In this case, UP moves the selection box up and DOWN moves the selection box down. Enter and RIGHT select the menu. UP, DOWN, RIGHT are all mapped to the arrow keys by default. I tried to pick the defaults that seemed the most natural.
ESC brings up the menu but also exits the menu and moves up the menu selection tree. When the main menu is exited then we need to go back to the game already in progress. This is handled by returning the saved game state. When MenuState is created, it saves PlayState into a local variable. When the menu exits, it returns this state and the game magically resumes. Because all the variables are inside this PlayState object, the score, the position of the piece and even the timing of the game is all preserved and it appears to have resumed seamlessly.
I absolutely loved seeing this state pattern work. I had forever wondered how games transitioned scenes and created save game files. Now I grok it.
June 23rd, 2009 - 01:08
Awesome write up. It is rare to get this much detail about the creative & technical process of making a game. Thanks for sharing!