Home
Code & Snippets
Tutorials
GUI Docs
Projects
Drawings & Sketches
Links
Bio & Resume

Kindly hosted by


Support This Project


Tetris Tutorial: Part #3

Hello there and welcome to the third tutorial on how to make a decent Tetris game.
If you havenít read Tutorial #1 & Tutorial #2, please do so, otherwise you wonít understand most of this and next tutorials. So, letís get started.

This time we are going to introduce a falling figure with various time steps that a user can control. At the end, where the figure wonít be able to fall anymore, we are going to simply map itís blocks to game blocks array and drop some other figure Ė canít get any simpler!

So, letís get started.
If you take a look at a general tetris game, there are only two steps exist:

Step 1: Drop figure one line down
Step 2: Wait some little time if user considers to move a figure to left or right so it can fill the empty block space as shown in Picture 1.
---------------Repeat from Step 1---------------

Picture #1

Picture #1


We may also add a third step where user doesnít actually do anything Ė a step where the game do something when eliminating full lines Ė letís call it Step 3. So, hereís our new pipeline:

Step 1: Drop figure one line down
Step 2: Wait some little time if user considers to move a figure to left or right so it can fill the empty block space.
Step 3: Check if there are any full lines Ė if none, resume normal game state
Step 3.5: If there are, eliminate the bottom one neglecting any user input
Step 3.5.5: Go to step 3

Or in a pseudo-C++ form:

int LineFull = -1;
for (I=0; I<Number_of_lines; I++)
{
  if(LineIsFull(I))
  {
    LineFull = I;
    break;
  }
}

If(LineFull !=-1)
{
  // Do animations for that lineÖ
  }
  else
  {
    // Aquire user input
    // Etc
  }
}

Pretty simple as it seems here, but harder in actual implementation of the algorithm.
Letís introduce a function that will be checking for fullness of the line with given index:

bool CTetrisGame::LineIsFull(unsigned int line_index)
{
// Please note that here we use 'BlockSize' and 'BlockSpacing' for dynamic variables, not constant defines
// We will go into details of loading them later on.
  int num_blocks_x = Width(Window) / (BlockSize + BlockSpacing);
  for (int x=0; x<num_blocks_x; x++)
  {
    if(m_ppTetrisMatrix[line_index][x] == NULL)
      return false;
  }

  return true;
}

Also, we have to add a function in which we are going to do all state checks, line eliminations et cetera. Letís call it Update() and we are going to call it before actually drawing the whole thing.
One more idea: if a user would want to pause the game (e.g go to the menu screen), we are going to have to pause whole update process, but still draw it to the screen. So, let us introduce a variable into CTetrisGame class called m_eGameState that will keep track of our current game state. Also in file Common.h we add a definition for the variable TIME_AQUIRE_USER_INPUT that is equals to 500 Ė this variable will be the time (in milliseconds) that the user have to move figure left or right after the figure has fell one line down. To actually watch after the time passed and the current time, we add a variable in CtetrisGame called m_fLastTime, and we can retrieve the time by firstly declaring a timer variable as CTimer m_Timer as a member of aforementioned class.

Letís start with the hardest part - the Update() function:

void CTetrisGame::Update()
{
  if(m_eGameState == TetrisGame_Paused)
    return;

  int num_blocks_x = WINDOW_WIDTH / (BLOCK_SIZE + BLOCK_SPACING);
  int num_blocks_y = WINDOW_HEIGHT / (BLOCK_SIZE + BLOCK_SPACING);

  // If our game state is normal, proceed..
  if(m_eGameState == TetrisGame_Normal)
  {
   // Check the amount of time passed since last update
    double CurrentTime = m_Timer.getAbsoluteTime();
    if(CurrentTime - m_fLastTime >= TIME_AQUIRE_USER_INPUT)
    {
      // If a figure cannot be moved anymore downwardsÖ
      if(Move(DISPLACE_DOWN, true) == false)
      {
        // Check for full lines since there has to be no more user input
        for (int i=0; i<num_blocks_y; i++)
        {
          if(LineIsFull(i))
          {
          // There is a full line, assign the index and break from the loop
          m_iFullLine = i;
          m_eGameState = TetrisGame_EliminatingLine;
          break;
          }
        }
        // If thereís no full line, drop a new figure
        if(m_iFullLine == -1)
          DropRandomFigure();
      }
      else
      {
        // Our figure CAN be moved down, so move it now
        Move(DISPLACE_DOWN, false);
        // Update last time
        m_fLastTime = CurrentTime;
      }
    }
  }

  // If there are any full lines (from the check above) - animate that line until the end
  if(m_eGameState == TetrisGame_EliminatingLine)
  {
    // If AnimateLine() returns true, it means that all blocks have finished their current animation
    if(AnimateLine(m_iFullLine))
    {
      // Line blocks animations are complete, remove this line from game blocks array
      for (int i=0; i<num_blocks_x; i++)
        m_ppTetrisMatrix[m_iFullLine][i] = NULL;
  
      // Shift above lines down
      ShiftLines(m_iFullLine);
    
      // Restore memeber variables for next round
      m_eGameState = TetrisGame_Normal;
      m_iFullLine = -1;
    }
  }
}

