Implementation of Tetris in C language

catalogue

    1, Game effect display

    2, The complete code can be copied and run directly

    3, Required development environment

    4, Specific project realization

    ① Game welcome interface (welcome)

    ② Game background initGameScreen()

    ③ The square represents int block [] [] []

    ④ The new box represents nextBlock()

    ⑤ Design game loop (main)

    ⑥ Build user operation framework move (I)

    ⑦ Determine whether the box can move in the specified direction

    ⑧ Game failure check ()

    ⑨ Clear the box during descent (clearblock)

    ⑩ Judging box rotation rotatable()

    ① ① draw the block during descent (drawblock)

    ① ② delay wait ()

    ① ③ fixed block mark()

   ① ④ improvement of user operation framework Ⅱ mov()

   ① ⑤ eliminate block check() + down()

   ① ⑥ update score and grade addscore() + updategrade()

     Code integration operation

    5, Deficiencies

1, Game effect display

The orientation of each square is represented by a two-dimensional array

2, The complete code can be copied and run directly

#include<graphics.h>
#include<stdio.h>
#include<time.h>
#include<conio.h>	//kbhit()

int score = 0;	//Total score
int rank = 0;	//Grade

#define BLOCK_COUNT 5
#define BLOCK_WIDTH 5
#define BLOCK_HEIGHT 5

#define UNIT_SIZE 20 	// Small square width

#define START_X 130 		// Square landing box, square landing starting position
#define START_Y 30

#define KEY_UP 87 		// User operation
#define KEY_LEFT 65
#define KEY_RIGHT 68
#define KEY_DOWN 83
#define KEY_SPACE 32

#define MinX 30 		// Top left corner of the game
#define MinY 30
int speed = 500;	//Block landing speed


int NextIndex = -1;		//Next box
int BlockIndex = -1;		//Current box

typedef enum {		//Square orientation
	BLOCK_UP,
	BLOCK_RIGHT,
	BLOCK_LEFT,
	BLOCK_DOWN
}block_dir_t;

typedef enum {		//Square movement direction
	MOVE_DOWN,
	MOVE_LEFT,
	MOVE_RIGHT
}move_dir_t;

//Square color
int color[BLOCK_COUNT] = {
	GREEN,
	CYAN,
	MAGENTA,
	YELLOW,
	BROWN
};
int visit[30][15];	//Access array visit[i][j] = 1 indicates that there are squares in this position
int markColor[30][15];	//Corresponding position color
int block[BLOCK_COUNT * 4][BLOCK_WIDTH][BLOCK_HEIGHT] = {
	// |Shaped square
	{ 0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0 },
	 // L-shaped square
	{ 0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,1,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,0,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0 },
	// Field shaped square
	{ 0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 },
	// T-shaped square
	{ 0,0,0,0,0,0,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,1,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0 },
	// Z-shaped square
	{ 0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,1,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,1,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0 },
};

/***************************
 * Function: Welcome page
 * Input:
 *		nothing
 * return:
 *		nothing
 **************************/
void welcome() {
	//1. Initialize canvas
	initgraph(550, 660);
	system("pause");
	//2. Set window title
	HWND window = GetHWnd();//Get window, get current window
	SetWindowText(window, _T("Tetris Xiaoming is coming"));	//Set title

	//3. Set the game initial page
	setfont(40, 0, _T("Microsoft YaHei "));		//Set the font style of text (height, width (0 means adaptive), font)
	setcolor(WHITE);	// Set color
	outtextxy(205, 200, _T("Russian method"));

	setfont(20, 0, _T("Regular script"));
	setcolor(WHITE);	// Set color
	outtextxy(175, 300, _T("Programming, starting with Tetris"));

	Sleep(3000);
}

/***************************
 * Function: initialize game scene
 * Input:
 *		nothing
 * return:
 *		nothing
 **************************/
void initGameSceen() {
	char str[16];	//Storage score
	//1. Clear the screen
	cleardevice();
	//2. Painting scenes
	rectangle(27, 27, 336, 635);	//Box landing frame outer frame
	rectangle(29, 29, 334, 633);	//Box landing box
	rectangle(370, 50, 515, 195);	//Box prompt

	setfont(24, 0, _T("Regular script"));		//Write "next"
	setcolor(LIGHTGRAY);	//grey
	outtextxy(405, 215, _T("next:"));

	setcolor(RED);					//Write score
	outtextxy(405, 280, _T("fraction:"));

	//Write the score to str in the specified format
	sprintf_s(str, 16, "%d", score);
	//Here, set the character set to multi character to ensure that outextxy can write out the variable str
	outtextxy(415, 310, str);

	outtextxy(405, 375, _T("Grade:"));	//Grade

	//Writes rank to str in the specified format
	sprintf_s(str, 16, "%d", rank);
	//Here, set the character set to multi character to ensure that outextxy can write out the variable str
	outtextxy(415, 405, str);

	setcolor(LIGHTBLUE);	//Operating instructions
	outtextxy(390, 475, "Operating instructions:");
	outtextxy(390, 500, "↑: rotate");
	outtextxy(390, 525, "↓: decline");
	outtextxy(390, 550, "←: Shift left");
	outtextxy(390, 575, "→: Shift right");
	outtextxy(390, 600, "Space: suspend");
}

