C语言俄罗斯方块(VS2022版)

C语言俄罗斯方块

以下代码环境为 VS2022 C语言

演示视频

俄罗斯方块演示视频

一、前置知识

1.Win32 API 的使用

俄罗斯方块会用到Win32 API的键盘、光标操作。Win32 API 光标隐藏定位和键盘读取等常用函数 读者可参考这篇博客。

2.宽字符的使用

为解决横纵字符坐标统一,需要使用宽字符。C语言宽字符 wchar_t 类型 这篇博客已经整理完毕,这里就不赘述了。

二、封装核心数据与框架介绍

在 tetris.h 中:

c 复制代码
typedef enum GAME_STATUS
{
	NORMAL = 1,					// 正常运行
	END_NORMAL,					// 正常退出
	BEYOND_WALL					// 超过墙体
} GAME_STATUS;

typedef struct BOX
{
	int _curNum;				// 当前方块号码
	int _cur[4][4];				// 当前方块

	int _nexNum;				// 下一个方块号码
	int _nex[4][4];				// 下一个方块

	int _spinJudge[4][4];		// 临时判断旋转框架
} BOX;

typedef struct POS				// 界面定位
{
	int _x;
	int _y;
} POS;

typedef struct Tetris
{
	GAME_STATUS _status;		// 游戏状态

	BOX _Box;					// 方块准备
	POS _pos;					// 方块定位

	int _frame[HIGH][WIGHT];	// 屏幕
	int _spinAngle;				// 旋转方向
	int _sleep_time;			// 间隔时间
	int _score;					// 总分数
	int _get_score;				// 消除一行的分数
	int _fast_fall;				// 是否快速下落
	int _fall_time;				// 下落时间
	bool _check_block;			// 检查非主要方块
	bool _block_other;			// 是否打印非主要方块

	char* _block_color[7];		// 方块颜色

} Tetris, * pTetris;

游戏中方块可移动范围为高 20 格,宽 10 格的框架。为确保后续旋转后的水平检测、竖直检测和方块的颜色处理,这里实际采用高 26 格,宽 14 格的数字框架,存储方块颜色,并且映射到 Windows 的控制台画面上。

_Box 中 _cur 存储当前玩家操控的方块(主要方块),_nex 存储玩家下一个得到的方块, _spinJudge 用来存储旋转时判断的临时方块。

这里使用 _pos 来定位 _cur 数组的最左上角位置,方便后续移动与判断。蓝色表示被定位的方框。

三、核心操作介绍

由于篇幅问题,这里只讲解俄罗斯方块核心操作,包括:

  1. 旋转

  2. 检测

旋转操作

使用 4 * 4 的数组保存 7 个方块形状与对应颜色的数字编号,使用公式旋转框架中的数字编号,从而得到旋转的状态。

公式来源:【百万好评】国外技术大神C++游戏编程实战教程,油管580W收藏,新手10小时入门,并快速达到游戏开发能力(中英字幕)P1 1.俄罗斯方块

游戏中只需要 顺时针 90 度 和逆时针 90 度,则:

c 复制代码
void block_number_spin(int arr[4][4], int spinAngle)
{
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			switch (spinAngle)
			{
			case 1:		// 旋转90°
				arr[i][j] = 12 + i - (j * 4);
				break;
			case 2:		// 旋转270°	也就是逆时针90°
				arr[i][j] = 3 - i + (j * 4);
				break;
			}
		}
	}
}

操作如图:

我们只需计算坐标便得到对应顺时针 90 度的编号,如: i = 0, j = 0 时,数组[0][0] 原来编号元素为 0,通过公式 12 + i - (j * 4) 即 12 + 0 - (0 * 4) 得 12。

i = 3, j = 3 时,数组[3][3] 原来编号元素为 15,通过公式 有 12 + 3 - (3 * 4) 得 3。

并且我们发现,最初的编号数组和顺时针 90 度后的数组可以连续使用:

这意味着不用公式,只需准备好 最初编号数组、顺时针旋转 90 度的数组、逆时针旋转 90 度的数组,便可完成旋转操作。

在 7 个方块 7 * 4 = 28 个状态,只需要 每个方块准备一个,其他 21 个状态都可以用旋转来处理。

c 复制代码
void getSpinArr(int(*judgeArr)[4], int(*dirArr)[4], int* login, int shape)
{
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (login[0] == dirArr[i][j])
			{
				judgeArr[i][j] = 1 + shape;
			}
			else if (login[1] == dirArr[i][j])
			{
				judgeArr[i][j] = 1 + shape;
			}
			else if (login[2] == dirArr[i][j])
			{
				judgeArr[i][j] = 1 + shape;
			}
			else if (login[3] == dirArr[i][j])
			{
				judgeArr[i][j] = 1 + shape;
			}
		}
	}
}