Pretty hardÖ Ummm.. Letís see the way it goes.
Firstly we test if the game is paused, and if it is, we do nothing in Update() (e.g return Ė exit). Then we check for the time passed, and if it exceeds the defined value, we drop the figure one line down Ėand here where the trick lies. Since we do not change the game state, it simply goes again thru the same part it went before for the SAME amount of time - in our game it is 500 msecs before dropping figure again one line down.
Pretty tricky, in my old version Iíve used 3 phases (drop figure down, wait for user input, check for full lines), here we are using only 2! A much more elegant and simpler approach ?.
Okay, after weíre done with user input and figure dropping, we move onto lines checking. Here it is very simple Ė animate current line until all blocks animations are complete (AnimateLine() returns false), and after that remove that line blocks from the game blocks array and shift above lines down and restore game state and line index.

Letís move on. Now we need to add auxiliary functions: ShiftLines() and DropRandomFigure(). Iíve copied the code from InitGL() to create a figure (square) into DropRandomFigure() since thatís about what we need now - later on we add a random number generator to generate truly random figures. Letís see what its contents are:

void CTetrisGame::DropRandomFigure()
{
  CTetrisFigure *figure = new CTetrisFigure;
  figure->Create(4);
  
  int num_blocks_x = WINDOW_WIDTH / (BLOCK_SIZE + BLOCK_SPACING);
  int num_blocks_y = WINDOW_HEIGHT / (BLOCK_SIZE + BLOCK_SPACING);
  
  figure->GetBlock(0)->SetPosX(num_blocks_x / 2);
  figure->GetBlock(0)->SetPosY(num_blocks_y - 2);
  
  figure->GetBlock(1)->SetPosX((num_blocks_x / 2) + 1);
  figure->GetBlock(1)->SetPosY(num_blocks_y - 2);

  figure->GetBlock(2)->SetPosX((num_blocks_x / 2) + 1);
  figure->GetBlock(2)->SetPosY(num_blocks_y - 1);
  
  figure->GetBlock(3)->SetPosX(num_blocks_x / 2);
  figure->GetBlock(3)->SetPosY(num_blocks_y - 1);
  
  m_pCurrentFigure = figure;
  MapFigure(m_pCurrentFigure, true);
}

As I said, a copy & paste task.
Letís see how ShiftLines() function works:

void CTetrisGame::ShiftLines(unsigned int line_eliminated)
{
  int num_blocks_x = WINDOW_WIDTH / (BLOCK_SIZE + BLOCK_SPACING);
  int num_blocks_y = WINDOW_HEIGHT / (BLOCK_SIZE + BLOCK_SPACING);
  // Remeber, we are shifting lines
  // that are above the eliminated one
  for (int i=line_eliminated+1; i<num_blocks_x; i++)
  {
    // Shift this line down
    for (int j=0; j<num_blocks_x; j++)
    {
      // Move this line down with coordinates
      if(m_ppTetrisMatrix[i][j] !=NULL)
      m_ppTetrisMatrix[i][j]->Move(DISPLACE_DOWN, false);
      
      // Move line down in pointers (inside game blocks array)
      m_ppTetrisMatrix[i-1][j] = m_ppTetrisMatrix[i][j];
      // Eliminate moved line in pointers (inside game blocks array)
      m_ppTetrisMatrix[i][j] = NULL;
    }
  } 
}

And thatís it, weíre done! Now letís see how our game looks now on the screen!
Some minor things that are left Ė add these lines to InitiGL():

gTetrisGame.Create();
gTetrisGame.DropRandomFigure();

In Tutorial 4 we will go into loading our data from XML files. See ya later!