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

C语言俄罗斯方块

  • 演示视频
  • 一、前置知识
    • 1.Win32 API 的使用
    • 2.宽字符的使用
  • 二、封装核心数据与框架介绍
  • 三、核心操作介绍
    • 旋转操作
    • 检测操作
      • 水平检测
      • 竖直检测
      • 代码化简
  • 四、源码展示
    • 在 tetris.h 中:
    • 在 tetris.c 中:
    • 在 test.c 中:

以下代码环境为 VS2022 C语言

演示视频

俄罗斯方块演示视频

一、前置知识

1.Win32 API 的使用

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

2.宽字符的使用

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

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

在 tetris.h 中:

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 度,则:

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 个状态都可以用旋转来处理。

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 数组右边时,覆盖需要左移动,反之右移动:
在这里插入图片描述

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

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 数组上边时,覆盖需要下移动,反之上移动:
在这里插入图片描述
使用递归处理竖直检测操作:

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;
}

代码化简

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

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 表示 向左(或向上)移动。

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

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 中:

#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 中:

#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;elsetetris->_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 中:

#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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/419922.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

码上进阶_刷题模块测试_用例设计

码上进阶_刷题模块测试_用例设计 系统概述&#xff1a; 码上进阶是为程序员专门打造的交流平台&#xff0c;采用主流的微服务框架和C端技术栈作为技术基础。在这个平台上&#xff0c;程序员 可以通过刷题、练习和模拟面试来提升自己的面试能力。 功能测试&#xff1a; 登录…

SpringBoot OAuth2自定义登陆/授权页

背景 5 月份的时候&#xff0c;我实践并整理了一篇博客&#xff1a;SpringBoot搭建OAuth2&#xff0c;该博客完成之后&#xff0c;很长一段时间里我都有种意犹未尽的感觉。诚然&#xff0c;我把OAuth2搭起来了&#xff0c;各种场景的用例也跑通了&#xff0c;甚至源码也看了&am…

99.WEB渗透测试-信息收集-网络空间搜索引擎shodan(1)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;98.WEB渗透测试-信息收集-Google语法&#xff08;12&#xff09; 信息收集方向-网络空间…

【IDEA配置一个maven项目(详细操作流程)】

目录 一、安装Maven 1、官网下载maven链接地址&#xff1a;Maven – Download Apache Maven 2、下载完成后&#xff0c;解压到某一路径下。E:\JavaTools\apache-maven-3.9.8为例&#xff0c;实际配置环境变量时以自己安装的路径为准。 二、配置环境变量 1、右键此电脑–&g…

springboot、flowable 生成图片发布到Docker乱码问题

flowable自带的方法生成图片时&#xff0c;如设置字体为宋体&#xff0c;则本地测试没有问题&#xff0c;因为windows自带宋体字体库&#xff0c;但是如果发布到Docker&#xff0c;则会出现乱码问题&#xff0c;因为大部分Docker并不包含宋体字体库&#xff1b; 通过Java代码&a…

基于springboot+vue实现的在线商城系统

系统主要功能&#xff1a; &#xff08;1&#xff09;商品管理模块&#xff1a;实现了商品的基本信息录入、图片上传、状态管理等相关功能。 &#xff08;2&#xff09;商品分类模块&#xff1a;实现了分类的增删改查、分类层级管理、商品分类的关联等功能。 &#xff08;3&…

一个穷稳且病多的中年案例

调整 理性消费&#xff0c;量入为出 重视健康&#xff0c;提前规划 多元收入&#xff0c;提升自我 心态平和&#xff0c;知足常乐 提示&#xff1a;最后悔买“方”。 “方”和“車”对现金流的影响非常大。 全都是大额消耗性支出。 保持健康也需要物质基础。 为何收入或…

深度学习应用 - 自然语言处理(NLP)篇

序言 在信息技术的浩瀚星空中&#xff0c;深度学习犹如一颗璀璨的新星&#xff0c;正引领着人工智能领域的深刻变革。作为这一领域的核心分支&#xff0c;自然语言处理&#xff08; NLP \text{NLP} NLP&#xff09;更是借助深度学习的力量&#xff0c;实现了前所未有的飞跃。自…

BookStack在线文档管理系统本地Docker部署与远程访问详细教程

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

电池的电-热-寿命模型是什么?

