Sign in to follow this  
Ekim_Gram

Help with Tetris clone

Recommended Posts

It was about a week ago that I decided to start another project (after my paddleball game) which would be Tetris. I've done side scrolling shooters before and the generic pong game but I found those easier than they should be. I'll tell you this much, Tetris came and kicked me in the ass. So far I've got the game board drawn out (easy) and I'm working on the pieces right now. What I need help with right now is how would I go about rotating the pieces? I'm using SDL as my graphics API if it matters. Right now I'm using a 4x4 array of SDL_Surfaces and whichever ones aren't declared NULL, they are assigned to a blue colored brick shape (for now). Here's my [trimmed] source:
typedef struct
{
	int positions;		// How many different positions does this block have?
	int currentPos;	        // What position is it in?
	int xpos;
	int ypos;
	SDL_Surface *bricks[4][4];     // The individual bricks
} Block;

Block T_Block;

int main(int argc, char *argv[])
{
	CEngine Engine;
	SDL_Surface *background;
	background = Engine.SetSurface("data/images/background.bmp");
	SDL_Surface *block;
	block = Engine.SetSurface("data/images/blue_block.bmp");
	bool done = false;
	int move=20;
	T_Block.xpos = 20;
	T_Block.ypos = 20;
	while (!done)
	{
		switch(T_Block.currentPos)
		{
		case 0:
			T_Block.bricks[0][0] = NULL;
			T_Block.bricks[0][1] = NULL;
			T_Block.bricks[0][2] = NULL;
			T_Block.bricks[0][3] = NULL;
			T_Block.bricks[1][0] = NULL;
			T_Block.bricks[1][1] = NULL;
			T_Block.bricks[1][2] = block;
			T_Block.bricks[0][3] = NULL;
			T_Block.bricks[2][0] = NULL;
			T_Block.bricks[2][1] = block;
			T_Block.bricks[2][2] = block;
			T_Block.bricks[3][0] = NULL;
			T_Block.bricks[3][1] = block;
			T_Block.bricks[3][2] = NULL;
			T_Block.bricks[3][3] = NULL;
			break;
		case 1:
			T_Block.bricks[0][0] = block;
			T_Block.bricks[0][1] = block;
			T_Block.bricks[0][2] = NULL;
			T_Block.bricks[1][0] = NULL;
			T_Block.bricks[1][1] = block;
			T_Block.bricks[1][2] = block;
			T_Block.bricks[2][0] = NULL;
			T_Block.bricks[2][1] = NULL;
			T_Block.bricks[2][2] = NULL;
			T_Block.bricks[3][0] = NULL;
			T_Block.bricks[3][1] = NULL;
			T_Block.bricks[3][2] = NULL;
			T_Block.bricks[3][3] = NULL;
			break;
		}
		// Draw everything
		Engine.DrawImage(background, screen, 0, 0);
		for (int i=0; i<4; i++)
		{
			for (int j=0; j<4; j++)
			{
				Engine.DrawImage(T_Block.bricks[i][j], screen, (i*T_Block.xpos)+move, j*T_Block.ypos);
			}
		}
		SDL_Flip(screen);
	}

	return 0;
}



The switch/case statements are what I'm talking about. Right now, the block that I'm drawing is the one shaped as such:
    __ __ 
   |  |  |
+--+--+--'
|__|__|
That's the first [case 1] position of the block. I'm just clueless on how I should store each brick position in the block for every single type of block there is (the T block, L block, etc.) And just so nobody else does so, don't link me to the Tetris in an hour thread because I would like to try and figure this one out by myself and I'd rather spend my time asking for help and getting better answers. Thanks a bunch in advance!

Share this post


Link to post
Share on other sites
it seems like you are concerned about rotating the blocks while also minimizing the pain of doing so. first, the SDL_Surface* array (4x4) is bad - its a waste of memory. instead of storing the images (4x4 matrix) use booleans or ints to denote whether the block is there, and store the single image for the entire block in one SDL_Surface* variable. to explain, if you have

// and L shape (sort of
bool array[4][4] = {
true, true, false, false,
true, true, false, false,
true true, false, false,
true, true, true, true
};

you would be able to perform rotations by simple dealing with it like a matrix. using that above (converted to ints), to rotate to the right it'd be
(the 'or' is a separate block, for example purposes)

1 1 0 0 or 1 1 1 1
1 1 0 0 0 1 1 0
1 1 0 0 0 1 1 0
1 1 1 1 1 1 1 1

// rotated to the right
0 0 0 1 or 1 0 0 1
0 0 0 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 0 0 1

