• Advertisement
Sign in to follow this  

Rotating Tetris blocks

This topic is 4169 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