一、背景 电池的电-热-寿命模型在工程领域具有重要意义&#xff0c;它是一种描述电池性能、温度与使用寿命之间相互关系的复杂模型。具体工程意义体现在以下几个方面&#xff1a; 性能预测&#xff1a; 通过电-热-寿命模型&#xff0c;工程师可以预测在不同负载条件下电池的…

基于YOLOv8的PCB缺陷检测算法,加入一种基于内容引导注意力(CGA)的混合融合方案(一)

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文内容&#xff1a;针对基于YOLOv8的PCB缺陷检测算法进行性能提升&#xff0c;加入各个创新点做验证性试验。 1&#xff09;提出了一种基于内容引导注意力(CGA)的混合融合方案&#xff0c;mAP0.5由原始的0.966提升至0.975 1.PCB缺陷…

【数据结构】排序算法篇二

【数据结构】排序算法篇二 1. 快速排序&#xff08;hoare版本&#xff09;&#xff08;1&#xff09;基本思想&#xff1a;&#xff08;2&#xff09;动态图解&#xff1a;&#xff08;3&#xff09;代码实现&#xff1a;&#xff08;4&#xff09;特性总结&#xff1a; 2. 快速…

Spring Boot属性注入的多种方式!

Spring Boot的一个问题&#xff0c;证明你是不是真正的 "会用" Spring boot ?Spring Boot的一个问题&#xff0c;直接暴露你是不是真正使用Spring Boothttps://mp.weixin.qq.com/s?__bizMzkzMTY0Mjc0Ng&mid2247484040&idx1&sn64ad15d95e44c874cc890973…

uboot源码分析uboot启动流程,uboot-CMD命令调用关系

uboot的最终目的是引导启动内核加载系统&#xff0c;根据这个线索我们可以首先找到uboot引导内核的main函数&#xff0c;查看系统引导的执行跳转的函数 main_loop。 下面对uboot函数的调用关系和主要调用函数进行分析。 一、uboot函数调用关系梳理 函数调用如下&#xff1a; …

Oracle Linux 8.10安装Oracle19c(19.3.0)完整教程

安装前请仔细将文档通读一遍&#xff0c;安装过程中根据安装命令仔细核对&#xff0c;特别留意一些字体加粗或标红的字样&#xff0c;遇到问题请及时咨询公司 1、基础环境 1.1、操作系统 cat /etc/redhat-release 1.2、主机名 医院默认分配的主机名可能跟其他主机会有重复&a…

Idea配置 阿里云 Spring Initializr URL

Idea默认Strart services url Idea中默认使用为https://start.spring.io/&#xff0c;国内网络如果不稳定创建工程会很慢修改为阿里云地址 https://start.aliyun.com/

局域网文件分发如何实现?掌握这4个秘籍,文件一键分发破次元!

局域网文件分发是许多企业和组织在日常工作中常见的需求&#xff0c; 有效的文件分发可以显著提高工作效率。 以下是四种实现局域网文件一键分发的秘籍&#xff1a; 1.使用终端监控软件的文件分发功能 软件示例&#xff1a;安企神等。 步骤简述&#xff1a; 安装软件&…

IP学习——oneday

1.什么是网络&#xff1f;为什么需要网络&#xff1f; 空间&#xff0c;时间&#xff1b;传统的邮件传输要考虑到距离&#xff0c;网络解决了空间距离&#xff08;太远&#xff09;、解决了时间问题&#xff08;旧音乐等&#xff09; 云:面向客户的虚拟化服务 运营商公司主营…

麒麟信安重庆渠道伙伴行业研讨会,共探国产化发展机遇

9月5日下午&#xff0c;麒麟信安举办重庆渠道伙伴行业研讨会。研讨会旨在探讨国产化浪潮下操作系统相关产业的发展机遇与挑战&#xff0c;以及如何在各关键领域实现市场拓展与应用&#xff0c;共商合作、共创未来。 会议伊始&#xff0c;麒麟信安详细阐述了公司以国产自主操作系…

攻防世界 unseping

unseping 攻防世界web新手练习 -unseping_攻防世界web新手题unseping-CSDN博客 这道题对我来说还是有点难&#xff0c;什么oct绕过命令执行第一次遇到捏&#xff0c;所以基本是跟着别人的wp写的&#xff0c;一点点记录吧 先对源码进行分析 <?php highlight_file(__FILE…