Top

XNA Tetris in 24 Hours

March 23, 2008

papertetristeaser.jpgThe “Tetris in 24 Hours” challenge is over, and the total dev time clocked in at just over 15 hours. This was about more than just coding up Tetris for the 360. It was also about figuring out some aspects of XNA I was unfamiliar with, as well as parsing someone else’s code (namely, the GameStateManagement example from creators.xna.com).

I would really like to release this code, and maybe allow someone else to jump in and add the polish something like this needs, but I really don’t know the legalities involving Tetris clones. If someone has anything to offer in that regard, leave a comment. Until then, I can talk about the project, what issues came up, and give a brief overview of my process.  I also had the idea of throwing a contest — basically a “build a dope clone of a classing game in XNA in under a week” type thing, with some kind of cool prize(s).  Would anyone be interested?  I was playing a Flash version of the old Activision game Kaboom and I thought it would be fun to gather a bunch of people together and all work on a version of that (or some other) classic game.  If interested, definitely let me know.  Maybe a few of the other XNA blogs could get in on it?

Anyways, on with it…

Doing a quickie Tetris is interesting for me, because I’m a little bit before the time of Tetris. The first game I ever wrote was something more along the lines of Combat for the Atari 2600. Tetris might be ubiquitous as the “Hello World” of game programming, but it didn’t exist when I got started. Incidentally, the first project I started but never finished was a clone of one of my favorite early games, Jumpman.

Now that you know roughly how old I am, I should say that the point of all this is that I’ve never written a Tetris clone before. Consequently, I had to plow through it and figure out the logic of the game myself.

free image hosting
Paper Tetris (click for full size)

Basics

Initially, I had decided to represent the game state as a 10×20 array of values, each representing a square of the game grid.

  • 0 for empty
  • 1 for a “fixed” block (one not in motion)
  • 2 for a shape block (one in motion)

Thus already we have three classes to deal with: the TetrisGame (which includes the game arena), the Shape objects, and the Block objects that make up the Shape and the TetrisGame arena.

free image hostingThe blocks all come from a single texture (shown left), which includes 7 hand drawn blocks in 7 colors. There is a whole lot of static in this project, and this is an example of it. Since we’re going to have a lot of blocks, and since blocks are going to come and go, it doesn’t make a whole lot of sense to reload textures all the time. So the texture file gets loaded from the content processor once, and the region of the texture is determined by what kind of shape the block belongs to.

So we have an enum, BlockColor, that enumerates these 7 colors, and maps them to a Rectangle defining the region of the texture needed.

We also need to store a Vector2 for each Block, so that it is aware of where it should be drawn.

Now for the shapes. Again, we have an enum, ShapeTypes, which enumerates the 7 shape types: square, T, bar, left Z, right Z, left L and right L. The shape constructor looks at what type of shape is getting created, creates a set of blocks of the appropriate color, and stores them in its block array.

Shape Rotation

So how is the block array handled? We have a 4×4 array of Blocks. When the block is created, depending on the type, the array is populated with new Block objects. Why 4×4? To handle rotation. While the bar only occupies the top row of block cells in the shape’s block array, when it rotates it suddenly occupies the left 4. We need a big enough space to hold the blocks even as their orientation changes during rotation.

To make rotation sensible, each shape also maintains two ints representing its pivot. This varies from shape to shape. The simplest description of the pivot point (and the one that makes the most sense in code) is that it’s the block that doesn’t move during rotation. So when we rotate the block array, we need to shift all the blocks (and the shape itself) in order to make sure this block remains unmoved.

Array Rotation

Before this, I hadn’t ever rotated a 2D array. Or, at least I don’t remember rotating a 2D array, but somehow I must have. At any rate, I had to sit down and figure it out.

To rotate an array left: swap block (i,j) with block (3 - j, i)

To rotate an array right: swap block (i,j) with block (j, 3 - i)

That handles the rotation, but now our pivot has been swapped! To calculate the new pivot:

For left rotation: pivot (x, y) becomes (y, 3 - x)

For right rotation: pivot (x, y) becomes (3 - y, x)

We can now calculate the pivot shift, or how much we need to adjust our shape in order to make the new pivot block occupy the same space as the original.

  1. pivotShiftX = oldPivotX - newPivotX;
  2.  
  3. PivotShiftY = oldPivotY - newPivotY;

But there’s a problem! We can’t just go shifting our shape around carelessly. What if that shift means that one of our shape block overlaps with a fixed block? We need to check that the shift is valid before we do this.

To handle situations like these, I’ve written a few helper functions. One of them takes the shifted shape and the game arena array, lays them on top of each other, and scans for conflicts. If one is found, the rotation is invalid, and doesn’t occur.

The process is similar for falling blocks. Before a block falls, we want to see if it is above a fixed block. If so, we’re at the end of the turn.

Gotchas

A few aspects of Tetris are a bit more complicated than I thought they would be. For starters, we don’t want a shape to become fixed the instant one of its blocks is directly above a fixed block. In order to do this, we need to set a little delay timer to start counting, and only fix the block once the timer is up. This allows the player to slide blocks under other blocks, or just take a last opportunity to place a block.

Removing rows is also a little more involved than I originally thought. I had initially scanned the arena for complete rows, accumulating a variable called neighbors whenever two removed rows were adjacent, and then, once scoring was worked out, went through the array removing marked rows and sliding all the rows above down by one. This isn’t terribly tough, I just found it funny that I originally used a Queue to store the rows marked for removal, when I should have used a Stack. Which one you use will depend on whether you scan from the top or the bottom, but since you’re likely to start at index 0, you’re probably going to want a Stack. Of course, my initial way resulted in rows occasionally being moved to below the bottom line.

Also, a few notes about the GameStateManagement sample, which I used as the starting point for this project. I recommend getting very familiar with it if you plan on using it as your game base. There are several oddities that pop up once you start doing anything actually game-like with it.

For one thing, be aware of content unloading when game screens get removed. If you have a shared element in common with another screen, you may end up losing it.

You will probably also want to scrap the input handler class altogether. I found it to be pretty useless for what I needed to do — in particular, I wanted to handle long key presses by making a move only when enough time had elapsed. I didn’t want pieces just dropping levels every call to update! Unfortunately, the GameStateManagement samples input handler had no concept of time.

One last point about sound and music: this project has two music waves (for the menu, and for the game), and one sound effect (when rows are removed). Sound is pretty ridiculously easy, but I kind of like the way I handle sound management. I will be writing about my SoundManager class in a couple days.

Comments

5 Responses to “XNA Tetris in 24 Hours”

  1. Gary the Llama on March 24th, 2008 5:06 am

    You really should release the code, it would be a great learning experience for others.

    Regarding the legal issues, I found this on the Tetris Wikipedia entry:

    According to circulars available from the United States Library of Congress, a game cannot be copyrighted (only patented), which would invalidate much of TTC’s copyright claim on the game,[20] leaving the trademark on Tetris as TTC’s most significant claim on any government-granted monopoly.

    Some players prefer Tetris brand games; others prefer homemade tetromino games downloaded from the Internet, which are given names such as “N-Blox” or “Lockjaw” so as not to infringe trademarks. In late 1997[21] and in mid-2006,[22] TTC’s legal counsel sent cease and desist letters to web sites that misused the Tetris trademark to refer to homemade tetromino games.

  2. GameDevKicks.com on March 24th, 2008 6:52 am

    XNA Tetris in 24 Hours…

    You’ve been kicked (a good thing) - Trackback from GameDevKicks.com…

  3. Ziggyware XNA News and Tutorials on March 30th, 2008 7:36 am

    If you dont want to release the code, maybe release an xbox 360 version :)

  4. Typh on April 7th, 2008 2:21 pm

    Forget the code, that is the coolest Tetris interface I’ve ever seen. Love the concept.

  5. ahhsumX on May 2nd, 2008 12:09 pm

    This is a sweet looking Tetris. I love the graph paper/drawn concept. Even if you don’t release the source you should definitely release the game itself for download to play.

    …although I’d love to toy around with the source.

Got something to say?





Bottom