Problems with a tetris clone written in C++/SFML

Started by
7 comments, last by shiryu 13 years, 3 months ago
Hello,
Like many other people, after reading this article, I decided to make my own version of Tetris. So I started learning SFML and after one week of work, that's what it looks for the moment.



It works well but I have some littles problems.
First, in Tetris, when a piece land, it can be moved again at least once before getting it's final position, but I can't see how to do it. I try making the game wait a little when a piece lands, but it doesn't work.
Second, (it's stupid, I agree) I don't how to make a SFML program pause.

Please I need help, you can view the source code here, and also suggestions and feedbacks about the source code.

P. S. Sorry if my English is not good enough, I'm french.
Advertisement
Good job, I like the look of your game, nice and clean.

1) Moving a non-falling block.
Each frame you must have a main loop that looks something like

a) Handle input
c) Collision detection
b) Move blocks
c) Render

So the trick is to allow horizontal movement even if vertical movement is not possible. Keep a timer value. Every frame increase this time until some limit, e.g. 2 seconds. If the timer reaches 2s then lock that block and drop a new one. If in any frame you find that the block have moved then reset the timer to 0. This way you don't have to special-case non-moving blocks, you just have to note that if the block haven't moved for X seconds, then drop a new block.

2) The only difference between paused and active is that game logic is run. So just check if the game is in pause mode and skip game logic. Pseudo-code:
while( true ) {    get_input()    if( paused ) {        handle_input_paused()            } else {        handle_input()        do_logic()    }    render()}

Of course, handling timers that run across a pause in the game can be a bit tricky.
How do you structure your code?

In tetris, there's two kinds of events:

timer event: which advances the pieces down at every given time.
keyboard event: the piece is manipulated immediately when the keyboard is pressed.

These two are totally separated, so your problem becomes a non issue. In fact, you shouldn't have to take care about it at all.


So: you have a timer event: the piece is advanced down and touches the bottom. This doesn't mean the piece has reached the bottom yet. It will reach it in the next update only, so you can press the keyboard and manipulate the piece as many times you can press the key before the next timer event.


So:

Check collision BEFORE you advance the piece. If there's space to advance -> advance.

I hope that's clear.
Thanks for your replies. I try to implement your advises.
Please check if I correctly understand what you propose
Now I have in addition to the falling clock, a game clock which is reinitialised each time a piece is moved. And when a piece lands, I check if the elapsed time since the last reinitialisation is superior or equal to a wait time. If yes, I drop a new block
That's the code

void Game::handleUserInput(sf::Clock &gameClock){	sf::Event event;			while(renderArea->GetEvent(event))	{		if(event.Type == sf::Event::Closed)			renderArea->Close();				if(event.Type == sf::Event::KeyPressed)		{			gameClock.Reset();							if(event.Key.Shift)				holdCurrentPiece();						if(event.Key.Code == sf::Key::Left)				gameArea.moveCurrentPieceLeft();											if(event.Key.Code == sf::Key::Right)				gameArea.moveCurrentPieceRight();							if(event.Key.Code == sf::Key::Up)				gameArea.rotateCurrentPieceRight();						if(event.Key.Code == sf::Key::Space)			{                                // HARD DROP				gameArea.dropCurrentPiece();				// DROP A NIEW PIECE			}					}	}		const sf::Input &input = renderArea->GetInput();	        /* SOFT DROP */	if(input.IsKeyDown(sf::Key::Down))	{		gameArea.moveCurrentPieceDown();				gameClock.Reset();	}}void Game::play(){        sf::Clock fallingClock, gameClock;	fallingClock.Reset();	gameClock.Reset();		float currentTime = 0, precTime = 0;	while(renderArea->IsOpened())	{		currentTime = fallingClock.GetElapsedTime();				if(currentTime - precTime >= getFallIterationDelay())		{			gameArea.moveCurrentPieceDown();			precTime = currentTime;		}			if(gameArea.isCurrentPieceFallen())		{			if(gameClock.GetElapsedTime() >= WAIT_TIME)				// DROP A NEW PIECE		}				handleUserInput(gameClock);				render();	}


Is it right ?
Now, it works but only if I a key is pressed when the piece is one row before falling. I think the problem is elsewhere, maybe in the isCurrentPieceFallen() method.
I recommend that instead of checking if the piece is resting on another and then deciding that it must stick, that you only make it stick when you try to move it down again and find that you cant.

It's a matter of the order in which things are done. It should be:
Check, move, wait... check, move, wait...
Not:
Check, wait... move, check, wait... move
(Where 'check' means to see if the block should now become fixed)
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
About new piece generation, you shouldn't generate a purely random new piece each time. You should pre-generate a "shuffled bag" of 14 pieces containing two of each possible piece; this way, you're guaranteed to get an even distribution throughout -- this is how most post-1990 Tetris games behave.

Also, pieces should never come pre-rotated; they should always come out the same way, at their default rotation.

Regarding your problem, it's customary to have a "countdown" on the current piece that's falling down.

If the piece is resting on something, decrement the countdown by a certain amount each frame. If the piece is moved and is not resting on anything anymore (i.e. can fall down further), reset the counter.

If the counter reaches zero, that's when you lock the piece. The countdown time should be proportional to the current level.

Some games implement an additional counter that counts the number of times the counter has been reset, and force a lock if the player tries to abuse his counter-reset privilege to stall the game (by rotating the piece to make it "kick up" endlessly).
Thank you, guys, it works well now.
Quote:About new piece generation, you shouldn't generate a purely random new piece each time. You should pre-generate a "shuffled bag" of 14 pieces containing two of each possible piece; this way, you're guaranteed to get an even distribution throughout

Sorry kiwibonga, but I don't understand what you mean, if I pre-generate a bag of 14 pieces, will not I get cycles of the same sequence of pieces
Actually, you generate a new shuffled bag every time you reach the end of the current bag.
Thanks kiwibonga, I change the way of generating current piece according to your advice.
I think this community is probably the most important in the field of game programming, so I want feedbacks and criticisms about my source code from its members to help me improve it.

This topic is closed to new replies.

Advertisement