Monday, December 2, 2013

Function Invocation

Javascript functions can be invoked in four ways:
functions,
methods,
constructors,
call() and apply()

They differs from each other in various ways, and most importantly in invocation context and returning mechanisms.

First of all, let's introduce a definition called invocation expression. An invocation expression consists of a function expression that evaluates to a function object followed by an open parenthesis, a comma-separated list of zero or more argument expressions, and a close parenthesis.

Now let's dive into each type of function invocation one by one. The first one is called Function Invocation. When a function is invoked as function, it's invoked using invocation expression.  The line
sum(1, 1); in the example below is a function invocation. It has the function expression sum, an open and close parenthesis and argument expressions 1, 1.

function sum(x, y){
  console.log('The sum of x and y is '+ (x+y))
}

sum(1, 1);

Method invocation is similar. It's just a function stored in a property of an object. Specify the object on which this method is called before the invocation expression when you invoke a method. See the example below. calculator is the name of the object and add() is the invocation expression. Any function that is used as method is effectively passed an implicit argument - the object through which it is invoked.

var calculator = {
  operator1: 1, 
  operator2: 1, 
  add: function(){
    console.log('The sum is '+(this.operator1+this.operator2));
  }
}
calculator.add();

If a function or method invocation is preceded by the keyword new, then it is a constructor invocation. A constructor invocation creates a new, empty object that inherits from the prototype property of the constructor. 

var arr = new Array(); //By convention, constructors start with capitals

Javascript functions are also objects. It has its own methods. Two of them are called apply and call, which can be used to invoke the function indirect, and by using them, you can also specify the invocation context as the first argument. See example below.

function sum(x, y){
  console.log('The sum of x and y is '+ (x+y))
}

sum.call(undefined, 1, 1);
sum.apply(null, [1, 1]);

Invocation Context for different types of function invocation.

Non Strict ModeStrict Mode
Function Invocationglobalundefined
Method Invocationthe caller objectthe caller object
Constructor Invocationthe new objectthe new object
Indirect Invocationcan be specifiedcan be specified

Sunday, December 1, 2013

Javascript - Why Assign this value in self

In Javascript, we see this line of code frequently
self = this;

We know this refers to the invocation context, but why we need to assign its value to a variable. The reason is this keyword does not have a scope, and nested functions do not inherit the this value of their caller. Thus if a nested function is invoked as a function, its this keyword actually refers to global object (non-strict mode) or undefined (strict mode). In order to access the invocation context of the outer function, we need to assign the value of this keyword to a variable. Recalling from our previous blog Variable Scope & Function Scope, we have variables are visible within the function in which they are defined and within any functions that are nested within that function. Now let's take a look at the following code snippet.

var o = {
  m: function(){
    var self = this;

    console.log(this===o);   // return true
    f(); //invoking nested function as a function

    //nested function
    function f(){
      console.log(this===o);  // return false
      console.log(self===o);  // return true
    }
  }
}

o.m();

Tuesday, November 26, 2013

Variable Scope & Function Scope

According to the book Javascript The Definitive Guide, The scope of a variable is the region of your program source code in which it is defined. A global variable has global scope; it is defined everywhere in your Javascript code. On the other hand, variables declared within a function are defined only within the body of the function. Open up your javascript interactive console and let's do some experiments.

js> var a_global_var = 'global';
js> function local_scope_demo(){
var a_local_var = 'local';
}
js> a_global_var
"global"
js> a_local_var
typein:25: ReferenceError: a_local_var is not defined

As we can see here, we can access global variable outside the function body but not local variable, because it is only accessible within the body of the function.

From the example above, we can actually see another feature of javascript, called Function scope: variables are visible within the function in which they are defined and within any functions that are nested within that function. 

Compare the two code snippet of Java and Javascript.

public class HelloWorld {

    public static void main(String[] args) {
        for(int i=0;i<10;i++){
                System.out.println("i in the for loop is "+i);
        }
        System.out.println("i out of for loop is "+i);
    }
}

Compile and run this Java code and you'll get the following error. That's because Java has block scope, which means each block of code within curly braces has its own scope, and variables are not visible outside of the block in which they are declared.

cannot find symbol
symbol  : variable i
location: class HelloWorld
 System.out.println("i out of for loop is "+i);
                                            ^
1 error

However, the following javascript code will run successfully all because Javascript is a function-scope language.

function function_scope_demo(){
for(var i=0;i<10;i++){
console.log(k);
}
console.log(k);  // k is 10
}

Wednesday, July 17, 2013

Recreating Tetris using Javascript and HTML5 - Part 3

 Add one more control feature
The Tetris game I'm modeling after, allows player to push any key (except up, down, left and right) to move a falling Tetrimino immediately down to the bottom and gets itself freezed. In my game, I'm gonna use the space key to handle this funciton.

To implement this functionality, I added the following functions to Tetrimino. Yep, that's it! These two functions takes care of the all the Tetriminoes in all directions. Of course, you also need to add the event handler in function monitorKeyboard

self.moveToBottom = function(){
 var numLevelMoveDownS_1 = self.s_1.findTheHighestFixedSquareUnder() - self.s_1.vPosition - 1;
 var numLevelMoveDownS_2 = self.s_2.findTheHighestFixedSquareUnder() - self.s_2.vPosition - 1;
 var numLevelMoveDownS_3 = self.s_3.findTheHighestFixedSquareUnder() - self.s_3.vPosition - 1;
 var numLevelMoveDownS_4 = self.s_4.findTheHighestFixedSquareUnder() - self.s_4.vPosition - 1;
 var numLevelMoveDown = Math.min(numLevelMoveDownS_1, numLevelMoveDownS_2, numLevelMoveDownS_3, numLevelMoveDownS_4)
 
 self.s_1.vPosition += numLevelMoveDown;
 self.s_2.vPosition += numLevelMoveDown;
 self.s_3.vPosition += numLevelMoveDown;
 self.s_4.vPosition += numLevelMoveDown;
}