void spin(pTetris tetris)
{
	int(*pcur)[4] = tetris->_Box._cur;
	// 临时用的计算数组
	static int temp[4][4] = { { 0, 1, 2, 3 },
							  { 4, 5, 6, 7 },
							  { 8, 9, 10, 11 },
							  { 12, 13, 14, 15 } };

	// 顺时针 也就是 右转
	static int rightArr[4][4] = { { 12, 8, 4, 0 },
								  { 13, 9, 5, 1 },
								  { 14, 10, 6, 2 },
								  { 15, 11, 7, 3 } };

	// 逆时针 也就是 左转
	static int leftArr[4][4] = { { 3, 7, 11, 15 },
								 { 2, 6, 10, 14 },
								 { 1, 5, 9, 13 },
								 { 0, 4, 8, 12 } };

	// 临时用的记录下标
	int login[4] = { 0 };

	// 记录
	int num = 0;
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (pcur[i][j])
			{
				login[num++] = temp[i][j];
			}
		}
	}

	// 旋转形状记录
	int shape = tetris->_Box._curNum;

	/*
	* 先用数组(judgeArr) 保存旋转后的状态,
	* 然后将数组进行水平、竖直检查
	* 若水平方向既要左移又要右移,说明碰到不能旋转的情况,
	* 需要取消旋转,也就是 pcur 不拷贝 judgeArr
	*/
	int(*judgeArr)[4] = tetris->_Box._spinJudge;

	// 放下
	if (tetris->_spinAngle == 1) // 顺时针 
	{
		getSpinArr(judgeArr, rightArr, login, shape);
	}
	else
	{
		getSpinArr(judgeArr, leftArr, login, shape);
	}
}

当然,也可以枚举 7 个方块的所有状态,这里省略。

检测操作

水平检测

旋转后的水平空间不够时需要水平(左右)移动,出现以下情况则不能旋转:

  1. 当前水平空间不够

黑色为非主要方块,红色为玩家操控的方块,蓝色方框为旋转后的情况。

  1. 方块移动后水平空间在相反方向再次移动

    如图,绿色方框执行右移动,需要再次检测(蓝色),但发现需要左移动,也就是第二次移动与第一次移动方向相反。

并且需要注意什么时候左右移动,经过我个人单独测试(不一定对) ,当非主要方块出现在 4 * 4 数组右边时,覆盖需要左移动,反之右移动:

使用递归可以简洁处理水平操作:

c 复制代码
int judgeSpinLevelMove(pTetris tetris, int moveDirection)
{
	int x = tetris->_pos._x + moveDirection;
	int y = tetris->_pos._y;

	int leftMove = 0;
	int rightMove = 0;

	int(*frame)[WIGHT] = tetris->_frame;
	int(*judgeArr)[4] = tetris->_Box._spinJudge;

	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (frame[y + i][x + j + 1] < 0 && judgeArr[i][j]) // 检测是否占位 
			{
				if (x + j >= x + 2)		// 检查在 右半 框架		
				{
					if (frame[y + i][x + j + 1 - 1] >= 0) 
					{
						leftMove = 1;
					}
				}

				if (x + j < x + 2) 		// 检查在 左半 框架
				{
					if (frame[y + i][x + j + 1 + 1] >= 0) 	
					{
						rightMove = 1;
					}
				}
			}
		}
	}

	int lastXDir = x - tetris->_pos._x;		// 上一次移动的方向,大于0表示右移动,为负表示左移动。
	if (leftMove && !rightMove && lastXDir <= 0)
	{
		return judgeSpinLevelMove(tetris, moveDirection - 1) - 1;
	}
	else if (rightMove && !leftMove && lastXDir >= 0)
	{
		return judgeSpinLevelMove(tetris, moveDirection + 1) + 1;
	}
	else if ((rightMove && leftMove) || (rightMove && lastXDir < 0) || (leftMove && lastXDir > 0))
	{
		// 此时不能旋转,递归深度不会超过 5 次,移动范围也就是[-5, 5](左移5次,右移5次), 
		// 以负20为标记表示不能旋转
		return -20;					
	}
	return 0;
}

竖直检测

竖直检测可以类比水平检测,有:

  1. 当前竖直空间不够

    黑色为非主要方块,红色为玩家操控的方块,蓝色方框为旋转后的情况。

  2. 方块移动后竖直空间在相反方向再次移动

    如图,绿色方框执行下移动,需要再次检测(蓝色),但发现需要上移动,也就是第二次移动与第一次移动方向相反。

并且需要注意什么时候上下移动,经过我个人单独测试(不一定对) ,当非主要方块出现在 4 * 4 数组上边时,覆盖需要下移动,反之上移动:

使用递归处理竖直检测操作:

c 复制代码
int judgeSpinVerticalMove(pTetris tetris, int moveDirection)
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y + moveDirection;

	int upMove = 0;
	int downMove = 0;

	int(*frame)[WIGHT] = tetris->_frame;
	int(*judgeArr)[4] = tetris->_Box._spinJudge;

	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (frame[y + i][x + j + 1] < 0 && judgeArr[i][j]) // 检测是否占位 
			{
				if (y + i >= y + 2)	// 检查超出 上半 框架		
				{
					if (frame[y + i - 1][x + j + 1] >= 0) 
					{
						upMove = 1;
					}
				}

				if (y + i < y + 2)	// 检查超出 下半 框架
				{
					if (frame[y + i + 1][x + j + 1] >= 0) 
					{
						downMove = 1;
					}
				}
			}
		}
	}

	int lastYDir = y - tetris->_pos._y;
	if (upMove && !downMove && lastYDir <= 0)
	{
		return judgeSpinVerticalMove(tetris, moveDirection - 1) - 1;
	}
	else if (downMove && !upMove && lastYDir >= 0)
	{
		return judgeSpinVerticalMove(tetris, moveDirection + 1) + 1;
	}
	else if ((downMove && upMove) || (downMove && lastYDir > 0) || (upMove && lastYDir < 0))
	{
		return -20;					// 此时不能旋转,以负20为标记
	}
	return 0;
}

