MATLAB Tetris – The Main Algorithm


As I promised (after more than a week), here it is, the explanation for my MATLAB Tetris code.

Board Concept and Its Representation

Since Tetris will involve a board or field of blocks, how to represent those fields in the code must be determined first before writing the code. This ‘field’ array will be modified throughout the game and will be used for visualization. For simplicity, I used a standard 20×10 array for this ‘field’ array (because Tetris field is 20 blocks in height and 10 blocks in width). However, to avoid confusion when visualizing the game, I let the first row of the ‘field’ array represent the most-upper blocks on the field instead of the most-lower blocks, and so on. In this ‘field’ array, each elements will represent whether a block in the field is occupied or not and what color that occupied in. 0 means the block is not occupied, while a number of 1 to 7 respectively represent a different block color.

Aside from that, a representation of the falling Tetris object, the shape database, and the color database must also be determined. Since tetris object is always consist of four blocks (hence, the name is Tetris), I decided to use three 1×4 arrays to represent the falling object, ‘r_current’, and ‘color_current’. Each element on those arrays respectively represent each falling block position in field row and column and it color. Thus, as the database for the falling blocks’ shape, the following constant variable are declared in cells format as follows:

shapemodifier_x{1}=[3,4,1,2];
shapemodifier_y{1}=[1,1,1,1];
shapemodifier_x{2}=[2,2,3,2];
shapemodifier_y{2}=[2,3,1,1];
shapemodifier_x{3}=[2,2,3,2];
shapemodifier_y{3}=[2,3,2,1];
shapemodifier_x{4}=[2,2,3,2];
shapemodifier_y{4}=[2,3,3,1];
shapemodifier_x{5}=[3,2,2,3];
shapemodifier_y{5}=[2,2,3,1];
shapemodifier_x{6}=[2,3,3,2];
shapemodifier_y{6}=[2,2,3,1];
shapemodifier_x{7}=[2,2,3,3];
shapemodifier_y{7}=[1,2,2,1];
shapemodifier_x{8}=[2,2,2];
shapemodifier_y{8}=[2,3,1];

By using this cell formatted shape database, I can easily created random falling block shape by creating random integer as the cell index.

To represent a color, similar variable is also declared, but array format is used instead of cell format. In this variable, each row represent a color code where the columns respectively represent red, green, and blue color intensity. This variable will be used later to visualize the game.

colormodifier=[1,0,0;...
               0,1,0;...
               0,0,1;...
               1,1,0;...
               1,0,1;...
               0,1,1;...
               1,1,1];

The General Gameplay

As in the previous game, this game also uses while loop as its main loop for the gameplay. However, unlike the other game, I use two while loops (one is nested) for tetris. The main  (outer) loop is will be used to cycle the game until it ends (game over), while the nested loop is used to accomodate the falling of one Tetris object from the top to the bottom. Since usually Tetris game provide a preview of the next upcoming block, the main loop will be initialized by declaring the next falling Tetris object (its shape and color) randomly. In the main loop, the current falling object is declared from the next falling object and a new next falling object is selected again, randomly. Next, the current falling object is placed in the field and a check is done to see if the blocks has reached the top of the field, hence, game over. If it is not, then the nested (inner) loop is executed to move the falling object down while the player move it to the left or right, rotate it, or even color swap it. After the falling object reach the bottom, the nested loop will be exitted and continued with a check to see that a complete block set has been made (The definition of complete block set differs for each game mode. Check out the MATLAB Tetris preview to see and understand the three different game mode). If yes, the field must be re-modified accordingly (also differs for each game mode). The following code shows how the main (outer) loop is laid out:

%Creating first tetris block as loop initialization
switch gamemode
  case 1
    shape_next=ceil(7*rand);
    color_next=7*ones(1,4);
  case 2
    shape_next=ceil(7*rand);
    color_next=ceil(7*rand)*ones(1,4);
  case 3
    shape_next=ceil(7*rand);
    color_next=ceil(7*rand(1,4));
