Sign in to follow this  
Xycaleth

Tetris OOP help

Recommended Posts

Xycaleth    2391
Looks like I left the other topic too long before replying again. http://www.gamedev.net/community/forums/topic.asp?topic_id=489976 Continuing from there...I've had a little break, and have been busy with school work, and now I've started thinking about rewriting my Tetris game using OOP. One major problem I'm having though is where to put certain bits of code. At the moment, I have the game loop in the main() function:
int main ( int argc, char *argv[] )
{
	GameState	*gameState = new GameState();
	GameView	*gameView = new GameView (gameState);
	
	if ( !gameView->Init ("Falling Blocks") )
	{
		return 1;
	}
	
	Controller	*controller = new Controller (gameState, gameView);
	
	while ( 1 )
	{
		controller->PollEvents();
		if ( gameState->IsGameOver() )
		{
			break;
		}
		
		gameState->Update();
		gameView->Update();
	}
	
	return 0;
}

But I'm not sure if this is the best place to put it. I've read some people prefer to create an 'Application' class which contains the game loop but as there is only one instance of it, wouldn't it be the same as putting it in the main() function? Second problem I'm having is the collision detection. I'm not sure how to go about doing it because originally, I was planning to have the collision detection in my Tetromino class, but it doesn't know where any of the existing pieces are. In my GameState::Update function:
void GameState::Update()
{
	// some code....
	
	if ( IsRunning() && !m_playArea->IsClearingRows() )
	{
		m_currentTetromino->Move (MD_DOWN);
		
		int rowsCompleted = m_playArea->ClearCompleteRows();
		if ( rowsCompleted > 0 )
		{
			UpdateScore (rowsCompleted);
		}
	}
	
	m_time = newGameTime;
}

Is where I move the tetromino - would it be better to check the collision here, after I've moved the tetromino, and then tell it to move back if there is a collision? Or is there a better way?

Share this post


Link to post
Share on other sites
fastcall22    10839
I would check if the new transformation will collide, then ignore the transformation if it collided...

or

You could perform the transformation, then if it collides, then undo the transformation.

EDIT:
Quote:
Original post by Xycaleth
it doesn't know where any of the existing pieces are


But your play-field should, so ask it if your piece is conflicting.


Your main() code is fine. And in a sense, it is the same as creating an application class, but some reasons for having an application class might be that OOP objects may need to refer to application-specific variables, such has the engine or other such things, to avoid using global variables...

Share this post


Link to post
Share on other sites
kiwibonga    183
Yeah, you can pretty much go either way, the player won't know it happened.

What I'm wondering is why you have a tetromino class... There's pretty much only one tetromino in play at any given time, and the blocks in the well don't need to be represented as much more than a single byte in an array. The game class should be responsible for checking collisions, since it handles both the current tetromino in play and the well at the same time. The way I did it for my tetris clone was just a simple struct in my game class that kept track of the x, y, piece type and rotation of the current piece.

As for the question about the app class... I can think of several useful things... If you want to implement multiplayer, all you'd need is to duplicate your game class and assign it a different controller class to have different key mappings... If you want to reuse the same framework for a different game later, you can also benefit from making an app class. It also works the other way around, if you want to change the framework without having to rewrite/copy and paste the meat of the game logic into a new project.

Remember to think of why you want to divide your code into classes... If you want to learn about the benefits of OOP, put yourself in a context where your classes are useful for a specific goal, otherwise you'll run into design dead-ends.

Share this post


Link to post
Share on other sites
Xycaleth    2391
Quote:
What I'm wondering is why you have a tetromino class...

I was thinking of having the Tetromino class do the moving/rotating of the tetromino.

Now another problem I'm having is accessing the Tetromino class from the Controller class (for rotating, moving, etc). At first I was thinking of having a GetCurrentTetromino method in GameState - similar to the way I access the PlayArea from the Controller class, but I don't think this is very good OO design (creating a method just for the sake of accessing a private member)...

Share this post


Link to post
Share on other sites
dmatter    4826
Quote:
Original post by Xycaleth
Quote:
What I'm wondering is why you have a tetromino class...

I was thinking of having the Tetromino class do the moving/rotating of the tetromino.

Seems fair.
When I made Tetris I had a class like this, it was essentially a tile-map and orientation together. The order that tiles were iterated depended on this orientation which made life easy for the renderer.

The grid position of the tetrad was actually stored in the GameState class; this made sense since only one of the two (current and next) tetrads need a grid position. Screen-space coordinates are unimportant to the model; they're the view's domain.

Quote:
Now another problem I'm having is accessing the Tetromino class from the Controller class (for rotating, moving, etc). At first I was thinking of having a GetCurrentTetromino method in GameState - similar to the way I access the PlayArea from the Controller class, but I don't think this is very good OO design (creating a method just for the sake of accessing a private member)...

Why does the controller need direct access to these private members?
You could either make them public or since the controller has access to a GameState instance you could just have the controller interact with it:

gameState->dropTetrad();
gameState->rotateTetrad(Tetromino::CLOCK_WISE);
gameState->canDrop();
gameState->canRotate(Tetromino::CLOCK_WISE);
gameState->fullRowCount();
gameState->clearFullRows();
gameState->increaseScore();
gameState->pushRandomBlock();
gameState->beginNextLevel();
etc