self.moveToBottomAndFreeze = function(){
 self.moveToBottom();
 self.freeze();
 var n = checkLinesToClear();
}

var monitorKeyboard = function (){
            $(document).keydown(function(evt){
              ...
              else if(evt.which == 32){
               currTetrimino.moveToBottomAndFreeze();
              }
            });
         }

Pause/Restart Game
While playing, player might want to pause/restart game. To implement this, we need to stop the execution of our gameLoop and later restart it. Below is the code we need to add.

var gameOn = true;
var gameLoop;

var pause_restart = function(){
 if(gameOn){
  clearInterval(gameLoop);
  gameOn = false;
 }
 else{
  startGame();
  gameOn = true;
 }

}
var monitorKeyboard = function (){
            $(document).keydown(function(evt){
              if(evt.which == 39 && gameOn){
               currTetrimino.checkAndMoveToRight();
              }
              else if(evt.which == 37 && gameOn){
               currTetrimino.checkAndMoveToLeft();
              }
              else if(evt.which == 40 && gameOn){
               currTetrimino.checkAndMoveDown();
              }
              else if(evt.which == 38 && gameOn){
               currTetrimino.checkAndRotate();
              }
              else if(evt.which == 32 && gameOn){
               currTetrimino.moveToBottomAndFreeze();
              }
              else if(evt.which == 80){
               pause_restart();
              }
            });
         }
var startGame = function(){
 gameLoop = setInterval(function(){
  clearCanvas();
  generateNextTetrimino();
  updatePosition();
  drawOnCanvas();
 }, 1000/50);
}

The global variable gameOn indicates whether the game is in play or paused. We assign the returning value of the setInterval function to another global variable gameLoop, on which we can call clearInterval later to pause the game. I also wrap up the whole gameLoop within a new function startGame, which we can use to restart the game. We also need to add one more keyboard handler in function monitorKeyboard. I use 'p' as the key to control pausing and restarting. Don't forget to disable all the other keys when game is paused by adding "&&gameOn" after each condition.

Game Statistics
For now, we haven't written any code that deal with game statistics, i.e. at which level the game is, how many lines have been cleared, and total points. We've already reserve some space for game statistics at the right hand side of the game area. Now let's put something into it.

var statsGameLevel = 1, 
    statsLinesCleared = 0,
    statsPoints = 0;

var updateStats = function(lines){
 statsLinesCleared += lines;
 statsPoints += lines*lines*50 + lines*50;

 if(statsLinesCleared < 100){
  statsGameLevel = 1
  currSpeed = speedLevels[0];
 }
 else if(statsLinesCleared >= 100 && statsLinesCleared < 200){
  statsGameLevel = 2
  currSpeed = speedLevels[1];
 }
 else if(statsLinesCleared >= 200 && statsLinesCleared < 300){
  statsGameLevel = 3
  currSpeed = speedLevels[2];  
 }
 else if(statsLinesCleared >= 300 && statsLinesCleared < 400){
  statsGameLevel = 4
  currSpeed = speedLevels[3];  
 }
 else if(statsLinesCleared >= 400){
  statsGameLevel = 5
  currSpeed = speedLevels[4];  
 }
}

Three more global variables, statsGameLevel, statsLinesCleared, statsPoints are added. statsGameLevel  is the difficulty level of the game, divided into 5 level. From level 1 to level 5, the speed is gonna be 20, 16, 12, 10, 8. statsLinesCleared stores the number of lines have been cleared so far, for each 100 lines clear, the difficulty level will increased by 1, unless the difficulty level is 5, and it is the highest level. StatsPoints stores the points player earns for clearing lines; if a single line is cleared, player gets 100 points, and the more lines cleared at one time, player gets bonus points. The lines cleared at one time and the points earned are listed in the table below.

Lines Cleared Points Earned
1 100
2 300
300 600
4 1000

Function updateStats takes number of lines cleared as a single parameter, and updates all the three statistics variables. And this function needs to be called right after when function checkLinesToClear is called.

self.moveToBottomAndFreeze = function(){
 self.moveToBottom();
 self.freeze();
 var n = checkLinesToClear();
 updateStats(n);
}

 self.fall = function(){
  if(self.counter >= self.speed){
   var s_1_can_fall = self.s_1.canFall();
   var s_2_can_fall = self.s_2.canFall();
   var s_3_can_fall = self.s_3.canFall();
   var s_4_can_fall = self.s_4.canFall();

   if(s_1_can_fall && s_2_can_fall && s_3_can_fall && s_4_can_fall){
    self.moveDown(); 
   }
   else{
    self.freeze();
    var n = checkLinesToClear();
    updateStats(n);
   }

   self.counter = 0;
  }
  self.counter++;
 }

Like any other objects that's on canvas, you need a function to draw the game statistics on canvas, shown below.

var drawStats = function(){
 ctx.fillStyle = "Black";
 ctx.font = '15pt Calibri';
 ctx.fillText("LEVEL: ", 210, 120);
 ctx.fillText("LINES: ", 210, 160);
 ctx.fillText("POINTS: ", 210, 200);
 ctx.fillText(statsGameLevel, 210, 140);
 ctx.fillText(statsLinesCleared, 210, 180);
 ctx.fillText(statsPoints, 210, 220);
}

Make The Game Nicer
Currently all the squares are all drawn in pure black color, that's neat but may not be so user-friendly. So we're gonna use the image shown below to fill out the square.



And now our game looks like this.




















There are three place we need to add or change the code. First, declare a new global Image object and points it to square.png. In the function drawFixedSquares and the draw function of Square. Comment out the code that draw the squares and replace it with the code that draws the square.png.

var squareImage = new Image();
squareImage.src = 'square.png';

var drawFixedSquares = function(){
 for(var i=0; i<20; i++){
  for(var j=0; j<10; j++){
   if(gameGrid[i][j] == 1){
    // ctx.fillStyle = "Black";  
    // ctx.fillRect(j*squareLength, i*squareLength, squareLength, squareLength);
    try{
     ctx.drawImage(squareImage, 0, 0, squareLength, squareLength, j*squareLength, i*squareLength, squareLength, squareLength)
    } catch(e)
    {
     console.log('drawImage not work')
    }
   }
  }
 }
}

 self.draw = function(){
  if(self.active){
   // ctx.fillStyle = self.color;  
   // ctx.fillRect(self.hPosition*squareLength, self.vPosition*squareLength, squareLength, squareLength);

   try{
    ctx.drawImage(squareImage, 0, 0, squareLength, squareLength, self.hPosition*squareLength, self.vPosition*squareLength, squareLength, squareLength)
   } catch(e)
   {
    console.log('drawImage not work')
   }
  }
 }

End The Game
As we know, in a Tetris game, if the Tetriminoes reaches the top of the game area, the game ends. In our game, we also need that mechanism. One thing to notice is that the Tetriminoes reaching the top does not necessarily mean the end of a game. For instance, if we have a layout like below, and the next Tetrimino is J. Upon its appearance, the player immediately move the J to the left most side, which ends with layout shown below. Because this layout doesn't prevent the next Tetrimino from generating, it is not the end of the game. The way to check the end of a game is to check if there is any fixed squares there're in the initial position of next Tetrimino. And this should be checked right before each new Tetrimino is generated on canvas. Added code is shown below.

var generateNextTetrimino = function(){
 if(!currTetrimino.active){
  currTetrimino = RandomGenOneBag();

  if(checkEndOfGame(currTetrimino))
   endGame();
 }
  
}

var checkEndOfGame = function(tetri){
 if(gameGrid[tetri.s_1.vPosition][tetri.s_1.hPosition] == 1 || gameGrid[tetri.s_2.vPosition][tetri.s_2.hPosition] == 1 || gameGrid[tetri.s_3.vPosition][tetri.s_3.hPosition] == 1 || gameGrid[tetri.s_4.vPosition][tetri.s_4.hPosition] == 1){
  return true;
 }

 return false;
}

var endGame = function(){
 clearInterval(gameLoop);
 gameOn = false;
 setTimeout(function(){
  ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
  ctx.fillRect(0, 0, 200, canvasHeight);

  ctx.fillStyle = "Black";
  ctx.font = '25pt Calibri';
  ctx.fillText("GAME OVER", 15, 180);
 },100)
}

The new function endGame stops the gameLoop, marks gameOn as false and draw a half transparent white color on the game area and put the "Game Over" in the middle.



















Add The Clearing Line Animation
In a typical Tetris game, it usually has some kind of animation when clearing lines. Old version of Tetris game usually make the lines that're going to be cleared twinkle. The twinkling can be achieved by alternating between blank line and square-filled line. Three functions are added shown below.

var lineDisappear = function(linesToClear){
 var len = linesToClear.length;
 for(var i=0; i<len; i++){
  ctx.fillStyle = 'White';
  ctx.beginPath();
  ctx.rect(0, linesToClear[i]*squareLength, canvasWidth, squareLength);
  ctx.closePath();
  ctx.fill();
 }
}

var lineAppear = function(linesToClear){
 var len = linesToClear.length;
 for(var i=0; i<len; i++){
  for(var j=0;j<10;j++){
   ctx.drawImage(squareImage, 0, 0, squareLength, squareLength, j*squareLength, linesToClear[i]*squareLength, squareLength, squareLength)
  }
 }
}

var clearingLineAnimation = function(linesToClear){
 if(linesToClear.length==0)
  return;

 setTimeout(function(){
  clearInterval(gameLoop);
 }, 1000/10);

 var animateTimes = 0;
 clearLineAnimation = setInterval(function(){
  if(animateTimes<6){
   if(animateTimes%2 ==0)
    lineDisappear(linesToClear);
   else if(animateTimes%2 ==1)
    lineAppear(linesToClear);

   animateTimes++;
  }
  else{
   clearInterval(clearLineAnimation);
   startGame();
   updateGameGrid(linesToClear);
  }
   
 }, 1000/10);
}
var checkLinesToClear = function(){
 var fixSquareNum;
 var linesToClear = [];
 var counter = 0;

 for(var i=0; i<20; i++){
  fixSquareNum = 0;
  for(var j=0; j<10; j++){
   fixSquareNum += gameGrid[i][j];
  }
  if(fixSquareNum == 10){
   linesToClear[counter] = i;
   counter++;
  }
 }
 clearingLineAnimation(linesToClear);
        updateGameGrid(linesToClear);
 return counter;
}

clearingLineAnimation takes an array of line number as parameter, if the array is not empty, it will first cease the game loop, and then it will call function lineAppear, and lineDisappear alternatively, three time for each of them. Finally, it ceases the clearLineAnimation and restart the game loop. Note that we also need to move the code updateGameGrid(linesToClear) from function checkLinesToClear to clearingLineAnimation. This way, we can see that whenever a line is ready to be cleared, it first twinkles and then all the other lines  get updated. If we leave it as it was, the animation just doesn't look right.

Implement The Next Tetrimino Display Area
Our final step is to implement the functionality that while dealing with the current Tetrimino, the user can see which Tetrimino will come next. The next Tetrimino is usually shown above the statistics. It only takes 10 minutes to implement this functionality. See the code change below.

var nextTetrimino = RandomGenOneBag();

var drawOnCanvas = function(){
 drawLine();
 currTetrimino.draw();
 nextTetrimino.drawStandBy();
 drawFixedSquares();
 drawStats();
}

self.drawStandBy = function(){
 if(self.active){

  try{
   ctx.drawImage(squareImage, 0, 0, squareLength, squareLength, self.hPosition*squareLength+155, self.vPosition*squareLength+30, squareLength, squareLength)
  } catch(e)
  {
   console.log('drawImage not work')
  }
 }
}

self.drawStandBy = function(){
 if(self.active){
  self.s_1.drawStandBy();
  self.s_2.drawStandBy();
  self.s_3.drawStandBy();
  self.s_4.drawStandBy();
 }
}

var generateNextTetrimino = function(){
 if(!currTetrimino.active){
  currTetrimino = nextTetrimino;
  nextTetrimino = RandomGenOneBag();

  if(checkEndOfGame(currTetrimino))
   endGame();
 } 
}

First of all, you need to add a new global variable nextTetrimino and assign a random Tetrimino to it. This global variable stands for the next Tetrimino that will be shown above the statistics. To make the next Tetrimino show at the right place, I wrote two functions, both called drawStandBy, and added them to Square and Tetrimino respectively. The first drawStandBy function belongs to Square, and the only difference between the draw function and drawStandBy is that the latter increases the horizontal value and vertical value by 155 and 30 respectively, which draws the square right above the statistics. The drawStandBy function of Tetrimino just calls the drawStandBy function of Square, which draws a Tetrimino above the statistics. Don't forget to call drawStandBy function in drawOnCanvas. Finally, you need to change the function generateNextTetrimino a little bit: whenever the current Tetrimino becomes inacitve, assign the nextTetrimino to currTetrimino and assign a new random Tetrimino to nextTetrimino.

Congrats! Now you have a fully functional Tetris game. Get the code here.
Play the game here.





Tuesday, July 2, 2013

Recreating Tetris using Javascript and HTML5 - Part 2



In Part 1, we have created a Tetris game of square version. All the game logic has been implemented in Part 1. In Part 2, we are going to create Tetriminos and implement the related logic.


There are seven Tetriminos in a typical Tetris game, as shown on the left. We'll refer to them as I, J, L, O, S, T, Z from left to right, top to bottom. We're going to create them one by one. Start with I. I has two directions, horizontal and vertical. And it consists of four squares, as the same as other Tetriminos. When it comes to determine whether a Tetrimino should become inactive, sometimes we need to check all the four squares. Thus it would be a good idea to name these squares. The picture below shows how I name them in numbers. Knowing all its features, now let's create I.


var I = function(speed){
 var self = this;
 self.s_1 = new Square(speed, 3, 0);
 self.s_2 = new Square(speed, 4, 0);
 self.s_3 = new Square(speed, 5, 0);
 self.s_4 = new Square(speed, 6, 0);
 self.direction = dir_left;
 self.active = true;
 self.speed = speed;
 self.counter = 0;

 self.moveToLeft = function(){
  self.s_1.hPosition--;
  self.s_2.hPosition--;
  self.s_3.hPosition--;
  self.s_4.hPosition--;
 }

 self.moveToRight = function(){
  self.s_1.hPosition++;
  self.s_2.hPosition++;
  self.s_3.hPosition++;
  self.s_4.hPosition++;
 }

 self.moveDown = function(){
  self.s_1.vPosition++;
  self.s_2.vPosition++;
  self.s_3.vPosition++;
  self.s_4.vPosition++;
 }

 self.checkAndMoveToLeft = function(){
  if(self.direction == dir_left){
   if(self.s_1.hPosition > 0 && !self.s_1.fixedSquareOnLeft()){
    self.moveToLeft();
   }
  }
  else if(self.direction == dir_up){
   if(self.s_1.hPosition > 0 && !self.s_1.fixedSquareOnLeft() && !self.s_2.fixedSquareOnLeft() && !self.s_3.fixedSquareOnLeft() && !self.s_4.fixedSquareOnLeft()){    
self.moveToLeft();
   }
  }
 }
 self.checkAndMoveToRight = function(){
  if(self.direction == dir_left){
   if(self.s_4.hPosition < 9 && !self.s_4.fixedSquareOnRight()){
    self.moveToRight();
   }
  }
  else if(self.direction == dir_up){
   if(self.s_1.hPosition < 9 && !self.s_1.fixedSquareOnRight() && !self.s_2.fixedSquareOnRight() && !self.s_3.fixedSquareOnRight() && !self.s_4.fixedSquareOnRight()){    
self.moveToRight();
  
 }
  }
 }

 self.checkAndMoveDown = function(){
  if(self.direction == dir_left){
   if(self.s_1.vPosition < 19 && !self.s_1.fixedSquareUnder() && !self.s_2.fixedSquareUnder() && !self.s_3.fixedSquareUnder() && !self.s_4.fixedSquareUnder()){    
self.moveDown();
   }
  }
  else if
(self.direction == dir_up){
   if(self.s_4.vPosition < 19 && !self.s_4.fixedSquareUnder()){
    self.moveDown();
   }
  }
 }

 self.checkAndRotate = function(){
  var rotate_center_i = self.s_2.vPosition;
  var rotate_center_j = self.s_2.hPosition;

  if(self.direction == dir_left){
   if(rotate_center_i <= 17 && (rotate_center_i==0 || gameGrid[rotate_center_i-1][rotate_center_j]==0) && gameGrid[rotate_center_i+1][rotate_center_j]==0 && gameGrid[rotate_center_i+2][rotate_center_j]==0){    
self.direction = dir_up;

    // rotate
    self
.s_1.vPosition = rotate_center_i-1;
    self.s_1.hPosition = rotate_center_j;
    self.s_3.vPosition = rotate_center_i+1;
    self.s_3.hPosition = rotate_center_j;
    self.s_4.vPosition = rotate_center_i+2;
    self.s_4.hPosition = rotate_center_j;
   }
  }
  else if(self.direction == dir_up){
   if(rotate_center_j>=1 && rotate_center_j<=7 && gameGrid[rotate_center_i][rotate_center_j-1]==0 && gameGrid[rotate_center_i][rotate_center_j+1]==0 && gameGrid[rotate_center_i][rotate_center_j+2]==0){
    self.direction = dir_left;

    // rotate
    self.s_1.vPosition = rotate_center_i;
    self.s_1.hPosition = rotate_center_j-1;
    self.s_3.vPosition = rotate_center_i;
    self.s_3.hPosition = rotate_center_j+1;
    self.s_4.vPosition = rotate_center_i;
    self.s_4.hPosition = rotate_center_j+2;
   }
  }
 }

 self.fall = function(){
  if(self.counter >= self.speed){
   var s_1_can_fall = self.s_1.canFall();
   var s_2_can_fall = self.s_2.canFall();
   var s_3_can_fall = self.s_3.canFall();
   var s_4_can_fall = self.s_4.canFall();

   if(s_1_can_fall && s_2_can_fall && s_3_can_fall && s_4_can_fall){
    self.moveDown(); 
   }
   else{
    self.freeze();
    self.activee;
    checkLinesToClear();
   }

   self.counter = 0;
  }
  self.counter++;
 }

 self.freeze = function(){
  gameGrid[self.s_1.vPosition][self.s_1.hPosition] = 1;
  gameGrid[self.s_2.vPosition][self.s_2.hPosition] = 1;
  gameGrid[self.s_3.vPosition][self.s_3.hPosition] = 1;
  gameGrid[self.s_4.vPosition][self.s_4.hPosition] = 1;
 }

 self.draw = function(){
  if(self.active){
   self.s_1.draw();
   self.s_2.draw();
   self.s_3.draw();
   self.s_4.draw();
  }
 } 
  
 return self;
}

First of all, I has 7 non-function properties, s_1, s_2, s_3, s_4, direction, speed, and active. s_1~4 wrap the squares that form I. speed is how long I is going to stay at a certain height, same as the speed of squares we implemented before. The larger the number is, the slower it falls. active marks if I is still in falling, just like the active property of a square. Direction, is where the square s_1 points to. There are totally 4 directions for all the seven Tetriminoes, up, down, left, right. But not all the Tetriminoes have all the 4 directions. I, for instance, has only two of them, up and left. When it rotates, it rotates using square s_2 as the rotating point, as shown in the picture below. Note how the Square function is modified, I add two more parameters, x and y, which are the initial hPosition and vPosition of the new Square. See below

var Square = function(speed, x, y){
        ...
 self.vPosition = y;
 self.hPosition = x;
        ...


So for each new I, it will always appear at the top of the game area with s_1 pointing to left. The three function moveToLeft, moveToRight, and moveDown are pretty simple, they just move each square one position to whatever the direction is. The following four functions, checkAndMoveToLeft, checkAndMoveToRight, checkAndMoveDown and checkAndRotate are specific to I. They all first check if I is in currently in a postion that can be moved to left, right, down or rotate, and then perform the action.  The function fall uses the function canFall of Square to check if all the squares of an I is allowed to fall. If one of them is blocked, then I will be fixed using the function freeze. Otherwise, it is allowed to move down. Whenever an I freezes, we mark it as inactive and call function checkLinesToClear to see if there are any lines that can be cleared. The original function fall of Square is actually replaced by the function canFall, because you need to move some of the logic from Square to Tetriminoes. See below,


// self.fall = function(){
//  if(self.counter >= self.speed){
//   if(!self.fixedSquareUnder()){
//    self.vPosition++;    
//   }
//   else{
//    gameGrid[self.vPosition][self.hPosition] = 1;
//    self.active = false;
//    checkLinesToClear();
//   }
//   self.counter = 0;
//  }
//  self.counter++;

//  return self.active;
// }
self.canFall = function(){
 if(self.fixedSquareUnder())
  self.active = false;

 return self.active;
}

The last function draw just draw each of the square on the canvas. Among all these functions, we can easily find that almost half of them can be shared by other Tetriminoes. So we're gonna add another level of wrapping, creating a new function called Tetrimino.


var Tetrimino = function(speed){
 var self = this;
 self.active = true;
 self.speed = speed;
 self.counter = 0;

 self.moveToLeft = function(){
  self.s_1.hPosition--;
  self.s_2.hPosition--;
  self.s_3.hPosition--;
  self.s_4.hPosition--;
 }

 self.moveToRight = function(){
  self.s_1.hPosition++;
  self.s_2.hPosition++;
  self.s_3.hPosition++;
  self.s_4.hPosition++;
 }

 self.moveDown = function(){
  self.s_1.vPosition++;
  self.s_2.vPosition++;
  self.s_3.vPosition++;
  self.s_4.vPosition++;
 }

 self.fall = function(){
  if(self.counter >= self.speed){
   var s_1_can_fall = self.s_1.canFall();
   var s_2_can_fall = self.s_2.canFall();
   var s_3_can_fall = self.s_3.canFall();
   var s_4_can_fall = self.s_4.canFall();

   if(s_1_can_fall && s_2_can_fall && s_3_can_fall && s_4_can_fall){
    self.moveDown(); 
   }
   else{
    self.freeze();
    self.active = false;
    checkLinesToClear();
   }

   self.counter = 0;
  }
  self.counter++;
 }

 self.freeze = function(){
  gameGrid[self.s_1.vPosition][self.s_1.hPosition] = 1;
  gameGrid[self.s_2.vPosition][self.s_2.hPosition] = 1;
  gameGrid[self.s_3.vPosition][self.s_3.hPosition] = 1;
  gameGrid[self.s_4.vPosition][self.s_4.hPosition] = 1;
 }

 self.draw = function(){
  if(self.active){
   self.s_1.draw();
   self.s_2.draw();
   self.s_3.draw();
   self.s_4.draw();
  }
 }

 return self;
}

As we can see, I move properties/functions active, speed, moveToLeft, moveToRight, moveDown, fall, , freeze, and draw into the new function Tetrimino, because they are the same among all the Tetriminoes. And change the beginning of the function I a little bit, see below

var I = function(speed){
 var self = new Tetrimino(speed);
        ...

To experiment what we've achieved, we still need to modify some existing code. Now we've create our first Tetrimino, we actually need to control the Tetrimino directly, so we need to change the minitorKeyboard function, shown below

var monitorKeyboard = function (){
            $(document).keydown(function(evt){
              if(evt.which == 39){
               currTetrimino.checkAndMoveToRight();
              }
              else if(evt.which == 37){
               currTetrimino.checkAndMoveToLeft();
              }
              else if(evt.which == 40){
               currTetrimino.checkAndMoveDown();
              }
              else if(evt.which == 38){
               currTetrimino.checkAndRotate();
              }
              else if(evt.which == 32){

              }
            });
         }

which uses the four function, checkAndMoveToRight, checkAndMoveToLeft, checkAndMoveDown and checkAndRotate we've mentioned before. Also you need to change all the occurrence of currentSquare to currTetrimino, and assign new objects of I to currTetrimino, as shown belown

var generateNextTetrimino = function(){
 if(!currTetrimino.active) 
  currTetrimino = new I(currSpeed);
}
var currTetrimino = new I(currSpeed);

add pic ===>

Now you have a Tetris game that works perfectly, but it has only one type of Tetrimino. What we need to do next, obviously, is to implement the other six types of Tetriminoes. You can just model the other Tetriminoes after I. Some might take more work, as they have four directions, while the others, like O, which is unable to rotate, takes much less work to implement. I won't put those code in here, and you can download them from my google drive folder. The only thing I might need to point out is that, Z, instead of rotating on s_2, actually rotates in a way that all its squares change their position. The picture below shows how their position are changed.

Now we have created all the seven Tetriminoes. What to do next is to determine the sequence of Tetriminoes. We know that's implemented using some kind of random generator. But how random could that be? It could be as easy as giving the seven Tetriminoes equal odds to appear. I tried that and played for a while and it just didn't feel right. So I did a little bit research on the Internet. It turned out that different game developer uses different random generator. One common rondom generator is to generate a sequence of all seven one-sided tetrominoes (I, J, L, O, S, T, Z) permuted randomly, as if they were drawn from a bag. Then it deals all seven tetrominoes to the piece sequence before generating another bag. 

The code to implement the rondom generator.

var typeI = 0, 
    typeJ = 1, 
    typeL = 2, 
    typeO = 3, 
    typeS = 4, 
    typeT = 5, 
    typeZ = 6; 
var bag = [typeI, typeJ, typeL, typeO, typeS, typeT, typeZ];

var RandomGenOneBag = function(){
 if(bag.length == 0){
  bag = [typeI, typeJ, typeL, typeO, typeS, typeT, typeZ];
 }

 var index = ~~(Math.random()*bag.length)

 var ranNum = bag[index];
 bag.splice(index, 1);

 switch(ranNum)
  {
  case typeI:
    return new I(currSpeed);  
    break;
  case typeJ:
    return new J(currSpeed);
    break;
  case typeL:
    return new L(currSpeed);
    break;
  case typeO:
    return new O(currSpeed);
    break;
  case typeS:
    return new S(currSpeed);
    break;
  case typeT:
    return new T(currSpeed);
    break;
  case typeZ:
    return new Z(currSpeed);
    break;
  default:
    throw 'something wrong!'
  } 
}

This way, all types Tetriminoes will be selected (randomly) at least once in one iteration. Once the bag is empty, it's regenerated again.


Tuesday, June 25, 2013

Recreating Tetris using Javascript and HTML5 - Part 1

After learning three tutorials about HTML5/Javascript game,
Tutorial: Simple game with HTML5 Canvas
No Tears Guide to HTML5 Games
HTML5 Game Development Tutorial: Breakout
I decided to create a game by myself. The trend on the web is to recreate classic games, Pac Man, Snakes, Breakout, just name a few. For me, I decided to take a shot at Tetris. Tetris is so popular that everybody knows how to play and I don't even need to explain the rules. So let's start my little project right now. (Warning: this tutorial will not include the details like how to draw a line on html 5 canvas, you need to look it up the Internet).

Play the game

Before we start thinking about which data structure to use and writing any line of code,  let me give you a basic idea of developing a game, which is shared among all the three tutorials I've read. We all know how a cartoon is made - to make any object moving, we need at least two static pictures. By switching the two pictures fast enough, the object become dynamic in human eyes. Our game is made in the same way. There will always be a method, let's call it update() for now, that calculate the current position of all the objects on our canvas and another function draw() which draws the objects on canvas. By calling these two methods frequently enough, say 20 milliseconds a time, we can make objects moving around on the canvas.

Now you've got the basic idea of how a game is created, so let's start programming. First of all, we're gonna  need a canvas which displays our Tetris game. For a typical Tetris game, there should be 10 squares horizontally and 20 squares vertically. In our Tetris game, I'm going to make each square 20 pixel long, which maks our canvas 400 pixel high and 200 pixel wide.  This is enough for playing the game, but in a real game we also need some area to display the next Tetriminos that's gonna fall and game statistics like levels, scores and lines. So we need to widen our canvas a little bit to 300 pixel.

The initial html file and javascript file look like this
<!DOCTYPE html>
<html>
  <head>
      <meta http-equiv="Content-type" content="text/html; charset=utf-8">      <title>Tetris</title>
      <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>      <style type="text/css">
        body {
          margin:0px;
          padding:0px;
          text-align:center;
        }
        canvas {
          margin-left: auto;
          margin-right: auto;
          border:1px solid black;
        }
      </style>
  </head>
  <body>
    <h1>Tetris</h1>
    <canvas id="canvas" width="300" height="400"></canvas>
    <script src="game.js"></script>
  </body>
</html>

var WIDTH = 300, 
 HEIGHT = 400,
 c = document.getElementById('canvas'),
 ctx = c.getContext('2d');

setInterval(function(){
 clearCanvas();
 updatePosition();
 drawOnCanvas();
}, 1000/50);

var clearCanvas = function(){
 ctx.fillStyle = 'White';
 ctx.beginPath();
 ctx.rect(0, 0, WIDTH, HEIGHT);
 ctx.closePath();
 ctx.fill();
}

var drawLine = function(){
 ctx.beginPath();
 ctx.moveTo(200, 0);
 ctx.lineTo(200, 400);
 ctx.stroke();
}

var updatePosition = function(){
 
}

var drawOnCanvas = function(){
 drawLine();
}

In the html file, we create a canvas, whose width and height are 300px and 400px respectively. We also  link this html to an external javacript file, where we put all our game logic. Nothing fancy here except that you need to make sure you put your javacript link code "<script src="game.js"></script>" right before the closing tag of "body" element. Otherwise your javacript is gonna be executed before the html is loaded, resulting in an error. In the javascript file, we have three main functions, clearCanvas, updatePosition, and drawOnCanvas. They are all put in the function which is called once per 20 millisecond, implemented by the function setInterval(). This function (we will call it game loop throughout this tutorial), which we set as the first parameter of function setInterval(), acts just like the update function and draw function I've mentioned in the second paragraph. Within each call, we clear everything that's been drawn on the canvas, then update the position of all the objects that're gonna be drawn, and finally draw them on canvas. For now there is nothing in the updatePosition() function and only one function in drawOnCanvas() function, which basically just draw a black line to separate the gaming area and game statistics area.

For now, our Tetris Game looks like this.



















Everything looks good. Time to create Tetriminos? Not yet. We'll start with creating the squares that form all kinds of Tetriminos. After we create the square object, implementing Tetriminos will become much easier.

var speedLevels = [20, 16, 12, 10, 8], 
 currSpeed = speedLevels[0];

var Square = function(speed){
 var self = this;
 self.color = "Black";
        self.vPosition = 0;
self.hPosition = 4;
 self.speed = speed;
 self.temp = 0;

 self.fall = function(){
  if(self.temp == self.speed){
   self.vPosition++;
   self.temp = 0;
  }
  self.temp++;
 }

 self.draw = function(){
  console.log(self.vPosition*squareLength);
  ctx.fillStyle = self.color;  
                ctx.fillRect(self.hPosition*squareLength, self.vPosition*squareLength, squareLength, squareLength); } 
        return self;
}

In the code above, we create a function to generate squares. It takes one parameter, the speed, which decides the falling speed of the square. In this Tetris game, we have five levels. As the level goes up, the falling speed increases. You might notice that the speed of a higher level is lower than the speed of a lower level. That's because the number is actually not the falling speed, it is the number of times a square stays at a certain position. The longer it stays at one position, the slower it falls. After creating the "Square" function, we can now call the "fall" function in "updatePosition" function, and "draw" function in "drawOnCanvas" function. Now you have a square falling down from the top of the game area.

When the square reaches the bottom, it continues falling down to the invisible part of the canvas. This is not what we want, we want it to stay still when it reaches the bottom. Also we want the squares coming down one at a time, meaning at any point there should be only one active square. To achieve these, I added the following code.

var gameGrid = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]];