/*****************************************
 * Function: clear the box in the box prompt box
 * Input:
 *		nothing
 * return:
 *		nothing
 ****************************************/
void clearBlock() {
	setcolor(BLACK);
	setfont(23, 0, "Regular script");
	for (int i = 0; i < BLOCK_HEIGHT; ++i) {
		for (int j = 0; j < BLOCK_WIDTH; ++j) {
			int x = 391 + j * UNIT_SIZE;
			int y = 71 + i * UNIT_SIZE;
			outtextxy(x, y, "■");
		}
	}
}

/*****************************************
 * Function: clear blocks during landing
 * Input:
 *		x,y - Coordinates of the square (position of the upper left corner of the two-dimensional array)
 *		block_dir_t - Square direction
 * return:
 *		nothing
 ****************************************/
void clearBlock(int x, int y, block_dir_t blockDir) {
	setcolor(BLACK);
	//	setfont(23, 0, "italics");
	int id = BlockIndex * 4 + blockDir;
	for (int i = 0; i < BLOCK_HEIGHT; ++i) {
		for (int j = 0; j < BLOCK_WIDTH; ++j) {
			if (block[id][i][j] == 1) {
				int x2 = x + j * UNIT_SIZE;
				int y2 = y + i * UNIT_SIZE;
				outtextxy(x2, y2, "■");
			}
		}
	}
}

/*****************************************
 * Function: draw a square at the beginning of the prompt box and landing box
 * Input:
 *		x,y - Coordinates of the square (position of the upper left corner of the two-dimensional array)
 * return:
 *		nothing
 ****************************************/
void drawBlock(int x, int y) {
	setcolor(color[NextIndex]);
	setfont(23, 0, "Regular script");
	for (int i = 0; i < BLOCK_HEIGHT; ++i) {
		for (int j = 0; j < BLOCK_WIDTH; ++j) {
			if (block[NextIndex * 4][i][j] == 1) {
				int x2 = x + j * UNIT_SIZE;
				int y2 = y + i * UNIT_SIZE;
				outtextxy(x2, y2, "■");
			}
		}
	}
}

/*****************************************
 *Function: draw the square in the descending process
 *Input:
 *		x,y - Coordinates of the square (position of the upper left corner of the two-dimensional array)
 *		block_dir_t - Square direction
 * return:
 *		nothing
 ****************************************/
void drawBlock(int x, int y, block_dir_t dir) {
	setcolor(color[BlockIndex]);
	setfont(23, 0, "Regular script");
	int id = BlockIndex * 4 + dir;
	for (int i = 0; i < BLOCK_HEIGHT; ++i) {
		for (int j = 0; j < BLOCK_WIDTH; ++j) {
			if (block[id][i][j] == 1) {
				//Erase the i-th row and j-th column of the box
				outtextxy(x + j * UNIT_SIZE, y + i * UNIT_SIZE, "■");
			}
		}
	}
}

/*****************************************
 *Function: generate a new box in the box prompt box
 *Input:
 *		nothing
 *return:
 *		nothing
 ****************************************/
void nextblock() {
	clearBlock();
	//Generate random numbers and randomly select blocks
	srand((unsigned)time(NULL));	//Use the return value of the time function as a random seed
	NextIndex = rand() % BLOCK_COUNT;	//Generate random numbers from 0 to 5
	drawBlock(391, 71);
}

 /*****************************************
  *Function: judge whether it can move to the specified direction at the specified position
  *Input:
  *		x,y - Square position
  *		moveDir - The direction you want to move next
  *		blockDir - The direction of the current square
  * return:
  *		true - Can move
  *		false - Cannot move
  ****************************************/
