I need a bit of help with my Tetris clone again.

Started by
8 comments, last by darookie 19 years, 4 months ago
Ok, as you all (or some (or none)) of you know, I've been working on a Tetris clone on and off for the past month. I've been busy so I've only been able to work on this during the weekends. After having a revelation about linked lists, I decided to try using those again to store the positions of my pieces. Now, I have the following defined struct:

typedef struct strBlock *BlockPtr_s;
typedef struct strBlock
{
	// Default constructor
	strBlock(){}

	// Constructor that takes a 4x4 array of bools to initialize the Grid
	strBlock(bool Blocks[4][4])
	{
		for (int i=0; i<4; i++)
			for (int j=0; j<4; j++)
				Grid[j] = Blocks[j];
	}

	// A 4x4 grid, identifying how the block will be drawn. A cell with the value of 1
	// will be drawn.
	bool Grid[4][4];

	// This is where the linked list comes in. This will hold the positions of the bricks
	// in the block when it undergoes a rotation to the right
	BlockPtr_s RightRotation;

	// ^^^ Likewise
	BlockPtr_s LeftRotation;
} Block_s;

I think the code is commented well enough for you guys to understand whats going on. And on top of that struct, I'm planning on having a wrapper class to handle all the other stuff dealing with the shape like drawing, rotating, and possibly collisions. Right now, I don't even know if you can call it a skeleton but here it is:

// Piece class will be used for the pieces.
// It will handle all rotation, and drawing functions.
class Piece
{
public:
	Piece(){}
	
	// Rotates the piece right
	void RotateRight();

	// Likewise
	void RotateLeft();
private:
	// Private Block pointer.
	BlockPtr_s Block;
};


Now, to get to my question - Should I just make 19 BlockPtr_s structs to represent each position of every piece and just link the related ones together in a list and then have an enum to decide what type of BlockPtr_s in the Piece class is? I was thinking that but then it leads to a loophole that will lead to many switch/case statements which I really don't want to do. Anybody have suggestions for my dilemma? As you all know, help is much appreciated.
Advertisement
Bump. I think 6 days is a good enough wait.
(warning -- this is longer than i intended)
since no one has replied, i'll throw in some input.
Quote:Now, to get to my question - Should I just make 19 BlockPtr_s structs to represent each position of every piece and just link the related ones together in a list and then have an enum to decide what type of BlockPtr_s in the Piece class is?

