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

Kindly hosted by


Support This Project


Tetris Tutorial: Part #4

Hello there and welcome to the third tutorial on how to make a decent Tetris game.
If you haven’t read previous tutorials please do so, otherwise you won’t understand most of this and next tutorials.

Hello there and welcome to the fourth tutorial on how to make a decent Tetris game!
If you haven’t read previous tutorials please do so, otherwise you won’t understand most of this and next tutorials.

In this tutorial we will be going into the basics of data loading from XML files. So, let’s get started.

This function is going to be our main loading function. It has 2 parameters – filename of the XML file or the XML node that contains all the data required for this object.

virtual int LoadFromFile(char *filename, TiXmlNode *node);

Let’s add this function to the CBlock, CTetrisFigure and CTetrisGame classes
and implement them.
The CBlock (simplest from above) will require only a few attributes from the XML file, the X/Y Position of the block and the Animations list (we will go into that in our next tutorial).

Let’s take a look on how the basic XML file looks like:

<Game BlockSize="20" BlockSpacing="1" Window="0,640,480,0">
<Figure Name="SimpleBlock" Type="Template">
<Block PosX="0" PosY="0"/>
</Figure>
<Figure Name="X" Type="Template">
<Block PosX="0" PosY="0"/>
<Block PosX="1" PosY="0"/>
<Block PosX="-1" PosY="0"/>
<Block PosX="0" PosY="1"/>
<Block PosX="0" PosY="-1"/>
</Figure>
<Figure Name="Line" Type="Template">
<Block PosX="0" PosY="0"/>
<Block PosX="1" PosY="0"/>
<Block PosX="-1" PosY="0"/>
</Figure>
...
...
< /Game>

So, our tetris block here will only contain it’s position’ offset from the center block. From now on we will consider center block’ position as (Number of Blocks X / 2, Number of Blocks Y – 1). So, the block with offset 1,1 will be at top right position of the center block.

Also, from this file we can see that each figure listed here is a template. We will have a database of default figures (and blocks later) that we can access and retrieve figures based on random index – hence, random figures! Also, it’s much faster to actually copy the memory than to open and close the file, read/seek it all over again each time we want to drop a new random figure. So, each figure will contain a name and a type, although its not much of a use for us right now.