代码化简

水平检测和竖直检测重复代码较多,可以简化:

c 复制代码
void judgeVertical(pTetris tetris, int i, int j, int lastMoveDir, int* posiDir, int* negaDir)
{
	int x = tetris->_pos._x, y = tetris->_pos._y + lastMoveDir;
	if (tetris->_frame[y + i][x + j + 1] < 0 && tetris->_Box._spinJudge[i][j])
	{
		if (i >= 2 && tetris->_frame[y + i - 1][x + j + 1] >= 0)
		{
			*negaDir = 1;
		}
		else if (i < 2 && tetris->_frame[y + i + 1][x + j + 1] >= 0)
		{
			*posiDir = 1;
		}
	}
}

void judgeLevel(pTetris tetris, int i, int j, int lastMoveDir, int* posiDir, int* negaDir)
{
	int x = tetris->_pos._x + lastMoveDir, y = tetris->_pos._y;
	if (tetris->_frame[y + i][x + j + 1] < 0 && tetris->_Box._spinJudge[i][j])
	{
		if (j >= 2 && tetris->_frame[y + i][x + j + 1 - 1] >= 0)
		{
			*negaDir = 1;
		}
		else if (j < 2 && tetris->_frame[y + i][x + j + 1 + 1] >= 0)
		{
			*posiDir = 1;
		}
	}
}

int judgeSpinMove(pTetris tetris, int (*judgeMove)(pTetris, int, int, int, int*, int*), int lastMoveDir)
{
	int positiveDir = 0, negativeDir = 0;
	for (int i = 0; i < 16; ++i)
	{
		judgeMove(tetris, i / 4, i % 4, lastMoveDir, &positiveDir, &negativeDir);
	}

	if ((positiveDir && negativeDir) || (positiveDir && lastMoveDir < 0) || (negativeDir && lastMoveDir > 0))
	{
		return -20;
	}
	if (!positiveDir && !negativeDir)
	{
		return 0;
	}

	int moveDir = (positiveDir == 1 ? 1 : -1);
	return judgeSpinMove(tetris, judgeMove, lastMoveDir + moveDir) + moveDir;
}
  1. 将水平检测和竖直检测不同部分用函数分开,用回调函数的方法进行简化。

  2. positiveDir 表示 向右(或向下)移动,negativeDir 表示 向左(或向上)移动。

则旋转加判断函数可写为:

c 复制代码
void spin(pTetris tetris)
{
	int(*pcur)[4] = tetris->_Box._cur;
	// 临时用的计算数组
	static int temp[4][4] = { { 0, 1, 2, 3 },
							  { 4, 5, 6, 7 },
							  { 8, 9, 10, 11 },
							  { 12, 13, 14, 15 } };

	// 顺时针 也就是 右转
	static int rightArr[4][4] = { { 12, 8, 4, 0 },
								  { 13, 9, 5, 1 },
								  { 14, 10, 6, 2 },
								  { 15, 11, 7, 3 } };

	// 逆时针 也就是 左转
	static int leftArr[4][4] = { { 3, 7, 11, 15 },
								 { 2, 6, 10, 14 },
								 { 1, 5, 9, 13 },
								 { 0, 4, 8, 12 } };

	// 临时用的记录下标
	int login[4] = { 0 };

	// 记录
	int num = 0;
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (pcur[i][j])
			{
				login[num++] = temp[i][j];
			}
		}
	}

	// 旋转形状记录
	int shape = tetris->_Box._curNum;

	// 用数组(judgeArr) 保存旋转后的状态,
	int(*judgeArr)[4] = tetris->_Box._spinJudge;

	// 放下
	if (tetris->_spinAngle == 1) // 顺时针 
	{
		getSpinArr(judgeArr, rightArr, login, shape);
	}
	else
	{
		getSpinArr(judgeArr, leftArr, login, shape);
	}

	// 判断旋转后位置的合理性
	// 水平方向
	//int levelCondition = judgeSpinLevelMove(tetris, 0);
	// 竖直方向
	//int uprightCondition = judgeSpinVerticalMove(tetris, 0);

	int levelCondition = judgeSpinMove(tetris, judgeLevel, 0);
	
	int uprightCondition = 0;
	if (levelCondition >= -10)	// 当水平检测不过时,不用竖直检测
	{
		uprightCondition = judgeSpinMove(tetris, judgeVertical, 0);
	}

	if (levelCondition >= -10 && uprightCondition >= -10)
	{
		tetris->_pos._x += levelCondition;		// 改变水平坐标
		tetris->_pos._y += uprightCondition;	// 改变竖直坐标

		memcpy(pcur, judgeArr, sizeof(int) * 4 * 4);	
	}
	memset(judgeArr, 0, sizeof(int) * 4 * 4);
}