bool moveable(int x0, int y0, move_dir_t moveDir, block_dir_t blockDir) {
	//The upper left corner of the calculation box is 30 × 15 game area location (rows and columns)
	int x = (y0 - MinY) / UNIT_SIZE;
	int y = (x0 - MinX) / UNIT_SIZE;
	int ret = 1;
	int id = BlockIndex * 4 + blockDir;
	if (moveDir == MOVE_DOWN) {
		for (int i = 0; i < BLOCK_HEIGHT; ++i) {
			for (int j = 0; j < BLOCK_WIDTH; ++j) {
				//Conditions for not moving down: the solid block has reached the bottom (x+i+1==30), or there is a block at the bottom	
				if (block[id][i][j] == 1 &&
					(x + i + 1 == 30 || visit[x + i + 1][y + j] == 1)) {
					ret = 0;
				}
			}
		}
	}
	else if (moveDir == MOVE_LEFT) {
		for (int i = 0; i < BLOCK_HEIGHT; ++i) {
			for (int j = 0; j < BLOCK_WIDTH; ++j) {
				//Conditions for not moving to the left: the solid box has reached the left boundary (y+j==0), or there is a box on the left
				if (block[id][i][j] == 1 &&
					(y + j <= 0 || visit[x + i][y + j - 1] == 1)) {
					ret = 0;
				}
			}
		}
	}
	else if (moveDir == MOVE_RIGHT) {
		for (int i = 0; i < BLOCK_HEIGHT; ++i) {
			for (int j = 0; j < BLOCK_WIDTH; ++j) {
				//Conditions under which the downward movement is not possible: the solid block has reached the right boundary (y + j + 1 > = 15), or there is a block on the right
				if (block[id][i][j] == 1 &&
					(y + j + 1 >= 15 || visit[x + i][y + j + 1] == 1)) {
					ret = 0;
				}
			}
		}
	}
	return ret;
}

/*****************************
 *Function: check whether the game is over
 *Input:
 *		nothing
 * return:
 *		nothing
 *****************************/
void failCheck() {
	//The end condition of the game is that the newly drawn box on the top will be "solidified", and the newly drawn box on the top will face up and move down
	if (!moveable(START_X, START_Y, MOVE_DOWN, (block_dir_t)BLOCK_UP)) {
		setcolor(WHITE);
		setfont(45, 0, "Official style");
		outtextxy(75, 300, "Game Over!");
		Sleep(1000);
		system("pause");
		closegraph();
		exit(0);
	}
}

/**************************
 * Function: delay waiting
 * Input:
 *
 * return:
 *		nothing
 *************************/
void wait(int interval) {
	int count = interval / 10;
	for (int i = 0; i < count; ++i) {
		Sleep(10);
		//If the user presses a key during hibernation, the hibernation ends
		if (_kbhit()) {
			return;
		}
	}
}

/*****************************************
 * Function: judge whether the current box can rotate in the specified direction
 * Input:
 *		x,y - Square position (2D array coordinates)
 *		dir - Square rotation direction
 * return:
 *		true - Can rotate
 *		false - Cannot rotate
 ****************************************/
bool rotatable(int x, int y, block_dir_t dir) {
	//First, judge whether you can continue to move down
	if (!moveable(x, y, MOVE_DOWN, dir)) {
		return false;
	}
	int x2 = (y - MinY) / UNIT_SIZE;
	int y2 = (x - MinX) / UNIT_SIZE;
	int id = BlockIndex * 4 + dir;
	for (int i = 0; i < BLOCK_HEIGHT; ++i) {
		for (int j = 0; j < BLOCK_WIDTH; ++j) {
			//No rotation condition: the left and right boundaries are out of bounds or there is a block "blocking"
			if (block[id][i][j] == 1 && (y2 + j < 0 || y2 + j >= 15 || visit[x2 + i][y2 + j] == 1)) {
				return false;
			}
		}
	}
	return true;
}

/*****************************************
 * Function:
 * Input:
 *
 * return:
 *		nothing
 ****************************************/
void mark(int x, int y, block_dir_t dir) {
	int id = BlockIndex * 4 + dir;
	int x2 = (y - MinY) / UNIT_SIZE;
	int y2 = (x - MinX) / UNIT_SIZE;
	for (int i = 0; i < BLOCK_HEIGHT; ++i) {
		for (int j = 0; j < BLOCK_WIDTH; ++j) {
			if (block[id][i][j] == 1) {
				visit[x2 + i][y2 + j] = 1;
				markColor[x2 + i][y2 + j] = color[BlockIndex];
			}
		}
	}
}

/*****************************************
 * Function: read the user's operation and update the landing box from time to time
 * Input:
 *		nothing
 * return:
 *		nothing
 ****************************************/
void move() {
	int x = START_X;	//Box start position
	int y = START_Y;
	int k = 0;
	block_dir_t blockDir = (block_dir_t)BLOCK_UP;
	int curSpeed = speed;	//Define the current square landing speed
	//Judge whether the game is over before reading the user's operation
	failCheck();
	//Continuous descent
	while (1) {
		int curSpeed = speed;	//Define the current square landing speed
		//Clear box
		clearBlock(x, k + y, blockDir);
		//Judge the selected direction
		if (_kbhit()) {
			int key = _getch();
			if (key == KEY_SPACE) {
				system("pause");
			}
			else if (key == KEY_UP) {
				block_dir_t nextDir = (block_dir_t)((blockDir + 1) % 4);
				if (rotatable(x, y + k, nextDir)) {
					blockDir = nextDir;
				}
			}
			else if (key == KEY_LEFT) {
				if (moveable(x, y + k + 20, MOVE_LEFT, blockDir)) {
					x -= UNIT_SIZE;
				}
			}
			else if (key == KEY_RIGHT) {
				if (moveable(x, y + k + 20, MOVE_RIGHT, blockDir)) {
					x += UNIT_SIZE;
				}
			}
			else if (key == KEY_DOWN) {
				curSpeed = 50;
			}
		}
		k += 20;
		//Draw square
		drawBlock(x, y + k, blockDir);
		//dormancy
		wait(curSpeed);
		//The curing process of the block ends the cycle after the block is fixed, and the move of the current block is completed
		if (!moveable(x, y + k, MOVE_DOWN, blockDir)) {
			mark(x, y + k, blockDir);
			break;
		}
	}
}