end
%Performing loop for the game
gamestat=true;
while gamestat
  %Setting next tetris block as current tetris
  shape_current=shape_next;
  color_current=color_next;
  %Generating the next tetris block randomly
  switch gamemode
    case 1
      shape_next=ceil(7*rand);
      color_next=7*ones(1,4);
    case 2
      shape_next=ceil(7*rand);
      color_next=ceil(7*rand)*ones(1,4);
    case 3
      shape_next=ceil(7*rand);
      color_next=ceil(7*rand(1,4));
  end
  %Creating preview for the next tetris block
  c_next=shapemodifier_x{shape_next};
  r_next=shapemodifier_y{shape_next};
  %Drawing preview
  preview=addblock2field(preview_nt,r_next,c_next,color_next);
  graphic_p=drawfield(preview,[3,4],square,squaresize);
  imshow(graphic_p,'Parent',previewaxes)
  %Placing current tetris block in the field
  c_current=shapemodifier_x{shape_current}+3;
  r_current=shapemodifier_y{shape_current};
  %Checking for game over
  if ~checkblockposition(r_current,c_current,field)
    %Stopping game
    gamestat=false;
    break
  end
  %Moving tetris block
  playstat=true;
  pausestat=false;
  while playstat
    ... #To be explained
  end
  %Checking block position and updating field
  if (gamemode==1)||(gamemode==2)
    %Updating tetris block into field permanently
    field=addblock2field(field,r_current,c_current,...
                         color_current);
  elseif gamemode==3
    %Updating tetris block into field permanently
    field=addblock2field(field,r_current,c_current,...
                         color_current);
    %Forcing hanging blocks to fall down
    field=forcefalldown(field);
  end
  %Checking field for complete block
  if (gamestat==1)||(gamestat==2)
    ... #To be explained
  elseif gamestat==3
    ... #To be explained
  end
end

As stated previously, in this code, three variables (‘r_current’, ‘c_current’, and ‘color_current’) were used to define the falling object’s condition. These variables were initialized by choosing randomly from the ‘shapemodifier_x’ and ‘shapemodifier_y’ array declared previously. Before the nested loop begin, those three variables represent the falling object’s position at the middle top of the field (that’s why ‘c_current’ is initialized by adding it by three, that is to make the falling block placed in the middle top of the field). In the nested loop (the while playstat loop), these three variables will be updated over time, as it slowly falls down and as player move/rotate it. At the end of the nested loop, these three variables will represent the falling object’s condition at the most bottom part of the field (final condition). Then those final conditions are used to update the ‘field’ array by using addblock2field function. Because of simple representation of ‘field’ array and the falling object’s variables, the addblock2field function is simple and can be written in five lines as shown below. This means that the falling blocks has been added permanently to the ‘field’ array (Later when explaining the nested while loop, you’ll understand why I say permanently). After the falling blocks are permanently added, a check was done to see if there is a complete block set.

function field=addblock2field(field,row,col,color)
  for dotID=1:numel(row)
    field(row(dotID),col(dotID))=color(dotID);
  end
end

Notice that in the main while loop above, several local function was used other than addblock2field function. forcefalldown function is used to force all hanging blocks to fall down in the third game mode (hence no hanging blocks in this game mode), while checkblockposition checks the validity of the falling object’s position (thus, preventing stacking two blocks in one position). Aside from that, a special local function, drawfield, is also used to generate an image visualization of the current ‘field’ array. These local functions will be described later.

Falling Down The Tetris Object

In the main while loop, the falling objects is placed at the top of the field, somewhat enters the nested loop, and then comes out the final position of the falling objects. What happened when the falling objects slowly falls down and being moved/rotated by player is accommodated in this nested loop. In this nested loop, for each single loop, the falling blocks position was moved downward by one block (hence, slowly falling). However, to allow player to control the falling blocks, another two while loops are executed before the falling blocks are moved downward. The first while loop is the pause loop, and as the name suggest, it is created to accommodate game pause. The second one is the control loop, where in this loop, for a set amount of time (depending on the game speed), the downward moving is delayed, thus providing player control over the falling blocks. For every action done in this control loop, a check is done to make sure that the action does not make the falling blocks go outside the field or stack over the occupied blocks. If it does not, the action is approved and the falling block’s variables (‘r_current’, ‘c_current’, and ‘color_current’) is updated. Then, the visualization was updated by redrawing the field, however, it should be noticed the ‘field’ array is not updated yet. This is done because ‘field’ array is the reference for the checkblockposition function. Thus, it will only be updated outside this nested loop, after the falling blocks reaches the bottom. A set amount of time (and a set of control chance) the control loop is exited, the falling blocks are moved downwards by one, and the nested while loop continues from the top until the falling blocks can be no longer moved downwards (hence, it has reached the bottom).