四、源码展示

在 tetris.h 中:

c 复制代码
#pragma once

#define _CRT_SECURE_NO_WARNINGS 1

#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 1) ? 1 : 0)

#include <stdbool.h>
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include <locale.h>
#include <stdio.h>
#include <time.h>

#define HIGH 26				// 存储高
#define WIGHT 14			// 存储宽

#define WALL L'█'
//#define WALL L'■'

typedef enum GAME_STATUS
{
	NORMAL = 1,					// 正常运行
	END_NORMAL,					// 正常退出
	BEYOND_WALL					// 超过墙体
} GAME_STATUS;

typedef struct BOX
{
	int _curNum;				// 当前方块号码
	int _cur[4][4];				// 当前方块

	int _nexNum;				// 下一个方块号码
	int _nex[4][4];				// 下一个方块

	int _spinJudge[4][4];		// 临时判断旋转框架
} BOX;

typedef struct POS				// 界面定位
{
	int _x;
	int _y;
} POS;

typedef struct Tetris
{
	GAME_STATUS _status;		// 游戏状态

	BOX _Box;					// 方块准备
	POS _pos;					// 方块定位

	int _frame[HIGH][WIGHT];	// 屏幕
	int _spinAngle;				// 旋转方向
	int _sleep_time;			// 间隔时间
	int _score;					// 总分数
	int _get_score;				// 消除一行的分数
	int _fast_fall;				// 是否快速下落
	int _fall_time;				// 下落时间
	bool _check_block;			// 检查非主要方块
	bool _block_other;			// 是否打印非主要方块

	char* _block_color[7];		// 方块颜色

} Tetris, * pTetris;

void SetPos(int x, int y);		// 固定画面

void HideCursor();				// 隐藏光标

void blockCopy(int arr[4][4], int num); // 方块复制

void mapPrint();				// 地图打印

void initFrame(pTetris tetris);	// 初始化逻辑面板

void GameStart(pTetris tetris);	// 游戏初始化

void print_main_block_to_screen(pTetris tetris);

void print_other_block_to_screen(pTetris tetris);

void clean_main_block_to_screen(pTetris tetris);

void login_cur_to_frame(pTetris tetris);

void clean_cur_to_frame(pTetris tetris);

bool levelMoveJudge(pTetris tetris, int dir);

void GameRun(pTetris tetris);	// 游戏运行

void GameOver(pTetris tetris);

在 tetris.c 中:

c 复制代码
#include "tetris.h"

void SetPos(int x, int y)  // 光标移动到(x,y)的位置,可以让画面刷新
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { x, y };
	SetConsoleCursorPosition(handle, pos);
}

void HideCursor()  // 隐藏光标
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cursor_info;  // 第二个值为0表示隐藏光标
	GetConsoleCursorInfo(handle, &cursor_info);
	cursor_info.bVisible = false;
	SetConsoleCursorInfo(handle, &cursor_info);
}

void FontSize()
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_FONT_INFOEX fontInfo;						// 字体大小设置
	fontInfo.cbSize = sizeof(CONSOLE_FONT_INFOEX);
	GetCurrentConsoleFontEx(handle, FALSE, &fontInfo);	// 获得字体信息

	fontInfo.dwFontSize.Y = 25;

	SetCurrentConsoleFontEx(handle, FALSE, &fontInfo);	// 设置字体信息
}

void blockCopy(int arr[4][4], int num) // 方块复制
{
	static int arr1[7][4][4] = { { {0, 0, 0, 0},  // 0.长方形
							{1, 1, 1, 1},
							{0, 0, 0, 0},
							{0, 0, 0, 0} },
						  { {0, 0, 0, 0},  // 1.正方形
							{0, 2, 2, 0},
							{0, 2, 2, 0},
							{0, 0, 0, 0} },
						  { {0, 0, 0, 0},  // 2.T形
							{0, 0, 3, 0},
							{0, 3, 3, 3},
							{0, 0, 0, 0} },
						  { {0, 0, 0, 0},  // 3.L形
							{0, 0, 4, 0},
							{4, 4, 4, 0},
							{0, 0, 0, 0} },
						  { {0, 0, 0, 0},  // 4.反L形
							{0, 5, 0, 0},
							{0, 5, 5, 5},
							{0, 0, 0, 0} },
						  { {0, 0, 0, 0},  // 5.Z形
							{0, 6, 6, 0},
							{0, 0, 6, 6},
							{0, 0, 0, 0} },
						  { {0, 0, 0, 0},  // 6.反Z形
							{0, 7, 7, 0},
							{7, 7, 0, 0},
							{0, 0, 0, 0} } };
	// 复制
	memcpy(arr, arr1[num], sizeof(int) * 4 * 4);
}

