# Lesson 11 - Tetris in MonoGame: Level Features

In the previous lesson, Tetris in MonoGame: Score and Level Completing, we finished the Tetris level and implemented a score and game pause.

Today's tutorial is the last one where we're going to take look at the level. We're going to improve the block rotation and add a ghost block to the game.

## Improving the Block Rotation

The way our block is rotated isn't certainly very natural. In the original Tetris, the blocks are defined in all rotated positions manually, some blocks have only two positions (like the I-block), some only one (the O-block). However, such manual declaration isn't very technical and would hardly improve our programming skills. So far, our solution rotates each block as a square matrix of the size 4x4. The problem is that most blocks are three tiles large, some even just two.

First, let's edit our blocks in the text file so that they're always aligned in the upper left corner of the matrix. Of course, we have to work with the block as it was a square, and determine its position according to its longest side (this is especially important for the I-block). Therefore, we'll modify the `netfx.dll` file as following:

```1000
1110
0000
0000

0010
1110
0000
0000

1100
1100
0000
0000

0000
1111
0000
0000

0100
1110
0000
0000

1100
0110
0000
0000

0110
1100
0000
0000

1000
0000
0000
0000

1010
0100
1010
0000

0100
1110
0100
0000

1110
0100
0100
0000

1100
0000
0000
0000```

Let's open the `Block` class and add a public `edge` field to it:

`public int edge;`