self.fall = function(){
 if(self.counter >= self.speed){
  if(self.checkFalling()){
   self.vPosition++;    
  }
  else{
   gameGrid[self.vPosition][self.hPosition] = 1;
   self.active = false;
  }
  self.counter = 0;
 }
 self.counter++;
}self.checkFalling = function(){
 if(gameGrid[self.vPosition+1][self.hPosition] == 1)
  return false;
 else
  return true;
}
var drawFixedSquares = function(){
 for(var i=0; i<20; i++){
  for(var j=0; j<10; j++){
   if(gameGrid[i][j] == 1){
    ctx.fillStyle = "Black";  
    ctx.fillRect(j*squareLength, i*squareLength, squareLength, squareLength);
   }
  }
 }
}
var generateNextSquare = function(){
 if(!currentSquare.active) 
  currentSquare = new Square(currSpeed);
}
var currentSquare = new Square(currSpeed);

To keep track of which part of the game area has been occupied, I created a 20*10 array called gameGrid. The number in the array can be 0, not occupied, or 1, occupied. I added an extra line at the bottom, which represents a line of fixed squares and locates right beneath the bottom line to prevent the falling square from sinking down beneath the bottom. Remember there are two properties of a square called vPosition and hPosition. They corresponds to the indices of a square in the gameGrid. For instance, if you have a square of vPosition equal to 4, and hPosition equal to 6. Then it is in position gameGrid[4][6]. The "checkFalling" function is added to the Square function. It checks if there is an fixed square right under the current postion of the calling square. The "fall" function of Square is getting modified, so that right before a square fall down to a lower height, it checks if there is already some fixed square there. If there is, this square is marked as inactive and corresponding position in gameGrid is marked as occupied. The drawFixedSquares function is added as a global function, which draws all the fixed squares we keep track of in gameGrid. We also need a global variable, currentSquare, to hold the current active square. And the function  generateNextSquare is used to generate the next square when the current square become inactive. You need to put generateNextSquare in our game loop.

