Tetris implementation, need help

Started by
15 comments, last by azure kitsune 15 years, 3 months ago
Hi everyone, I'm pretty new to game programming (and programming in general). A few days ago, I decided to sit down and write a Tetris program (in C++). Right now, it works, but there are certain parts of the code that I am not satisfied with. Here's what I have. - A Block class. It represents a certain square on the tetris grid. It has an x-coordinate and y-coordinate. - A Tetromino class. An array of 4 blocks. - A TetrisGrid class. Contains a 2D array of the grid (1 if a space is filled and 0 if not), and also the current Tetromino falling in the grid. For the different types of Tetrominos, I created subclasses (IBlock, LBlock, etc.) so that they correctly set the coordinates of the 4 blocks. I realized that this was kind of useless because the piece in TetrisGrid was declared to be Tetromino, so the methods in the subclass wouldn't work (for example, I wanted to override the rotate() method for the O block). What should I do here? Should I just condense all the different subclasses into the Tetromino class and use a lot of 'if' statements? Also, my current way of detecting collision is this: Move the tetromino where it wants, check to see if it is in a valid position, if it is then move back. I think this is rather inefficient but I can't come up with anything better. Can anyone help? Thanks! :)
Advertisement
Hi well i will try and answer these questions for you i will first start off by saying the collision detection is actualy probaly the best because alot of other methods would envolve heaver math and be less efficent.

As for the subclass i am not completly sure what it is you are going for but if you want to overide a function with slicing (polymophism) then you have to make the function yo uwant virtual that way if the subclass you slice into will overide it and if there is none the origanal will remain the same. If you have not already and i sort of assumed you have check out polymorphism of C++ class and advanced inhertiance.

Hope this helps if not try and specifiy what it si you are exactly trying to do.
It may seem like the question is oveus but we have not seen the code.


Regards Jouei.

Behold the allmighty power of the if statment may you treble in its presence...
Thanks for your reply Jouei. Okay, I'll keep my current collision detection algorithm.

Here's the other part I was talking about

class Block {private:	int x;	int y;public:	int get_x() {		return x;	}	int get_y() {		return y;	}	void set_x(int value) {		x = value;	}	void set_y(int value) {		y = value;	}	void set(int value_x, int value_y) {		x = value_x; y = value_y;	}	void translate(int delta_x, int delta_y) {		x += delta_x; y += delta_y;	}	void descend() {		y++;	}	void move_left() {		x--;	}	void move_right() {		x++;	}	};class Tetromino {protected:	Block block[4];public:	void set_center(int, int);		void descend() {		for (int i = 0; i < 4; i++) {			block.translate(0,1);		}	}		void ascend() {		for (int i = 0; i < 4; i++) {			block.translate(0,-1);		}	}		void move_side(int dir) {		if (dir == -1) {			move_left();		} else if (dir == 1) {			move_right();		}	}		void move_left() {		for (int i = 0; i < 4; i++) {			block.translate(-1,0);		}	}	void move_right() {		for (int i = 0; i < 4; i++) {			block.translate(1,0);		}	}		int get_block_x(int i) {		return block.get_x();	}		int get_block_y(int i) {		return block.get_y();	}		void rotate_cw() {		int axis_x = block[0].get_x();		int axis_y = block[0].get_y();				for (int i = 1; i <= 3; i++) {			int old_x = block.get_x();			int old_y = block.get_y();			block.set(old_y - axis_y + axis_x, axis_x - old_x + axis_y);		}	}		void rotate_ccw() {		int axis_x = block[0].get_x();		int axis_y = block[0].get_y();				for (int i = 1; i <= 3; i++) {			int old_x = block.get_x();			int old_y = block.get_y();			block.set(axis_y - old_y + axis_x, old_x - axis_x + axis_y);		}	}};class OPiece : public Tetromino {public:	OPiece(){}	OPiece(int x, int y) {		set_center(x,y);	}	void set_center(int x, int y) {		block[0].set(x,y);		block[1].set(x+1,y);		block[2].set(x,y+1);		block[3].set(x+1,y+1);	}};	 // there's more in the middle, but I took them out.class ZPiece : public Tetromino {public:	ZPiece(){}	ZPiece(int x, int y) {		set_center(x,y);	}	void set_center(int x, int y) {		block[0].set(x,y);		block[1].set(x-1,y);		block[2].set(x+1,y+1);		block[3].set(x,y+1);	}};	class TetrisGrid {public:	TetrisGrid() {		for (int i = 0; i < WIDTH; i++) {			for (int j = 0; j < HEIGHT; j++) {				grid[j] = 0;			}		}	}		// some functions		void new_piece() {		switch(rand()%7+1){		case 1:			currentPiece = new IPiece(WIDTH/2,0);			break;		case 2:			currentPiece = new LPiece(WIDTH/2,0);			break;		case 3:			currentPiece = new OPiece(WIDTH/2,0);			break;		case 4:			currentPiece = new SPiece(WIDTH/2,0);			break;		case 5:			currentPiece = new TPiece(WIDTH/2,0);			break;		case 6:			currentPiece = new JPiece(WIDTH/2,0);			break;		case 7:			currentPiece = new ZPiece(WIDTH/2,0);			break;		}	}		// some more functions		static const int HEIGHT = 20;	static const int WIDTH = 10;	private:	int grid[WIDTH][HEIGHT];	Tetromino *currentPiece;};


