0
\$\begingroup\$

I'm attempting to make a Tetris clone and want to be as accurate to the games as I can. I found a chart that explains the speed curve that's supposedly used in games that follow the official Tetris guideline: https://harddrop.com/wiki/Tetris_Worlds

1G = 1 cell per frame

  • Level 1: 0.01667G
  • Level 2: 0.021017G
  • Level 3: 0.026977G
  • Level 4: 0.035256G
  • Level 5: 0.04693G
  • Level 6: 0.06361G
  • Level 7: 0.0879G
  • Level 8: 0.1236G
  • Level 9: 0.1775G
  • Level 10: 0.2598G
  • Level 11: 0.388G
  • Level 12: 0.59G
  • Level 13: 0.92G
  • Level 14: 1.46G
  • Level 15: 2.36G

Another thing that might be worth noting is, like most Tetris games, my loop is updating at 60fps.

So given all this, there are two things that I'm having problems understanding.

1: The speed curve doesn't always translate to whole numbers.

For example, at level 2: 1 / 0.021017 = 47.5805300471. So every 47.5805300471 frames the game is supposed to drop the piece one row. Am I supposed to round these numbers? I can't process a partial frame so I don't understand why the speed curve comes out as a decimal.

2: Level 14 and 15 drop the piece more than one row per frame

Because it's dropping more than one row per frame, this would make some moves impossible in the game. For example, if I wanted to move a piece horizontally into a one cell space and the game is dropping the piece more than one row at a time, it will skip the row entirely and make the move impossible. Is this correct or am I missing something?

Impossible Tetris move?

\$\endgroup\$

3 Answers 3

1
\$\begingroup\$

A good approach is to have two time-based counters.

  • How long does the tetromino exist
  • How long have you simulated its physics

When you spawn a new tetromino, start both at 0.

In the beginning of your update-loop, add the time since the last execution of the update-loop to the existence time. I personally prefer to keep all times in seconds so you can easily experiment with your updates-per-second rate without having to modify too much code. But if you are completely sure you are never going to try anything else but 60 updates per second, you can of course use frames instead of seconds as your unit of time.

Next, check the difference between existence time and simulated time. While the existence time is larger than the physics time:

  1. Move the tetromino down one row
  2. Add the "time per drop" to the simulated time

Or in pseudocode:

time_exists += time_since_last_update // or +1 if you are using a fixed framerate
while (time_exists > time_simulated)
    tetromino.y -= 1
    time_simulated += 1 / cells_per_time_unit

That means if you have a falling speed of 2.5 cells per frame, then the above code will alternate between moving it 2 cells and 3 cells.

This, by the way, means that starting from level 14 it will be physically impossible for the player to make certain moves because the line on which they need to make that move gets skipped. So if you expect your players to become so good at tetris that this starts to matter, then you should use a higher logical update-rate than 60 FPS, even if your actual graphics render rate is capped.

\$\endgroup\$
4
  • \$\begingroup\$ Thanks for the detailed response :). The answer to my first question seems so obvious now after your explanation. For my second question, I hadn't considered running the logic update faster than 60fps, but that makes total sense as a solution. Unfortunately, because I'm programming this in JavaScript and running it in a browser, I'm reliant on requestAnimationFrame, which means that max speed my loop can run is tied to the refresh rate of the monitor. This isn't a huge deal though. I just wanted to make sure that I understood the speed curve. \$\endgroup\$
    – Dan
    Jun 16, 2018 at 15:02
  • \$\begingroup\$ @Dan You can use a setInterval for your logic function and a requestAnimationFrame for your drawing function. Or you could just forget about the above answer and just schedule a "drop current tetromino by one cell" function with setInterval with the correct amount of ms for the current level and trust the JavaScript engine with the scheduling. But note that most browsers do not give you very good accuracy guarantees for setInterval. \$\endgroup\$
    – Philipp
    Jun 16, 2018 at 15:21
  • \$\begingroup\$ setInterval might work, but for simplicity and compatibility's sake, I think I will just continue with the bug of having impossible moves at the two highest levels. It's either that, use a different speed curve. I read that older versions of Tetris (like the nes) couldn't move pieces more than one row at a time. Perhaps they had similar constraints? "Older games cannot move the tetromino down more than one cell per frame (60 cells per second)." harddrop.com/wiki/Drop#Gravity \$\endgroup\$
    – Dan
    Jun 16, 2018 at 15:38
  • \$\begingroup\$ @Dan Older hardware like the NES had no floating point operations, so doing things by non-integer intervals was cumbersome and unperformant. \$\endgroup\$
    – Philipp
    Jun 16, 2018 at 17:49
1
\$\begingroup\$

"For example, at level 2: 1 / 0.021017 = 47.5805300471. So every 47.5805300471 frames the game is supposed to drop the piece one row."

I think it works more like the following (every frame):

  1. increase timer by gravity (0.021017)
  2. go to step 4
  3. move the tetrimino down and decrease timer by 1
  4. if timer is equal to or larger than 1, go to step 3, otherwise finish

As for the impossible placement, it is indeed impossible.

\$\endgroup\$
0
\$\begingroup\$

In addition to Philipps answer:

Most programming Environments offer really precise timers. If you save the exact moment of a keypress you can still handle actions made in between frames as if you where handling them real time.

Suppose your tetromino moved 2 Blocks per second and you had a framerate of 1fps. If the input occured until 500ms after the last frame you will make the calculations as if the tetromino hadn't moved. Anything after 500ms you will handle it as if it had already moved 1 block. Using timers you cane have high precision input even on low framerates.

This might require a bit of fiddling for it to behave the way you feel like it should.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .