Jump to content
  • Advertisement
Sign in to follow this  

Rotating Tetris blocks

This topic is 4260 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

This is one of my many attempts at making something other than a pong or scrolling shooter (i.e. Galaga) type game. What I have is a 32x32 texture of a single block. To make the shapes, I have two arrays for X and Y positions. I create the blocks like so:
private void setupBlock()
{
    blockX[0] = blockStartX;              //This is 100
    blockY[0] = blockStartY;              //This is 100
    blockX[1] = blockX[0] + block.Width;  //This is 132
    blockY[1] = blockY[0];                //This is 100
    blockX[2] = blockX[0];                //This is 100
    blockY[2] = blockY[0] + block.Height; //This is 132
    blockX[3] = blockX[0] - block.Width;  //This is 68
    blockY[3] = blockY[0] + block.Height; //This is 132
}

That sets up an "S" shaped block. As of right now, I was just doing some tinkering around, so that is the only block that is drawn at the moment [wink] In the rendering loop it draws each block at each X and Y coordinate in the respective arrays. I've got left, right, and down movements, but what gets me is how to rotate the blocks. I can't seem to wrap my head around it. Maybe the way I'm drawing/creating the blocks in the first place is a kind of roundabout way to do it and I'm making things harder for myself. What I want to do is have one block kind of act like a pivot point the other blocks move around. I thought about just making a texture for each block shape, but figured that might be a lot of extra work when clearing lines. Any tips or insight would be very much appreciated. I've been coding for about 7 years, but never any games (other than the ones mentioned above. I usually give up when I get over my head [rolleyes] but I don't really want to give up on this) Thanks, Bill

Share this post


Link to post
Share on other sites
Advertisement
So I found this:
http://www.gamedev.net/community/forums/topic.asp?topic_id=192483

And I do believe I will take a look.

Guess I should check stickies [wink]

Share this post


Link to post
Share on other sites
Here's how I did it... thought a 4x4 would have been better, I did this so that there was a center... Probably doing an offset on the location after doing certain rotations would solve the problem.

/*********************************************************\
| Creator: Michael Hamilton, Dec 22 2005 |
| Project Info: ASCII Tetris |
| Notes: Feel free to redistribute, keep this note intact |
| with the original creator information, but do whatever |
| you want with this code just don't claim ownership of |
| the code base. |
| http://www.mutedvision.net maxmike@gmail.com |
\*********************************************************/


#include <cstdlib>
#include <ctime>
#include <iostream>
#include <string>
#include <conio.h>

using namespace std;

static clock_t start = (clock_t) 0;

/* Reset the timer */
void timer_reset(void)
{
start = clock();
}

/* Compute elapsed time in seconds */
double timer_elapsed(void)
{
return (double)(clock() - start) / CLOCKS_PER_SEC;
}


class Shapes{
public:
Shapes(){
char tmpallShapes[7][5][5] = {
' ',' ',' ',' ',' ',
' ',' ',' ',' ',' ',
' ','*','*','*',' ',
' ',' ','*',' ',' ',
' ',' ',' ',' ',' ',

' ',' ',' ',' ',' ',
' ',' ','*',' ',' ',
' ',' ','*',' ',' ',
' ',' ','*',' ',' ',
' ',' ','*',' ',' ',

' ',' ',' ',' ',' ',
' ',' ','*',' ',' ',
' ',' ','*',' ',' ',
' ','*','*',' ',' ',
' ',' ',' ',' ',' ',

' ',' ',' ',' ',' ',
' ',' ','*',' ',' ',
' ',' ','*',' ',' ',
' ',' ','*','*',' ',
' ',' ',' ',' ',' ',

' ',' ',' ',' ',' ',
' ',' ',' ',' ',' ',
' ','*','*',' ',' ',
' ','*','*',' ',' ',
' ',' ',' ',' ',' ',

' ',' ',' ',' ',' ',
' ','*','*',' ',' ',
' ',' ','*','*',' ',
' ',' ',' ',' ',' ',
' ',' ',' ',' ',' ',

' ',' ',' ',' ',' ',
' ',' ','*','*',' ',
' ','*','*',' ',' ',
' ',' ',' ',' ',' ',
' ',' ',' ',' ',' '
};
for(int i = 0;i < 7; i++){
for(int j = 0;j < 5; j++){
for(int k = 0;k < 5; k++){
allShapes[j][k] = tmpallShapes[j][k];
}
}
}
}
char allShapes[7][5][5];
};

int randomNum(int max){
return ((double)rand() / ((double)(max)+(double)(1)) );
}

class Tower;