void mapPrint()				// 地图打印
{
	for (int i = 0; i < 24; i += 2)		// 上
	{
		SetPos(i, 0);
		wprintf(L"%lc", WALL);
	}
	for (int i = 0; i < 24; i += 2)		// 下
	{
		SetPos(i, 21);
		wprintf(L"%lc", WALL);
	}
	for (int i = 1; i <= 20; ++i)		// 左
	{
		SetPos(0, i);
		wprintf(L"%lc\n", WALL);
	}
	for (int i = 1; i <= 20; ++i)		// 右
	{
		SetPos(22, i);
		wprintf(L"%lc\n", WALL);
	}
}

void initFrame(pTetris tetris)
{
	for (int i = 0; i < HIGH - 1; ++i)	// 行
	{
		tetris->_frame[i][1] = -10;
		tetris->_frame[i][WIGHT - 2] = -10;
	}
	for (int i = 1; i < WIGHT - 1; ++i)	// 列
	{
		tetris->_frame[HIGH - 2][i] = -10;
	}
}

void createBlockNumber(pTetris tetris)
{
	tetris->_pos._x = 4;
	tetris->_pos._y = 1;

	tetris->_Box._curNum = tetris->_Box._nexNum;
	tetris->_Box._nexNum = rand() % 7;
}

void currentCopyNext(pTetris tetris)
{
	memcpy(tetris->_Box._cur, tetris->_Box._nex, sizeof(int) * 4 * 4);
}

void printCurrentBlock(pTetris tetris)
{
	SetPos(26, 2);

	printf("当前方块:");
	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			SetPos(26 + (j * 2), 3 + i);
			if (tetris->_Box._cur[i][j] > 0)
			{
				char* getColor = tetris->_block_color[tetris->_Box._curNum];

				printf("%s", getColor);
				wprintf(L"%lc", WALL);
				printf("\033[0m");
			}
			else
			{
				printf("  ");
			}
		}
	}
}

void printNextBlock(pTetris tetris)
{
	SetPos(26, 8);

	printf("下一个方块:");
	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			SetPos(26 + (j * 2), 9 + i);
			if (tetris->_Box._nex[i][j] > 0)
			{
				char* getColor = tetris->_block_color[tetris->_Box._nexNum];

				printf("%s", getColor);
				wprintf(L"%lc", WALL);
				printf("\033[0m");
			}
			else
			{
				printf("  ");
			}
		}
	}
}

void printGetScore(pTetris tetris)
{
	SetPos(26, 14);
	printf("当前分数:%d", tetris->_score);
}

void printOperatorInfo()
{
	SetPos(26, 16);

	printf("顺时针旋转: ↑");
	SetPos(26, 17);
	printf("逆时针旋转: ↓");
	SetPos(26, 18);
	printf("左移动:     ←");
	SetPos(26, 19);
	printf("右移动:     →");
	SetPos(26, 20);
	printf("空格加速开关");
}

void initInfo(pTetris tetris)
{
	printCurrentBlock(tetris);
	printNextBlock(tetris);
	printGetScore(tetris);
	printOperatorInfo();
}

void initBlockColor(pTetris tetris)
{
	static char* block_color[7] = { "\033[31m", "\033[33m", "\033[36m" , "\033[34m", "\033[35m" , "\033[32m" ,"\033[36;0m" };	// 方块颜色

	memcpy(tetris->_block_color, block_color, sizeof(char*) * 7);
}

void GameStart(pTetris tetris)
{
	tetris->_sleep_time = 100;
	tetris->_status = NORMAL;
	tetris->_get_score = 10;
	tetris->_score = 0;
	tetris->_Box._curNum = 2;
	tetris->_fall_time = 10;

	initBlockColor(tetris);

	// 方块准备
	tetris->_Box._nexNum = rand() % 7;
	blockCopy(tetris->_Box._nex, tetris->_Box._nexNum);

	createBlockNumber(tetris);
	currentCopyNext(tetris);
	blockCopy(tetris->_Box._nex, tetris->_Box._nexNum);

	FontSize();				// 字体大小

	mapPrint();				// 地图打印
	initFrame(tetris);		// 初始化逻辑面板
	initInfo(tetris);		// 打印提示信息
	print_other_block_to_screen(tetris);
}

void print_other_block_to_screen(pTetris tetris)
{
	int(*frame)[WIGHT] = tetris->_frame;

	for (int i = 4; i < HIGH - 2; ++i)
	{
		for (int j = 2; j < WIGHT - 2; ++j)
		{
			if (frame[i][j] < 0)
			{
				SetPos((j - 1) * 2, i - 3);

				int theNumberColor = -frame[i][j] - 1;
				char* getColor = tetris->_block_color[theNumberColor];

				printf("%s", getColor);
				wprintf(L"%lc\033[0m", WALL);
			}
			if (frame[i][j] == 0)
			{
				SetPos((j - 1) * 2, i - 3);
				printf("  ");
			}
		}
	}
}

void print_main_block_to_screen(pTetris tetris)
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;

	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			if (tetris->_Box._cur[i][j] > 0 && y + i > 3)
			{
				SetPos((x + j) * 2, y + i - 3);

				char* getColor = tetris->_block_color[tetris->_Box._curNum];
				printf("%s", getColor);
				wprintf(L"%lc\033[0m", WALL);
			}
		}
	}
}

