Tetris.js – Implementation of Tetris Using Javascript and JQuery (Part II)

This week we are going to define different tetrominoes shape and rotate them.

4. Define Tetrominoes

Check out this part on GitHub

The seven one-sided Tetriminos in their Tetris Worlds colors. Top row, left to right: I, J, L, O. Bottom row: S, T, Z. http://en.wikipedia.org/wiki/Tetris

We will need to define all seven of these shapes so that our game knows which shape to display. We will need to think about what is the best way to store these information so that we can draw up different shapes easily. We will use the following logic:

  1. Write a function that takes in a shape name and a one-point coordinate as origin.
  2. The function will return an array of coordinates that matches the shape name, relative to the one-point coordinate.

We will first need to split up our tetris.currentCoor.

//Variable to store current coordiates
tetris.origin = {row:5,col:5};
tetris.currentShape = 'L';
tetris.currentCoor;

Using these information, once we have our new function set up, it should know that it needs to return the ‘T’ shape relative to the {row:5, col:5} origin coordinates. We will also declare a null value for currentCoor for now as a placeholder. Let’s begin writing our function:

tetris.shapeToCoor = function(shape,origin){
  if(shape === 'L'){
    return [{row:origin.row, col:origin.col},
            {row:origin.row-1, col:origin.col},
            {row:origin.row+1, col:origin.col},
            {row:origin.row+1, col:origin.col+1}]
  } 
}

The shapeToCoor function will take in two parameters as variable: shape and origin. So far, our function will first match shape to our if statement, and if shape === ‘L’, it will return an array of coordinates based on the origin provided.

The first coordinate is the origin. The second coordinate is the cell above. The third coordinate is the cell below. and the last coordinate is the cell to the lower-right.

We will then need to re-define our tetris.currentCoor using our new function. In our document-ready function, add the following between drawPlayField and fillCells:

tetris.currentCoor = tetris.shapeToCoor(tetris.currentShape,tetris.origin);

Now if you save and reload your game, you should see and ‘L’ shape, and you should be able to move it using the left and right arrow key.

4.L

Now go ahead and try to define ‘J’,’I’,’O’,’S’,’T’ and ‘Z’ on your own by adding more else..if statement to shapeToCoor. One thing you should be careful of is that you use the center point of the shape as your origin. This will make rotating the shape easier in the next section. You can refer to the source code on github here if you get stuck.

5. Rotate your Shapes

Check out this part on GitHub

The easiest way to rotate your shape is to basically think of them as different shapes. For example, we can define shape ‘L90′,’L180’, and ‘L270’ to represent the ‘L’ shape rotated by 90,180, and 270 degrees clockwise, respectively. Before we do that, let’s list out all the possible shapes and orientation we will need.

  • L Shape: L, L90, L180, L270
  • J Shape: J, J90, J180, J270
  • I Shape: I, I90
  • O Shape: O
  • S Shape: S, S90
  • T Shape: T, T90, T180, T270
  • Z Shape: Z, Z90

You will see that not all shape need 4 different orientation. For example, the S shape at 180 degrees clockwise will look the same as its origin position. Let’s define L90 together by adding the following else..if statement in our shapeToCoor funciton:

else if(shape === 'L90'){
  return [{row:origin.row,col:origin.col},
          {row:origin.row,col:origin.col+1},
          {row:origin.row,col:origin.col-1},
          {row:origin.row+1,col:origin.col-1}];
}

Note that our ‘L90’ will have the same origin coordinates as our ‘L’ shape. In order to achieve a rotating effect, we will need to use the center point as our origin.

Now we will need a function to rotate the shape. Basically, we want to achieve the following:

  1. Call rotate function when ‘up’ arrow key is pressed
  2. Set currentShape to ‘L90’ if currentShape === ‘L’
  3. Draw new shape

Let’s begin writing our rotate function:

tetris.rotate = function(){
  this.fillCells(this.currentCoor,'');
  if(this.currentShape === 'L'){
    this.currentShape = 'L90';
  } else if(this.currentShape === 'L90'){
    this.currentShape = 'L';
  }
  
  this.currentCoor = this.shapeToCoor(this.currentShape,this.origin);
  this.fillCells(this.currentCoor,'black');
}

Similar to our move function, we will first erase the current shape by calling this.fillCells(this.currentCoor,”). We will then add an if statement to change currentShape to ‘L90’ if it is ‘L’, as well as an else..if statement to change currentShape to ‘L’ if it is ‘L90‘. We will then  renew our currentCoor based on the new shape, and fillCells with ‘black’.

We will then need to add the following to our keydown function so that rotate is called every time the up arrow is pressed.

else if (e.keyCode === 38){
  tetris.rotate();
}

Now saved and reload your page. You should be able to rotate your shape by pressing the up arrow.

4.rotate

Go ahead and add the rest of the shapes to shapeToCoor and rotate functions. You can check the source code here if you have any questions. We will do our first debugging in our next section.

6. Fixing Two Bugs

Check out this part on GitHub

If you experiment with our game right now, you will find a couple bugs. The most noticeable one is that if we move our shape left or right, and then rotate it, our currentShape will pop back to the original location.

6.bug1

To fix this, we need to trace in our codes back to the root of the problem. Let visit our rotate function first. When we call our rotate function, it first uses the currentShape to determine what is the new shape. Then, it uses the origin variables to determine what are our new coordinates. Since our shape pop back to its original location, it means that our origin variable has not been updated after we move our shapes.

tetris.move = function(direction){
  var reverse = false;
  this.fillCells(this.currentCoor,'');
  
  for(var i=0;i<this.currentCoor.length;i++){
    if(direction === 'right'){
      this.currentCoor[i].col++;
      if(this.currentCoor[i].col>9){
        reverse = true;
      }
    } else if (direction === 'left'){
      this.currentCoor[i].col--;
      if(this.currentCoor[i].col<0){
        reverse = true;
      }
    }
  }
  this.fillCells(this.currentCoor,'black');

  if(reverse && direction === 'left'){
    this.move('right');
  } else if (reverse && direction === 'right'){
    this.move('left');
  }
}

Referring back to our move function, we can see that only currentCoor is changed, and there is no change to our origin variable after we move the shape. In order to fix that, let’s add the following to our move function right after the for loop.

//move origin
 if(direction === 'right'){
   this.origin.col++;
 } else if (direction === 'left'){
   this.origin.col--;
 }

Without making dramatic change to our move function, this if statement will add 1 to origin.col if right is pressed, and subtract 1 from origin.col if left is pressed.

There is another bug in our game. If we move our shape all the way to the left, and then rotate it, you will see that the shape rotated as if the border doesn’t exist.

6.bug2

We had a similar bug when we were building our move function. We can use the same logic to fix our rotate function.

tetris.rotate = function(){
  var lastShape = this.currentShape;
  this.fillCells(this.currentCoor,'');

  //all the if...else if statements...

  this.currentCoor = this.shapeToCoor(this.currentShape,this.origin);

  for(var i=0;i<this.currentCoor.length;i++){
    if(this.currentCoor[i].col>9 || this.currentCoor[i].col<0){
      this.currentShape = lastShape;
    }
  }

  this.currentCoor = this.shapeToCoor(this.currentShape,this.origin);
  this.fillCells(this.currentCoor,'black');
}

We first declare a variable called lastShape, and we will set it equal to the currentShape. After all rotation logics and currentCoor has been renewed, we will then loop around the currentCoor and see if any of the coordinates has a col value greater than 9 or less than 0. If so, we will set currentShape back to lastShape. Finally, we will renew our currentCoor again using the new shape, and then fillCells in ‘black’.

Save and reload your game. You should see shape will not rotate if rotating will cause it to go out of bound.

That’s it for now. Next post we will add gravity, spawn random shape, and the ability to clear rows when it is full.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s