Tetris Pieces Rotation

Started by
8 comments, last by Suleyman 7 years, 11 months ago
Hello everyone!
I am currently at the last stage of the Tetris Clone using SDL library, the only thing left is rotation. For each piece I have a separate class, and in order to move the pieces down I use a method:

void PieceZ::movePieceDown()
    {
      drawBlock(x1,y1++);
      drawBlock(x2,y3++);
      drawBlock(x3,y3++);
      drawBlock(x4,y4++);
    }
This method is called like that: current->movePieceDown(); where current represents a pointer to the current piece. Now to rotate the piece I have another method:

 void PieceZ::rotatePiece()
    {
      drawBlock(newX1,newY2++);
      drawBlock(x2,y2);
      drawBlock(newX3,newY3++);
      drawBlock(newX4,newY4++);
    }
New X and Y coordinates are calculated elsewhere, with x2 and y2 taken as the origin.
My question is how do I swap current->movePieceDown() with current->rotatePiece() while the piece is moving? Taking into acount that I am handling keyboard events in another method.
Thank you very much in advance!!!
Advertisement

Mostly


if (mustRotate) {
    current->rotatePiece();
} else {
    current->movePieceDown();
}

however, I would suggest you don't move the piece as a side effect as drawing.

It' much easier to understand and debug if you make 3 routines: draw, movedown, and rotate.

(eg, if you debug your code now, then what you see on the screen is different from what you have in variables)

An alternative is to have a single x/y position for the entire piece (eg one of the blocks), and a single "orientation" variable.

Drawing then draws a number of blocks relative to the single x/y position, depending on the orientation.

Edit: Decoupling rotating from drawing also helps with your problem, as in a decoupled situation, you can just rotate the piece anywhere, and as often as you like.

Mostly


if (mustRotate) {
    current->rotatePiece();
} else {
    current->movePieceDown();
}

however, I would suggest you don't move the piece as a side effect as drawing.

It' much easier to understand and debug if you make 3 routines: draw, movedown, and rotate.

(eg, if you debug your code now, then what you see on the screen is different from what you have in variables)

An alternative is to have a single x/y position for the entire piece (eg one of the blocks), and a single "orientation" variable.

Drawing then draws a number of blocks relative to the single x/y position, depending on the orientation.

Edit: Decoupling rotating from drawing also helps with your problem, as in a decoupled situation, you can just rotate the piece anywhere, and as often as you like.

Thank you very much for your answer! Initially I had a method drawPiece, but I removed it. If I understood correctly, in order to find the solution I have to temporarily replace movePieceDown with drawPiece which will help me understand the problem. Is that right?

You now have rotation or movement coupled with drawing. That means

a. You cannot draw a piece two times at the same place. Eg "show the next piece" is non-trivial.

b. You use post-increment, so you draw a block at (x1, y1), and internally its position is (x1, y1+1). That's confusing during debugging. (could be solved by doingpre-increments if you want).

c. Movement down must be done at drawing.

d. Rotation must be done at drawing.

The result of the latter two is your question, I think. "How to handle rotation while drawing, since I cannot rotate a piece without drawing it". (To see, answer the question "why can't I rotate the piece in the keyboard handler?" for yourself, another one to ponder about is "would that be better than I have now?" (this is a difficult one).)

There are 2 ways out.

1. Move the keyboard information to the drawing routine (your question and my first answer).

2. Decouple rotation from drawing, so you can rotate without drawing it. (ie make it possible to rotate in the keyboard handler.)

In the second item, if you can rotate without drawing, it makes sense to also remove the move-down side-effect from the drawing code as well, as it increases orthogonality and predictability of the functions.

Trying it is often a good strategy if you cannot vision whether something works, or how. The only downside is that it costs time, but that may not be a big problem.

You have to decouple your transformation functions. Each GameObject(or tetris piece in this case) should have a list of functions to do different things. IE.


tetrisPiece->Fall();//Move down each required tick
tetrisPiece->Rotate(float rads)//Rotate rads Radians which can be called in your keyboard handler
tetricPiece->Translate(float deltaX, float deltaY, float deltaZ)//Translate the object. If it is in 2d space, ignore the z value

There are plenty other options and methods to write and view this. The point being, a function should be designed to accomplish one goal or task. A single function that accomplishes more than one task is inherently a bad design.

Remove your x and y parameters from your draw function and remove all transformation logic from your draw function and instead put it into the above named functions instead.

You now have rotation or movement coupled with drawing. That means

a. You cannot draw a piece two times at the same place. Eg "show the next piece" is non-trivial.

b. You use post-increment, so you draw a block at (x1, y1), and internally its position is (x1, y1+1). That's confusing during debugging. (could be solved by doingpre-increments if you want).

c. Movement down must be done at drawing.

d. Rotation must be done at drawing.

The result of the latter two is your question, I think. "How to handle rotation while drawing, since I cannot rotate a piece without drawing it". (To see, answer the question "why can't I rotate the piece in the keyboard handler?" for yourself, another one to ponder about is "would that be better than I have now?" (this is a difficult one).)

There are 2 ways out.

1. Move the keyboard information to the drawing routine (your question and my first answer).

2. Decouple rotation from drawing, so you can rotate without drawing it. (ie make it possible to rotate in the keyboard handler.)

In the second item, if you can rotate without drawing, it makes sense to also remove the move-down side-effect from the drawing code as well, as it increases orthogonality and predictability of the functions.

Trying it is often a good strategy if you cannot vision whether something works, or how. The only downside is that it costs time, but that may not be a big problem.