while playstat
  %Creating infinite loop to accomodate game pause
  while pausestat
    pause(0.01)
  end
  %Creating and displaying temporary field
  tempfield=addblock2field(field,r_current,c_current,...
                           color_current);
  %Drawing graphic
  graphic=drawfield(tempfield,fieldsize,square,squaresize);
  imshow(graphic,'Parent',mainaxes)
  %Providing loop to move the current tetris block
  loopID=0;
  maxloop=max(gamefps-gamespeed,1);
  while loopID<maxloop
    %Creating checkpoint for time counter
    tic
    %Calculating future position
    switch movestat
      case 'none'
        r_temp=r_current;
        c_temp=c_current;
      case 'left'
        r_temp=r_current;
        c_temp=c_current-1;
        pause(1/controlspeed)
      case 'right'
        r_temp=r_current;
        c_temp=c_current+1;
        pause(1/controlspeed)
      case 'rotatecw'
        [c_temp,r_temp]=rotate90cw(c_current,...
                                   r_current,...
                                   c_current(1),...
                                   r_current(1));
        pause(1/controlspeed)
      case 'rotateccw'
        [c_temp,r_temp]=rotate90ccw(c_current,...
                                    r_current,...
                                    c_current(1),...
                                    r_current(1));
        pause(1/controlspeed)
      case 'forwardcolorshift'
        r_temp=r_current;
        c_temp=c_current;
        col_temp=[color_current(:,end),...
                  color_current(:,1:end-1)];
        color_current=col_temp;
        pause(1/controlspeed)
      case 'backwardcolorshift'
        r_temp=r_current;
        c_temp=c_current;
        col_temp=[color_current(:,2:end),...
                  color_current(:,1)];
        color_current=col_temp;
        pause(1/controlspeed)
      end
    %Checking future position
    if checkblockposition(r_temp,c_temp,field)
      r_current=r_temp;
      c_current=c_temp;
    end
    %Updating tempfield
    tempfield=addblock2field(field,r_current,c_current,...
                                   color_current);
    %Drawing graphic
    graphic=drawfield(tempfield,fieldsize,...
                      square,squaresize);
    imshow(graphic,'Parent',mainaxes)
    %Controlling gamespeed
    pause(1/gamefps)
    %Advancing loop
    if accelstat
      break
    else
      loopID=loopID+1;
    end
  end
  %Moving the current tetris block downward
  r_current=r_current+1;
  %Stopping the current tetris block
  if ~checkblockposition(r_current,c_current,field)
    r_current=r_current-1;
    playstat=false;
  end
end

It should be noticed that in this nested loop, two variables, ‘pausestat’ and ‘movestat’, exist without being declared previously. These variables are global variables and their values are modified by the KeyPressFcn. The value of ‘pausestat’ are false initially and will become true if the player presses pause button. The similar thing goes with the ‘movestat’. Its value will change accordingly with player’s control. This is how player control the falling blocks.

The Check

After the falling blocks is placed into the field, ‘field’ array is updated permanently. A check is needed to see if a complete block set has been made. If a complete block set has been made, those block set must be eliminated and the field must be updated and modified due to those blocks elimination (for example, forcing all blocks to uniformly fall down on game mode 1). After, field modification, another check has to be made again to see if another complete block set has been made. This is what usually called chain or combo (And what makes the game interesting). However, this means that another loop is necessary.