void clean_main_block_to_screen(pTetris tetris)
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;

	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			if (tetris->_Box._cur[i][j] > 0 && y + i > 3)
			{
				SetPos((x + j) * 2, y + i - 3);
				printf("  ");
			}
		}
	}
}

void login_cur_to_frame(pTetris tetris)
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;

	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			if (tetris->_Box._cur[i][j] > 0)
			{
				tetris->_frame[y + i][x + j + 1] = tetris->_Box._cur[i][j];	
			}
		}
	}
}

void clean_cur_to_frame(pTetris tetris)
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;

	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			if (tetris->_Box._cur[i][j] > 0)
			{
				tetris->_frame[y + i][x + j + 1] = 0;						
			}
		}
	}
}

bool levelMoveJudge(pTetris tetris, int dir)
{
	// 检测移动是否可行
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;
	int(*frame)[WIGHT] = tetris->_frame;

	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			if (frame[y + i][x + j + 1] > 0 && frame[y + i][x + j + 1 + dir] < 0)
			{
				return false;
			}
		}
	}
	return true;
}

void judgeVertical(pTetris tetris, int i, int j, int lastMoveDir, int* posiDir, int* negaDir)
{
	int x = tetris->_pos._x, y = tetris->_pos._y + lastMoveDir;
	if (tetris->_frame[y + i][x + j + 1] < 0 && tetris->_Box._spinJudge[i][j])
	{
		if (i >= 2 && tetris->_frame[y + i - 1][x + j + 1] >= 0)
		{
			*negaDir = 1;
		}
		else if (i < 2 && tetris->_frame[y + i + 1][x + j + 1] >= 0)
		{
			*posiDir = 1;
		}
	}
}

void judgeLevel(pTetris tetris, int i, int j, int lastMoveDir, int* posiDir, int* negaDir)
{
	int x = tetris->_pos._x + lastMoveDir, y = tetris->_pos._y;
	if (tetris->_frame[y + i][x + j + 1] < 0 && tetris->_Box._spinJudge[i][j])
	{
		if (j >= 2 && tetris->_frame[y + i][x + j + 1 - 1] >= 0)
		{
			*negaDir = 1;
		}
		else if (j < 2 && tetris->_frame[y + i][x + j + 1 + 1] >= 0)
		{
			*posiDir = 1;
		}
	}
}

int judgeSpinMove(pTetris tetris, int (*judgeMove)(pTetris, int, int, int, int*, int*), int lastMoveDir)
{
	int positiveDir = 0, negativeDir = 0;
	for (int i = 0; i < 16; ++i)
	{
		judgeMove(tetris, i / 4, i % 4, lastMoveDir, &positiveDir, &negativeDir);
	}

	if ((positiveDir && negativeDir) || (positiveDir && lastMoveDir < 0) || (negativeDir && lastMoveDir > 0))
	{
		return -20;
	}
	if (!positiveDir && !negativeDir)
	{
		return 0;
	}

	int moveDir = (positiveDir == 1 ? 1 : -1);
	return judgeSpinMove(tetris, judgeMove, lastMoveDir + moveDir) + moveDir;
}

void getSpinArr(int(*judgeArr)[4], int(*dirArr)[4], int* login, int shape)
{
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (login[0] == dirArr[i][j])
			{
				judgeArr[i][j] = 1 + shape;
			}
			else if (login[1] == dirArr[i][j])
			{
				judgeArr[i][j] = 1 + shape;
			}
			else if (login[2] == dirArr[i][j])
			{
				judgeArr[i][j] = 1 + shape;
			}
			else if (login[3] == dirArr[i][j])
			{
				judgeArr[i][j] = 1 + shape;
			}
		}
	}
}

void spin(pTetris tetris)
{
	int(*pcur)[4] = tetris->_Box._cur;
	// 临时用的计算数组
	static int temp[4][4] = { { 0, 1, 2, 3 },
							  { 4, 5, 6, 7 },
							  { 8, 9, 10, 11 },
							  { 12, 13, 14, 15 } };

	// 顺时针 也就是 右转
	static int rightArr[4][4] = { { 12, 8, 4, 0 },
								  { 13, 9, 5, 1 },
								  { 14, 10, 6, 2 },
								  { 15, 11, 7, 3 } };

	// 逆时针 也就是 左转
	static int leftArr[4][4] = { { 3, 7, 11, 15 },
								 { 2, 6, 10, 14 },
								 { 1, 5, 9, 13 },
								 { 0, 4, 8, 12 } };

	// 临时用的记录下标
	int login[4] = { 0 };

	// 记录
	int num = 0;
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (pcur[i][j])
			{
				login[num++] = temp[i][j];
			}
		}
	}

	// 旋转形状记录
	int shape = tetris->_Box._curNum;

	// 用数组(judgeArr) 保存旋转后的状态,
	int(*judgeArr)[4] = tetris->_Box._spinJudge;

	// 放下
	if (tetris->_spinAngle == 1) // 顺时针 
	{
		getSpinArr(judgeArr, rightArr, login, shape);
	}
	else
	{
		getSpinArr(judgeArr, leftArr, login, shape);
	}

	// 判断旋转后位置的合理性
	// 水平方向
	//int levelCondition = judgeSpinLevelMove(tetris, 0);
	// 竖直方向
	//int uprightCondition = judgeSpinVerticalMove(tetris, 0);

	int levelCondition = judgeSpinMove(tetris, judgeLevel, 0);
	
	int uprightCondition = 0;
	if (levelCondition >= -10)	// 当水平检测不过时,不用竖直检测
	{
		uprightCondition = judgeSpinMove(tetris, judgeVertical, 0);
	}

	if (levelCondition >= -10 && uprightCondition >= -10)
	{
		tetris->_pos._x += levelCondition;		// 改变水平坐标
		tetris->_pos._y += uprightCondition;	// 改变竖直坐标

		memcpy(pcur, judgeArr, sizeof(int) * 4 * 4);	
	}
	memset(judgeArr, 0, sizeof(int) * 4 * 4);
}

