Home > Game Development Walkthroughs > Tetris Aside: Coding for T-Spins

Tetris Aside: Coding for T-Spins

October 13, 2012 Leave a comment Go to comments

(the source code and EXE file for this update of SimpleTetris can be found at the end of the article)

One of my Tetris-playing colleagues pointed out to me recently that T-spins do not work in my implementation of SimpleTetris. Although I hadn’t checked it explicitly, I was quite surprised by this as I knew the code I had written should support them just fine.

A nice double t-spin opener.

The orange-coloured T-piece here will fall 2 more rows, at which point it can be rotated such that it will complete the bottom 2 rows. This ‘impossible’ rotation of T-pieces where in real life the piece would be locked into place is the premise of the T-spin.

For those not in the know, the Tetris Wikia T-spin page has some excellent explanations and diagrams of what T-spins are; namely, when you rotate a T-shaped Tetris piece in a way that would be impossible in real life (due to the corners being obstructed by other blocks) such that it fits snugly into a T or notch-shaped hole (see image).


Since the collision detection code that executes in SimpleTetris when a piece is rotated merely checks if the rotated piece is touching anything already in the bucket – crucially, not caring about whether the rotation was actually possible in the first place or not – this should have just worked, but it doesn’t. The first problem can be found in the actual data definition of the T-piece:

// T-piece
{
	{ 0, 0, 0, 0 },
	{ 0, 1, 0, 0 },
	{ 1, 1, 1, 0 },
	{ 0, 0, 0, 0 }
},
{
	{ 0, 0, 0, 0 },
	{ 0, 1, 0, 0 },
	{ 0, 1, 1, 0 },
	{ 0, 1, 0, 0 }
},
{
	{ 0, 0, 0, 0 },
	{ 1, 1, 1, 0 },
	{ 0, 1, 0, 0 },
	{ 0, 0, 0, 0 }
},
{
	{ 0, 1, 0, 0 },
	{ 1, 1, 0, 0 },
	{ 0, 1, 0, 0 },
	{ 0, 0, 0, 0 }
}

This is part of the array which contains the basic layouts of all the tetriminos in each of their possible rotation states. For T-spins to work, the square which connects the three corner squares should always remain in the same place when the rotation is performed (ie. it should be the center of rotation). This is so that when the player rotates a T-piece, only the three corners of the T move, with the other square which connects them remaining at the same point on the board. In the definitions of T-piece rotations above, the bottom two rotation states have a different center of rotation to the top two. This is easily fixed by moving a few lines around:

// T-piece
{
	{ 0, 0, 0, 0 },
	{ 0, 1, 0, 0 },
	{ 1, 1, 1, 0 },
	{ 0, 0, 0, 0 }
},
{
	{ 0, 0, 0, 0 },
	{ 0, 1, 0, 0 },
	{ 0, 1, 1, 0 },
	{ 0, 1, 0, 0 }
},
{
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 1, 1, 1, 0 },
	{ 0, 1, 0, 0 }
},
{
	{ 0, 0, 0, 0 },
	{ 0, 1, 0, 0 },
	{ 1, 1, 0, 0 },
	{ 0, 1, 0, 0 }
}

All we’ve done here is shift the bottom row to the top row in the 3rd and 4th rotations, such that the non-corner square of the T-piece now always occupies the same point in the 4×4 grid and is thus the center of rotation in all cases.

T-spins will now in general work without any actual changes to the game code or logic, however there is a problem when the T-piece is touching the bottom of the bucket which must be addressed.

Updating the bucket logic

In the code where we rotate a tetrimino, we apply the following logic:

  1. Save the tetrimino’s current position and rotation
  2. Determine whether the tetrimino is currently touching any of the four sides of the bucket and store this information
  3. Move to the next rotation in the sequence
  4. If the tetrimino was touching a bucket edge before rotation (step 2), move the rotated piece such that it is aligned with each edge that it was touching before rotation. For example, if the piece was touching the bottom and left of the bucket before rotation, re-align it after rotation so that it is still touching the bottom and left of the bucket. Example: an I-piece is vertical at the right-hand edge of the bucket. You rotate it. It is now horizontal and sticking out of the right-hand side. We move it left so that the I-piece’s right-hand edge is at the right-hand edge of the bucket. This step is for two reasons: 1. to prevent pieces from becoming partially outside the bucket, and 2. to stop pieces from moving up as a result of rotation at the bottom of the bucket when the rotation of a piece would leave one or more empty rows underneath it that were occupied in the previous rotation (otherwise, the player could keep rotating L-pieces forever, for example)
  5. If the tetrimino is now in collision with another block already in the bucket (meaning that it is occupying one or more squares in the bucket that are already occupied), the rotation is disallowed and the previous position and rotation restored. Otherwise the rotation has succeeded

