THIS IS NOT A TETRIS® PRODUCT

Tetris is a registered trademark of The Tetris Company LLC.

So what's the generic for Tetris?

If you want to learn to make a clone of a popular falling tetramino game, here's how. First you'll need some constants:

#define N_PIECES 7   /* number of different pieces */
#define N_FLIPS 4    /* number of rotations of each piece */
#define N_BLOCKS 4   /* maximum number of blocks in a piece */
#define N_PLAYERS 2  /* it'll be hard to fit more than two players
  on one box, that is, unless you want to make a network game */
#define FIELD_WIDTH 10
#define FIELD_HEIGHT 20

The canonical seven tetraminoes are

 ___     ___     ___    _____   _____   _____   _______
|_  |_  |   |  _|  _|  |  ___| |_   _| |___  | |_______|
  |___| |___| |___|    |_|       |_|       |_|

To represent these, you'll need to make some sort of shape table. (Not related in any way to Apple II shape tables.)

const int n_blocks[N_PIECES] =
{
  4, 4, 4, 4, 4, 4, 4  /* don't assume they're all 4;
             you may want to make pentominoes later */
};

const int x_blocks[N_PIECES][N_FLIPS][N_BLOCKS] =
{
  {{0,1,2,1},{1,1,1,0},{2,1,0,1},{1,1,1,2}}, /* T piece */
  /* other 6 pieces omitted */
};

const char gYBlocks[N_PIECES][N_FLIPS][N_BLOCKS] =
{
  {{1,1,1,2},{0,1,2,1},{1,1,1,0},{2,1,0,1}},
  /* the rest is an exercise for the reader */
};

The previous table, given for the T piece, is made of four sets of four (x, y) pairs (one for each block in each rotation) that place a block at a given offset from the piece's origin.

  0 1 2 3     0 1 2 3     0 1 2 3     0 1 2 3
0 . . . .   0 . X . .   0 . X . .   0 . X . .
1 X X X .   1 X X . .   1 X X X .   1 . X X .
2 . X . .   2 . X . .   2 . . . .   2 . X . .
3 . . . .   3 . . . .   3 . . . .   3 . . . .

"Piece's origin?" Oh, now you need a struct, class, record (or whatever your language calls the beast) that holds the player data for each player.

typedef struct PLAYER
{
  int piece_x;  /* piece's origin on playfield */
  int piece_y;
  int drop_delay;  /* delay until next piece drop time;
                      calculated from current line count */
  int cur_piece[2];  /* 0 is falling; 1 is "next" piece */
  int cur_flip;  /* 0 to 3; index of rotation */
  long score;  /* Don't want the problem in Microsoft Tetris
                  where the score would wrap from 32K to -32K */
  int lines;

  unsigned char playfield[FIELD_HEIGHT][FIELD_WIDTH];
} PLAYER;

PLAYER p[N_PLAYERS];
PLAYER *cur_player;

You'll also need methods to read the players' keyboards or joypads (platform-specific) and to autorepeat the signals appropriately (left and right should be click, delay, clickclickclick, down should begin repeating immediately, and rotate clockwise and anticlockwise shouldn't repeat at all).

To draw the piece, loop through all the pieces:

for(i = 0; i < n_blocks[piece]; i++)
  draw_block(piece_x + gXBlocks[piece][flip][i],
             piece_y + gYBlocks[piece][flip][i],
             color);

Similar code is used to check for collision with the playfield or to lock a piece into the playfield. You'll need to write a method that returns TRUE if any block of the falling piece overlaps a block on the playfield (or FALSE otherwise). Remember to treat x < 0, x >= FIELD_WIDTH, and y >= FIELD_HEIGHT as walls. When the player press a move or rotate key, or the drop timer expires, follow these steps:

  • Move the piece
  • If is_overlap()
    • Move the piece back
    Else
    • Erase the old location of the piece
    • Draw the new piece

When a downward motion results in a collision, lock the piece into place using code similar to the draw code. To check for lines, simply look for rows of playfield that don't contain any 0 elements.

Moving lines down is very complex. Tetris for Game Boy and many other versions use the naive algorithm (slide rows down exactly one unit), but it fails and leaves a floating block on this test case and several others:

     _
    | |_
    |  _|
    |_|                _
 ___     ___       ___| |_ ___
|   |  _|   | __\ |   |  _|   | __\      _
|  _| |_    |   / |  _|_|_    |   /  _  |_|  ___
|_|     |___|     |_|     |___|     |_|     |___|

The correct method involves making a copy of the playfield and flood filling connected regions with "region codes" (unrelated to DVD region coding). It can be seen in Quadra, Tetanus, and Bombliss. Some coders treat the playfield as a bitmap (with array accesses replaced by getpixel/putpixel) so they can use the host graphics API's services for flood filling.

(return to) Tetris Metanode

Oh, and if you're on AIM, MSN, or ICQ, here's a good message to send to a former buddy before you block em:

You've been blocked.
 _______   _
|_______|_| |_
| |   | |  _| |_
| |___| |_| |_  |
|___|___|_____|_|

Log in or register to write something here or to contact authors.