/*****************************************
 *Function: draw the box just landed from the top, update the box in the prompt box, and call the box landing function move()
 *Input:
 *		nothing
 * return:
 *		nothing
 ****************************************/
void newblock() {
	BlockIndex = NextIndex;
	//Draw the square that just fell from the top
	drawBlock(START_X, START_Y);
	//Pause for the new box to appear
	Sleep(200);
	//Draw the next box in the upper right corner
	nextblock();
	//Block landing
	move();
}

/*****************************************
 * Function: eliminate the i-th line and move the upper line down
 * Input:
 *		nothing
 * return:
 *		nothing
 ****************************************/
void down(int x) {
	for (int i = x; i > 0; --i) {
		for (int j = 0; j < 15; ++j) {
			if (visit[i - 1][j] == 1) {
				visit[i][j] = 1;
				markColor[i][j] = markColor[i - 1][j];
				setcolor(markColor[i][j]);
				outtextxy(20 * j + MinX, 20 * i + MinY, "■");
			}
			else {
				visit[i][j] = 0;
				setcolor(BLACK);
				outtextxy(20 * j + MinX, 20 * i + MinY, "■");
			}
		}
	}
	//Clear topmost grid
	setcolor(BLACK);
	for (int j = 0; j < 15; ++j) {
		visit[0][j] = 0;
		outtextxy(20 * j + MinX, MinY, "■");
	}
}

/*****************************************
 * Function: update score
 * Input:
 *		nothing
 * return:
 *		nothing
 ****************************************/
void addScore(int lines) {
	char str[32];
	score += lines * 10;
	sprintf_s(str, 32, "%d", score);
	setcolor(RED);
	outtextxy(415, 310, str);

}

/*************************
 * Function: update level
 * Input:
 *		nothing
 * return:
 *		nothing
 *************************/
void updateGrade() {
	//Update level
	//Assume 50 points
	rank = score / 50;
	char str[32];
	sprintf_s(str, 32, "%d", rank);
	setcolor(RED);
	outtextxy(415, 405, str);
	//Update speed
	if (speed <= 100) {
		speed = 100;
	}
	else {
		speed = 500 - rank * 20;
	}
}

/*************************
 * Function: check whether there are full rows of squares
 * Input:
 *		nothing
 * return:
 *		nothing
 *************************/
void check() {
	int i, j;
	int clearLines = 0;
	for (i = 29; i >= 0; i--) {
		// Check if line i is full
		for (j = 0; j < 15 && visit[i][j]; j++);
		//When you do this, there are two situations:
		// 1. If line i is not full, it means there is a vacancy. At this time, J < 15
		// 2. Line i is full, and j > = 15
		if (j >= 15) {
			// At this time, line i is full, so you need to eliminate line i
			down(i);  //Eliminate line i and move all the upper lines down
			i++;  // Because there is i -- in the outermost loop, let's first i + +, so that we can check this line again in the next loop
			clearLines++;
		}
	}
	// Update score
	addScore(clearLines);

	// Update level (update level prompt, update speed)
	updateGrade();
}

int main() {
	welcome();
	initGameSceen();
	//Generate new block
	nextblock();
	//	system("pause");
	Sleep(800);

	//Initialize access array
	memset(visit, 0, sizeof(visit));

	while (1) {
		newblock();
		//Eliminate full lines and update scores and speeds
		check();
	}
	system("pause");
	closegraph();
	return 0;
}

3, Required development environment

1) Install VS2019, or other versions of VS

2) Install easyX graphics library

4, Specific project realization

① Game welcome interface (welcome)

There will be a game welcome page before the game starts. The whole page will last about three seconds, and then enter the game scene. In the game welcome page, in addition to displaying the game name, you should also modify the window title.

/***************************
 * Function: Welcome page
 * Input:
 *		nothing
 * return:
 *		nothing
 **************************/
void welcome() {
	//1. Initialize canvas
	initgraph(550, 660);
	//2. Set window title
	HWND window = GetHWnd();//Get window, get current window
	SetWindowText(window, _T("Tetris Xiaoming is coming"));	//Set title

	//3. Set the game initial page
	setfont(40, 0, _T("Microsoft YaHei "));		//Set the font style of text (height, width (0 means adaptive), font)
	setcolor(WHITE);	// Set color
	outtextxy(205, 200, _T("Russian method"));

	setfont(20, 0, _T("Regular script"));
	setcolor(WHITE);	// Set color
	outtextxy(175, 300, _T("Programming, starting with Tetris"));

	Sleep(3000);
}

② Game background initGameScreen()

Draw game scenes

/***************************
 * Function: initialize game scene
 * Input:
 *		nothing
 * return:
 *		nothing
 **************************/
void initGameSceen() {
	char str[16];	//Storage score
	//1. Clear the screen
	cleardevice();
	//2. Painting scenes
	rectangle(27, 27, 336, 635);	//Box landing frame outer frame
	rectangle(29, 29, 334, 633);	//Box landing box
	rectangle(370, 50, 515, 195);	//Box prompt

	setfont(24, 0, _T("Regular script"));		//Write "next"
	setcolor(LIGHTGRAY);	//grey
	outtextxy(405, 215, _T("next:"));

	setcolor(RED);					//Write score
	outtextxy(405, 280, _T("fraction:"));

	//Write the score to str in the specified format
	sprintf_s(str, 16, "%d", score);
	//Here, set the character set to multi character to ensure that outextxy can write out the variable str
	outtextxy(415, 310, str);

	outtextxy(405, 375, _T("Grade:"));	//Grade

	//Writes rank to str in the specified format
	sprintf_s(str, 16, "%d", rank);
	//Here, set the character set to multi character to ensure that outextxy can write out the variable str
	outtextxy(415, 405, str);

	setcolor(LIGHTBLUE);	//Operating instructions
	outtextxy(390, 475, "Operating instructions:");
	outtextxy(390, 500, "W: rotate");
	outtextxy(390, 525, "S: decline");
	outtextxy(390, 550, "A: Shift left");
	outtextxy(390, 575, "D: Shift right");
	outtextxy(390, 600, "Space: suspend");
	system("pause");
}

③ The square represents int block [] [] []

The orientation of each square is represented by a two-dimensional array. This time, a total of five kinds of blocks are designed, each block has four orientations, and each rigid block and its orientation are stored in a three-dimensional array. 1 means that the point is a square.

#define BLOCK_COUNT 5
#define BLOCK_WIDTH 5
#define BLOCK_HEIGHT 5

int block[BLOCK_COUNT * 4][BLOCK_WIDTH][BLOCK_HEIGHT] = {
	// |Shaped square
	{ 0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0 },
	 // L-shaped square
	{ 0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,1,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,0,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0 },
	// Field shaped square
	{ 0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0 },
	// T-shaped square
	{ 0,0,0,0,0,0,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,1,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0 },
	// Z-shaped square
	{ 0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,1,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0 },
	{ 0,0,0,0,0,0,0,1,0,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0 },
};

④ The new box represents nextBlock()

In the box prompt box, each generation of a new box consists of two actions: first, erase the box, and then draw a new box.

/*****************************************
 * Function: clear the box in the box prompt box
 * Input:
 *		nothing
 * return:
 *		nothing
 ****************************************/
void clearBlock() {
	setcolor(BLACK);
	setfont(23, 0, "Regular script");
	for (int i = 0; i < BLOCK_HEIGHT; ++i) {
		for (int j = 0; j < BLOCK_WIDTH; ++j) {
			int x = 391 + j * UNIT_SIZE;
			int y = 71 + i * UNIT_SIZE;
			outtextxy(x, y, "■");
		}
	}
}


/*****************************************
 * Function: draw a square at the beginning of the prompt box and landing box
 * Input:
 *		x,y - Coordinates of the square (position of the upper left corner of the two-dimensional array)
 * return:
 *		nothing
 ****************************************/
void drawBlock(int x, int y) {
	setcolor(color[NextIndex]);
	setfont(23, 0, "Regular script");
	for (int i = 0; i < BLOCK_HEIGHT; ++i) {
		for (int j = 0; j < BLOCK_WIDTH; ++j) {
			if (block[NextIndex * 4][i][j] == 1) {
				int x2 = x + j * UNIT_SIZE;
				int y2 = y + i * UNIT_SIZE;
				outtextxy(x2, y2, "■");
			}
		}
	}
}

/*****************************************
 *Function: generate a new box in the box prompt box
 *Input:
 *		nothing
 *return:
 *		nothing
 ****************************************/
void nextblock() {
	clearBlock();
	//Generate random numbers and randomly select blocks
	srand((unsigned)time(NULL));	//Use the return value of the time function as a random seed
	NextIndex = rand() % BLOCK_COUNT;	//Generate random numbers from 0 to 5
	drawBlock(391, 71);
}

⑤ Design game loop (main)

In the game box, each time a new box is generated, it will enter the landing processing of the new box. After processing, it will cycle