all you need to do is swap each row with each column (going coutnerclockwise), and thus you only need a RotateRight and RotateLeft function instead of storing the actual position (which you wouldn't need to know to rotate, but would be useful for when it lands).

with the above, to draw you'd either :
1) have an SDL_Surface* object for your block, in which case you would update it (clear and redraw) before blitting to the screen
2) have simply an SDL_Surface* (again, only one - unless you're using an array for effects or something) that contains the block image data. the draw function would loop through the bool (or int) array, and if its true (or of a certain value), blit the block at (x + (iteration * width), y + (iteration * height), width, height).

the other benefit is you can load the block format from a textfile, and thus make your game a little more versatile, in terms of adding content easily (just load the starting config and the image file).

hope that was somewhat useful, and i apologize for the length.
cheers.

Share this post


Link to post
Share on other sites
My original plan was to hardcode all the different positions for each piece into the program. I like you way of using bools to establish the image given off for the piece but I'm still clueless on how to store it for each piece.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
stormrunner's suggestion about using bools is a useful one, but his suggestion about the piece rotation won't work correctly. The problem is that Tetris pieces don't necessarily rotate around their center, but instead rotate around a fixed block, or in the case of the square, doesn't rotate at all. The solution that I used when programming my Tetris clone was to do what you wanted to do, ie hardcode all possible positions for the pieces. You can use the fact that all Tetris blocks have at most 4 different orientations, and all of them will fit within a 4x4 grid, as stormrunner suggested.

One other thing that I did was instead of using boolean values, use int values. I did this because you can associate a number with a color, 1=red, 2=blue, etc. This makes placing the blocks into the playing field easy, because you can just copy the numbers over into the field, and assuming your drawing code is correct, the playing field will look right.

The diagram shown right under "Standard Tetris Pieces" here
should give you what you want to do.

Hope this helps.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Oops, the HTML tags didn't work

This is the link:
http://www.colinfahey.com/2003jan_tetris/tetris_standard_specifications.htm

Share this post


Link to post
Share on other sites
I would say go with stormrunner's idea. I don't think hardcoding all the different possibilities is the most efficient way to do it.
If you use the matrix rotation idea, all you need is a rotate clockwise function, which should work with every block. In my tetris clone, I used a midpoint value for each block to make sure their rotations were handled correctly. Also, I think it's easier to store the rotating block as an array of points instead of using the 4 x 4 matrix. Then all you need to do is rotate each point around the specified midpoint.

-Shane

Share this post


Link to post
Share on other sites
Image Hosted by ImageShack.us

Using that diagram, I don't think it would be possible to come up with a 4x4 matrix rotation method that supports how all of those pieces rotate. I'm thinking of just hardcoding it all in right now because it seems so much easier to do right now. Unless you have any other suggestions?

Share this post


Link to post
Share on other sites
Well, you could always count the different rotations as different "kinds of block", and just implement the rules for turning one into another.

Other than that, here's a text diagram that might clear things up a bit:


x>0 1 2 3 0 1 2 3
y
0 a b c d m i e a
1 e f g h __\ n j f b
2 i j k l ^^/ o k g c
3 m n o p p l h d

(x, y) --> (3-y, x)


That won't rotate things exactly as they are done in the picture, but it *will* at least rotate them correctly (i.e. keeping them within the bounds of the 4x4 box and preserving shape). Actually, that diagram bothers me a bit because they're trying to rotate about a point in middle of a grid square in the 4x4 box (note even dimensions) rather than the middle 'corner'. If you try to do that four times in a row (always rotating in the same direction) to recover the original shape, you go outside the box in the 'I' case, and the 'S' and 'Z' are jittery (the two 'shapes' for each are drawn each in two different 'locations'). Though actually I don't think you can avoid the jitters for 'S' and 'Z' without special-case hacking.

Share this post


Link to post
Share on other sites
Have you considered using a mathmatical model to store point data. Then you store one generic superblock that makes up the pieces in various different colors.

(reffer to the drawings at the bottom of the post :) )

You only translate and check the 1's and forget the 0's so each piece could safely overlap zones of other pieces. The leftover 0's could be used for (if 0 turns to 1; collision has taken place -> stop piece from moving etc, score +1).

This could take place for each individual piece. Any blank spaces will have a 0, 1's will be for piece parts, and -1 could be for collided pieces (aka they arnt moving).

As for the rules of the game, you could have one row at the very top where pieces are synthesized, and if any of those indicies change from 0 to -1 then gameover. if they change from 0 to 1 its no problem b/c the pieces are shifted downward anyway once per time unit.

The lower bound could work the same way except they would be set to -1 by default so the collision detector would stop all incoming pieces. When it detects that a row is all -1 tetris will occur and the row will then become 0 -> or a 'free space'.

The core of your app could be centered around detecting these various states and doing two things; acting on events (aka tetris, gameover states, and point accumulation over time), and shifting down all the pieces as far as they can go in one time unit.

Since your question was more 'how do I rotate'. You may want to store the various states beforehand in an, vector<GamePiece> vPieces, object.

Doing mathmatical rotations in code can be tedious, and if your up to the challenge then go for it :) otherwise write the states out by hand and put them into a text file. I have real easy way to load them too -> at end of post.

#include <vector> //For VS.NET
#include <vector.h> //I think this is how VS 6.0 and before did it... try it without the .h if it dosent work

using namespace std;

struct GamePiece
{
GamePiece(int stateA[4][4], int stateB[4][4], etc..)
{
//copy from newValues to values, all the numbers by index
}
int valuesA[4][4]; //assuming the bigest piece is at most 4 units on any side
int valuesB[4][4];
};

...somewhere in code...
vector<GamePiece> vPieces;
....

//load up that pieces values (theoretically you have unlimited space to put your pieces in a vector... havent tested past a couple hundred thousand.)
vPieces.push_back(GamePiece(myPieceStateA, myPieceStateB etc...));

//now access whatever pieces data

int X = vPieces[index].valuesA[1][1]; //X now equals valueA[1][1] of piece [index] that is contained within the vector.

..

I've done a 2d app using a similar approach (minesweeper :) )

A sample model for the plug and board piece (I dont know the name ;P Whatever pieces they appear to be (remember 1's are 'real' blocks')

Piece 1:

0 1 0 0
1 1 1 0
0 0 0 0
0 0 0 0

Piece 2:

0 1 0 0
0 1 0 0
0 1 0 0
0 1 0 0


Easy way to load point data:

(assume you have the file open already using fstream and you included 'stdio.h')

while(cin.readline(myBuffer))
{
sscanf(myBuffer, "%i %i %i %i", partA, partB, partC, partD);
}

This chunk of code will read through each line and pick up one row of point data which could include the entire structure of 16 integers if you wish.

Then just vPieces.push_back(GamePieces({partA, partB, partC, etc..});

I hope this helps :)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this