Help on Designing an OOP Tetris-clone

Started by
20 comments, last by Zahlman 17 years ago
Quote:Original post by ToohrVyk
The difference between tetris pieces is shape

Interesting. In my last tetris clone, I had a class Shape which stored information about the shape (duh). There were 28 instances of this class for the seven basic shapes in four orientations (7*4 = 28).

Then I had a class Piece which stored information about the position, plus it referenced a Shape. rotate() just changed the reference to another shape. No need to rotate every brick by hand :)

Am I the first one to seperate Shape and Piece into two different classes? They are clearly two distinct things.

BTW I don't think representing a Shape with a 4x4 matrix is the most logical or efficient solution. A vector of four coordinate pairs seems better to me, because when you process the piece (check for collisions, insert it into the gameboard), you only have to do four operations instead of 16.
Advertisement
Quote:Original post by DevFred
Am I the first one to seperate Shape and Piece into two different classes? They are clearly two distinct things.


Technically, they aren't.

The difference between shape and piece is same as between class and instance.

Class is formal definition. Instance is one version of this class.

All L pieces are the same. Whether they get rotated or not, they do not change the nature of what they are - they are still exactly the same shape, and they will always be pieces.

For example, in an RPG, a human character is just that. Entity <- Creature <- Player. There will be many humans, some tall, some short, some facing left, some right, ....

They are still the same class - PlayerObject, with its race property set to human, it's orientation to some matrix, and position to a vector.

Renderer is the responsible for loading the graphical representation, such as mesh and textures.

In case of tetris, there is no such representation, since the rendering is done by filling blocks.

For tetris I wrote long long time ago, I simply used arrays of 4x4 representing every possible rotation of every object. Each of those also had an achor point around which it was rotate.