setInterval(function(){
 clearCanvas();
 generateNextSquare();
 updatePosition();
 drawOnCanvas();
}, 1000/50);


Now when you run the game, you can see a square falling down from the top of the game area, and stops when it reaches the bottom. After the first square become inactive, comes the second square and then the third ...




















What we're going to do next is to make the square move to right or left when we push the right or left button. You can control your Tetriminos using four keys, up, down, left, right. The left, right button moves the Tetriminos to the left or right, the down button makes the Tetriminos fall faster. And the up button rotates a Tetrimino.


var monitorKeyboard = function (){
            $(document).keydown(function(evt){
              if(evt.which == 39){
    if(currentSquare.hPosition < 9 && !currentSquare.fixedSquareOnRight() )
       currentSquare.hPosition++;
              }
              else if(evt.which == 37){
    if(currentSquare.hPosition > 0 && !currentSquare.fixedSquareOnLeft() )
     currentSquare.hPosition--;
              }
              else if(evt.which == 40){
    if(currentSquare.vPosition < 19 && !currentSquare.fixedSquareUnder() )
     currentSquare.vPosition++;
              }
            });
         }

self.fixedSquareUnder = function(){
 if(gameGrid[self.vPosition+1][self.hPosition] == 1)
  return true;
 else
  return false;
}

