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 数组的最左上角位置,方便后续移动与判断。蓝色表示被定位的方框。
三、核心操作介绍
由于篇幅问题,这里只讲解俄罗斯方块核心操作,包括:
-
旋转
-
检测
旋转操作
使用 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 个方块的所有状态,这里省略。
检测操作
水平检测
旋转后的水平空间不够时需要水平(左右)移动,出现以下情况则不能旋转:
- 当前水平空间不够
黑色为非主要方块,红色为玩家操控的方块,蓝色方框为旋转后的情况。
- 方块移动后水平空间在相反方向再次移动
如图,绿色方框执行右移动,需要再次检测(蓝色),但发现需要左移动,也就是第二次移动与第一次移动方向相反。
并且需要注意什么时候左右移动,经过我个人单独测试(不一定对),当非主要方块出现在 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;
}
竖直检测
竖直检测可以类比水平检测,有:
-
当前竖直空间不够
黑色为非主要方块,红色为玩家操控的方块,蓝色方框为旋转后的情况。 -
方块移动后竖直空间在相反方向再次移动
如图,绿色方框执行下移动,需要再次检测(蓝色),但发现需要上移动,也就是第二次移动与第一次移动方向相反。
并且需要注意什么时候上下移动,经过我个人单独测试(不一定对),当非主要方块出现在 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;
}
-
将水平检测和竖直检测不同部分用函数分开,用回调函数的方法进行简化。
-
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;
}