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