This is generally the most difficult part of the project:
in response to up-arrow key presses, we should rotate the falling piece 90
degrees counterclockwise. We do this in the same way we handled other
changes to the falling piece: we make the change, test if it is legal,
and if not, we unmake the change.
Explaining rotateFallingPiece:
As noted, this method is similar to moveFallingPiece, in that it makes the
rotation and then calls fallingPieceIsLegal (the same method used by
moveFallingPiece) and undoes any illegal changes. As for the actual
rotation, this is accomplished by changing the two-dimensional array of
booleans that represent the falling piece. A new 2d array is created,
and the old array is mapped to the new array according to a 90-degree
counterclockwise rotation. To see how this works, consider this
picture, which shows a grid that is rotated counterclockwise (the corners
are highlighted to make the rotation clear):
[To avoid any confusion, note that it would not be possible to generate
these particular boards during an actual Tetris game.]
First, we see that the dimensions reverse: in this example, the old
grid was 7 rows by 10 columns, whereas the new grid is 10 rows by 7 columns.
Next, consider what happens as we move in the old grid from red to green
(that is, moving downward with rows increasing from 0 to 6): this maps
in the new grid to moving across with columns increasing from 0 to 6 .
Thus, our new column is equal to our old row. That is the
easier dimension. Now consider the other dimension, as we move in the
old grid from red to white (that is, moving across with columns increasing
from 0 to 9: this maps in the new grid to moving up with rows
decreasing from 9 to 0. Thus, our new row is equal to (9
minus our old column). More generally, we replace "9" with "one
less than the number of columns".
Writing rotateFallingPiece:
Following the plan just described, we start by storing the old piece (the
2-d array of booleans), its location, and its dimensions in local variables.
Next, we compute the new dimensions, by reversing the old dimensions.
Next, we compute the new location. Our goal is to keep the center of
the falling piece constant (or, given that this is not possible if we have
an even number of rows or columns, to keep the center as constant as possible).
Keeping the center of the falling piece constant during rotation is the
most difficult part of Tetris, so read this part very carefully (though
that is always good advice!). Besides making rotation more intuitive, we
want to keep the center constant so that if we rotate around and around, the
center does not "drift"
-- a full 360 degree turn should bring us back to where we started. To
meet these conditions, we observe that we just need to adjust the left
column and top row of the falling piece by subtracting half of the
change in the size of each dimension that results from each turn, where
the change in the size of each dimension equals the difference between the
number of rows and columns (though you have to think about whether this
difference should be positive or negative). Read and re-read the
preceding paragraph. Draw pictures. Make sense out of it.
When you finally convert it into Java, you will find there are two simple
lines that make rotation-about-a-fixed-center work:
fallingPieceRow -=
<something>;
fallingPieceCol -= <something else>;
The preceding 2 lines are simple to write (once you figure out what
goes on the right-hand side), but tricky to confirm, and
even trickier to come up with independently. Anyhow, now that we have
the new location and dimensions, we create an entirely new piece (that is, a
new 2d array of booleans) and load it with a rotation of the old piece
according to the algorithm described above, and set fallingPiece equal to
this new 2d array.
Finally, we check if this rotation makes the falling piece go off the
board or collide with a non-empty cell on the board (simply reusing our code
from the previous steps), and if either of these conditions occurs, we
restore the piece, its location, and its dimensions to their original
values.
Updating keyPressed:
We modify the keyPressed handler to call rotateFallingPiece in response to
an up-arrow key press.
Testing the code
Hint: Remember to press the up-arrow key to rotate the falling piece
(and try to move it off the board!), and to press some non-arrow keys to
start with new falling pieces to test the code! Verify that the piece
rotates counterclockwise, that the center basically stays fixed, and in
particular that 360 degree turns result in no change to the falling piece.
David Kosbie |