The game node will contain some useful data such as block size and spacing (instead of #define’s in the header file) and also the game window rectangle (we will get into that later).

Not so hard, but remember, that the XML file have to have correct syntax in order for our program (or any other) to actually use it. If you are not sure if there are any errors, open the XML file with Internet Explorer, and it will display where and what kind of error has occured (if there is any).

OK, let’s take a look at CtetrisBlock::LoadFromFile() function:

int CTetrisBlock::LoadFromFile(char *filename, TiXmlNode *this_node)
{
  TiXmlNode *main_node = NULL;
  
  // if a user passed the filename, try to open and parse it
  TiXmlDocument doc(filename);
  if(filename !=NULL)
  {
    bool loadOkay = doc.LoadFile();
    
    if ( !loadOkay )
      return false;
    
    // if file parsed successefully, assign pointer to the first child of the document.
    main_node = doc.FirstChild();
  }
  else	// No, user hasn’t given any file for us to read
    main_node = this_node;
  
  const char *value = NULL;
  TiXmlElement* element = NULL;
  // Convert node to element so we can read its attributes
  element = main_node->ToElement();
  
  Make sure that the block XML data MUST have those basic attributes - X & Y position
  if(element->Attribute("PosX") == NULL && element->Attribute("PosY") == NULL)
    return 0;

  value = element->Attribute("PosX");  // Read position balues
  if(value !=NULL)   // Convert string to number
  {
    m_iPosX+= atoi(value);
    value = NULL;
  }

  value = element->Attribute("PosY");
  if(value !=NULL)  // Convert string to number
  {
    m_iPosY+= atoi(value);
    value = NULL;
  }
  
  return 1;
}

OK, simple so far. Let’s take a look on the CTetrisFigure::LoadFromFile() function:

int CTetrisFigure::LoadFromFile(char *filename, TiXmlNode *this_node)
{
  // Same thing as in CTetrisBlock, ensuring that we don’t miss filename passed
  TiXmlNode *main_node = NULL;
  
  TiXmlDocument doc(filename);
  if(filename !=NULL)
  {
    bool loadOkay = doc.LoadFile();
    
    if ( !loadOkay )
      return false;
    
    main_node = doc.FirstChild();
  }
  else
    main_node = this_node;

  // Remember that a figure is simply an array of blocks, 
  // so it might not have any additional attributes than those blocks
  const char *value = NULL;
  TiXmlElement* element = NULL;
  element = main_node->ToElement();
  
  // Traverse children of our current XML data node in search for a child with name of ‘Block’
  TiXmlNode *child_node = NULL;
  child_node = main_node->FirstChild();
  
  // Count number of blocks in the figure by counting the children with name of ‘block’
  int num_blocks = 0;
  
  while(child_node !=NULL)
  {
    element = child_node->ToElement();
    
    if((stricmp(child_node->Value(), "block") == 0) && (stricmp(element->Attribute("Type"), "template") == 0))
      num_blocks++;
    
    child_node = main_node->IterateChildren(child_node);
  }
  
  // Re-create figure with given number of blocks
  Destroy();
  Create(num_blocks);
  
  // Load those blocks into our newly re-created figure
  int count = 0;
  while(child_node !=NULL)
  {
    element = child_node->ToElement();
  
    if((stricmp(child_node->Value(), "block") == 0) && (stricmp(element->Attribute("Type"), "template") == 0))
    {
      if(!m_ppBlocks[count]->LoadFromFile(NULL, child_node))
        return -1;
    }
    
    child_node = main_node->IterateChildren(child_node);
  }
  
  // All went OK, so return 1
  return 1;
}

What we are doing here is going thru the XML data and searching for children elements with name of ‘block’; if there is such a child, we count it and load it after.
And, at last, we have come to our CTetrisGame::LoadFromFile(). This one is the longest, although don’t you think that it’s the hardest part! ?

int CTetrisGame::LoadFromFile(char *filename, TiXmlNode *this_node)
{
  TiXmlNode *main_node = NULL;
  
  TiXmlDocument doc(filename);
  if(filename !=NULL)
  {
    bool loadOkay = doc.LoadFile();
    
    if ( !loadOkay )
      return false;
    
    main_node = doc.FirstChild();
  }
  else
    main_node = this_node;
  
  const char *value = NULL;
  TiXmlElement* element = NULL;
  element = main_node->ToElement();
  
  // Make sure that these are a MUST attributes of a game, and if there’s 
  // no string for that attribute, return 0 (negative)
  // Look for this attribute to know how big our block is going to be in this 
  // game (remember, there can be two or more games running simultaneously on the screen)
  value = element->Attribute("BlockSize");
  if(value !=NULL)  // Convert string to number
  {
    m_iBlockSize = atoi(value);
    value = NULL;
  }
  else 
    return 0;
  
  value = element->Attribute("BlockSpacing");
  if(value !=NULL)  // Convert string to number
  {
    m_iBlockSpacing = atoi(value);
    value = NULL;
  }
  else
    return 0;
  
  // Load game local window coordinates (we will use it later on)
  value = element->Attribute("Window");
  if(value !=NULL)
  {
    sscanf(value, "%d,%d,%d,%d", &m_GameWindow.m_iLeft, &m_GameWindow.m_iRight, &m_GameWindow.m_iTop, &m_GameWindow.m_iBottom);
    value = NULL;
  }
  else
    return -1;
  
  // Count the number of template figures/blocks by going thru XML data 
  // and counting figures with ‘type’ attribute equal to ‘template’
  TiXmlNode *child_node = NULL;
  child_node = main_node->FirstChild();
  
  while(child_node !=NULL)
  {
    element = child_node->ToElement();
    
    if((stricmp(child_node->Value(), "figure") == 0) && (stricmp(element->Attribute("Type"), "template") == 0))
      m_iNumTemplateFigures++;
    else if(stricmp(child_node->Value(), "block") == 0)
      m_iNumTemplateBlocks++;
  
    child_node = main_node->IterateChildren(child_node);
  }
  
  // Allocate array for those figures...
  if(m_iNumTemplateFigures > 0)
    m_pTemplateFigures = new CTetrisFigure[m_iNumTemplateFigures];
  else
    return 0;
  
  // Load those template figures…
  child_node = main_node->FirstChild();
  int figure_count = 0, block_count = 0;
  
  while(child_node !=NULL)
  {
    if((stricmp(child_node->Value(), "figure") == 0) && (stricmp(element->Attribute("Type"), "template") == 0))
    {
      if(m_pTemplateFigures[figure_count].LoadFromFile(NULL, child_node))
        figure_count++;
      else
        return 0;
    }
    else if(stricmp(child_node->Value(), "block") == 0)
    {
      if(m_pTemplateBlocks[block_count].LoadFromFile(NULL, child_node))
        figure_count++;
      else
        return 0; 
    }
    
    child_node = main_node->IterateChildren(child_node);
  }
  
  // Set global variables for this game – block size and width.
  gBlockSize = m_iBlockSize;
  gBlockSpacing = m_iBlockSpacing;
  
  gGameWindow = m_GameWindow;
  
  gNumBlocksX = gGameWindow.Width() / (gBlockSize + gBlockSpacing);
  gNumBlocksY = gGameWindow.Height() / (gBlockSize + gBlockSpacing);
  // OK, variables are set
  Destroy();
  Create();
  
  return 1;
}

Pheww! That was quite a big chunk of code!
One more word about those global variables. I’ve been struggling a lot with them (e.g. how and where to place them, maybe encapsulate – or not) and finally I decided to do the following thing – each game before rendering is going to simply set those variables so that their figures and blocks will know how to draw themselves by taking those values!
Pretty simple, yet elegant.
Well, the rest stays the same way it was, except now we’re going to replace our old code with the new one in InitGL() function to actually load our data from XML file:

if(gTetrisGame.LoadFromFile("game.xml", NULL))
  gTetrisGame.DropRandomFigure();

In Tutorial 5 we will go into loading animations from XML file and actually displaying them - you have to agree that it’s kind of boring playing with simple coloured blocks the WHOLE game.