You might wish to combine some of those too:

boolean success = gameState->attemptDrop();

Share this post


Link to post
Share on other sites
kiwibonga    183
Quote:
Original post by Xycaleth
Quote:
What I'm wondering is why you have a tetromino class...

I was thinking of having the Tetromino class do the moving/rotating of the tetromino.

Now another problem I'm having is accessing the Tetromino class from the Controller class (for rotating, moving, etc). At first I was thinking of having a GetCurrentTetromino method in GameState - similar to the way I access the PlayArea from the Controller class, but I don't think this is very good OO design (creating a method just for the sake of accessing a private member)...

You probably shouldn't use your controller class to issue commands... Its purpose is to detect keypresses. Make it store the current keypresses, and then have the gamestate read from them and issue commands as needed. Otherwise, you've got bits of game logic in the controller class, which is probably not what you want to do...

Share this post


Link to post
Share on other sites
Xycaleth    2391
Quote:
Its purpose is to detect keypresses. Make it store the current keypresses, and then have the gamestate read from them and issue commands as needed.

I thought the idea was that the model (GameState) didn't know anything about the controller, but the controller tells the model what to do?

Share this post


Link to post
Share on other sites
kiwibonga    183
Quote:
Original post by Xycaleth
Quote:
Its purpose is to detect keypresses. Make it store the current keypresses, and then have the gamestate read from them and issue commands as needed.

I thought the idea was that the model (GameState) didn't know anything about the controller, but the controller tells the model what to do?


To illustrate, it's a bit as if you had two people and one (the controller) was instructing the other (the gamestate) to dig a hole with a shovel. The controller doesn't have to know the details of how digging works, he just has to tell the gamestate to do it.

What your controller is doing is taking the shovel from the gamestate and digging the hole himself.

Now, it works well regardless, but the issue is really with maintainability of your code... Those things the controller does are really game logic-specific. You wouldn't be able to take this controller class and reuse it later in another game, for example. Same thing if you've got a class in charge of all the functions that display things (or a set of classes that make up your graphics engine) and you've got bits of code in there that are gameplay-specific.

Share this post


Link to post
Share on other sites
dmatter    4826
Compare:
Quote:
Original post by kiwibonga
You probably shouldn't use your controller class to issue commands... Its purpose is to detect keypresses. Make it store the current keypresses, and then have the gamestate read from them and issue commands as needed. Otherwise, you've got bits of game logic in the controller class, which is probably not what you want to do...

To:
Quote:
Original post by kiwibonga
Quote:
Original post by Xycaleth
I thought the idea was that the model (GameState) didn't know anything about the controller, but the controller tells the model what to do?


To illustrate, it's a bit as if you had two people and one (the controller) was instructing the other (the gamestate) to dig a hole with a shovel. The controller doesn't have to know the details of how digging works, he just has to tell the gamestate to do it.

What your controller is doing is taking the shovel from the gamestate and digging the hole himself.

Now, it works well regardless, but the issue is really with maintainability of your code... Those things the controller does are really game logic-specific. You wouldn't be able to take this controller class and reuse it later in another game, for example. Same thing if you've got a class in charge of all the functions that display things (or a set of classes that make up your graphics engine) and you've got bits of code in there that are gameplay-specific.

Your posts are contradictory; I agree with the latter one though.

The controller is there to make decisions and issue commands to the model (and perhaps view) based on the game's logic, the game's state and any events it listens to, like key presses for example.
There is no hard and fast rule about the implementation of the Model-View-Controller architecture so the model may or may not know about the controller and I have seen implementations that do this; I personally don't think it should though.

Share this post


Link to post
Share on other sites
Xycaleth    2391
I think I understand what you're saying now :) Input control looks like this now:
bool Controller::PollEvents()
{
if ( SDL_PollEvent (&m_event) )
{
bool keysPressed[SDLK_LAST] = { false };

switch ( m_event.type )
{
case SDL_KEYDOWN:
keysPressed[m_event.key.keysym.sym] = true;
break;

case SDL_KEYUP:
keysPressed[m_event.key.keysym.sym] = false;
break;

case SDL_QUIT:
return false;
break;

default:
break;
}

m_gameState->HandleInput (static_cast<const bool*>(keysPressed));
}

return true;
}


void GameState::HandleInput ( const bool* keys )
{
switch ( m_playState )
{
case GS_RUNNING:
if ( keys[SDLK_p] )
{
m_playState = GS_PAUSED;
}

if ( keys[SDLK_DOWN] )
{
m_playArea->MoveTetromino (Tetromino::DOWN);
}

if ( keys[SDLK_LEFT] )
{
m_playArea->MoveTetromino (Tetromino::LEFT);
}

// etc
break;
}

// etc
}

Hopefully that's looking better. I've almost finished making the game now, there's just one final hoop I need to jump through! I need to draw a preview of the next tetromino that's going to appear, but what would the best way to do this be? I've thought about making a method in the Tetromino class called GetBlocks which would return a pointer to a 4x4 array for the specified tetromino, but I don't think this would work very well.

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