Make a simple Tetris game with C/C++

Make a simple Tetris game with C/C++


insert image description here

0 ready

The installation of EasyX is very simple, you can search the following on Baidu, but you must install Visual Studio before installation . The following sections show how to use it.

1 Game interface design

1.1 Interface Layout

First of all, we need to choose a chapter picture as the background of the game. We can download a suitable background picture from the picture website.

Secondly, divide the game area and the display area on the background picture. Generally, the game area is in the middle, and the display areas are on both sides. The game area is used to control the movement, elimination and rotation of blocks, etc.; the display area is used to display speed and scores. Here is the game interface I designed:

insert image description here

Open the picture with a drawing tool, you can see that the background size is 800*600 pixels, and at the same time set the game area (dotted line box, custom size) in the middle of the picture, and add text boxes for speed and score.

1.2 Display interface with EasyX

At this point, I believe you should be able to create a VS project and install EasyX.

Create a folder imp and put the pictures in section 1.1 in it. The imp folder is in the same directory as main.cppthe file .
Displaying graphics needs to call the header file graphics.h. We need to know the size of the displayed graphics, here is 800*600, and the displayed position is (0, 0)

#include <graphics.h>

// 绘图窗口初始化
	initgraph(imp_width, imp_heght);
	loadimage(&background, _T("img/background.png"));
	putimage(0, 0, &background);

Displayed as follows:

background display

This is a simple display example, and subsequent blocks are displayed in this way.

1.3 Music playback

First use Kugou to download a nice piece of music, and then put the music in main.cppthe same .

I use two pieces of music here, and they play randomly every time I open them

program:

#include "Windows.h"
#include <time.h>

#pragma comment (lib, "winmm.lib")

void Music::palyMusic()
{
    
    
	int chFlag;
	srand((unsigned)time(NULL));
	chFlag = rand() % 2;
	
	if (chFlag == 0)
	{
    
    
		//mciSendString("close 1.mp3", NULL, 0, NULL);
		mciSendString("open 2.mp3", NULL, 0, NULL);
		mciSendString("play 2.mp3 repeat", NULL, 0, NULL);
		mciSendString("setaudio 2.mp3 volume to 100", 0, 0, 0);
	}
	else
	{
    
    
		//mciSendString("close 2.mp3", NULL, 0, NULL);
		mciSendString("open 1.mp3", NULL, 0, NULL);
		mciSendString("play 1.mp3 repeat", NULL, 0, NULL);
		mciSendString("setaudio 1.mp3 volume to 100", 0, 0, 0);
	}
}

2 block design

2.1 Block display

Design 7 block types:

const int blocks[7][4] = {
    
    
		1,3,5,7, // I
		2,4,5,7, // Z 1型
		3,5,4,6, // Z 2型
		3,5,4,7, // T
		2,3,5,7, // L
		3,5,7,6, // J
		2,3,4,5, // 田
	};

The representation of the square is shown in the figure below:

square display

The above two pictures show how the blocks are represented in the game. The blocks can be represented by the horizontal and vertical coordinates of the interface. We can use the initial position of the drop as the horizontal coordinate, and the left boundary of the block as the vertical coordinate. In fact, it is the upper left corner of the game area as the coordinate.

Then the principle of the display is known, that is, to operate the array, what about the graphics of the box?

small square display

The display of the small square can be based on this picture. It is not difficult to see from the above picture that the size of the small square is: 20*20 pixels. If we want to display the first small square, we can load this image and start from coordinates (0, 0) to display an image with a length and width of 20 pixels. Agreed, if the third green square is to be displayed, it starts at coordinates (40, 0).

The following is part of the program implemented:

//计算小方块位置
blockType = rand() % 7;
for (int i = 0; i < 4; i++)
{
    
    
	smallBlock[i][0] = blocks[blockType][i] / 2;
	smallBlock[i][1] = blocks[blockType][i] % 2 + 1;//离左边界一格显示,方便旋转
}

//小方块显示函数
void Graph::block()
{
    
    
	IMAGE imgTmp;
	loadimage(&imgTmp, _T("img/small.png"));
	SetWorkingImage(&imgTmp);
	//putimage(0, 0, &imgTmp);
	for (int i = 0; i < 7; i++) {
    
    
		this->imgs[i] = new IMAGE;
		getimage(this->imgs[i], i * blocks_size, 0, blocks_size, blocks_size);
	}
	SetWorkingImage();
}

initial position

2.2 Generate a block randomly

The principle of randomly generating blocks is to clear and initialize the array that records the falling blocks

