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;
}
相关推荐
Clockwiseee1 小时前
文件上传总结
运维·服务器·学习·文件上传
苜柠2 小时前
Wpf学习片段
学习
欢乐熊嵌入式编程2 小时前
智能手表固件升级 OTA 策略文档初稿
嵌入式硬件·学习·智能手表
起床学FPGA2 小时前
异步FIFO的学习
学习·fpga开发
依年南台3 小时前
搭建大数据学习的平台
大数据·学习
小虎卫远程打卡app3 小时前
视频编解码学习10之成像技术原理
学习·计算机视觉·视频编解码
Despacito0o4 小时前
RGB矩阵照明系统详解及WS2812配置指南
c语言·线性代数·矩阵·计算机外设·qmk
X Y O4 小时前
神经网络初步学习——感知机
人工智能·神经网络·学习·感知机
小王努力学编程4 小时前
高并发内存池(三):TLS无锁访问以及Central Cache结构设计
jvm·数据结构·c++·学习
hnlucky4 小时前
通俗易懂版知识点:Keepalived + LVS + Web + NFS 高可用集群到底是干什么的?
linux·前端·学习·github·web·可用性测试·lvs