SQUARISM addicted to pixels

Making Tetris

Posted on June 22, 2009

The Piece Bag

At this point I had pieces falling, rotating, stacking etc. I next did gameplay feel (where the player has a chance to move a piece at the last second, preventing cheating with "infinite spin"). This also included polishing when a piece sticks and when the timer says a new piece is coming. In reality, it came down to procedural code ordering and some if statements.

Gameplay feel was nice so next I polished it some more with a nicer way of generating random pieces. I read a thread on Quinn's official forums (a Mac Tetris clone) where they described a better method of getting random pieces detailed on tetrisconcept. It's simple enough:

  1. put 7 pieces in a bag
  2. jumble
  3. get one
  4. when empty: create a new bag of 7 pieces

It keeps you from getting too many or too little pieces of a certain type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import java.util.Random;
 
public class PieceBag {
 
	Piece pieceBag[] = new Piece[7];		// a bag of 7 tetris pieces
	int pieceBagI;
 
	float x;	// piece origin
	float y;	// piece origin
 
	public PieceBag(float x, float y) {
		this.x = x;
		this.y = y;
		initPieces();
		jumble();
		pieceBagI = 0;
	}
 
	void initPieces() {
		pieceBag[0] = new LPiece(this.x, this.y);
		pieceBag[1] = new OPiece(this.x, this.y);
		pieceBag[2] = new TPiece(this.x, this.y);
		pieceBag[3] = new SPiece(this.x, this.y);
		pieceBag[4] = new ZPiece(this.x, this.y);
		pieceBag[5] = new IPiece(this.x, this.y);
		pieceBag[6] = new JPiece(this.x, this.y);
	}
 
	public void jumble() {
		Random rng = new Random();   // i.e., java.util.Random.
        int n = pieceBag.length;        // num of items left to shuffle
        while (n > 1) 
        {
            int k = rng.nextInt(n);  // 0 <= k < n.
            n--;                     // n is now the last pertinent index;
            Piece temp = pieceBag[n];     // swap array[n] with array[k]
            pieceBag[n] = pieceBag[k];
            pieceBag[k] = temp;
        }
 
	}
 
	// get piece from bag
	public Piece getPiece() {
		// if we are on piece 7 then jumble the bag again
		if (pieceBagI == pieceBag.length) {
			initPieces();			
			jumble();
			pieceBagI = 0;
		}
 
		Piece piece = pieceBag[pieceBagI];
		pieceBagI++;
		return piece;
	}
}

Offscreen Drawing and Performance

At some point when the gameplay was finished and I was testing things, I started seeing opportunities for performance improvement. The gray grid on the black game field was painfully inefficient. Every frame it would iterate width and height and draw the lines each time. So some 50 lines were being drawn when they never move. Perfect time to create a reusable buffer. I had never done this in processing (or at all), I had seen the code in OpenGL but never had a reason to do it. I looked at the docs and found how to create an image on the fly and draw lines into this image object. This image is then displayed once every frame and it's much easier on the CPU. This is called offscreen drawing (and probably other things).

The grid (and other art) is displayed once on the screen with an offset to the root window. Everything in Tatris is original artwork and no textures are used. So this offscreen drawing is used quite a bit. The game starts up, creates a bunch of image buffers on the fly and then displays those buffers as a bitmap on the screen to make the grid pattern, the score box, the next piece box and so on. The code is very similar to loading an image from disk and displaying it, the only difference is the image is created dynamically when the game loads.

Another performance strategy I used early on was to do the cheapest collision detection first. That way, if a method returns true it skips the most expensive operations. For example if I had two cheap tests called checkLeft and checkRight but also had a very intensive test called checkEverything, I'd order it like this:

1
2
3
4
//psuedocode
if (!checkLeft()) { return false; }
if (!checkRight()) { return false; }
if (!checkEverything()) { return false; }

Bugs and Versioning

Around this time (I think), bugs continued to pop up with the collision detection. Pieces would go through the floor, pieces would rotate into the stationary blocks on the playfield and all kinds of other weirdness would pop up. I spent a week refactoring and reworking a ton of logic. When bugs started popping up, I created a local git repository for CM control so I could rollback, diff etc. The collision bugs exist primarily because I made the design more difficult (pieces really exist in a 2d space). I solved most things and started working on removing done lines off the field. This turned out to be harder than I thought and I spent 2 days implementing different algorithms for clearing lines. Eventually I got one that was really clean and it worked all the time with all cases. I then did frills like scoring, difficulty and GUI/HUD type graphics stuff.

