Get up to 80 % extra points for free! More info:

Lesson 8 - Tetris in MonoGame: Game Board

In the previous lesson, Tetris in MonoGame: Block Generator, we implemented the block generator for our Tetris game.

Today we're going to create another game object - the game board.

Game Board

We already have the level component, which is the main game scene, we got the random block generator, and the falling block object. But we're missing the game board.

It's an object that'll contain a two-dimensional array of the fallen blocks, a method that checks whether the falling block collides with the game board, a method that adds the fallen block into the array, a method that removes complete rows, and, of course, a method that renders all the tiles on the game board.

Let's add a GameBoard class to the project, make it public and add the necessary using statements:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

Fields

As with the block, we'll add a tiles array here:

private int[,] tiles;

Next, we'll add the width and the height of the game board (measured in tiles):

private int width;
private int height;

The last field will be the game board's rendering position in the level background:

private Vector2 position;

Clearing Tiles

Let's add a simple method that'll clear the game board by initializing the tiles array:

public void Clear()
{
    tiles = new int[width, height];
}

Constructor

Now we'll add a constructor, which will take the game board dimensions and position as parameters, and will initialize the game board instance:

public GameBoard(int width, int height, Vector2 position)
{
    this.width = width;
    this.height = height;
    this.position = position;
    Clear();
}

Placing a New Block

Another method will be InsertBlock(), which will return the spawn coordinates of a new block we pass as a parameter. This position will make the block appear at the center of the game board's top edge.

public Vector2 InsertBlock(Block block)
{
    return new Vector2(((int)width / 2) - 1, 0);
}

Nothing too complicated, we just divide the game board width by two to get the center. The method should consider various block shapes we pass to it, and sometimes set the Y coordinate to -1 because the block's first row may be empty. But for the sake of simplicity we'll omit that for now.

Merging the Block and the Board

Now let's implement the Merge() method, which will take a block as a parameter and add its tiles to the ones that are already fallen and became the fixed part of the game board. We'll loop through all the block's tiles, and in case any of the tiles isn't out of the game board's top edge (this can happen when the game is over, so we have to check it), we'll put it at its respective position among the game board fallen tiles.

public void Merge(Block block)
{
    // looping through all block tiles
    for (int j = 0; j < 4; j++)
        for (int i = 0; i < 4; i++)
        {
            // getting tile coordinates in the game board
            int x = Convert.ToInt32(i + block.position.X);
            int y = Convert.ToInt32((j + block.position.Y));
            // the tile isn't empty and isn't out of the game board
            if ((block.Tiles[i, j] > 0) && (y >= 0))
                tiles[x, y] = block.Tiles[i, j];
        }
}

A little awkward here is that we have to convert the real vector components to integers. But generally, using vectors is still a good choice.

Block Collision

Another important method that needs to be implemented is the Collision() method, which will return true if the block collides with any of the game board tiles. The method will take the block and its position as parameters. Although the block itself already contains its position, it'll come in handy to pass the position separately, because we're going to ask: "Would the block collide if it were at the position...".

Initially, we'll assume that there's no collision. So let's add a collision variable and set it to false. Then, as always, we'll loop through all the tiles:

public bool Collision(Block block, Vector2 position)
{
    bool block = false;
    for (int j = 0; j < 4; j++)
        for (int i = 0; i < 4; i++)

Now we have to deal with a rather complex condition, which we'll split into more parts to describe it. If the block tile isn't empty:

if ((block.Tiles[i, j] > 0) &&

and its position is out of the game board:

((j + position.Y >= height) || (i + position.X < 0) || (i + position.X >= width) ||

or it overlaps with any game board tile:

(tiles[Convert.ToInt32(i + position.X), Convert.ToInt32(j + position.Y)] > 0)))

then a collision occurred:

collision = true;

Finally, we'll return the result. The complete method code is as follows:

public bool Collision(Block block, Vector2 position)
{
    // we'll assume there is no collision
    bool collision = false;
    // looping through all block tiles
    for (int j = 0; j < 4; j++)
        for (int i = 0; i < 4; i++)
               // If the block tile isn't empty and
            if ((block.Tiles[i, j] > 0) &&
               // its position is out of the game board or
               ((j + position.Y >= height) || (i + position.X < 0) || (i + position.X >= width) ||
               // it overlaps any game board tile
               (tiles[Convert.ToInt32(i + position.X), Convert.ToInt32(j + position.Y)] > 0)))
                    // then a collision occurred
                    collision = true;
    return collision;
}

All we need now is removing complete rows and rendering.

Removing Rows

The RemoveRows() method will remove all complete tile rows in the game board, and of course move down the tiles that are above the row. The return value will be of the int type and it'll indicate how many rows have been removed. This will be important to calculate the score later.

First we'll create a counter of removed rows and set it to 0. Furthermore, we'll loop through all game board rows. Finally, we'll return the counter value:

public int RemoveRows()
{
    // complete rows check
    int count = 0;
    for (int count = 0; count < height; count++)
    {

    }
    return count;

In each loop iteration, we have to find out whether the row is complete. At first we'll assume that the current row is complete. Then we'll go through each of the row tiles, and if we find one that is empty, we'll conclude that the row isn't complete:

// let's assume the row is complete
bool complete = true;
// if there is any tile empty, it's not complete
for (int i = 0; i <= (width - 1); i++)
    if (tiles[i, row] == 0)
        complete = false;

In the case there's a complete row, all above rows need to be moved down by one tile and the complete rows counter has to be incremented. We'll start from the bottom:

// complete row removing
if (complete)
{
    // moving down the above rows
    for (int j = (row - 1); j > 0; j--)
        for (int i = 0; i < width; i++)
            tiles[i, j + 1] = tiles[i, j];
    count++;
}

Just to make sure, here's the whole method code:

public int RemoveRows()
{
    // complete rows check
    int count = 0;
    for (int row = 0; row < height; row++)
    {
        // let's assume the row is complete
        bool complete = true;
        // if there is any tile empty, it's not complete
        for (int i = 0; i <= (width - 1); i++)
            if (tiles[i, row] == 0)
                complete = false;
        // complete row removing
        if (complete)
        {
            // moving down the above rows
            for (int j = (row - 1); j > 0; j--)
                for (int i = 0; i < width; i++)
                    tiles[i, j + 1] = tiles[i, j];
            count++;
        }
    }
    return count;
}

Rendering the Game Board

Let's add the rendering method. There's nothing complicated about it, the game board is rendered exactly the same as the block:

public void Draw(SpriteBatch spriteBatch, Texture2D[] sprites)
{
    for (int j = 0; j <= (height - 1); j++)
        for (int i = 0; i <= (width - 1); i++)
            if (tiles[i, j] != 0)
                spriteBatch.Draw(sprites[tiles[i, j] - 1], new Vector2(position.X + i * sprites[0].Width, position.Y + j * sprites[0].Height), Color.White);
}

The GameBoard class is now complete.

In the next lesson, Tetris in MonoGame: Functional Game Core, we'll make the game functional and try to play it :)


 

Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.

Download

By downloading the following file, you agree to the license terms

Downloaded 34x (15.74 MB)
Application includes source codes

 

Previous article
Tetris in MonoGame: Block Generator
All articles in this section
Tetris From Scratch
Skip article
(not recommended)
Tetris in MonoGame: Functional Game Core
Article has been written for you by David Capka Hartinger
Avatar
User rating:
3 votes
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn university David learned IT at the Unicorn University - a prestigious college providing education on IT and economics.
Activities