self.fixedSquareOnRight = function(){
 if(gameGrid[self.vPosition][self.hPosition+1] == 1)
  return true;
 else
  return false;
}

self.fixedSquareOnLeft = function(){
 if(gameGrid[self.vPosition][self.hPosition-1] == 1)
  return true;
 else
  return false;  
}

To monitor user's input, I wrote a function called monitorKeyboard, which uses .keydown() to monitor a key down event. When player pushes the right key, it first checks if the square is right next to the right edge of the game area or if there is a fixed square at its right. If either of the two situation occurs, then the square cannot be moved to the right. Similar code applies to the left key and down key. You can call monitorKeyboard before the game loop. The other three function fixedSquareUnder, fixedSquareOnRight and fixedSquareOnLeft are added to the Square function. Now you can control the square by making it move to right, left, or fall faster. Experiment the new functionality by initializing the gameGrid with multiple 1's. See if the fixed squares prevent a falling square from moving to left, right or down.



















At this point, if there is a line which is full of fixed squares. The game does not do anything. In a complete Tetris game, when any line is full of fixed squares, we want it to be cleared, and any lines with fixed squares on higher levels moves downward. I wrote six functions to implement this logic.
var checkLinesToClear = function(){
 var fixSquareNum;
 var linesToClear = [];
 var counter = 0;

 for(var i=0; i<20; i++){
  fixSquareNum = 0;
  for(var j=0; j<10; j++){
   fixSquareNum += gameGrid[i][j];
  }
  if(fixSquareNum == 10){
   linesToClear[counter] = i;
   counter++;
  }
 }
 updateGameGrid(linesToClear); return counter;
}

var updateGameGrid = function(linesToClear){
 var len = linesToClear.length;
 var linesToMove; 

 if(len==0) return;

 for(var m=0; m<len; m++){
  clearLine(linesToClear[m]);
 }

 for(var i=19; i>-1; i--){
  if(!emptyLine(i)){
   linesToMove = levelsToMoveDown(linesToClear, i);   if(linesToMove>0)
     moveLineDown(i, linesToMove);
  }
 }
}

var emptyLine = function(currLine){
 for(var j=0; j<10; j++){
  if(gameGrid[currLine][j]==1){
   return false;
  }
 } 
 return true;
}

var levelsToMoveDown = function(linesToClear, currLine){
 var len = linesToClear.length;
 var counter = len;

 for(var i=0; i<len; i++){
  if(linesToClear[i]==currLine){
   return 0;
  }else if(linesToClear[i]>currLine)
  {
   return counter;
  }
  else{
   counter--;
  }
 } return counter;
}

var clearLine = function(lineNumber){
 for(var i=0; i<10; i++){
  gameGrid[lineNumber][i] = 0;
 }
}

var moveLineDown = function(lineNumber, lines){
 for(var i=0; i<10; i++){
  gameGrid[lineNumber+lines][i] = gameGrid[lineNumber][i];
 } 
 clearLine(lineNumber);
}

The checkLinesToClear function checks if there is any line that is full of fixed squares. And store the line number (0~19) of these lines into an array. It then calls function updateGameGrid, which clears the lines full of squares and moves all the lines on higher levels down. The game checks lines to clear each time a square becomes inactive, so checkLinesToClear is added to the fall function as shown below.

self.fall = function(){
 if(self.counter >= self.speed){
  if(!self.fixedSquareUnder()){
   self.vPosition++;    
  }
  else{
   gameGrid[self.vPosition][self.hPosition] = 1;
   self.active = false;
   checkLinesToClear();
  }
  self.counter = 0;
 }
 self.counter++;
}

The other functions are just helpers. emptyLine takes the line number (0~19) as argument and returns true if there are some fixed squares within that line, and false otherwise. levelsToMoveDown returns the number of levels a line need to move down to after some fully loaded lines are cleared. The number of levels to move down might be different for different lines. clearLine clear a fully loaded line, i.e. setting the corresponding positions in gameGrid to 0s. moveLineDown moves a line down by given levels.  Now you can experiment the new functionality by initializing the gameGrid with lines that just need one square to be cleared. Below is how I initialize the gameGrid.




When the falling square reaches the bottom, the bottom line is cleared, and concurrently all the lines with fixed squares move down one level.

Now we have a fully functional Tetris game (the square version). In the next tutorial, I'm going to show you how to create Tetriminos and implement the control over them based on what we've completed.