void Graph::random()
{
    
    
	blockType = rand() % 7;

	for (int i = 0; i < 4; i++)
	{
    
    
		smallBlock[i][0] = blocks[blockType][i] / 2;
		smallBlock[i][1] = blocks[blockType][i] % 2 + 1;//离左边界一格显示,方便变形
	}
	colBasis = 0;
	rowBasis = 0;
}

2.3 Block records

We not only need to record the blocks that are currently being operated, but also need to record the blocks that have fallen and have not been eliminated. The game area can be regarded as a two-dimensional array, and a 29*14 two-dimensional array is opened for recording.

	//背景图像大小
	const unsigned int imp_width = 800;
	const unsigned int imp_heght = 600;
	//小方块大小
	const unsigned int blocks_size = 20;

	//游戏区边界
	const unsigned int left_margin = 240;
	const unsigned int right_margin = 485;
	const unsigned int down_margin = 570;
	const unsigned int up_margin = 10;
	const int rows = 29;
	const int cols = 14;
	
	//记录方块的数组
	vector<vector<int>> allBlock;

There is a little trick here, because the blocks need to display different colors, so we can use the value of the two-dimensional array allBlock as the value of the color

When the value of allBlock[i][j] is 0, it means that there is no block at this position;
when the value of allBlock[i][j] is greater than 0, it means that there is a block at this position, and the displayed color is allBlock[i][j ] of the expression

//已静止方块显示
	for (int i = rows-1; i > 3; --i)
	{
    
    
		for (int j = 0; j < cols; ++j)
		{
    
    
			if(allBlock[i][j]!=0)
				putimage(left_margin + j * blocks_size, up_margin + i * blocks_size, imgs[allBlock[i][j]-1]);
		}
	}

3 block movement and rotation

3.1 Movement of cubes

The movement of the block is a core: the movement of the block = the operation on the array

The whereabouts of the square = row coordinates + 1
left movement of the square = column coordinates - 1
right movement of the square = column coordinates + 1

The premise is that it needs to judge whether it is out of bounds or whether there is a block in the next position of the move

The procedure is as follows:

void Graph::moveLeft()
{
    
    
	for (int i = 0; i < 4; i++)
	{
    
    
		if (smallBlock[i][1] <= 0 || allBlock[smallBlock[i][0]][smallBlock[i][1] - 1] >= 1)
			return;
	}
	for (int i = 0; i < 4; i++)
	{
    
    
		--smallBlock[i][1];
	}
	--colBasis;
}

void Graph::moveDown()
{
    
    
	for (int i = 0; i < 4; i++)
	{
    
    
		if (smallBlock[i][0] >= rows)
			return;
	}
	for (int i = 0; i < 4; i++)
	{
    
    
		++smallBlock[i][0];
	}
	++rowBasis;
}

void Graph::moveRight()
{
    
    
	for (int i = 0; i < 4; i++)
	{
    
    
		if (smallBlock[i][1] >= cols - 1 || allBlock[smallBlock[i][0]][smallBlock[i][1] + 1] >= 1)
			return;
	}
	for (int i = 0; i < 4; i++)
	{
    
    
		++smallBlock[i][1];
	}
	++colBasis;
}

Of course, the premise of moving is that user key input is required, so there must be a function for judging key input and a function for reading key values. I use function here _kbhit()to determine whether there is key input, and use function _getch()to read key values.

//控制方块移动
	if (_kbhit() && graph.startFlag)//如果键盘有输入
	{
    
    
		graph.keyPlay();
	}
void Graph::keyPlay()
{
    
    
	int ch = 0;
	ch = _getch();
	switch (ch)
	{
    
    
		//WASD键(小写)
		case 119: changeBlock();//上键
			break;
		case 97: moveLeft();//左键
			break;
		case 115: moveDown();//下键
			break;
		case 100: moveRight();//右键
			break; 
		//上下左右键
		case 72: changeBlock();//上键
			break;
		case 75: moveLeft();//左键
			break;
		case 80: moveDown();//下键
			break;
		case 77: moveRight();//右键
			break;
	}
}

3.2 Rotation of blocks

cube rotation

As shown in the figure above, the rotation of the cube can be achieved with a few lines of code, but the following issues still need to be paid attention to:

  • Rotate around what?
  • The squares are constantly falling, and the rows and columns are always changing
  • There are some blocks on the border that cannot be rotated

For the first question, if you want the rotation of the square to look less awkward, it is most appropriate to rotate at the center of the 4*4 square, that is, 2, 3, 4, and 5 in the figure are the core of the rotation.