class TetrisBlock:public Shapes{
public:
TetrisBlock(Tower *tow){srand((unsigned)time( NULL ));
nextUp = rand() % (7); x = 3; y = -1; tower = tow;
setBlock(rand() % (7));
}
~TetrisBlock(){}

void setBlock(int i){
if(i>4){
i = i;
}
for(int j = 0;j < 5; j++){
for(int k = 0;k < 5; k++){
shape[j][k] = allShapes[nextUp][j][k];
}
}
nextUp = i;
x = 5; y = -1;
}
void rotateLeft();

void drop();

void moveLeft();
void moveRight();
char shape[5][5];
int x, y;
int nextUp;
Tower *tower;
};

class Tower{
public:
Tower(){
for(int i = 0;i<15;i++){
for(int j = 0;j<20;j++){
bucket[j] = ' ';
}
}
score = 0;
}
~Tower(){}
char nextBlockImg;
void checkLines(){
bool fail = 0;
int totalCleared = 0;
int linesCleared[20];
char tmpLine[15];
char tmpBucket[15][20];
for(int i = 0;i<15;i++){
for(int j = 0;j<20;j++){
tmpBucket[j] = ' ';
}
}
for(int j = 0;j < 20;j++){
fail = 0;
for(int i = 0;i< 15;i++){
if(bucket[j]!='*'){
fail = 1;
tmpLine = ' ';
}else{
tmpLine = '*';
}
}
if(!fail){
linesCleared[j] = 1;
totalCleared++;
}else{
linesCleared[j] = 0;
}
}
if(totalCleared > 0){
for(int p = 178;p>175;p--){
system("CLS");
cout << "_________________"<< "Next: "<< nextBlockImg <<endl;
for(int j = 0;j<20;j++){
cout << "|";
if(linesCleared[j] == 1){
for(int i = 0;i<15;i++){
cout << char(p);
if(totalCleared==4 && i == 3){
i = 10;
cout << "TETRIS!";
}
}
}else{
for(int i = 0;i<15;i++){
cout << bucket[j];
}
}
cout << "|";
if(j == 0){
cout << "Score: " << score << " Level: " << int(score/100)+1;
}
cout << endl;
}
cout << "_________________"<<endl;
timer_reset();
while(timer_elapsed()<.5){;}
}
int offset = 0;
for(int j = 19;j>=0;j--){
if(linesCleared[j]){
offset++;
}else{
for(int i = 0;i<15;i++){
tmpBucket[j+offset] = bucket[j];
}
}
}
/*for(int j = 19-offset;j>=0;j--){
for(int i = 0;i<15;i++){
tmpBucket[j] = ' ';
}
}*/

for(int i = 0;i<15;i++){
for(int j = 0;j<20;j++){
bucket[j] = tmpBucket[j];
}
}
score += (totalCleared*10);
}
}

void add(TetrisBlock *toAdd){
for(int i = 0;i<5;i++){
for(int j = 0;j<5;j++){
if(toAdd->shape[j]=='*'){
bucket[toAdd->x+i][toAdd->y+j] = toAdd->shape[j];
}
}
}
checkLines();
if(toAdd->y < 1){
system("CLS");
cout << "You have been defeated" << endl << " Score: " << score << " Level: " << int(score/100)+1;
cout << endl << endl;
cout << "Bonus:" << endl;
switch(int((score/100)+1)){
case 0:
cout << "Noob";
break;
case 1:
cout << "Novice";
break;
case 2:
cout << "Brick Layer";
break;
case 3:
cout << "Puzzler";
break;
case 4:
cout << "Tetris";
break;
case 5:
cout << "Advanced";
break;
case 6:
cout << "Advanced Brick Layer";
break;
case 7:
cout << "Advanced Puzzler";
break;
case 8:
cout << "Advanced Tetris";
break;
case 9:
cout << "Expert";
break;
case 10:
cout << "Tetris Master";
break;
default:
cout << "Tetris God";
}
cout << endl << endl;
system("PAUSE");
exit(1);
}
}

void display(TetrisBlock *toDisplay){
bool disp;
char tmpBucket[15][20];

for(int i = 0;i<5;i++){
for(int j = 0;j<5;j++){
if(toDisplay->shape[j]=='*'&&toDisplay->x+i>=0&&toDisplay->x+i<15&&toDisplay->y+j>=0&&toDisplay->y+j<20){
tmpBucket[toDisplay->x+i][toDisplay->y+j] = toDisplay->shape[j];
}
}
}
switch(toDisplay->nextUp){
case 0:
nextBlockImg = 194;
break;
case 1:
nextBlockImg = 179;
break;
case 2:
nextBlockImg = 192;
break;
case 3:
nextBlockImg = 217;
break;
case 4:
nextBlockImg = 254;
break;
case 5:
nextBlockImg = 's';
break;
case 6:
nextBlockImg = 'z';
break;
}
cout << "_________________"<< "Next: "<< nextBlockImg <<endl;
for(int j = 0;j<20;j++){
cout << "|";
for(int i = 0;i<15;i++){
disp = 1;
if(tmpBucket[j]=='*'){
cout << tmpBucket[j];
disp = 0;
}
if(disp){
cout << bucket[j];
}
}
cout << "|";
if(j == 0){
cout << "Score: " << score << " Level: " << int(score/100)+1;
}
cout << endl;
}
cout << "_________________"<<endl;
}

char bucket[15][20];
double score;
};