int main() {
	welcome();
	initGameSceen();
	//Generate new block
	nextblock();
	Sleep(800);

	//Initialize access array
	memset(visit, 0, sizeof(visit));

	while (1) {
		newblock();
	}
	system("pause");
	closegraph();
	return 0;
}

/*****************************************
 *Function: draw the box just landed from the top, update the box in the prompt box, and call the box landing function move()
 *Input:
 *		nothing
 * return:
 *		nothing
 ****************************************/
void newblock() {
	BlockIndex = NextIndex;
	//Draw the square that just fell from the top
	drawBlock(START_X, START_Y);
	//Pause for the new box to appear
	Sleep(200);
	//Draw the next box in the upper right corner
	nextblock();
	//Block landing
	move();
}

⑥ Build user operation framework move (I)

User operation frame: break whether the game is over → erase the current box → user key operation → draw a new box → delay waiting → whether the box needs to be solidified (solidification indicates that the operation on the current box is over).

#define KEY_UP 87 		// User operation
#define KEY_LEFT 65
#define KEY_RIGHT 68
#define KEY_DOWN 83
#define KEY_SPACE 32

/*****************************************
 * Function: read the user's operation and update the landing box from time to time
 * Input:
 *		nothing
 * return:
 *		nothing
 ****************************************/
void move() {
	//Judge whether the game is over before reading the user's operation
	failCheck();
	//Continuous descent
	while (1) {
		//Clear box
	        //to do
		//Judge the selected direction
		if (_kbhit()) {
			int key = _getch();
			if (key == KEY_SPACE) {
				//to do
			}
			else if (key == KEY_UP) {
				//to do
			}
			else if (key == KEY_LEFT) {
				//to do
			}
			else if (key == KEY_RIGHT) {
				//to do
			}
			else if (key == KEY_DOWN) {
				//to do
			}
		}
		//Draw square
		    //to do
		//dormancy
		    //to do
		//The curing process of the block ends the cycle after the block is fixed, and the move of the current block is completed
	        //to do
	}
}

⑦ Determine whether the box can move in the specified direction

When the new box just touches the "solidified" box when it is drawn from the top, the game is over, so we just need to judge whether the box can move down. Here, the function of judging whether the box can move in the specified direction is realized first.

/*****************************************
  *Function: judge whether it can move to the specified direction at the specified position
  *Input:
  *		x,y - Square position
  *		moveDir - The direction you want to move next
  *		blockDir - The direction of the current square
  * return:
  *		true - Can move
  *		false - Cannot move
  ****************************************/
bool moveable(int x0, int y0, move_dir_t moveDir, block_dir_t blockDir) {
	//The upper left corner of the calculation box is 30 × 15 game area location (rows and columns)
	int x = (y0 - MinY) / UNIT_SIZE;
	int y = (x0 - MinX) / UNIT_SIZE;
	int ret = 1;
	int id = BlockIndex * 4 + blockDir;
	if (moveDir == MOVE_DOWN) {
		for (int i = 0; i < BLOCK_HEIGHT; ++i) {
			for (int j = 0; j < BLOCK_WIDTH; ++j) {
				//Conditions for not moving down: the solid block has reached the bottom (x+i+1==30), or there is a block at the bottom	
				if (block[id][i][j] == 1 &&
					(x + i + 1 == 30 || visit[x + i + 1][y + j] == 1)) {
					ret = 0;
				}
			}
		}
	}
	else if (moveDir == MOVE_LEFT) {
		for (int i = 0; i < BLOCK_HEIGHT; ++i) {
			for (int j = 0; j < BLOCK_WIDTH; ++j) {
				//Conditions for not moving to the left: the solid box has reached the left boundary (y+j==0), or there is a box on the left
				if (block[id][i][j] == 1 &&
					(y + j <= 0 || visit[x + i][y + j - 1] == 1)) {
					ret = 0;
				}
			}
		}
	}
	else if (moveDir == MOVE_RIGHT) {
		for (int i = 0; i < BLOCK_HEIGHT; ++i) {
			for (int j = 0; j < BLOCK_WIDTH; ++j) {
				//Conditions under which the downward movement is not possible: the solid block has reached the right boundary (y + j + 1 > = 15), or there is a block on the right
				if (block[id][i][j] == 1 &&
					(y + j + 1 >= 15 || visit[x + i][y + j + 1] == 1)) {
					ret = 0;
				}
			}
		}
	}
	return ret;
}

⑧ Game failure check ()

Game failure detection. When the newly drawn box cannot move down, it indicates that the game has failed.

/*****************************
 *Function: check whether the game is over
 *Input:
 *		nothing
 * return:
 *		nothing
 *****************************/
void failCheck() {
	//The end condition of the game is that the newly drawn box on the top will be "solidified", and the newly drawn box on the top will face up and move down
	if (!moveable(START_X, START_Y, MOVE_DOWN, (block_dir_t)BLOCK_UP)) {
		setcolor(WHITE);
		setfont(45, 0, "Official style");
		outtextxy(75, 300, "Game Over!");
		Sleep(1000);
		system("pause");
		closegraph();
		exit(0);
	}
}

⑨ Clear the box during descent (clearblock)

If the game does not fail, it indicates that the user can continue the operation. Clear the box in the landing box before reading the user's operation.

/*****************************************
 * Function: clear blocks during landing
 * Input:
 *		x,y - Coordinates of the square (position of the upper left corner of the two-dimensional array)
 *		block_dir_t - Square direction
 * return:
 *		nothing
 ****************************************/
void clearBlock(int x, int y, block_dir_t blockDir) {
	setcolor(BLACK);
	//	setfont(23, 0, "italics");
	int id = BlockIndex * 4 + blockDir;
	for (int i = 0; i < BLOCK_HEIGHT; ++i) {
		for (int j = 0; j < BLOCK_WIDTH; ++j) {
			if (block[id][i][j] == 1) {
				int x2 = x + j * UNIT_SIZE;
				int y2 = y + i * UNIT_SIZE;
				outtextxy(x2, y2, "■");
			}
		}
	}
}

⑩ Judging box rotation rotatable()

If the block can move downward in the direction to be turned, it indicates that the block can rotate. Therefore, it only needs to make less use of the moveable function.

/*****************************************
 * Function: judge whether the current box can rotate in the specified direction
 * Input:
 *		x,y - Square position (2D array coordinates)
 *		dir - Square rotation direction
 * return:
 *		true - Can rotate
 *		false - Cannot rotate
 ****************************************/
bool rotatable(int x, int y, block_dir_t dir) {
	//First, judge whether you can continue to move down
	if (!moveable(x, y, MOVE_DOWN, dir)) {
		return false;
	}
	int x2 = (y - MinY) / UNIT_SIZE;
	int y2 = (x - MinX) / UNIT_SIZE;
	int id = BlockIndex * 4 + dir;
	for (int i = 0; i < BLOCK_HEIGHT; ++i) {
		for (int j = 0; j < BLOCK_WIDTH; ++j) {
			//No rotation condition: the left and right boundaries are out of bounds or there is a block "blocking"
			if (block[id][i][j] == 1 && (y2 + j < 0 || y2 + j >= 15 || visit[x2 + i][y2 + j] == 1)) {
				return false;
			}
		}
	}
	return true;
}

① ① draw the block during descent (drawblock)

Draw a new square according to the user's operation each time

/*****************************************
 *Function: draw the square in the descending process
 *Input:
 *		x,y - Coordinates of the square (position of the upper left corner of the two-dimensional array)
 *		block_dir_t - Square direction
 * return:
 *		nothing
 ****************************************/
void drawBlock(int x, int y, block_dir_t dir) {
	setcolor(color[BlockIndex]);
	setfont(23, 0, "Regular script");
	int id = BlockIndex * 4 + dir;
	for (int i = 0; i < BLOCK_HEIGHT; ++i) {
		for (int j = 0; j < BLOCK_WIDTH; ++j) {
			if (block[id][i][j] == 1) {
				//Erase the i-th row and j-th column of the box
				outtextxy(x + j * UNIT_SIZE, y + i * UNIT_SIZE, "■");
			}
		}
	}
}

① ② delay wait ()

After each user operation, it will enter the delay waiting, and the waiting time will be determined according to the landing speed of the current box. During the delay waiting, if the user is detected to have a key operation, the waiting will end.

/**************************
 * Function: delay waiting
 * Input:
 *
 * return:
 *		nothing
 *************************/
void wait(int interval) {
	int count = interval / 10;
	for (int i = 0; i < count; ++i) {
		Sleep(10);
		//If the user presses a key during hibernation, the hibernation ends
		if (_kbhit()) {
			return;
		}
	}
}

① ③ fixed block mark()

Each time a new block is drawn, judge whether the block can continue to move. If it cannot move, it indicates that the block needs to be solidified.

/*****************************************
 * Function: block fixing
 * Input:
 *		x,y - Block coordinates
 *		dir - Square orientation
 * return:
 *		nothing
 ****************************************/
void mark(int x, int y, block_dir_t dir) {
	int id = BlockIndex * 4 + dir;
	int x2 = (y - MinY) / UNIT_SIZE;
	int y2 = (x - MinX) / UNIT_SIZE;
	for (int i = 0; i < BLOCK_HEIGHT; ++i) {
		for (int j = 0; j < BLOCK_WIDTH; ++j) {
			if (block[id][i][j] == 1) {
				visit[x2 + i][y2 + j] = 1;
				markColor[x2 + i][y2 + j] = color[BlockIndex];
			}
		}
	}
}

① ④ improvement of user operation framework Ⅱ mov()