For the second problem, you can switch the row and column of the block to the initial position, then perform the formula in the above figure, and then switch back. Here, you can set two variables to determine the distance between the block and the initial position.

For the third problem, temporarily store the row and column numbers of the squares, transform them, and then check whether there are any squares outside the boundary. If so, the rotation step is considered invalid.

The determination of the initial position when rotating is also very critical, because some rotations at the boundary cannot be done

initial position

Program implementation:

void Graph::changeBlock()
{
    
    
	int temp[4][2] = {
    
     0 };

	for (int i = 0; i < 4; i++)
	{
    
    
		//配合偏置,进行方块的旋转
		temp[i][0] = smallBlock[i][1] - colBasis;
		temp[i][1] = 3 - (smallBlock[i][0] - rowBasis);

		temp[i][0] += rowBasis;
		temp[i][1] += colBasis;

		//检查合法性
		if (temp[i][1] == 0 || temp[i][1] == cols - 1)
			return;
	}
	for (int i = 0; i < 4; i++)//若合法,实行
	{
    
    
		smallBlock[i][0] = temp[i][0];
		smallBlock[i][1] = temp[i][1];
	}
}

3.3 Collision and Elimination of Cubes

The elimination of squares needs to consider the following issues

  • Impact checking
  • one line elimination algorithm

3.3.1 Collision

Collision detection is easy to implement. Since I have set up boundary detection for left and right movement, I only need to judge the four squares here, that is to say, it is enough to judge whether there are squares under them. If yes, return 1

int Graph::check()
{
    
    
	int row, col;

	for (int i = 0; i < 4; i++)//若合法,实行
	{
    
    
		row = smallBlock[i][0]+1;
		col = smallBlock[i][1];

		if (row >= this->rows || allBlock[row][col] >= 1)
		{
    
    
			if (rowBasis == 0)
				return 2;
			else
				return 1;
		}
	}
	return 0;
}

3.3.2 Elimination

For the elimination of one row, a two-dimensional array is used to record all the positions. If there is a square at (i, j), then; after the allBlock[i][j]=1collision detection is completed, the entire array is traversed, and the number of rows moved by each row is calculated. records, minimizing time complexity.

int clearRowNum[30] = {
    
     0 };
int num=0;
//unordered_map<int, int>map;
if (check()==1)
{
    
    
	for (int i = 0; i < 4; i++) 
	{
    
    
		int row = smallBlock[i][0];
		int col = smallBlock[i][1];
		allBlock[row][col] = blockType+1;
	}
	//消除一行
	for (int i = rows-1; i > 3; --i)
	{
    
    
		for (int j = 0; j < cols; ++j)
		{
    
    
			if (allBlock[i][j] == 0)
			{
    
    
				clearRowNum[i] = num;
				break;
			}
			else if (j == cols-1)//该行需要消除
			{
    
    
				++num;
				clearRowNum[i] = 0;
			}
		}
	}

	for (int i = rows - 2; i > 3; --i)
	{
    
    
		if (clearRowNum[i] != 0)
		{
    
    
			for (int j = 0; j < cols; ++j)
			{
    
    
				allBlock[i + clearRowNum[i]][j] = allBlock[i][j];
			}
		}
	}
}

3.3.3 Score and falling speed

At the same time, score calculation and speed calculation can be added to the elimination function. The general logic is that every time a row is eliminated, the score increases; the higher the score, the faster the falling speed;

//设置速度,得分越多,速度越快
score += num * cols;

speed = 100+score/10;

3.3.4 game over

When the small square is in the initial position, it can be judged that the game is over when there is a square under it.

if (row >= this->rows || allBlock[row][col] >= 1)
{
    
    
	if (rowBasis == 0)
		return 2;
	else
		return 1;
}

After game over, the interface will always display game over until the Enter key is entered

//游戏结束
	if (!graph.startFlag)
	{
    
    
		settextcolor(WHITE);
		settextstyle(40, 0, "黑体");
		setbkmode(TRANSPARENT);
		char s[10] = "Game Over";
		outtextxy(300, 280, s);

		if (_kbhit() && _getch() == 13)//如果键盘有输入
		{
    
    
			graph.init();
		}
	}

game over

4 Make exe file

How to package the project program with Visual Studio can refer to:

How does Visual Studio package project programs into software

5 summary

The last thing I want to say is that the movement and rotation of the square is basically an operation on the array.

As for some follow-up functions such as highest score records and next block prompts, they are all icing on the cake. Interested friends can try to add them.

Program download:
Tetris game program download

Guess you like

Origin blog.csdn.net/u011895157/article/details/129232565