void numSwap(pTetris tetris) // 将数字转为对应的负数然后重新生成新方块
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;
	int(*frame)[WIGHT] = tetris->_frame;

	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (frame[y + i][x + j + 1] > 0 && x + j < WIGHT - 2 && y + i < HIGH - 2)
			{
				frame[y + i][x + j + 1] = -frame[y + i][x + j + 1];
			}
		}
	}
	tetris->_block_other = true;	// 在屏幕打印非主要方块
}

void blockLock(pTetris tetris) // 锁定接触到非移动数字的方块
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;

	int(*frame)[WIGHT] = tetris->_frame;

	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (frame[y + i][x + j + 1] > 0 && frame[y + i + 1][x + j + 1] < 0)// 方块检测 
			{
				numSwap(tetris);
				return;
			}
		}
	}
}

bool is_ridBlock(pTetris tetris) // 判断是否需要消除方块
{
	int(*frame)[WIGHT] = tetris->_frame;

	for (int i = HIGH - 3; i > 0; --i)
	{
		int countBlock = 0;
		for (int j = 2; j < WIGHT - 2; ++j)
		{
			if (frame[i][j] < 0)	
			{
				++countBlock;
			}
		}
		if (countBlock == 10)
			return true;
	}
	return false;
}

void ridBlock(pTetris tetris) // 消除方块判定
{
	int(*frame)[WIGHT] = tetris->_frame;
	int rowRid[HIGH - 1] = { 0 };

	for (int i = HIGH - 3; i > 0; --i)
	{
		int countBlock = 0;
		int countBlank = 0;
		for (int j = 2; j < WIGHT - 2; ++j)	
		{
			if (frame[i][j] < 0)
			{
				countBlock++;
			}
			if (frame[i][j] == 0)
			{
				++countBlank;
			}
		}
		if (countBlank == 10)	// 当前行若为空行,代表上面都没有方块,直接退出
		{
			break;
		}
		if (countBlock == 10)
		{
			rowRid[i] = rowRid[i + 1] + 1;
			// 清除当前行
			for (int j = 2; j < WIGHT - 2; ++j)
				frame[i][j] = 0;
		}
		else
		{
			rowRid[i] = rowRid[i + 1];
		}
	}

	int theRidScore = 0;
	for (int i = HIGH - 3; i > 0; --i)
	{
		int moveDown = rowRid[i];
		theRidScore = theRidScore > moveDown ? theRidScore : moveDown;
		if (rowRid[i - 1] != moveDown)
		{
			continue;
		}

		// rowRid 保存当前行需要向下移动多少行,注意 i - k 中 当 i > 4 时,可能会出现越界情况
		for (int k = 0; k < moveDown; ++k)
		{
			for (int j = 2; j < WIGHT - 2; ++j)	
			{
				frame[i + k][j] = frame[i + k - 1][j];
				frame[i + k - 1][j] = 0;
			}
		}
	}

	for (int i = 1; i <= theRidScore; ++i)
	{
		tetris->_score += i * tetris->_get_score;
	}
}

void fallJudge(pTetris tetris)
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;

	int(*frame)[WIGHT] = tetris->_frame;
	int(*pcur)[4] = tetris->_Box._cur;

	for (int i = 3; i >= 0; --i)
	{
		for (int j = 3; j >= 0; --j)
		{
			if (pcur[i][j] > 0 && frame[y + i + 1][x + j + 1] < 0)	
			{
				tetris->_check_block = true;
				break;
			}
		}
	}
}

void fall(pTetris tetris)
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;

	int(*frame)[WIGHT] = tetris->_frame;
	int(*pcur)[4] = tetris->_Box._cur;

	for (int i = 3; i >= 0; --i)
	{
		for (int j = 3; j >= 0; --j)
		{
			if (pcur[i][j] > 0)	
			{
				frame[y + i][x + j + 1] = pcur[i][j];	
			}
		}
	}
}

void checkBeyondWall(pTetris tetris)
{
	int(*frame)[WIGHT] = tetris->_frame;

	for (int i = 3; i >= 0; --i)
	{
		for (int j = 2; j < WIGHT - 2; ++j)
		{
			if (frame[i][j] < 0)
			{
				tetris->_status = BEYOND_WALL;
				return;
			}
		}
	}
}