If I did this in OO way, it would be:
class Piece {  bool blocks[4][4];  int x_anchor; //(0..3)  int y_anchor;    //(0..3)  Piece &rotate();}Piece blocks[28];


Similar to your aproach, but I don't separate shape and piece. They are functionally the same, but rotate still returns the new piece.

This aproach however doesn't allow for animated rotations some games use.

Quote:BTW I don't think representing a Shape with a 4x4 matrix is the most logical or efficient solution. A vector of four coordinate pairs seems better to me, because when you process the piece (check for collisions, insert it into the gameboard), you only have to do four operations instead of 16.


Unsigned short is optimal representation. You know it's 4x4, and each spot can either have a block or not.

You can then use bitmasking to determine collisions. This may potentially be much faster, since you would end up with very simple operations, shifts and "ands", with one single comparison at the end, possibly resulting in good pipelining.

But the above is completely absurd in case of tetris, where performance in this part is pointless.

Quote:Original post by DevFred
Am I the first one to seperate Shape and Piece into two different classes?


No. This is basically the Flyweight pattern, if I understand you correctly.
Quote:Original post by Antheus
Quote:Original post by DevFred
Shape and Piece [...] are clearly two distinct things.

Technically, they aren't.

Maybe I wasnt clear enough on this. The class Shape defines how a Shape looks. Here is the interface:
public interface Shape{	/**	 * returns a new shape which is obtained by cutting the current shape to the	 * given thresholds.	 * 	 * @param bot	 *            everything below bot will be cut	 * @param top	 *            everything above top will be cut	 * 	 * @return null if everything has been cut away, an appropriate shape else	 */	public Shape cut(int bot, int top);	/**	 * checks wether the shape contains the given block.	 * 	 * @param x	 *            the column	 * @param y	 *            the row	 * 	 * @return true if the shape contains the given block	 */	public boolean contains(int x, int y);	public int getBot(int index);	public int getTop(int index);	public int getTetramino();	public int getOrientation();	public int getOffset();	public int getWidth();	public boolean equals(Object o);	public int hashCode();}

So you can ask a Shape: "Do you have a brick at x=3 and y=1?" for example. Theres 28 instances of a class which implements the interface Shape for the basic shapes.

The class Piece however is instantiated for every piece that you can see on the board. Ten L pieces residing on different positions on the board all share the same shape, but they differ in their position. Excerpt from the class Piece:
/** * a piece is basically a moving shape. *  * @author fred */public class Piece implements Flexible, Iterable<Integer>, Cloneable{	private Shape _shape;	private int _x;	private Moving _y;	/**	 * creates a new piece.	 * 	 * @param shape	 *            the shape	 * @param x	 *            the initial x position	 * @param y	 *            describes how the piece initially moves in the vertical	 *            direction	 */	public Piece(Shape shape, int x, Moving y)	{		assert shape != null;		assert x >= 0;		assert y != null;		_shape = shape;		_x = x;		_y = y;	}	/**	 * moves the piece one unit to the left.	 */	public void moveLeft()	{		_x--;	}	/**	 * moves the piece one unit to the right.	 */	public void moveRight()	{		_x++;	}	/**	 * changes the moving behaviour according to the given factory.	 */	public void changeMoving(MovingFactory factory)	{		assert factory != null;		_y = factory.create(_y);	}	public int getWidth()	{		return _shape.getWidth();	}	public int getX()	{		return _x;	}	public int getOffset()	{		return _shape.getOffset();	}	public void setShape(Shape shape)	{		assert shape != null;		_shape = shape;	}	public Shape getShape()	{		return _shape;	}	public Moving getMoving()	{		return _y;	}	public int getTetramino()	{		return _shape.getTetramino();	}}


Quote:
All L pieces are the same.

No, they differ in their position on the board.

Quote:
This aproach however doesn't allow for animated rotations some games use.

Why not?

Quote:
Quote:A vector of four coordinate pairs seems better to me, because [...] you only have to do four operations instead of 16.

Unsigned short is optimal representation. You know it's 4x4, and each spot can either have a block or not.

You can then use bitmasking to determine collisions. This may potentially be much faster, since you would end up with very simple operations, shifts and "ands", with one single comparison at the end, possibly resulting in good pipelining.

This is the optimal representation if you want to save memory, but I was talking about clear code (plus my solution will be faster, but I don't care about that).

Think about it: if you use a 4x4 matrix (however you represent it does not matter), you have to do two nested loops. 16 times you say: "if there's a brick in the piece and a brick in the board at the current position (x,y), then there e is a collision."

With my solution, the code reads: "for every position in piece: if there's a brick at that position on the board, there is a collision". Much cleaner.

Quote:
But the above is completely absurd in case of tetris, where performance in this part is pointless.

I concur.

BTW I used another approach in the code above, don't confuse it with the idea of four vectors ;) Basically I have a list of vertical slices which allows for even more efficient collision detection.
hi people, i was working on a homework for my Simulation course, i first did it in procedural, then i converted it to single object, to practice me OOP. if anyone intrested, i can post it too, if you have time to observ.

first of all THANKS to all the contributors, I pray to God for you all.

so, more about Tetris that is filling my mind. (i really want to write a platformer, a clone mario u say, but i know i have to write Teris and other stuff first. How to store the ball in pong is keeping my mind busy too).

01- I just don’t feel the "4x4 block" be the Object Oriented way, I still think having a class hierarchy is much better design.

02- As ToohrVyk recommended having an array of Bricks for game board instead of integers (as he again recommended) seemed
confusing to me, so there is a Boolean in Brick which shows whether there is a brick on corresponding board or not, but with
The "4x4 Block" way you recommended i cant use them together.
do u mean having a struct called Brick which has a boolean which shows its free or not and a X and Y for coordination’s?
Anything else? Did I understand correctly? Or a m!sunderstood?

03- What moves a piece in the board? I mean whenever we get the action of "move current block to right", which method of
Which object should handle it? Should I have a MoveRight() for my Block? Should I tell the Board to move it for me?
If i tell the piece to move right (which I think is better comparing to real world which WE actually move, Earth doesn’t move
us), I have to public the game board (or keep it public and have a get/set method for it).
(maybe its right, tell me if this method is good, I donno, i just know that i have to keep everything as private as possible,
donno why really)

04- could u describe more about "4x4 block" method? as i understood, u are telling me to SEE everything as a 4x4block of
Boolean (or whatever), so it arises some problems for me:

4-A: when u decide to put an L-shape on the screen, give the constructor a matrix (or a similar data type), so it will fill
The asked blocks to fill? or anything else?

04-B: how do u put it on the board? does the board have a method to accept the newly created block? How do u put it actually
on the board?

04-C: how a piece and the board relate? (i think they call it class diagram), i mean when u move the piece one-piece to
right, do u have to ask 16times that if the target is free or not? is there anyway to public an data type just for an special
object?

04-D: do make a "24 by 12" game board? 20 x 10 is standard, two more for showing the boarders? i think we need two more block
for each dimension since we are treating the blocks as 4x4, so i think we need "24x14 of Bricks".

05: i still have problems with getting Real time input from keyboard in C++, where should i wait for a key to press?
i have a main loop for game, and inside it i manage stuff, there should be a hooking thing like the one in C# that i hook the
keyboard with a method that handles it, tell me how to do it in VC++.


THANKs!
Quote:Original post by test86
01- I just don’t feel the "4x4 block" be the Object Oriented way, I still think having a class hierarchy is much better design.

Different classes behave differently. How would an L piece behave any differently then a J piece? L pieces and J pieces have EVERYTHING in common: they can be moved, they can be rotated, and they have a distinct form.

Just because each peace might have a different form does not mean you need different classes! For example, you can have a string containting "hello" and a string containing "world!". Both are strings. You don't need seperate classes for every string literal possible :)

Quote:
having an array of Bricks

What is a brick? What state does a brick have? I'm just a little confused on terminology.

Quote:
What moves a piece in the board?

user presses left arrow key
tell the piece to move one to the left (this will just decrease some x position member in piece)
ask the board if the piece collides with the rest of the board
if it collides, move it back, i.e. one to the right

Quote:
If i tell the piece to move right [...], I have to public the game board

No. Just implement a method "bool collidesWith(const Piece &piece)" in board. The board is responsible for testing if the piece fits or not, because it knows its own internal structure.

Quote:
4-A: when u decide to put an L-shape on the screen, give the constructor a matrix (or a similar data type), so it will fill
The asked blocks to fill? or anything else?

You could give the constructor an integer between 0 and 27, and then you use that integer to index a precomputed array which holds 28 of these 4x4 blocks.

Quote:
04-B: how do u put it on the board? does the board have a method to accept the newly created block? How do u put it actually on the board?

myBoard.put(piece)

Quote:
04-C: how a piece and the board relate?

A piece does not know about the existence of a board. Basically all the board has to know about pieces is this: Is there a brick at (x/y)? As long as the piece provides such a method, everything is fine.

Quote:
04-D: do make a "24 by 12" game board? 20 x 10 is standard, two more for showing the boarders? i think we need two more block
for each dimension since we are treating the blocks as 4x4, so i think we need "24x14 of Bricks".

I used a width of 16 in my old implementations, but I've forgotten why :)
Hi, thnx for answering my questions.
I have another problem with input.
I know how to do it in C# but in C++, i donno.
the problem is, where should i wait for user input? as u know when user doesnt press any button, the piece will go down once in each interval, but when user preses down, it has to move corresponding to the input.
i'll have a game loop where i first check for gameover, if not, i'll try to move down the current piece,etc. what should i do to get user input? should i WAIT in some place? or it can be event driven and like C#, hook keyboard input to a funcion so whenever a key is pressed, the funcion will be called automatically and no need for waiting.
for my old dos based Snake, i think i used Waiting method, which i waited at somepoint, if i remember correctly.

and today i'll start coding tetris, i'll post whatever i'll finish till tonight.

thnx in advance.
Quote:Original post by test86
I know how to do it in C# but in C++, i donno.

So why don't you just code the whole thing in C# then?
cuz i'm planning for future.
as i said in last topic, i'm planning to work in game industry and THE language is C++, i'm planning to write games for nintendo and both DS and Wii as far as i know are C++ based, like most of engines, graphically or visual or anything.
and most frameworks,OSes, Engines support C++ if not recommended.
C# made me lazy, mostly on things that hides from programmer, like pointers and stuff, i had to learn these things years ago but C# came out and i escaped from C++ to C#. add the framework that you have to have in order to run your game and as you know, current consoles dont have .net framework, maybe xbox360, which i'm not planning to work on.

thats it, and please dont talk about this topic in here and please let this topic be as its name tells, about my OOP tetris clone, lets talk about this subject in a more apropriate topic, or PM if its just you and me.
------
would anyone please answer to my last question about keyboard input?
Quote:Original post by test86
cuz i'm planning for future.

So I can restate the question: why do you want to do it in C++ then?
Quote:
as i said in last topic, i'm planning to work in game industry and THE language is C++, i'm planning to write games for nintendo and both DS and Wii as far as i know are C++ based, like most of engines, graphically or visual or anything.
and most frameworks,OSes, Engines support C++ if not recommended.

Oh, I see. But you must understand that the language syntax is not difficult to learn - the most difficult thing is to learn how to use an object oriented language. So I suggest you to stick to C# now, and learn C++ later - you still have some time to learn it. Or, to put it using other words: Learn the concepts, then implement the concepts in the different languages.
Quote:
C# made me lazy, mostly on things that hides from programmer, like pointers and stuff, i had to learn these things years ago but C# came out and i escaped from C++ to C#. add the framework that you have to have in order to run your game and as you know, current consoles dont have .net framework, maybe xbox360, which i'm not planning to work on.

If you know how pointers works, there's little chance that you'll forget that. I mean, once you get them, it's so simple that you can't forget them.

Quote:
thats it, and please dont talk about this topic in here and please let this topic be as its name tells, about my OOP tetris clone, lets talk about this subject in a more apropriate topic, or PM if its just you and me.

I understand this, but I still prefer to answer here [smile].

Quote:would anyone please answer to my last question about keyboard input?

Input handling is done differently on different platforms (I mean, whether you are using the console or Windows). You have to tell us what kind of API you are using - pure Win32, Windows.Forms, (managed) DirectX, XNA, OpenGL, a cross platform library like SDL?) if you want to get some usefull answer here.

Best regards,

This topic is closed to new replies.

Advertisement