Add the above implementation functions to the operation framework

void move() {
	int x = START_X;	//Box start position
	int y = START_Y;
	int k = 0;
	block_dir_t blockDir = (block_dir_t)BLOCK_UP;
	int curSpeed = speed;	//Define the current square landing speed
	//Judge whether the game is over before reading the user's operation
	failCheck();
	//Continuous descent
	while (1) {
		int curSpeed = speed;	//Define the current square landing speed
		//Clear box
		clearBlock(x, k + y, blockDir);
		//Judge the selected direction
		if (_kbhit()) {
			int key = _getch();
			if (key == KEY_SPACE) {
				system("pause");
			}
			else if (key == KEY_UP) {
				block_dir_t nextDir = (block_dir_t)((blockDir + 1) % 4);
				if (rotatable(x, y + k, nextDir)) {
					blockDir = nextDir;
				}
			}
			else if (key == KEY_LEFT) {
				if (moveable(x, y + k + 20, MOVE_LEFT, blockDir)) {
					x -= UNIT_SIZE;
				}
			}
			else if (key == KEY_RIGHT) {
				if (moveable(x, y + k + 20, MOVE_RIGHT, blockDir)) {
					x += UNIT_SIZE;
				}
			}
			else if (key == KEY_DOWN) {
				curSpeed = 50;
			}
		}
		k += 20;
		//Draw square
		drawBlock(x, y + k, blockDir);
		//dormancy
		wait(curSpeed);
		//The curing process of the block ends the cycle after the block is fixed, and the move of the current block is completed
		if (!moveable(x, y + k, MOVE_DOWN, blockDir)) {
			mark(x, y + k, blockDir);
			break;
		}
	}
}

① ⑤ eliminate block check() + down()

When the descending operation of a box is completed, find the "full line" box in the solidified box array. If there is a "full line" box, clear it, and then update the user score and level.

g

/*************************
 * Function: check whether there are full rows of squares
 * Input:
 *		nothing
 * return:
 *		nothing
 *************************/
void check() {
	int i, j;
	int clearLines = 0;
	for (i = 29; i >= 0; i--) {
		// Check if line i is full
		for (j = 0; j < 15 && visit[i][j]; j++);
		//When you do this, there are two situations:
		// 1. If line i is not full, it means there is a vacancy. At this time, J < 15
		// 2. Line i is full, and j > = 15
		if (j >= 15) {
			// At this time, line i is full, so you need to eliminate line i
			down(i);  //Eliminate line i and move all the upper lines down
			i++;  // Because there is i -- in the outermost loop, let's first i + +, so that we can check this line again in the next loop
			clearLines++;
		}
	}
	// Update score
	addScore(clearLines);

	// Update level (update level prompt, update speed)
	updateGrade();
}

/*****************************************
 * Function: eliminate the i-th line and move the upper line down
 * Input:
 *		nothing
 * return:
 *		nothing
 ****************************************/
void down(int x) {
	for (int i = x; i > 0; --i) {
		for (int j = 0; j < 15; ++j) {
			if (visit[i - 1][j] == 1) {
				visit[i][j] = 1;
				markColor[i][j] = markColor[i - 1][j];
				setcolor(markColor[i][j]);
				outtextxy(20 * j + MinX, 20 * i + MinY, "■");
			}
			else {
				visit[i][j] = 0;
				setcolor(BLACK);
				outtextxy(20 * j + MinX, 20 * i + MinY, "■");
			}
		}
	}
	//Clear topmost grid
	setcolor(BLACK);
	for (int j = 0; j < 15; ++j) {
		visit[0][j] = 0;
		outtextxy(20 * j + MinX, MinY, "■");
	}
}

① ⑥ update score and grade addscore() + updategrade()

Update the user score and level according to the number of clear box lines.

/*****************************************
 * Function: update score
 * Input:
 *		nothing
 * return:
 *		nothing
 ****************************************/
void addScore(int lines) {
	char str[32];
	score += lines * 10;
	sprintf_s(str, 32, "%d", score);
	setcolor(RED);
	outtextxy(415, 310, str);

}

/*************************
 * Function: update level
 * Input:
 *		nothing
 * return:
 *		nothing
 *************************/
void updateGrade() {
	//Update level
	//Assume 50 points
	rank = score / 50;
	char str[32];
	sprintf_s(str, 32, "%d", rank);
	setcolor(RED);
	outtextxy(415, 405, str);
	//Update speed
	if (speed <= 100) {
		speed = 100;
	}
	else {
		speed = 500 - rank * 20;
	}
}

Code integration operation

5, Deficiencies

  1. Use easyX drawing and import game pictures to make the game effect more realistic
  2. Preservation of game achievements
  3. Operation control slightly stuck

Keywords: C++ Algorithm

Added by TheMayhem on Sun, 13 Feb 2022 10:45:51 +0200