Sign in to follow this  

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

This topic is 2542 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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)

Share this post


Link to post
Share on other sites
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).

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

This topic is 2542 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

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