It is in step 4 where the problem occurs: if a T-piece has been rotated such that its flat side is vertical and its left or right corner is touching the bottom of the bucket, and is then rotated once such that its flat side is now facing the bottom, the re-alignment code will push the T-piece down one row: it was touching the bottom of the bucket before rotation, so the re-alignment code makes the flat side of the T-piece touch the bottom of the bucket after rotation. This changes the center of rotation for the T-piece for this one particular rotation scenario, and will ultimately prevent the rotation altogether as it will cause a collision with other, already-placed tetriminos. As a result, T-spin when the T-piece is touching the bottom row becomes impossible.

There are different solutions to this: the re-alignment code could be changed not to force bucket-bottom alignment (at the cost of the minor exploit mentioned above), it could be changed to only re-align tetriminos when their rotation actually causes them to become partially outside the bucket rather than based on the pre-rotation alignment state, T-pieces could be flagged as such in code and the re-alignment code disabled just for these, or we can use the following solution – a simple fudge:

// This is the code for step 5 above
if (bucket->IsCollision(this))
{
	bool resetPiece = true;

	// Fudge to allow T-spins when the T-piece is touching the bottom row
	if (atBottom)
	{
		PosY--;
		resetPiece = bucket->IsCollision(this);
	}

	if (resetPiece)
	{
		PosX = previousX;
		PosY = previousY;
		CurrentRotation = previousRotation;
	}
}

Instead of immediately abandoning the rotation attempt if there is a collision with an existing piece in the bucket, we instead first check if the colliding piece is aligned with the bottom of the bucket. If not, the rotation fails and the piece reset to its previous position and rotation as before. However, if it is aligned with the bottom, we give it another chance: first we move it up one row, then check for collisions again. For T-pieces, this effectively (in a clunky way) reverses the alignment correction when the flat side is touching the bottom, and restores the center of rotation to its original point. T-spin locks will now work as expected.

Detecting T-spins

Note that – in contrast to the T-spin documentation on Wikia – we are not electing to specifically recognize when a T-spin has cleared a line, or two lines (a so-called “T-spin double”), or give any kind of points bonus for this, although it is fairly simple to add. Similarly, we currently allow the same “impossible” rotations with any piece; you can for example pull off a tricky “L-spin” too.

If you want to award points for a T-spin, T-spin double and/or prevent impossible rotations for non-T-pieces, the solution is the same in all cases: add a boolean flag to each shape, true if it is the T-piece, false otherwise, only allow the additional bucket logic code to execute if the flag is true, and when a line is cleared, if the flag of the last fallen piece is true (the line was cleared by a T-piece), check the piece’s mobility to the left, right and up: if none are possible, it’s a T-spin lock and you can award bonus points (another way to check for a T-spin lock is to see if at least 3 of the 4 corners diagonal to the center of the T-piece are occupied when the piece comes to rest).

Endnotes

See Wikipedia’s Tetromino page for more information about Tetris shapes and terminology.

See Wikia’s Tetris T-spin page for more information about T-spins.

See Wikia’s Tetris Guideline page for the current (revised 2006) guideline rules for making Tetris clones behave in a manner compliant with The Tetris Company’s internal guidelines (note that SimpleTetris is not fully compliant at this time).

See Squidoo: How To Become A Professional At Tetris for Tetris strategy.

Thanks to Anders Heimsvik for pointing out that T-spins weren’t working in SimpleTetris.

Download

Here is a version of SimpleTetris with T-spins working as outlined above:

Download: Source code (.cpp) | Source code (.h) | Executable

Advertisement
  1. Lorenzo
    December 13, 2017 at 18:50

    Thanks! Really cool code!

  2. Nathan
    February 7, 2019 at 12:24

    “if the flag of the last fallen piece is true (the line was cleared by a T-piece), check the piece’s mobility to the left, right and up: if none are possible, it’s a T-spin lock and you can award bonus points”

    That is not really a conclusive test. It would give a false positive in this situation:
    https://imgur.com/a/JdAK6Fp

    • Nathan
      February 7, 2019 at 12:29

      Eh, nevermind. Reading the specifications closer, some games simply conclude t-spin by immobility like you suggested.

  1. No trackbacks yet.

Share your thoughts! Note: to post source code, enclose it in [code lang=...] [/code] tags. Valid values for 'lang' are cpp, csharp, xml, javascript, php etc. To post compiler errors or other text that is best read monospaced, use 'text' as the value for lang.

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: