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