My problem is when I call currentPiece->someMethod(), the program goes to the Tetromino class to find the method, not the subclass.

Jouei, I am not familiar with the concept you are talking about. Would it apply to my program now that I put up the code?
Quote:Original post by azure kitsune


My problem is when I call currentPiece->someMethod(), the program goes to the Tetromino class to find the method, not the subclass.

Jouei, I am not familiar with the concept you are talking about. Would it apply to my program now that I put up the code?


In the Tetromino class declare the function you want to call in the subclass as virtual. Something like this

class Tetromino {protected:virtual int your_function() { //code here }};


then in you derived class (subclass) overwrite the function like usual

class AnySubclass : public Tetromino {int your_function() { //code herre }};


Now the correct function should be called. Hope this was of any help [smile], if you want to look more into virtual functions check out this link Clicky

You didn't come into this world. You came out of it, like a wave from the ocean. You are not a stranger here. -Alan Watts

I would definitely suggest not putting a huge set of if statements in a single class but simply keep your current route, of having each piece shape subclass a main piece class that can have a certain rotate method. There is a certain way you can rotate each piece through the same algorithm by assigning the middle of a piece to instead being a corner be the actual middle of the piece, that means you will have negative values. Doing this results in no real changes being required for the rotate method.
For your testing if you can move it the best route IMO is to clone the piece and then TEST to see if it can be moved, if not then don't do the actual movement, however if there is no problem then do that to the actual piece. This gets rid of the actual moving it and then moving it back which if you use a GUI might look sketchy.
Quote:Original post by azure kitsune
For the different types of Tetrominos, I created subclasses (IBlock, LBlock, etc.)

Overkill. Different shapes don't really behave differently, they just have different data. You wouldn't subclass a string class to make a "hello" and a "world" string, would you?
Quote:Original post by DevFred
Quote:Original post by azure kitsune
For the different types of Tetrominos, I created subclasses (IBlock, LBlock, etc.)

Overkill. Different shapes don't really behave differently, they just have different data. You wouldn't subclass a string class to make a "hello" and a "world" string, would you?


Maybe composition/aggrogation. Have a block class which can contain different shape classes.

it keeps the behavier the same, but the shape is different.

Conforms to the OCP principle, and code reuse.
@Jouei && @VanillaSnake21: Thank you guys for your help. I didn't know about virtual functions before. (I'm taking AP Computer Science right now and it's ruining all of my C++!)

Quote:Original post by Lothia
For your testing if you can move it the best route IMO is to clone the piece and then TEST to see if it can be moved, if not then don't do the actual movement, however if there is no problem then do that to the actual piece. This gets rid of the actual moving it and then moving it back which if you use a GUI might look sketchy.


Thanks for the advice. I tried that and here is what I have in my TetrisGrid class:

	void rotate_piece(int dir) {		Tetromino *copy;		copy = new Tetromino(currentPiece);		copy->rotate(dir);		if (is_valid_position(copy)) {			delete currentPiece;			currentPiece = copy;		}	}


Is this right?

Quote:Original post by DevFred
Overkill. Different shapes don't really behave differently, they just have different data. You wouldn't subclass a string class to make a "hello" and a "world" string, would you?


I thought about it and I see your point. The only reason it worked out for me is because there are only 7 different tetrominoes. (If Tetris used hexominoes, I would have 60 subclasses!)

Quote:Original post by Alastair Gould
Maybe composition/aggrogation. Have a block class which can contain different shape classes.

it keeps the behavier the same, but the shape is different.

Conforms to the OCP principle, and code reuse.


I'm sorry. I don't really understand. Do you mean that I should create a Shape class for the 7 different shapes?
Quote:Original post by Alastair Gould
Maybe composition/aggrogation. Have a block class which can contain different shape classes.

it keeps the behavier the same, but the shape is different.

Conforms to the OCP principle, and code reuse.
I am of course not DevFred, so I can't with certainty speak on this other person's behalf, but I am reasonably certain the intention was to shift from subclassing to something more data driven. By making a series of shape classes, you are ending up with the exact same problem that you started with. Instead of having a bunch of shape classes, you would just pass a description of the shape in to the block's constructor [or by some other means]. Just as a primitive example, say the block classes constructor takes a 4x4 matrix of colors, where a null color marks a part that doesn't have a tetris piece. You have a known pivot within the matrix, so you aren't hard-coding a center. You can also easily make a bunch of different tetris pieces without all the headache and maintainability issues of subclassing all over the place. These blocks also become something you can arbitrarily load from a file.

It doesn't really stick out as bad for something this small scale, but this sort of hard-wired class-based distinction between things that are differentiated only by the data they hold results in a huge problem later, as projects get more complicated.
Quote:Original post by Drigovas
I am of course not DevFred, so I can't with certainty speak on this other person's behalf, but I am reasonably certain the intention was to shift from subclassing to something more data driven.

Spot on!

This topic is closed to new replies.

Advertisement