void GameRun(pTetris tetris)
{
	print_main_block_to_screen(tetris);
	login_cur_to_frame(tetris);							// 记入逻辑

	int game_fall = 0;

	do
	{
		if (KEY_PRESS(VK_UP))					// 顺时针转动
		{
			tetris->_spinAngle = 1;			
			clean_main_block_to_screen(tetris);
			clean_cur_to_frame(tetris);
			spin(tetris);
			printCurrentBlock(tetris);
			login_cur_to_frame(tetris);
			print_main_block_to_screen(tetris);
		}
		else if (KEY_PRESS(VK_DOWN))			// 逆时针转动
		{
			tetris->_spinAngle = 2;
			clean_main_block_to_screen(tetris);
			clean_cur_to_frame(tetris);
			spin(tetris);
			printCurrentBlock(tetris);
			login_cur_to_frame(tetris);
			print_main_block_to_screen(tetris);
		}
		else if (KEY_PRESS(VK_LEFT))			// 左移动
		{
			if (levelMoveJudge(tetris, -1))			// 需要检测
			{
				clean_main_block_to_screen(tetris);
				clean_cur_to_frame(tetris);
				tetris->_pos._x -= 1;
				print_main_block_to_screen(tetris);
				login_cur_to_frame(tetris);
			}
		}
		else if (KEY_PRESS(VK_RIGHT))			// 右移动
		{
			if (levelMoveJudge(tetris, 1))		// 需要检测
			{
				clean_main_block_to_screen(tetris);
				clean_cur_to_frame(tetris);
				tetris->_pos._x += 1;
				print_main_block_to_screen(tetris);
				login_cur_to_frame(tetris);
			}
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			if (tetris->_sleep_time == 100)
				tetris->_sleep_time = 5;
			else
				tetris->_sleep_time = 100;
		}
		//else if (KEY_PRESS(0x46))
		//{
		//	while (KEY_PRESS(0x46) == 0)
		//	{
		//		Sleep(tetris->_sleep_time);
		//	}
		//}

		++game_fall;

		if (game_fall > tetris->_fall_time)
		{
			game_fall = 0;

			// 下落判断
			fallJudge(tetris);

			// 检查是否接触非主要逻辑方块
			if (tetris->_check_block == true)
			{
				tetris->_check_block = false;
				blockLock(tetris);	// 将方块逻辑转成负数
				if (is_ridBlock(tetris))
				{
					ridBlock(tetris); // 消除一行方块判定
				}

				checkBeyondWall(tetris);

				createBlockNumber(tetris);
				currentCopyNext(tetris);
				blockCopy(tetris->_Box._nex, tetris->_Box._nexNum);
				printCurrentBlock(tetris);
				printNextBlock(tetris);
				printGetScore(tetris);
			}
			else
			{
				clean_main_block_to_screen(tetris);
				clean_cur_to_frame(tetris);
				++tetris->_pos._y;
				fall(tetris);
				print_main_block_to_screen(tetris);
			}
		}

		if (tetris->_block_other == true)
		{
			print_other_block_to_screen(tetris);
			tetris->_block_other = false;
		}

		Sleep(tetris->_sleep_time);

	} while (tetris->_status == NORMAL);
}

void GameOver(pTetris tetris)
{
	SetPos(10, 15);
	printf("游戏结束");
	SetPos(11, 16);
	system("pause");
}

在 test.c 中:

c 复制代码
#include "tetris.h"

void menu()
{
	printf("***********************\n");
	printf("**** 1.play 0.exit ****\n");
	printf("***********************\n");
}

void game()
{
	Tetris tetris = { 0 };

	GameStart(&tetris);

	GameRun(&tetris);

	GameOver(&tetris);
}

void test()
{
	setlocale(LC_ALL, "");
	system("mode con cols=50 lines=30");
	system("title 俄罗斯方块");

	HideCursor();
	srand((unsigned int)time(NULL));
	int input = 2;
	do
	{
		menu();
		printf("请输入操作:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			system("cls");
			game();
			system("cls");
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);
}

int main()
{
	test();

	return 0;
}
相关推荐
XH华4 小时前
初识C语言之二维数组(下)
c语言·算法
南宫生5 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
sanguine__5 小时前
Web APIs学习 (操作DOM BOM)
学习
数据的世界017 小时前
.NET开发人员学习书籍推荐
学习·.net
四口鲸鱼爱吃盐7 小时前
CVPR2024 | 通过集成渐近正态分布学习实现强可迁移对抗攻击
学习
白乐天_n7 小时前
腾讯游戏安全移动赛题Tencent2016A
安全·游戏
这是我588 小时前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物
Uu_05kkq8 小时前
【C语言1】C语言常见概念(总结复习篇)——库函数、ASCII码、转义字符
c语言·数据结构·算法
tealcwu8 小时前
【游戏设计原理】21 - 解谜游戏的设计
游戏·游戏策划
清梦20209 小时前
经典问题---跳跃游戏II(贪心算法)
算法·游戏·贪心算法