When you take a look at the well-known Tetris game you might think it should be very easy to code. But this is not the case. You will find some sources on the net, and all of them prove it is far from being easy, as they are using zillion lines of code.
Actually, I started analysing the matter, and I am not in the mood of finishing it. But you are welcome to do so and have a unique Tetris version of your own.
Where comes Math into TetrisThere are two main points where math comes into the play. One of them is rotating objects by angles of +/- 90 degrees. The other one is checking if a move is legal. To enable both, three areas had to be declared in two-dimensional arrays of boolean values:
- the complete area of little squares (theWorld[][])
- the area where the falling shape sits (acual[][])
- the area where it is going to be when the move is performed (next[][])
Initally, theWorld will consist of just the surrounding frame, i.e. the border. You might object this is a waste of precious RAM space, I sadly must agree.
Rotating ObjectsBefore you start rotating you need to know the pivot point which is the center of the rotation. Usually you take the "center of mass" as pivot point. The problem is as you always have four elements to rotate this center will never be located at integer coordinates. So you cannot prevent to make some error. Calculating the center of mass the standard way:
byte xm = 0;
byte ym = 0;
float n = 0; // so no integer division will be performed
for (byte z = 1; z < rows; z++)
for (byte s = 1; s < cols; s++)
if (actual[z][s]) {
xm = xm + s;
ym = ym + z;
n++;
}
xm = xm / n + 0.49; // for a better rounding
ym = ym / n + 0.49;
Once you know the pivot, rotation can be done in the standard way (note that "r" equals either +1 or -1):
for (byte z = 1; z < rows; z++)
for (byte s = 1; s < cols; s++) {
byte xa = xm - r * (z - ym);
byte ya = ym + r * (s - xm);
if (q(0, xa, cols) && q(0, ya, rows))
next[z][s] = actual[ya][xa];
else
next[z][s] = 0;
}
As
ARDUINO has no "between" function I implemented one of my own for better readability:
boolean q(int x, int y, int z) {
// true if x < y < z:
if (x >= y) return false;
if (y >= z) return false;
return true;
}
Checking for legal movesAfter calculating the area where the "next" will reside in you need to check for any collisions. As all areas were declared as "boolean" you can easily use the "and" function for "next" and "theWorld". If any location gives a "true" result, this move cannot be performed, otherwise "next" can be copied to "actual".
boolean bump = false;
byte cnt = 0;
// check for any bumps:
for (byte z = 0; z < rows; z++)
for (byte s = 0; s < cols; s++) {
cnt = cnt + next[z][s];
if (theWorld[z][s] and next[z][s]) bump = true;
}
if (cnt != 4) bump = true;
It also checks if you have "lost" any elements as they were outside the frame. If you encounter a collision after a "DOWN" move, the "actual" will be added to "theWorld", and a new object will be dropped.
Comments