no, that seems rather painful and not very flexible.
i like the linked list idea, as it makes keeping track of the rotations easier. however, i do not see where you would need to use multiple switch statements if you used a bit of inheritance/polymorphism. for instance, you'd have your block class
class Piece{public:	Piece(){}        // copy constructor        // useful if you have a piece maanger of some sort        Piece(const Piece& piece);        // destructor        virtual ~Piece() { /* elimiate objects */ }		// Rotates the piece right	void RotateRight();	// Likewise	void RotateLeft();        // draw the piece        virtual void draw() = 0;        // release the positions        // when the final position is determined        virutal void hitsBotton() = 0;        // load attributes        // either from a file or hardcoded        virutal void load() = 0;protected:	// Private Block pointer.	BlockPtr_s Block;        // the images to use        Image_Type* images;};

where the load() method either allows your block to set itself up (you've hardcoded it in the base class or loaded its attributes from a file - the reason being evident latter). the hitsBottom method would be used to release all the positions save the one it hit the bottom at (sorry-resource freak). moving on, each subclass is reponsible for implementing each of those functions (= 0;) and thus each can have its own behavior, both making it flexible to add a block type and easy to initialize each block (more below).

so, armed with your base class, you'd expand it like so
class RedBlock : public Piece{   public:     RedBlock();     ~RedBlock() { /* destroy stuff */ }     void draw() { /* draw stuff */ }     void load() { /* load stuff */ }     void hitsBottom() { /* eliminate last row */ }};

that was just an bare-bones example. the point of this is below ;) (i think)

now, i believe someone (Zahlman) awhile ago suggested you use the factory method to generate your blocks. here is an article on the factory method. a basic example would be
#define REDBLOCK 0#define BLUEBLOCK 1class BlockFactory{    public:      /* ctor, dtor, ect. .... */      public Piece* createPiece(int pieceType)      {         if(pieceType == REDBLOCK)             return new RedBlock; // can't call the ctor here             // thats why we have the load (or init) function         if(pieceType == BLUEBLOCK)             return new BlueBlock;         // return null if none of the above matches         return null;      }};//////////////////////////////////// somewhere else//////////////////////////////////BlockFactory blockFactory;Piece* newPiece = blockFactory.createPiece(REDBLOCK);newPiece.load(); // or newPiece.load("redBlock.txt"); - if you want/// in a loopnewPiece.draw(); // draws this piece

that would be the only if block you'd need to decide which block type to create. any call to a piece's draw/other function is handled by the subclass. btw, you could also use this without inheritance by declaring a method for each type of block you want create (hardcoded or otherwise).

hope that was somewhat useful/helpful.
cheers.
- stormrunner
Well, during these six days I've been thinking about using the 19 structs. The advantage is that there would only be 5 classes with the block member pointing to the head of one of the linked lists that would represent each shape. I'd hardcode all of the different positions in, or if I ever get around to it, store them in a file. Then using a switch/case statement, I'd use a random number generator to decide what type of block the piece is going to be.

I should really work on my English more. It may be my native language but strong language skills are the basis for communication.
If you are completely lost or need a fresh wind, see my tutorials on tetris @ http://acolyte.ozforces.com.au or http://openglgui.sourceforge.net .
Good luck.
You only need one Shape class and one Piece class. There are several instances of Shapes; the actual shape is encoded by the data member of the Shape class. Each instance of a piece points to one of the Shape prototypes.

I don't see why you think you're going to need multiple switches. Whenever a Piece needs to do collision detection or whatever, it just checks its this->Block->Grid for the info it needs (you should make the Grid const if you can). When rotating, you just set this->Block = this->Block->Left/RightRotation.
Quote:Original post by Zahlman
You only need one Shape class and one Piece class. There are several instances of Shapes; the actual shape is encoded by the data member of the Shape class. Each instance of a piece points to one of the Shape prototypes.


That's just confusing me. Here's what I'm trying to do, because I'm just not understanding what your telling me.

Alright, I have the strBlock class that holds the coordinates for each --brick-- to make up the --block-- in a 4x4 array. There are 19 different blocks and they all have different rotations that make a new block.

Now, I have the Piece class, which has a strBlock in it. Mind you, the initial strBlock is the --head-- of the linked list for each shape. The Piece class handles rotating the blocks, drawing them, and testing for collision detection.

1) There are seven different shapes in the game of Tetris. Whenever a new piece is spawned, the type of shape it is has to be determined, how would I go about doing this? Should I have an array of 7 strBlocks and use a random number generator to come up with a number to determine what strBlock of the seven linked list heads to point to?
I've got to be honest here. I think I answered your question fairly well when you asked it in this thread. You have to realize that there are many ways to correctly solve this problem. Some good, some bad, but many that work.

You've been doing a great deal of thinking and planning for this, but you might be better served with a little more 'doing'. When you actually write your implementation, you'll definitely run into some roadblocks, and you'll be nagged with the feeling that "I'm doing this the wrong way." It's okay. It happens to everyone.

Once you've written your program and it's working and fun, any flaws in your design will be intimate to you. You can try to be very foresighted and write flawless code, but there's a certain significance that only comes with having to deal with your own blunders :)

Anyway, what I'm saying, just write the damn thing! You'll answer many of your own questions... or at least figure out the right questions to ask (trust me, how you represent your blocks is not the greatest of your worries).
Quote:Original post by Ekim_Gram
1) There are seven different shapes in the game of Tetris. Whenever a new piece is spawned, the type of shape it is has to be determined, how would I go about doing this? Should I have an array of 7 strBlocks and use a random number generator to come up with a number to determine what strBlock of the seven linked list heads to point to?


Make it an array of strBlock * (i.e. blockPtr), so that you work with blocks that are already "linked up". When you make a piece, generate a random number and grab that index of the array: that gives you a pointer value, which you use to initialize the pointer value in the Piece. So now your Piece's shape is the indicated one.

