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

Lesson 7 - Tetris in MonoGame: Block Generator

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

Today we're going to create a random block generator, more precisely the generator of block patterns.

Patterns File

We'll store the block patterns in a text file. It's a much more convenient and elegant solution than putting it directly into source code, which should contain the game logic only. All such data, like maps or longer texts, should be stored in external files. The file should be secured with a hash or compiled directly into the .exe file of the game, to prevent players from editing it and using it for cheating (by forcing the game to generate easy bricks only). For the sake of simplicity, we'll just give the file very unusual name, so that no one would search for the block patterns in it :)

We'll add a new text file into the Robotris project (by adding a new item the same way as we always do, see the picture) and name it netfx.dll:

Add a new file into the project - Tetris From Scratch

We'll put block patterns as 4 lines and leave the 5th line empty. The computer doesn't care about it, but it's important to us to read the file more easily. Paste the following data into the file:

1000
1110
0000
0000

0010
1110
0000
0000

0000
0110
0110
0000

0000
1111
0000
0000

0100
1110
0000
0000

0000
1100
0110
0000

0000
0110
1100
0000

0000
0100
0000
0000

1010
0100
1010
0000

0100
1110
0100
0000

1110
0100
0100
0000

0000
0110
0000
0000

The first 7 block patters are from the classic Tetris game, more precisely the Tetris version A, which is also what we're programming. The next ones are new block patterns that are only present in the file, but we won't be using them in this course. You can then improve the game and use them as you want - in different game mode or start spawning them from a certain level.

The last thing that needs to be done is to check the netfx.dll file in Solution Explorer and then set the Copy to Output Directory property to Copy always in the Properties window:

Files in Visual Studio in the application folder - Tetris From Scratch

This will ensure that Visual Studio will add this file to the compiled game folder, from where we'll be able to load it. Everything is now ready for the generator.

Block Generator

The block generator will load all the block patterns from the text file into a pattern collection, and then randomly pick one of those patterns and use it to generate a new block.

We'll add a new class named BlockGenerator and set its access modifier to public:

public class BlockGenerator

We'll add a few fields - the patterns 4x4 int array, which we're already familiar with, the file field that'll specify the patterns file path, and the random number generator instance.

private List<int[,]> patterns;
private string file;
private Random randomGenerator;

We'll then initialize the fields in a constructor, which will take the patterns file path as a parameter:

public BlockGenerator(string file)
{
    patterns = new List<int[,]>();
    randomGenerator = new Random();
    this.file = file;
}

Parsing the File

Let's move to the file parsing. We'll create a LoadBlocks() method. Here we'll be opening the text file to read it:

public void LoadBlocks()
{
    using (StreamReader sr = new StreamReader(file))
    {

    }
}

Don't forget to add the following using statement (now I mean to add it at the very beginning of the source code):

using System.IO;

Into the method's using block, we'll add several variables before we start reading the file:

string line;
int[,] pattern = new int[4, 4];
int j = 0;
int lineNumber = 1;

The line variable is the last read line of the text file. The pattern variable is the usual 4x4 array in which we'll store each pattern loaded from the file. j is the vertical coordinate of the pattern array, while i is the horizontal one. lineNumber indicates which line is currently being read.

Let's start reading the file and add the following code right after the variables initialization:

while ((line = sr.ReadLine()) != null)
{
    // skip every 5th line
    if ((lineNumber % 5) != 0)
    {

    }
    lineNumber++;
}

The while loop keeps reading every line until it reaches the end of the file. The further code is executed only if the line number isn't divisible by five. That's because every 5th line is empty.

Let's go into the condition block and store the current line into the appropriate row of the pattern array:

for (int i = 0; i < 4; i++)
    pattern[i, j] = int.Parse(line[i].ToString());

We'll convert all the 4 characters in the line to string, and then parse them into the int type. We'll store the value in the pattern at the current coordinates.

Now we only have to utilize the j variable, which will be used to specify the pattern row. However, once it exceeds the value of 3 (the 4th, last line), we must reset it, add the pattern to the patterns collection, and then reset the pattern as well:

if (j > 3)
{
    patterns.Add(pattern);
    j = 0;
    pattern = new int[4, 4];
}

Because this piece of code was a bit more difficult to implement, here's the complete method code:

public void LoadBlocks()
{
    using (StreamReader sr = new StreamReader(file))
    {
        // variables initialization
        string line;
        int[,] pattern = new int[4, 4];
        int j = 0;
        int lineNumber = 1;
        // reading all lines of the text file
        while ((line = sr.ReadLine()) != null)
        {
            // skip every 5th line
            if ((lineNumber % 5) != 0)
            {
                // adding all numbers from the line into the pattern
                for (int i = 0; i < 4; i++)
                    pattern[i, j] = int.Parse(line[i].ToString());
                j++;
                // last line read?
                if (j > 3)
                {
                    // add complete pattern
                    patterns.Add(pattern);
                    // and reset all variables
                    j = 0;
                    pattern = new int[4, 4];
                }
            }
            lineNumber++;
        }
    }
}

Generating Random Block

Generating a random block is now trivial - we just have to pick a pattern and create a block based on this pattern. Let's add the Generate() method, which will take a number of patterns to generate from as a parameter. This way, with the parameter value of 7, we can generate simple blocks, with the value of 12 the more complex ones, and with a higher number you can even add some bonus ones.

public Block Generate(int count)
{
    int index = randomGenerator.Next(0, count);

    return new Block(patterns[index]);
}

Let's move to LevelComponent. We'll add a field that will contain an instance of our block generator:

private BlockGenerator blockGenerator;

Now let's move to Initialize() to remove both the test pattern and the block. Next, we'll create a block generator instance here also load the patterns:

blockGenerator = new BlockGenerator("netfx.dll");
blockGenerator.LoadBlocks();

After loading the patterns, we'll generate a block:

block = blockGenerator.Generate(7);

Let's run the game several times to see the block will always be random. Again, there is still the problem with the blocks centering, but we won't be solving it yet.

Random Tile Sprites

Although we have 15 different tile sprites, all block tiles are the same now. We'll want the newly created block instance to assign sprites for its tiles randomly. So the tiles won't just have the 0 to 1 values, but now it'll be 0 to 15. The block pattern that we pass in the constructor will always contain only the 0 and 1 values. The block will then generate these sprite indexes itself.

Let's move back to the Block class and add a field with the random number generator instance:

private Random randomGenerator;

We'll initialize it in the constructor:

randomGenerator = new Random();

We'll add a GenerateSprites() method that'll find all number one values in the pattern array and replace them with a random number ranging from 1 to 15. What may be confusing is that we specify the parameters 1 and 16 in the Next() method call, that's because the upper limit is exclusive.

private void GenerateSprites()
{
    // finding block tiles
    for (int j = 0; j < 4; j++)
        for (int i = 0; i < 4; i++)
            if (Tiles[i, j] > 0)
                Tiles[i, j] = randomGenerator.Next(1, 16);
}

We'll call the method in the constructor right after copying the tiles and creating the random generator instance:

GenerateSprites();

Now we have to edit the Draw() method to take the entire textures array, instead of a single sprite, and then render them according to the tile value:

public void Draw(Vector2 border, LepsiSpriteBatch spriteBatch, Texture2D[] sprites)
{
    for (int j = 0; j < 4; j++)
        for (int i = 0; i < 4; i++)
            if (Tiles[i, j] > 0)
                spriteBatch.Draw(sprites[Tiles[i, j] - 1], new Vector2(border.X + (i + position.X) * sprites[0].Width,
                border.Y + (j + position.Y) * sprites[0].Height), Color.White);
}

The block is done for now, but we still have to change its handling, so let's go back to LevelComponent.

We'll change the tileSprite field declaration to an array declaration, and we'll also rename it:

private Texture2D[] tileSprites;

Let's move to LoadContent() and load the whole texture array using the for loop, instead of loading a single sprite. We have to initialize the array first.

// Loading tile sprites
tileSprites = new Texture2D[15];
for (int i = 0; i < 15; i++)
    tileSprites[i] = robotrisGame.Content.Load<Texture2D>("Sprites\\Tiles\\" + (i + 1).ToString());

As the last modification we'll rename the field in the Draw() method:

block.Draw(gameBoardPosition, robotrisGame.spriteBatch, tileSprites);

The result:

Tetris sample game in MonoGame - Tetris From Scratch

In the next lesson, Tetris in MonoGame: Game Board, the game board awaits us.


 

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 39x (15.72 MB)
Application includes source codes

 

Previous article
Tetris in MonoGame: Block
All articles in this section
Tetris From Scratch
Skip article
(not recommended)
Tetris in MonoGame: Game Board
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