We'll add a private `GetEdge()` method to the class, in which we'll assume that the block has the edge value of `1`, that is the lowest possible value (we certainly won't have an empty block in our game).

```private void GetEdge()
{
edge = 1;
}```

We'll get the edge value in the method by going through all the block's tiles. If there's a non-empty tile that is further from the top left corner than is the current edge value, we'll update the edge value to match the used tile coordinates.

```for (int j = 0; j < 4; j++)
for (int i = 0; i < 4; i++)
{
// found a tile that is further from the top left corner than is the current edge value
if ((Tiles[i, j] > 0) && (((i + 1) > edge) || ((j + 1) > edge)))
{
if (i > j)
edge = i + 1;
else
edge = j + 1;
}
}```

We'll call the method in the constructor, right after generating the sprites:

`GetEdge();`

We've stored the edge. Now let's edit the rotation method, which is going to be a piece of cake (we'll only work with the part of the array specified by edge):

```public void Rotate()
{
// temp array
int[,] a = CopyTiles(Tiles);
// rotate the array by swapping the coordinates, as with a matrix
for (int y = 0; y < edge; y++)
for (int x = 0; x < edge; x++)
Tiles[x, y] = a[y, (edge - 1) - x];
}```

The `InsertBlock()` method in the `GameBoard` class will also be affected by the block size:

```public Vector2 InsertBlock(Block block)
{
return new Vector2((width / 2) - (block.edge / 2), 0);
}```

Let's try the game now. The rotation is much more precise now.

## The Ghost Block

Some Tetris versions show the spot the block will fall directly on if the down arrow cursor key would be pressed. Let's add this element in our game and call it ghost. Another suitable name would be crosshair if you like.

### Determining the Position

Let's go to `GameBoard.cs`, where we'll add a `CalculateGhostCoordinates()` method. It'll take the block as a parameter and will return the calculated position as `Vector2`. The returned position will be the position where the block would fall in case we pressed the down arrow cursor key. To calculate the position, we simply have to keep moving the block position down until it collides. The returned position will be 1 tile higher.

```public Vector2 CalculateGhostCoordinates(Block block)
{
Vector2 position = block.position;
// move the block position down until it collides
while (!Collision(block, position))
position.Y++;
// move the position back by one tile before the collision
return new Vector2(block.position.X, position.Y - 1);
}```

Since computing this position is now twice in our project (which isn't a very good programming habit), we'll have to replace the down arrow cursor key logic in the `Update()` method in `LevelComponent`. Instead of that logic, we'll call this method. The modified code will look like this:

```if (robotrisGame.NewKey(Keys.Down))
{
block.position = gameBoard.CalculateGhostCoordinates(block);
timeSinceLastFall += timeBetweenFalls;
}```

Let's stay in `LevelComponent` for a while and create a new private field for the ghost block position:

`private Vector2 ghostPosition;`

This field will be set in `Update()`, during the `Playing` state:

`ghostPosition = gameBoard.CalculateGhostPosition(block);`

### Rendering

We can now calculate the position. Now we need to render the ghost at that position. The ghost will look like the falling block. There's no need to create another object for it (although it'd be possible). Instead we'll just render the falling block again at different coordinates, and with modified transparency.

Let's go to `Block.cs` and edit the `Draw()` method. We'll make it private and put `_` (underscore) before its name. We'll also add two more parameters. The first one will be `position` of the `float` type, and will represent the block render position. This way we can render the block elsewhere than it actually is. We've already done something similar in the `Collision()` method. The second parameter named `alpha` will specify the block transparency. In the method body we just have to multiply the color by `alpha`. C# will understand we mean the `position` parameter of the method, not the class field that has the same name.

The method code will look like this:

```private void _Draw(Vector2 border, BetterSpriteBatch spriteBatch, Texture2D[] sprites, Vector2 position, float alpha)
{
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.Width,
border.Y + (j + position.Y) * sprites.Height), Color.White * alfa);
}```

Implementing block and ghost rendering methods will now be a piece of cake. The `Draw()` method will simply call `_Draw()` and pass it the `position` field and the value of `1` as the alpha:

```public void Draw(Vector2 leftBorder, BetterSpriteBatch spriteBatch, Texture2D[] sprites)
{
_Draw(leftBorder, spriteBatch, sprites, position, 1);
}```

The `DrawGhost()` will set all parameters of the `_Draw()` method:

```public void DrawGhost(Vector2 leftBorder, BetterSpriteBatch spriteBatch, Texture2D[] sprites, Vector2 position, float alpha)
{
_Draw(leftBorder, spriteBatch, sprites, position, alpha);
}```

If you now think that a `Draw()` method calling `DrawGhost()` would be enough, you're right. But from a design point of view, however, this doesn't seem logical.

Let's move onto the `Draw()` method of `LevelComponent`. We'll render the ghost block right after the falling block and rendering the next block:

`block.DrawGhost(gameBoardPosition, robotrisGame.spriteBatch, tileSprites, ghostPosition, 0.3f);`

Now let's run it: The game is much more pleasant to play.

### Pulsating

Let's add one more effect at the end. We'll let the ghost's transparency pulsate, as we did with the clouds color. Again, we'll create the pulse direction field of the `int` type, storing values `1` or `-1`. The transparency value itself will then be stored in a `float` field. Let's add both fields to the class:

```private int ghostDirection;
private float ghostAlpha;```

In `Initialize()` we'll set the default values:

```ghostAlpha = 0.0f;
ghostDirection = 1;```

In `Update()`, during the `Playing` state, we'll update the pulse behavior:

```ghostAlpha += 0.02f * ghostDirection;
if (ghostAlpha > 0.5)
ghostDirection = -1;
if (ghostAlpha < 0.2)
ghostDirection = 1;```

The `ghostAlpha` value now alternates between `0.2` and `0.5`.

Go to `Draw()` and change the ghost block and the next block rendering as follows:

```nextBlock.Draw(new Vector2(930, 200 + (40 * ghostAlpha)), robotrisGame.spriteBatch, tileSprites);
block.DrawGhost(gameBoardPosition, robotrisGame.spriteBatch, tileSprites, ghostPosition, ghostAlpha);```

Now run the game and enjoy it because it's complete We used the pulsating effect even to make the next block levitate.

In the next lesson, Tetris in MonoGame: Game Scene Management, we'll take a look at game scene management and start making a game menu.

Application includes source codes

Article has been written for you by David Capka The author learned IT at the Unicorn University - a prestigious college providing education on IT and economics.