Thank you for such a detailed answer, everything is clear, I understood your remarks, I will restructure my program so there is more delegation :) Thank you for your time!!!

You have to decouple your transformation functions. Each GameObject(or tetris piece in this case) should have a list of functions to do different things. IE.


tetrisPiece->Fall();//Move down each required tick
tetrisPiece->Rotate(float rads)//Rotate rads Radians which can be called in your keyboard handler
tetricPiece->Translate(float deltaX, float deltaY, float deltaZ)//Translate the object. If it is in 2d space, ignore the z value

There are plenty other options and methods to write and view this. The point being, a function should be designed to accomplish one goal or task. A single function that accomplishes more than one task is inherently a bad design.

Remove your x and y parameters from your draw function and remove all transformation logic from your draw function and instead put it into the above named functions instead.

Thank you very much, I always thought that groupping several operations in one is always good. Thank you for your remark, now I have to go and rewrite my code! :) Thank you!

You have to decouple your transformation functions. Each GameObject(or tetris piece in this case) should have a list of functions to do different things. IE.


tetrisPiece->Fall();//Move down each required tick
tetrisPiece->Rotate(float rads)//Rotate rads Radians which can be called in your keyboard handler
tetricPiece->Translate(float deltaX, float deltaY, float deltaZ)//Translate the object. If it is in 2d space, ignore the z value

There are plenty other options and methods to write and view this. The point being, a function should be designed to accomplish one goal or task. A single function that accomplishes more than one task is inherently a bad design.

Remove your x and y parameters from your draw function and remove all transformation logic from your draw function and instead put it into the above named functions instead.

You now have rotation or movement coupled with drawing. That means

a. You cannot draw a piece two times at the same place. Eg "show the next piece" is non-trivial.

b. You use post-increment, so you draw a block at (x1, y1), and internally its position is (x1, y1+1). That's confusing during debugging. (could be solved by doingpre-increments if you want).

c. Movement down must be done at drawing.

d. Rotation must be done at drawing.

The result of the latter two is your question, I think. "How to handle rotation while drawing, since I cannot rotate a piece without drawing it". (To see, answer the question "why can't I rotate the piece in the keyboard handler?" for yourself, another one to ponder about is "would that be better than I have now?" (this is a difficult one).)

There are 2 ways out.

1. Move the keyboard information to the drawing routine (your question and my first answer).

2. Decouple rotation from drawing, so you can rotate without drawing it. (ie make it possible to rotate in the keyboard handler.)

In the second item, if you can rotate without drawing, it makes sense to also remove the move-down side-effect from the drawing code as well, as it increases orthogonality and predictability of the functions.

Trying it is often a good strategy if you cannot vision whether something works, or how. The only downside is that it costs time, but that may not be a big problem.

Hey guys, based on your remarks and thanks to you I found a solution and wanted to post it in case it helps someone:

Basically I removed the method rotatePiece(), instead of that I have a method that calculates the rotation independently of drawing:


 void PieceZ::getRotation()
    {
      newX1 = (y1-y2) + x2;
      newY1 = (x2-x1) + y2;

      newX3 = (y3-y2) + x2;
      newY3 = (x2-x3) + y2;

      newX4 = (y4-y2) + x2;
      newY4 = (x2-x4) + y2;

      x1 = newX1;
      x3 = newX3;
      x4 = newX4;

      y1 = newY1;
      y3 = newY3;
      y4 = newY4;
    }

new values are set to 0 initially, and every time a user presses a button on the keyboard this method gets called, it calculates the new rotation in new and assigns it to x and y values. Which gives you a 90 degree counter clockwise rotation. Thank you again for the help guys!


void PieceZ::getRotation()
    {
      int newX1 = (y1-y2) + x2;
      int newY1 = (x2-x1) + y2;

      int newX3 = (y3-y2) + x2;
      int newY3 = (x2-x3) + y2;

      int newX4 = (y4-y2) + x2;
      int newY4 = (x2-x4) + y2;

      x1 = newX1;
      y1 = newY1;

      x3 = newX3;
      y3 = newY3;

      x4 = newX4;
      y4 = newY4;
    }

The "new*" variables are locally used (I hope, or that would be need some discussion too), no need to define them in your class, just define them here, by prefixing them with their type (ie "int" I assumed). Always try to keep variables as local as possible to avoid surprises.

Secondly, you organized computing new values on the i-th coordinate, it makes sense to also assign values back in that way, grouping the assignments on i-th point.

That makes it simpler to understand, and extend (ie if you need to add a x5/y5 at some later time).


void PieceZ::getRotation()
    {
      int newX1 = (y1-y2) + x2;
      int newY1 = (x2-x1) + y2;

      int newX3 = (y3-y2) + x2;
      int newY3 = (x2-x3) + y2;

      int newX4 = (y4-y2) + x2;
      int newY4 = (x2-x4) + y2;

      x1 = newX1;
      y1 = newY1;

      x3 = newX3;
      y3 = newY3;

      x4 = newX4;
      y4 = newY4;
    }

The "new*" variables are locally used (I hope, or that would be need some discussion too), no need to define them in your class, just define them here, by prefixing them with their type (ie "int" I assumed). Always try to keep variables as local as possible to avoid surprises.

Secondly, you organized computing new values on the i-th coordinate, it makes sense to also assign values back in that way, grouping the assignments on i-th point.

That makes it simpler to understand, and extend (ie if you need to add a x5/y5 at some later time).

You are right, there is no reason for "new" values to be global. I will also add the i's to make it simpler. Thank you very much for your help and for a little programming logic lesson! Have a nice day!

This topic is closed to new replies.

Advertisement