It was done. And then I emailed it to all my friends. Everyone liked it. People said they played it for 30minutes at a time. Good sign. Some collilsion bugs still happened. So I started working on it again after taking a break.

Unfortunately, the bug was extremely rare. I couldn't see a pattern or reason for it. But every so often, a piece would just go through another block. So I moved all my code from the .pde / Textmate world to a .java/Eclipse world so I could use the debugger. I created automated tests, fixed logic errors, did cleanup and a crapton of stuff. But now I had a problem. I have the .pde version which does stuff like fill(255) and rect(x,y,etc) and the .java version which does stuff like parent.fill(255) and parent.rect(x,y,etc). Not to mention, logic fixes and more robust tests built in. So how do I version this beast? Do I start a new project and call it "Tetris Eclipse"? So essentially I had a debug fork of sorts; an eclipse based processing project and a vanilla processing project.

I don't know. I've gotten distracted and have done other things since then. I'm creating a semi-interactive Elevator Simulation. I'm learning the iPhone API. I did branch my project in git and check in everything. Git was pretty smart actually, it knew that I had renamed my files .pde to .java (it can recognize that it's the same source even with changes). The CM tangle just kinda scared me off and I deleted the branch. As of right now, the code is versioned and works as far as the version that I sent off (I tagged it as 0.3).

Update: 0.3.1 has the collision detection bug fixed. It was a procedural ordering bug. When someone presses the space bar at the exact moment the active piece drops one line, it pushes the piece too far down. I just reordered that part of the game loop and it hasn't happened again. Finding this bug was both lucky and annoying because it was so damn rare.

There are 2 types of code (according to the Stack Overflow podcast):

Spolsky: Well, I think everybody feels that way, and that is actually true that there is - there's kind of two levels of code: there's the level where it runs and it's debugged and you're kinda happy with it and you can continue to work on it if you need to. Let's say there's three levels, that's the middle level. The bad level is you know it's bad and it's a pain to work on and any time you want to change something you know you're going to be pulling out your own hair, and then the top level is like you could publish this in a book because after you got it working you went over it and refactored it seventeen times and cleaned it up and did all kinds of extra work that didn't get you any extra functionality, but did make it code that anybody could dive into, so maybe you've renamed things, you've cleaned things up, you've reorganized things several times, you've gone through the code trying to make it like literary code where the comments just smoothly, seamlessly flow with the code so you can figure out what's going on.

Well I classify this project as a mix between what Joel Spolsky says is "bad" and "middle". Because I'm not done with it, I don't want to remove all the comments and code blocks that are there. There are tests and debug statements in there. There are parts that are very ugly to read. But there there are completed parts that are extremely clean and I wouldn't be ashamed of. In fact, I think the Game State pattern I did is very slick and extensible. I'd have no problem adding a splash loading screen or some new "scene". But because I still need to do things, I can't clean-up or refactor yet. Which brings me to the TODO list.

TODO

There's a ton of stuff still left as TODO: comments around but outside of those syntax and code level tasks, there's only a few things I'd like to do before declaring it 1.0 or what-not:

  1. Sound Effects - I want to record some bleeps and beeps from my analog modeling synth and put them in the game. Put some ambient music in there. Sound will be off by default because I think game sound is annoying most of the time. This is technically easy but artistically tricky because my standards are very high with sound choices.
  2. Refactor the grid - So pieces exist as real 2D space (vs the easy way in Drawing a Piece on Page 1). Yeah, it's neat and hard but it's caused horrible bugs. I need to burn it to the ground and make the pieces positionally in a bitmap on the field. There would be no need for collision detection and the whole thing would be much more efficient. All the other work could be used in another project like a platformer or side scroller; something with actual smooth animation and not the jerky tetris movement where it's actually not necessary.
  3. Start with a menu - The game starts right away. This is easy to fix.

So that was the 4-6 weeks of vacation that I burned learning a ton of stuff about gamedev. I did other things like jogging, music, quit WoW and got a bunch of yearly appointments out of the way. But nothing was as memorable as working on Tetris. The most important lesson of all that I learned is the value of taking a vacation for learning (sabbatical? geekcation?). It's now on my required life list of things to do from time to time. Because I cannot overstress the value of contiguous blocks of time. It's solid gold. Solid. Gold.

Comments (1) Trackbacks (1)
  1. Awesome write up. It is rare to get this much detail about the creative & technical process of making a game. Thanks for sharing!


Leave a comment