Note that the lists are double-linked and circular; the concept of a "head" is meaningless, except that you may arbitrarily choose one from each list to use in the "piece selection array". You could put pointers to all 19 into the array, too (with the result that some shapes become more likely, because you could get them in any orientation).

Edit: diagram. (I need better hosting!)

[Edited by - Zahlman on December 12, 2004 9:24:05 PM]
Here's what I had for my cheap Tetris:
	static const int FIELD_Y	= 20;	static const int FIELD_X	= 10;	static const int BLOCK_X	= 4;	static const int BLOCK_Y	= 4;	static const int BLOCK_TYPES= 7;	static const int ROTATIONS	= 4;	typedef int GameBlock[BLOCK_Y][BLOCK_X];	/// indices to our block field:	/// look at the rows to see, which blocks are requested for each rotation	/// look at each row to see the indices of the individual blocks for a single rotation	static const int blockIndex[BLOCK_TYPES*ROTATIONS] = {	// line cube S L T etc.		0,  2,  3,  7,  9, 11, 15,		// rotation 0		1,  2,  4,  8, 10, 12, 16,		// rotation 1		0,  2,  5,  7,  9, 13, 17,		// rotation 2		1,  2,  6,  8, 10, 14, 18,		// rotation 3	};	/// unique blocks - since not all blocks really rotate (e.g. the cube doesn't), 	/// only individual blocks are stored in this field ('1' means blocked, '0' means empty)	/// you can re-arrange the arrays to visualise the blocks (:	static const GameBlock blocks[] = {		{ {1,0,0,0}, {1,0,0,0}, {1,0,0,0}, {1,0,0,0} },		{ {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {1,1,1,1} },		{ {0,0,0,0}, {0,0,0,0}, {1,1,0,0}, {1,1,0,0} },		{ {0,0,0,0}, {1,1,0,0}, {1,0,0,0}, {1,0,0,0} },		{ {0,0,0,0}, {0,0,0,0}, {1,1,1,0}, {0,0,1,0} },		{ {0,0,0,0}, {0,0,1,0}, {0,0,1,0}, {0,1,1,0} },		{ {0,0,0,0}, {0,0,0,0}, {1,0,0,0}, {1,1,1,0} },		{ {0,0,0,0}, {0,0,0,0}, {0,1,1,0}, {1,1,0,0} },		{ {0,0,0,0}, {0,1,0,0}, {0,1,1,0}, {0,0,1,0} },		{ {0,0,0,0}, {0,0,0,0}, {1,1,0,0}, {0,1,1,0} }, 		{ {0,0,0,0}, {0,0,1,0}, {0,1,1,0}, {0,1,0,0} },		{ {0,0,0,0}, {0,0,0,0}, {0,1,0,0}, {1,1,1,0} },		{ {0,0,0,0}, {0,1,0,0}, {0,1,1,0}, {0,1,0,0} },		{ {0,0,0,0}, {0,0,0,0}, {1,1,1,0}, {0,1,0,0} },		{ {0,0,0,0}, {0,1,0,0}, {1,1,0,0}, {0,1,0,0} },		{ {0,0,0,0}, {1,1,0,0}, {0,1,0,0}, {0,1,0,0} },		{ {0,0,0,0}, {0,0,0,0}, {0,0,1,0}, {1,1,1,0} },		{ {0,0,0,0}, {0,1,0,0}, {0,1,0,0}, {0,1,1,0} },		{ {0,0,0,0}, {0,0,0,0}, {1,1,1,0}, {1,0,0,0} }	};	/// Try to rotate the current block	static void RotateBlock(const int x, const int y) {		// skip to next line in index array		int temp = currentBlock + BLOCK_TYPES;		// adjust on overflow		if (temp >= BLOCK_TYPES*ROTATIONS) {			temp -= BLOCK_TYPES*ROTATIONS;		}		// test if we can do this		if (CanPutBlock(x, y, blocks[blockIndex[temp]])) {			currentBlock = temp;		}	}

Some screenies [smile]:
Click to open in another window

Click to open in another window

I'd always choose the simple solution over the most elegant first.

Just my 0.02€ worth, I hope it helps anyway,
Pat.

This topic is closed to new replies.

Advertisement