void TetrisBlock::drop(){
bool dropit = 1;
for(int i = 0;i<5;i++){
for(int j = 0;j<5;j++){
if(shape[j]=='*'){
if(tower->bucket[i+x][j+y+1]=='*'||((j+y+1)>=20)){
tower->add(this);
setBlock(rand() % (7));
dropit = 0;
}
}
}
}
if(dropit){y++;}
}

void TetrisBlock::moveRight(){
bool move = 1;
for(int i = 0;i<5;i++){
for(int j = 0;j<5;j++){
if(shape[j]=='*'){
if(tower->bucket[i+x+1][j+y]=='*' || (i+x+1)>14){
move = 0;
}
}
}
}
if(move){x++;}
}

void TetrisBlock::moveLeft(){
bool move = 1;
for(int i = 0;i<5;i++){
for(int j = 0;j<5;j++){
if(shape[j]=='*'){
if(tower->bucket[i+x-1][j+y]=='*'|| (i+x-1)<0){
move = 0;
}
}
}
}
if(move){x--;}
}

void TetrisBlock::rotateLeft(){
bool rotate = 1;
char tmpShape[5][5];
for(int i = 0;i < 5;i++){
for(int j = 0;j<5;j++){
tmpShape[j] = shape[j][5-i];
}
}
for(int i = 0;i<5;i++){
for(int j = 0;j<5;j++){
if(tmpShape[j]=='*'){
if(tower->bucket[i+x][j+y]=='*'||i+x<0||i+x>14||j+y>19){
rotate = 0;
}
}
}
}
if(rotate){
for(int i = 0;i < 5;i++){
for(int j = 0;j<5;j++){
shape[j] = tmpShape[j];
}
}
}
}

int main(){
bool done = 0;
int i = 0;
char chr = ' ';
Tower tow;
bool dropped;
double droptime;
TetrisBlock curBlock(&tow);
while(!done){
timer_reset();
dropped = 0;
droptime = 1.00-(tow.score/1000);
if(droptime < .10){
droptime = .10;
}
while(timer_elapsed()<droptime){
if(kbhit()){
chr = getch();
}else{
chr = ' ';
}
switch(chr){
case 'a':
curBlock.moveLeft();
system("CLS");
tow.display(&curBlock);
break;
case 'd':
curBlock.moveRight();
system("CLS");
tow.display(&curBlock);
break;
case 's':
curBlock.drop();
system("CLS");
tow.display(&curBlock);
break;
case 'w':
curBlock.rotateLeft();
system("CLS");
tow.display(&curBlock);
break;
}
}
system("CLS");
curBlock.drop();
tow.display(&curBlock);
}
return 0;
}

Share this post


Link to post
Share on other sites
I had the same issue when trying to make a tetris game...here's my messy incomplete rotation code:


void Rotate(int piece, xx1, xx2, xx3, xx4, yy1, yy2, yy3, yy4, int * board)
{
int x1 = xx1, x2 = xx2, x3 = xx3, x4 = xx4, y1 = yy1, y2 = yy2, y3 = yy3, y4 = yy4;
switch(piece)
{
case 1:
if(y4 > y2)
{
if(board[x4+1][y4] == 0 && board[x4+1][y4+1] == 0 && board[x4+1][y4+2] == 0)
{
x2 += 1; y2 += 1; y1 += 2; x3 -= 1; y3 += 1;
}
}
else if(y2 < y1)
{
if(board[x2][y2-1] == 0 && board[x3+1][y3] == 0)
{
x1 -= 1 ; y1 -= 1; x3 -= 2; y3 -= 2; x4 +1= ; y4 -= 1;
}
}
else if(y4 < y2)
{
if(board[x2+1][y2] == 0 && board[x4][y4+-] == 0)
{
x2 += 1; y2 += 1; x1 += 1; y1 -= 1; x3 += 1; y3 -= 2;
}
}
else
{
if(board[x2][y2+1] == 0 && board[x4+1][y4] == 0)
{
x1 += 1; y1 += 1; x3 += 2; y3 += 2; y4 -= 1:
}
}
break;

case 2:
if(y4 > y3)
{
if(board[x2][y2+1] == 0 && board[x2][y2+2] == 0)
{
x1 -= 1; y1 += 1; y2 += 2; x3 -= 2; x4 -= 1; y4 -= 1;
}
}
else if(y2 > y1)
{
if(board[x1+1][y1] == 0 && board[x1+2][y1] == 0)
{
x4 -= 2; y3 += 1; x1 += 1; x2 += 2; y2 -= 1;
}
}
else if(y3 > y4)
{
if()
{
}
}
else()
{
if()
{
}
}
break;
case 3:
break;
case 4:
break;
case 5:
break;
case 6:
break;
}
if(InPlot(x1, x2, x3, x4, y1, y2, y3, y4))
{
xx1 = x1; xx2 = x2; xx3 = x3; xx4 = x4;
yy1 = y1; yy2 = y2; yy3 = y3; yy4 = y4;
}
}



This method works, but it's messy as hell and takes forever to think up.

Share this post


Link to post
Share on other sites
I guess if I were to just list the rotation code, it's very short (note: this is already posted, but is embedded in the program I posted above):


void TetrisBlock::rotateLeft(){
bool rotate = 1;
char tmpShape[5][5];
for(int i = 0;i < 5;i++){
for(int j = 0;j<5;j++){
tmpShape[j] = shape[j][5-i];
}
}
for(int i = 0;i<5;i++){
for(int j = 0;j<5;j++){
if(tmpShape[j]=='*'){
if(tower->bucket[i+x][j+y]=='*'||i+x<0||i+x>14||j+y>19){
rotate = 0;
}
}
}
}
if(rotate){
for(int i = 0;i < 5;i++){
for(int j = 0;j<5;j++){
shape[j] = tmpShape[j];
}
}
}
}

Share this post


Link to post
Share on other sites
You really do only need a 4x4 array; you rotate it in just the same way, and the fact that there is no "centre element" doesn't make any difference.

Also, please don't assign 1 and 0 to bools. Use true and false instead; respect the type system and write clearly. You don't take 1-or-0 quizzes in school, right? That's why bool is a separate type; so you don't need to think in terms of that sort of encoding. :)

You do do the comparison in a good way, which highlights my other bit of advice for beginners: don't compare booleans to boolean literals (although you can compare them to each other when it's appropriate - "a == b" is easier to write and almost always clearer than "!(a ^ b)" - just please don't use inequalities with them). It adds extra stuff that doesn't express any more information, and is awkward. Don't say "if it is true that it is raining, I will need my umbrella"; say "if it is raining, I will need my umbrella".

Oh, and you probably want to clean up those magic numbers. Otherwise, though, that looks quite solid. :)

Another approach is to generate all blocks ahead of time *with their rotations*, and link them up to each other. Then you have one "prototype" instance of each possible block configuration - call it a "shape" - and your block struct contains a handle to a shape. That looks like this:


struct Shape {
// Careful when using raw pointers as handles! I'll be storing the Shapes
// in a vector, which can be especially problematic because a vector relocates
// its storage when it grows. However, we'll be OK because we're going to
// create all the Shapes at once at start-up, and never add any more after
// we've linked them up, so they won't move around in memory.
Shape* left_rotation;
Shape* right_rotation;
char data[4][4];
// represents the actual geometry, e.g. with '*' for filled tiles
};

class ShapeFactory {
vector<Shape> shapes;
public:
ShapeFactory() {
// Push back shapes into the vector, setting their data.

// Then set their pointers to point at one another, such that for each
// shape, following a left_rotation link takes you to the shape that
// results from a left rotation, and similarly to the right.
}

Shape* random() {
return &(shapes[rand() % shapes.size()]);
// Please use a better randomizer if you can though :)
}

// DO NOT delete any shape pointers at any time! We're storing instances,
// not pointers; the pointers that we have only serve as aliases, and the
// vector is already managing memory for us. To step on its heels would be
// a serious error.
};

class Block {
Shape* s;
// etc.
public:
Block(const ShapeFactory& factory) : s(factory.random()) {}

void rotateLeft() {
s = s->left_rotation;
}
void rotateRight() {
s = s->right_rotation;
}
// other member functions can access s->data[j] when needed.
};




It's unlikely to be a good idea for Tetris (unless you are reading the data from a file and you want to define weird block shapes and "rotations" that aren't really rotations), but it's educational ;)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!