For the first and the second game mode, the checking part are as written below. First a check was done by simple checking if there is a row in the ‘field’ array that has no zero element values. If no, the check is ended and the flow goes back to the beginning of the main loop. If yes, the blocks on those rows will be eliminated and moving the blocks on the upperside of it downward to fill the gap. Especially for game mode 2, there is a chance that unglued hanging blocks occurs after a line removal. This blocks has to fall down, as it is shown in the 2:45-2:50 of the second video of MATLAB Tetris. To accomodate this feature, forcefalldown_obj is written. After this, another loop is done to check if there is another complete block set for chain/combo, and so on.

%Preallocating variable for chain-checking
checkstat=true;
n_chain=0;
%Performing loop check
while checkstat
  %Checking for complete line
  statarray=(field~=0);
  rowID=find(sum(statarray,2)==fieldsize(2));
  %Exitting loop if no complete blockset detected
  if isempty(rowID)
    break
  else
    n_chain=n_chain+1;
  end
  %Removing completed line and moving the upperside block down
  if ~isempty(rowID)
    for lineID=1:numel(rowID)
      field(2:rowID(lineID),:)=field(1:rowID(lineID)-1,:);
      graphic=drawfield(field,fieldsize,...
                        square,squaresize);
      imshow(graphic,'Parent',mainaxes)
      pause(2/animationspeed)
    end
  end
  %Adding and updating score
  if ~isempty(rowID)
    score=score+(sum(1:numel(rowID)));
    set(scoretext,'String',score)
  end
  %Forcing unglued blocks to fall down
  if gamemode==2
    loopstat=true;
    while loopstat
      tempfield=forcefalldown_obj(field);
      if isequal(tempfield,field)
        loopstat=false;
      else
        field=tempfield;
      end
    end
  end
  %Preventing continuous acceleration
  accelstat=false;
  movestat='none';
end

For the third game mode, a block set is considered complete block set if three or more same colored blocks are put together side-by-side (not diagonally), instead of completing a row. Thus, to find these complete block set another function, findcolorset is written. After that, those complete block set are eliminated and the ‘field’ array is updated after all hanging blocks are force to fall down by forcefalldown function. After this, another loop is done to check if there is another complete block set for chain/combo, and so on.

%Preallocating variable for chain-checking
checkstat=true;
n_chain=0;
%Performing check loop
while checkstat
  %Checking color block connectivity
  [r_obj,c_obj]=findcolorset(field,csetsizereq);
  n_obj=numel(r_obj);
  r_obj=cell2mat(r_obj);
  c_obj=cell2mat(c_obj);
  %Exitting loop if no complete blockset detected
  if n_obj==0
    break
  else
    n_chain=n_chain+1;
  end
  %Removing complete blockset
  for blockID=1:numel(r_obj)
    field(r_obj(blockID),c_obj(blockID))=0;
  end
  %Drawing graphic
  graphic=drawfield(field,fieldsize,...
                    square,squaresize);
  imshow(graphic,'Parent',mainaxes)
  pause(2/animationspeed)
  %Forcing hanging blocks to fall down
  field=forcefalldown(field);
  %Drawing graphic
  graphic=drawfield(field,fieldsize,...
                    square,squaresize);
  imshow(graphic,'Parent',mainaxes)
  pause(2/animationspeed)
  %Adding and updating score
  if n_obj~=0
    score=score+(n_chain*n_obj*numel(r_obj));
    set(scoretext,'String',score)
  end
  %Preventing continuous acceleration
  accelstat=false;
  movestat='none';
end

These three functions, findcolor, forcefalldown, and forcefalldown_obj, are actually the one that enables this Tetris game to have chain/combo play or whatever it is called. However, these function are somewhat complicated (Yeah, I spend most of the time debugging these function) and related to image processing. Thus, I decide that I’m going to explain more about these functions in another separated post. I also noticed that I haven’t explained anything about the drawfield function and other ‘hidden’ function that are related to the game visualization. However, I’m also gonna cover that in another post. These post will be focusing just on the main game flow.

So, having that said, I think that this post (after a week) is finally finished. So, I’m gonna end this long post now and back to my online game:D.

To be continued….

This entry was posted in Uncategorized and tagged , , . Bookmark the permalink.

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