俄罗斯方块的代码实现
文章目录
- 首先是头文件的引入部分
- 接下来是一些预处理指令
- 接下来定义了两个结构体:
- 接下来是全局变量`g_hConsoleOutput`,用于存储控制台输出句柄。
- 之后是一系列函数的声明
- 最后是`main`函数
- 源码
首先是头文件的引入部分
包括stdio.h
、string.h
、stdlib.h
、time.h
、conio.h
和windows.h
。这些头文件提供标准输入输出、字符串处理、内存管理、时间处理、控制台输入输出和Windows系统相关的函数
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include <windows.h>
接下来是一些预处理指令
主要根据不同的编译器版本来定义一些与特定环境相关的数据类型和宏定义。
#ifdef _MSC_VER // M$的编译器要给予特殊照顾#if _MSC_VER <= 1200 // VC6及以下版本#error 你是不是还在用VC6呐?!#else // VC6以上版本#if _MSC_VER >= 1600 // 据说VC10及以上版本有stdint.h了#include <stdint.h>#else // VC10以下版本,自己定义int8_t和uint16_ttypedef signed char int8_t;typedef unsigned short uint16_t;#endif#ifndef __cplusplus typedef int bool;#define true 1#define false 0#endif#endif
#else #include <stdint.h>#ifndef __cplusplus // 不用C++编译,需要stdbool.h里的bool#include <stdbool.h>#endif
#endif//=============================================================================
// 7种方块的4旋转状态(4位为一行)
static const uint16_t gs_uTetrisTable[7][4] =
{{ 0x00F0U, 0x2222U, 0x00F0U, 0x2222U }, // I型{ 0x0072U, 0x0262U, 0x0270U, 0x0232U }, // T型{ 0x0223U, 0x0074U, 0x0622U, 0x0170U }, // L型{ 0x0226U, 0x0470U, 0x0322U, 0x0071U }, // J型{ 0x0063U, 0x0264U, 0x0063U, 0x0264U }, // Z型{ 0x006CU, 0x0462U, 0x006CU, 0x0462U }, // S型{ 0x0660U, 0x0660U, 0x0660U, 0x0660U } // O型
};// =============================================================================
// 初始状态的游戏池
// 每个元素表示游戏池的一行,下标大的是游戏池底部
// 两端各置2个1,底部2全置为1,便于进行碰撞检测
// 这样一来游戏池的宽度为12列
// 如果想要传统的10列,只需多填两个1即可(0xE007),当然显示相关部分也要随之改动
// 当某个元素为0xFFFFU时,说明该行已被填满
// 顶部4行用于给方块,不显示出来
// 再除去底部2行,显示出来的游戏池高度为22行
static const uint16_t gs_uInitialTetrisPool[28] =
{0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U,0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U,0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U,0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xFFFFU, 0xFFFFU
};#define COL_BEGIN 2
#define COL_END 14
#define ROW_BEGIN 4
#define ROW_END 26//
接下来定义了两个结构体:
TetrisManager
和TetrisControl
。
TetrisManager
结构体存储了游戏的相关数据,包括游戏池、当前方块的坐标、下一个方块的类型和旋转状态、得分、已消行数等。
TetrisControl
结构体存储了与控制相关的数据,包括暂停状态、旋转方向、移动方向、游戏池中每个方块的颜色等。
typedef struct TetrisManager // 这个结构体存储游戏相关数据
{uint16_t pool[28]; // 游戏池int8_t x; // 当前方块x坐标,此处坐标为方块左上角坐标int8_t y; // 当前方块y坐标int8_t type[3]; // 当前、下一个和下下一个方块类型int8_t orientation[3]; // 当前、下一个和下下一个方块旋转状态unsigned score; // 得分unsigned erasedCount[4]; // 消行数unsigned erasedTotal; // 消行总数unsigned tetrisCount[7]; // 各方块数unsigned tetrisTotal; // 方块总数bool dead; // 挂
} TetrisManager;// =============================================================================
typedef struct TetrisControl // 这个结构体存储控制相关数据
{bool pause; // 暂停bool clockwise; // 旋转方向:顺时针为trueint8_t direction; // 移动方向:0向左移动 1向右移动// 游戏池内每格的颜色// 由于此版本是彩色的,仅用游戏池数据无法存储颜色信息// 当然,如果只实现单色版的,就没必要用这个数组了int8_t color[28][16];
} TetrisControl;
接下来是全局变量g_hConsoleOutput
,用于存储控制台输出句柄。
HANDLE g_hConsoleOutput; // 控制台输出句柄
之后是一系列函数的声明
用于初始化游戏、重新开始游戏、给方块、碰撞检测、方块移动、旋转方块、消行检测、按键控制等。
// 函数声明
// 如果使用全局变量方式实现,就没必要传参了
void initGame(TetrisManager *manager, TetrisControl *control); // 初始化游戏
void restartGame(TetrisManager *manager, TetrisControl *control); // 重新开始游戏
void giveTetris(TetrisManager *manager); // 给一个方块
bool checkCollision(const TetrisManager *manager); // 碰撞检测
void insertTetris(TetrisManager *manager); // 插入方块
void removeTetris(TetrisManager *manager); // 移除方块
void horzMoveTetris(TetrisManager *manager, TetrisControl *control); // 水平移动方块
void moveDownTetris(TetrisManager *manager, TetrisControl *control); // 向下移动方块
void rotateTetris(TetrisManager *manager, TetrisControl *control); // 旋转方块
void dropDownTetris(TetrisManager *manager, TetrisControl *control); // 方块直接落地
bool checkErasing(TetrisManager *manager, TetrisControl *control); // 消行检测
void keydownControl(TetrisManager *manager, TetrisControl *control, int key); // 键按下
void setPoolColor(const TetrisManager *manager, TetrisControl *control); // 设置颜色
void gotoxyWithFullwidth(short x, short y); // 以全角定位
void printPoolBorder(); // 显示游戏池边界
void printTetrisPool(const TetrisManager *manager, const TetrisControl *control); // 显示游戏池
void printCurrentTetris(const TetrisManager *manager, const TetrisControl *control); // 显示当前方块
void printNextTetris(const TetrisManager *manager); // 显示下一个和下下一个方块
void printScore(const TetrisManager *manager); // 显示得分信息
void runGame(TetrisManager *manager, TetrisControl *control); // 运行游戏
void printPrompting(); // 显示提示信息
bool ifPlayAgain(); // 再来一次
最后是main
函数
主函数main()
是程序的入口点,它包含了游戏的主要流程控制逻辑。
- 声明了两个结构体变量,
tetrisManager
和tetrisControl
,用于存储游戏的数据和控制信息。
int main()
{TetrisManager tetrisManager;TetrisControl tetrisControl;initGame(&tetrisManager, &tetrisControl); // 初始化游戏do{printPrompting(); // 显示提示信息printPoolBorder(); // 显示游戏池边界runGame(&tetrisManager, &tetrisControl); // 运行游戏if (ifPlayAgain()) // 再来一次{SetConsoleTextAttribute(g_hConsoleOutput, 0x7);system("cls"); // 清屏restartGame(&tetrisManager, &tetrisControl); // 重新开始游戏}else{break;}} while (1);gotoxyWithFullwidth(0, 0);CloseHandle(g_hConsoleOutput);return 0;
}
- 调用
initGame()
函数进行游戏的初始化。initGame()
函数主要完成以下操作:- 设置控制台输出句柄,隐藏光标。
- 设置控制台的标题为“俄罗斯方块控制台版
- 调用
restartGame()
函数重新开始游戏。
void initGame(TetrisManager *manager, TetrisControl *control)
{CONSOLE_CURSOR_INFO cursorInfo = { 1, FALSE }; // 光标信息g_hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); // 获取控制台输出句柄SetConsoleCursorInfo(g_hConsoleOutput, &cursorInfo); // 设置光标隐藏SetConsoleTitleA("俄罗斯方块控制台版");restartGame(manager, control);
}// 重新开始游戏
void restartGame(TetrisManager *manager, TetrisControl *control)
{memset(manager, 0, sizeof(TetrisManager)); // 全部置0// 初始化游戏池memcpy(manager->pool, gs_uInitialTetrisPool, sizeof(uint16_t [28]));srand((unsigned)time(NULL)); // 设置随机种子manager->type[1] = rand() % 7; // 下一个manager->orientation[1] = rand() & 3;manager->type[2] = rand() % 7; // 下下一个manager->orientation[2] = rand() & 3;memset(control, 0, sizeof(TetrisControl)); // 全部置0giveTetris(manager); // 给下一个方块setPoolColor(manager, control); // 设置颜色
}
- 使用一个
do-while
循环,循环条件为1
,表示无限循环,直到break跳出循环。- 调用
printPrompting()
函数,提示用户游戏操作。 - 调用
printPoolBorder()
函数显示游戏池边界。 - 调用
runGame()
函数运行游戏。 - 调用
ifPlayAgain()
函数询问玩玩家是否再次玩游戏。
- 调用
// 显示游戏池边界
void printPoolBorder()
{
int8_t y;SetConsoleTextAttribute(g_hConsoleOutput, 0xF0);
for (y = ROW_BEGIN; y < ROW_END; ++y) // 不显示顶部4行和底部2行
{
gotoxyWithFullwidth(10, y - 3);
printf("%2s", "");
gotoxyWithFullwidth(23, y - 3);
printf("%2s", "");
}gotoxyWithFullwidth(10, y - 3); // 底部边界
printf("%28s", "");
}// 定位到游戏池中的方格
#define gotoxyInPool(x, y) gotoxyWithFullwidth(x + 9, y - 3)// =============================================================================
// 显示游戏池
void printTetrisPool(const TetrisManager *manager, const TetrisControl *control)
{int8_t x, y;for (y = ROW_BEGIN; y < ROW_END; ++y) // 不显示顶部4行和底部2行{gotoxyInPool(2, y); // 定点到游戏池中的方格for (x = COL_BEGIN; x < COL_END; ++x) // 不显示左右边界{if ((manager->pool[y] >> x) & 1) // 游戏池该方格有方块{// 用相应颜色,显示一个实心方块SetConsoleTextAttribute(g_hConsoleOutput, control->color[y][x]);printf("■");}else // 没有方块,显示空白{SetConsoleTextAttribute(g_hConsoleOutput, 0);printf("%2s", "");}}}
}
// 显示当前方块
void printCurrentTetris(const TetrisManager *manager, const TetrisControl *control)
{int8_t x, y;// 显示当前方块是在移动后调用的,为擦去移动前的方块,需要扩展显示区域// 由于不可能向上移动,故不需要向下扩展y = (manager->y > ROW_BEGIN) ? (manager->y - 1) : ROW_BEGIN; // 向上扩展一格for (; y < ROW_END && y < manager->y + 4; ++y){x = (manager->x > COL_BEGIN) ? (manager->x - 1) : COL_BEGIN; // 向左扩展一格for (; x < COL_END && x < manager->x + 5; ++x) // 向右扩展一格{gotoxyInPool(x, y); // 定点到游戏池中的方格if ((manager->pool[y] >> x) & 1) // 游戏池该方格有方块{// 用相应颜色,显示一个实心方块SetConsoleTextAttribute(g_hConsoleOutput, control->color[y][x]);printf("■");}else // 没有方块,显示空白{SetConsoleTextAttribute(g_hConsoleOutput, 0);printf("%2s", "");}}}
}
// 显示下一个和下下一个方块
void printNextTetris(const TetrisManager *manager)
{int8_t i;uint16_t tetris;// 边框SetConsoleTextAttribute(g_hConsoleOutput, 0xF);gotoxyWithFullwidth(26, 1);printf("┏━━━━┳━━━━┓");gotoxyWithFullwidth(26, 2);printf("┃%8s┃%8s┃", "", "");gotoxyWithFullwidth(26, 3);printf("┃%8s┃%8s┃", "", "");gotoxyWithFullwidth(26, 4);printf("┃%8s┃%8s┃", "", "");gotoxyWithFullwidth(26, 5);printf("┃%8s┃%8s┃", "", "");gotoxyWithFullwidth(26, 6);printf("┗━━━━┻━━━━┛");// 下一个,用相应颜色显示tetris = gs_uTetrisTable[manager->type[1]][manager->orientation[1]];SetConsoleTextAttribute(g_hConsoleOutput, manager->type[1] | 8);for (i = 0; i < 16; ++i){gotoxyWithFullwidth((i & 3) + 27, (i >> 2) + 2);((tetris >> i) & 1) ? printf("■") : printf("%2s", "");}// 下下一个,不显示彩色tetris = gs_uTetrisTable[manager->type[2]][manager->orientation[2]];SetConsoleTextAttribute(g_hConsoleOutput, 8);for (i = 0; i < 16; ++i){gotoxyWithFullwidth((i & 3) + 32, (i >> 2) + 2);((tetris >> i) & 1) ? printf("■") : printf("%2s", "");}
}
// 显示得分信息
void printScore(const TetrisManager *manager)
{static const char *tetrisName = "ITLJZSO";int8_t i;SetConsoleTextAttribute(g_hConsoleOutput, 0xE);gotoxyWithFullwidth(2, 2);printf("■得分:%u", manager->score);gotoxyWithFullwidth(1, 6);printf("■消行总数:%u", manager->erasedTotal);for (i = 0; i < 4; ++i){gotoxyWithFullwidth(2, 8 + i);printf("□消%d:%u", i + 1, manager->erasedCount[i]);}gotoxyWithFullwidth(1, 15);printf("■方块总数:%u", manager->tetrisTotal);for (i = 0; i < 7; ++i){gotoxyWithFullwidth(2, 17 + i);printf("□%c形:%u", tetrisName[i], manager->tetrisCount[i]);}
}
// 显示提示信息
void printPrompting()
{SetConsoleTextAttribute(g_hConsoleOutput, 0xB);gotoxyWithFullwidth(26, 10);printf("■控制:");gotoxyWithFullwidth(27, 12);printf("□向左移动:← A 4");gotoxyWithFullwidth(27, 13);printf("□向右移动:→ D 6");gotoxyWithFullwidth(27, 14);printf("□向下移动:↓ S 2");gotoxyWithFullwidth(27, 15);printf("□顺时针转:↑ W 8");gotoxyWithFullwidth(27, 16);printf("□逆时针转:0");gotoxyWithFullwidth(27, 17);printf("□直接落地:空格");gotoxyWithFullwidth(27, 18);printf("□暂停游戏:回车");gotoxyWithFullwidth(25, 23);printf("■By:muchunfeng");
}
// 运行游戏
void runGame(TetrisManager *manager, TetrisControl *control)
{clock_t clockLast, clockNow;clockLast = clock(); // 计时printTetrisPool(manager, control); // 显示游戏池while (!manager->dead) // 没挂{while (_kbhit()) // 有键按下{keydownControl(manager, control, _getch()); // 处理按键}if (!control->pause) // 未暂停{clockNow = clock(); // 计时// 两次记时的间隔超过0.45秒if (clockNow - clockLast > 0.45F * CLOCKS_PER_SEC){clockLast = clockNow;keydownControl(manager, control, 80); // 方块往下移}}}
}
-
在
ifPlayAgain()
函数返回true的情况下,即玩家选择再次玩游戏,执行以下操作:- 使用
SetConsoleTextAttribute()
函数设置控制台文本颜色为默认颜色(0x7)。 - 调用
system("cls")
清屏,清除上一局游戏的画面。 - 调用
restartGame()
函数重新开始游戏。
- 使用
-
当
ifPlayAgain()
函数返回false的情况下,即玩家选择退出游戏,跳出循环。 -
在循环结束后,调用
gotoxyWithFullwidth(0, 0)
将光标定位到控制台的左上角。 -
最后调用
CloseHandle(g_hConsoleOutput)
关闭控制台输出句柄。
// 再来一次
bool ifPlayAgain()
{int ch;SetConsoleTextAttribute(g_hConsoleOutput, 0xF0);gotoxyWithFullwidth(15, 10);printf("游戏结束");gotoxyWithFullwidth(13, 11);printf("按Y重玩,按N退出");do{ch = _getch();if (ch == 'Y' || ch == 'y'){return true;}else if (ch == 'N' || ch == 'n'){return false;}} while (1);
}
源码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include <windows.h>#ifdef _MSC_VER #if _MSC_VER <= 1200 // VC6及以下版本#error 你是不是还在用VC6?!#else // VC6以上版本#if _MSC_VER >= 1600 // 据说VC10及以上版本有stdint.h了#include <stdint.h>#else // VC10以下版本,自己定义int8_t和uint16_ttypedef signed char int8_t;typedef unsigned short uint16_t;#endif#ifndef __cplusplus typedef int bool;#define true 1#define false 0#endif#endif
#else // 其他的编译器都好说#include <stdint.h>#ifndef __cplusplus // 不用C++编译,需要stdbool.h里的bool#include <stdbool.h>#endif
#endif// =============================================================================
// 7种方块的4旋转状态(4位为一行)
static const uint16_t gs_uTetrisTable[7][4] =
{{ 0x00F0U, 0x2222U, 0x00F0U, 0x2222U }, // I型{ 0x0072U, 0x0262U, 0x0270U, 0x0232U }, // T型{ 0x0223U, 0x0074U, 0x0622U, 0x0170U }, // L型{ 0x0226U, 0x0470U, 0x0322U, 0x0071U }, // J型{ 0x0063U, 0x0264U, 0x0063U, 0x0264U }, // Z型{ 0x006CU, 0x0462U, 0x006CU, 0x0462U }, // S型{ 0x0660U, 0x0660U, 0x0660U, 0x0660U } // O型
};// =============================================================================
// 初始状态的游戏池
// 每个元素表示游戏池的一行,下标大的是游戏池底部
// 两端各置2个1,底部2全置为1,便于进行碰撞检测
// 这样一来游戏池的宽度为12列
// 如果想要传统的10列,只需多填两个1即可(0xE007),当然显示相关部分也要随之改动
// 当某个元素为0xFFFFU时,说明该行已被填满
// 顶部4行用于给方块,不显示出来
// 再除去底部2行,显示出来的游戏池高度为22行
static const uint16_t gs_uInitialTetrisPool[28] =
{0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U,0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U,0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U,0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xC003U, 0xFFFFU, 0xFFFFU
};#define COL_BEGIN 2
#define COL_END 14
#define ROW_BEGIN 4
#define ROW_END 26// =============================================================================
typedef struct TetrisManager // 这个结构体存储游戏相关数据
{uint16_t pool[28]; // 游戏池int8_t x; // 当前方块x坐标,此处坐标为方块左上角坐标int8_t y; // 当前方块y坐标int8_t type[3]; // 当前、下一个和下下一个方块类型int8_t orientation[3]; // 当前、下一个和下下一个方块旋转状态unsigned score; // 得分unsigned erasedCount[4]; // 消行数unsigned erasedTotal; // 消行总数unsigned tetrisCount[7]; // 各方块数unsigned tetrisTotal; // 方块总数bool dead; // 挂
} TetrisManager;// =============================================================================
typedef struct TetrisControl // 这个结构体存储控制相关数据
{bool pause; // 暂停bool clockwise; // 旋转方向:顺时针为trueint8_t direction; // 移动方向:0向左移动 1向右移动// 游戏池内每格的颜色// 由于此版本是彩色的,仅用游戏池数据无法存储颜色信息// 当然,如果只实现单色版的,就没必要用这个数组了int8_t color[28][16];
} TetrisControl;HANDLE g_hConsoleOutput; // 控制台输出句柄// =============================================================================
// 函数声明
// 如果使用全局变量方式实现,就没必要传参了
void initGame(TetrisManager *manager, TetrisControl *control); // 初始化游戏
void restartGame(TetrisManager *manager, TetrisControl *control); // 重新开始游戏
void giveTetris(TetrisManager *manager); // 给一个方块
bool checkCollision(const TetrisManager *manager); // 碰撞检测
void insertTetris(TetrisManager *manager); // 插入方块
void removeTetris(TetrisManager *manager); // 移除方块
void horzMoveTetris(TetrisManager *manager, TetrisControl *control); // 水平移动方块
void moveDownTetris(TetrisManager *manager, TetrisControl *control); // 向下移动方块
void rotateTetris(TetrisManager *manager, TetrisControl *control); // 旋转方块
void dropDownTetris(TetrisManager *manager, TetrisControl *control); // 方块直接落地
bool checkErasing(TetrisManager *manager, TetrisControl *control); // 消行检测
void keydownControl(TetrisManager *manager, TetrisControl *control, int key); // 键按下
void setPoolColor(const TetrisManager *manager, TetrisControl *control); // 设置颜色
void gotoxyWithFullwidth(short x, short y); // 以全角定位
void printPoolBorder(); // 显示游戏池边界
void printTetrisPool(const TetrisManager *manager, const TetrisControl *control); // 显示游戏池
void printCurrentTetris(const TetrisManager *manager, const TetrisControl *control); // 显示当前方块
void printNextTetris(const TetrisManager *manager); // 显示下一个和下下一个方块
void printScore(const TetrisManager *manager); // 显示得分信息
void runGame(TetrisManager *manager, TetrisControl *control); // 运行游戏
void printPrompting(); // 显示提示信息
bool ifPlayAgain(); // 再来一次// =============================================================================
// 主函数
int main()
{TetrisManager tetrisManager;TetrisControl tetrisControl;initGame(&tetrisManager, &tetrisControl); // 初始化游戏do{printPrompting(); // 显示提示信息printPoolBorder(); // 显示游戏池边界runGame(&tetrisManager, &tetrisControl); // 运行游戏if (ifPlayAgain()) // 再来一次{SetConsoleTextAttribute(g_hConsoleOutput, 0x7);system("cls"); // 清屏restartGame(&tetrisManager, &tetrisControl); // 重新开始游戏}else{break;}} while (1);gotoxyWithFullwidth(0, 0);CloseHandle(g_hConsoleOutput);return 0;
}// =============================================================================
// 初始化游戏
void initGame(TetrisManager *manager, TetrisControl *control)
{CONSOLE_CURSOR_INFO cursorInfo = { 1, FALSE }; // 光标信息g_hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); // 获取控制台输出句柄SetConsoleCursorInfo(g_hConsoleOutput, &cursorInfo); // 设置光标隐藏SetConsoleTitleA("俄罗斯方块控制台版");restartGame(manager, control);
}// =============================================================================
// 重新开始游戏
void restartGame(TetrisManager *manager, TetrisControl *control)
{memset(manager, 0, sizeof(TetrisManager)); // 全部置0// 初始化游戏池memcpy(manager->pool, gs_uInitialTetrisPool, sizeof(uint16_t [28]));srand((unsigned)time(NULL)); // 设置随机种子manager->type[1] = rand() % 7; // 下一个manager->orientation[1] = rand() & 3;manager->type[2] = rand() % 7; // 下下一个manager->orientation[2] = rand() & 3;memset(control, 0, sizeof(TetrisControl)); // 全部置0giveTetris(manager); // 给下一个方块setPoolColor(manager, control); // 设置颜色
}// =============================================================================
// 给一个方块
void giveTetris(TetrisManager *manager)
{uint16_t tetris;manager->type[0] = manager->type[1]; // 下一个方块置为当前manager->orientation[0] = manager->orientation[1];manager->type[1] = manager->type[2];// 下下一个置方块为下一个manager->orientation[1] = manager->orientation[2];manager->type[2] = rand() % 7;// 随机生成下下一个方块manager->orientation[2] = rand() & 3;tetris = gs_uTetrisTable[manager->type[0]][manager->orientation[0]]; // 当前方块// 设置当前方块y坐标,保证刚给出时只显示方块最下面一行// 这种实现使得玩家可以以很快的速度将方块落在不显示出来的顶部4行内if (tetris & 0xF000){manager->y = 0;}else{manager->y = (tetris & 0xFF00) ? 1 : 2;}manager->x = 6; // 设置当前方块x坐标if (checkCollision(manager)) // 检测到碰撞{manager->dead = true; // 标记游戏结束}else // 未检测到碰撞{insertTetris(manager); // 将当前方块加入游戏池}++manager->tetrisTotal; // 方块总数++manager->tetrisCount[manager->type[0]]; // 相应方块数printNextTetris(manager); // 显示下一个方块printScore(manager); // 显示得分信息
}// =============================================================================
// 碰撞检测
bool checkCollision(const TetrisManager *manager)
{// 当前方块uint16_t tetris = gs_uTetrisTable[manager->type[0]][manager->orientation[0]];uint16_t dest = 0;// 获取当前方块在游戏池中的区域:// 游戏池坐标x y处小方格信息,按低到高存放在16位无符号数中dest |= (((manager->pool[manager->y + 0] >> manager->x) << 0x0) & 0x000F);dest |= (((manager->pool[manager->y + 1] >> manager->x) << 0x4) & 0x00F0);dest |= (((manager->pool[manager->y + 2] >> manager->x) << 0x8) & 0x0F00);dest |= (((manager->pool[manager->y + 3] >> manager->x) << 0xC) & 0xF000);// 若当前方块与目标区域存在重叠(碰撞),则位与的结果不为0return ((dest & tetris) != 0);
}// =============================================================================
// 插入方块
void insertTetris(TetrisManager *manager)
{// 当前方块uint16_t tetris = gs_uTetrisTable[manager->type[0]][manager->orientation[0]];// 当前方块每4位取出,位或到游戏池相应位置,即完成插入方块manager->pool[manager->y + 0] |= (((tetris >> 0x0) & 0x000F) << manager->x);manager->pool[manager->y + 1] |= (((tetris >> 0x4) & 0x000F) << manager->x);manager->pool[manager->y + 2] |= (((tetris >> 0x8) & 0x000F) << manager->x);manager->pool[manager->y + 3] |= (((tetris >> 0xC) & 0x000F) << manager->x);
}// =============================================================================
// 移除方块
void removeTetris(TetrisManager *manager)
{// 当前方块uint16_t tetris = gs_uTetrisTable[manager->type[0]][manager->orientation[0]];// 当前方块每4位取出,按位取反后位与到游戏池相应位置,即完成移除方块manager->pool[manager->y + 0] &= ~(((tetris >> 0x0) & 0x000F) << manager->x);manager->pool[manager->y + 1] &= ~(((tetris >> 0x4) & 0x000F) << manager->x);manager->pool[manager->y + 2] &= ~(((tetris >> 0x8) & 0x000F) << manager->x);manager->pool[manager->y + 3] &= ~(((tetris >> 0xC) & 0x000F) << manager->x);
}// =============================================================================
// 设置颜色
void setPoolColor(const TetrisManager *manager, TetrisControl *control)
{// 由于显示游戏池时,先要在游戏池里判断某一方格有方块才显示相应方格的颜色// 这里只作设置即可,没必要清除// 当移动方块或给一个方块时调用int8_t i, x, y;// 当前方块uint16_t tetris = gs_uTetrisTable[manager->type[0]][manager->orientation[0]];for (i = 0; i < 16; ++i){y = (i >> 2) + manager->y; // 待设置的列if (y > ROW_END) // 超过底部限制{break;}x = (i & 3) + manager->x; // 待设置的行if ((tetris >> i) & 1) // 检测的到小方格属于当前方块区域{control->color[y][x] = (manager->type[0] | 8); // 设置颜色}}
}// =============================================================================
// 旋转方块
void rotateTetris(TetrisManager *manager, TetrisControl *control)
{int8_t ori = manager->orientation[0]; // 记录原旋转状态removeTetris(manager); // 移走当前方块// 顺/逆时针旋转manager->orientation[0] = (control->clockwise) ? ((ori + 1) & 3) : ((ori + 3) & 3);if (checkCollision(manager)) // 检测到碰撞{manager->orientation[0] = ori; // 恢复为原旋转状态insertTetris(manager); // 放入当前方块。由于状态没改变,不需要设置颜色}else{insertTetris(manager); // 放入当前方块setPoolColor(manager, control); // 设置颜色printCurrentTetris(manager, control); // 显示当前方块}
}// =============================================================================
// 水平移动方块
void horzMoveTetris(TetrisManager *manager, TetrisControl *control)
{int x = manager->x; // 记录原列位置removeTetris(manager); // 移走当前方块control->direction == 0 ? (--manager->x) : (++manager->x); // 左/右移动if (checkCollision(manager)) // 检测到碰撞{manager->x = x; // 恢复为原列位置insertTetris(manager); // 放入当前方块。由于位置没改变,不需要设置颜色}else{insertTetris(manager); // 放入当前方块setPoolColor(manager, control); // 设置颜色printCurrentTetris(manager, control); // 显示当前方块}
}// =============================================================================
// 向下移动方块
void moveDownTetris(TetrisManager *manager, TetrisControl *control)
{int8_t y = manager->y; // 记录原行位置removeTetris(manager); // 移走当前方块++manager->y; // 向下移动if (checkCollision(manager)) // 检测到碰撞{manager->y = y; // 恢复为原行位置insertTetris(manager); // 放入当前方块。由于位置没改变,不需要设置颜色if (checkErasing(manager, control)) // 检测到消行{printTetrisPool(manager, control); // 显示游戏池}}else{insertTetris(manager); // 放入当前方块setPoolColor(manager, control); // 设置颜色printCurrentTetris(manager, control); // 显示当前方块}
}// =============================================================================
// 方块直接落地
void dropDownTetris(TetrisManager *manager, TetrisControl *control)
{removeTetris(manager); // 移走当前方块for (; manager->y < ROW_END; ++manager->y) // 从上往下{if (checkCollision(manager)) // 检测到碰撞{break;}}--manager->y; // 上移一格当然没有碰撞insertTetris(manager); // 放入当前方块setPoolColor(manager, control); // 设置颜色checkErasing(manager, control); // 检测消行printTetrisPool(manager, control); // 显示游戏池
}// =============================================================================
// 消行检测
bool checkErasing(TetrisManager *manager, TetrisControl *control)
{static const unsigned scores[5] = { 0, 10, 30, 90, 150 }; // 消行得分int8_t count = 0;int8_t k = 0, y = manager->y + 3; // 从下往上检测do{if (y < ROW_END && manager->pool[y] == 0xFFFFU) // 有效区域内且一行已填满{++count;// 消除一行方块memmove(manager->pool + 1, manager->pool, sizeof(uint16_t) * y);// 颜色数组的元素随之移动memmove(control->color[1], control->color[0], sizeof(int8_t [16]) * y);}else{--y;++k;}} while (y >= manager->y && k < 4);manager->erasedTotal += count; // 消行总数manager->score += scores[count]; // 得分if (count > 0){++manager->erasedCount[count - 1]; // 消行}giveTetris(manager); // 给下一个方块setPoolColor(manager, control); // 设置颜色return (count > 0);
}// =============================================================================
// 键按下
void keydownControl(TetrisManager *manager, TetrisControl *control, int key)
{if (key == 13) // 暂停/解除暂停{control->pause = !control->pause;}if (control->pause) // 暂停状态,不作处理{return;}switch (key){case 'w': case 'W': case '8': case 72: // 上control->clockwise = true; // 顺时针旋转rotateTetris(manager, control); // 旋转方块break;case 'a': case 'A': case '4': case 75: // 左control->direction = 0; // 向左移动horzMoveTetris(manager, control); // 水平移动方块break;case 'd': case 'D': case '6': case 77: // 右control->direction = 1; // 向右移动horzMoveTetris(manager, control); // 水平移动方块break;case 's': case 'S': case '2': case 80: // 下moveDownTetris(manager, control); // 向下移动方块break;case ' ': // 直接落地dropDownTetris(manager, control);break;case '0': // 反转control->clockwise = false; // 逆时针旋转rotateTetris(manager, control); // 旋转方块break;default:break;}
}// =============================================================================
// 以全角定位
void gotoxyWithFullwidth(short x, short y)
{static COORD cd;cd.X = (short)(x << 1);cd.Y = y;SetConsoleCursorPosition(g_hConsoleOutput, cd);
}// =============================================================================
// 显示游戏池边界
void printPoolBorder()
{int8_t y;SetConsoleTextAttribute(g_hConsoleOutput, 0xF0);for (y = ROW_BEGIN; y < ROW_END; ++y) // 不显示顶部4行和底部2行{gotoxyWithFullwidth(10, y - 3);printf("%2s", "");gotoxyWithFullwidth(23, y - 3);printf("%2s", "");}gotoxyWithFullwidth(10, y - 3); // 底部边界printf("%28s", "");
}// 定位到游戏池中的方格
#define gotoxyInPool(x, y) gotoxyWithFullwidth(x + 9, y - 3)// =============================================================================
// 显示游戏池
void printTetrisPool(const TetrisManager *manager, const TetrisControl *control)
{int8_t x, y;for (y = ROW_BEGIN; y < ROW_END; ++y) // 不显示顶部4行和底部2行{gotoxyInPool(2, y); // 定点到游戏池中的方格for (x = COL_BEGIN; x < COL_END; ++x) // 不显示左右边界{if ((manager->pool[y] >> x) & 1) // 游戏池该方格有方块{// 用相应颜色,显示一个实心方块SetConsoleTextAttribute(g_hConsoleOutput, control->color[y][x]);printf("■");}else // 没有方块,显示空白{SetConsoleTextAttribute(g_hConsoleOutput, 0);printf("%2s", "");}}}
}// =============================================================================
// 显示当前方块
void printCurrentTetris(const TetrisManager *manager, const TetrisControl *control)
{int8_t x, y;// 显示当前方块是在移动后调用的,为擦去移动前的方块,需要扩展显示区域// 由于不可能向上移动,故不需要向下扩展y = (manager->y > ROW_BEGIN) ? (manager->y - 1) : ROW_BEGIN; // 向上扩展一格for (; y < ROW_END && y < manager->y + 4; ++y){x = (manager->x > COL_BEGIN) ? (manager->x - 1) : COL_BEGIN; // 向左扩展一格for (; x < COL_END && x < manager->x + 5; ++x) // 向右扩展一格{gotoxyInPool(x, y); // 定点到游戏池中的方格if ((manager->pool[y] >> x) & 1) // 游戏池该方格有方块{// 用相应颜色,显示一个实心方块SetConsoleTextAttribute(g_hConsoleOutput, control->color[y][x]);printf("■");}else // 没有方块,显示空白{SetConsoleTextAttribute(g_hConsoleOutput, 0);printf("%2s", "");}}}
}// =============================================================================
// 显示下一个和下下一个方块
void printNextTetris(const TetrisManager *manager)
{int8_t i;uint16_t tetris;// 边框SetConsoleTextAttribute(g_hConsoleOutput, 0xF);gotoxyWithFullwidth(26, 1);printf("┏━━━━┳━━━━┓");gotoxyWithFullwidth(26, 2);printf("┃%8s┃%8s┃", "", "");gotoxyWithFullwidth(26, 3);printf("┃%8s┃%8s┃", "", "");gotoxyWithFullwidth(26, 4);printf("┃%8s┃%8s┃", "", "");gotoxyWithFullwidth(26, 5);printf("┃%8s┃%8s┃", "", "");gotoxyWithFullwidth(26, 6);printf("┗━━━━┻━━━━┛");// 下一个,用相应颜色显示tetris = gs_uTetrisTable[manager->type[1]][manager->orientation[1]];SetConsoleTextAttribute(g_hConsoleOutput, manager->type[1] | 8);for (i = 0; i < 16; ++i){gotoxyWithFullwidth((i & 3) + 27, (i >> 2) + 2);((tetris >> i) & 1) ? printf("■") : printf("%2s", "");}// 下下一个,不显示彩色tetris = gs_uTetrisTable[manager->type[2]][manager->orientation[2]];SetConsoleTextAttribute(g_hConsoleOutput, 8);for (i = 0; i < 16; ++i){gotoxyWithFullwidth((i & 3) + 32, (i >> 2) + 2);((tetris >> i) & 1) ? printf("■") : printf("%2s", "");}
}// =============================================================================
// 显示得分信息
void printScore(const TetrisManager *manager)
{static const char *tetrisName = "ITLJZSO";int8_t i;SetConsoleTextAttribute(g_hConsoleOutput, 0xE);gotoxyWithFullwidth(2, 2);printf("■得分:%u", manager->score);gotoxyWithFullwidth(1, 6);printf("■消行总数:%u", manager->erasedTotal);for (i = 0; i < 4; ++i){gotoxyWithFullwidth(2, 8 + i);printf("□消%d:%u", i + 1, manager->erasedCount[i]);}gotoxyWithFullwidth(1, 15);printf("■方块总数:%u", manager->tetrisTotal);for (i = 0; i < 7; ++i){gotoxyWithFullwidth(2, 17 + i);printf("□%c形:%u", tetrisName[i], manager->tetrisCount[i]);}
}// =============================================================================
// 显示提示信息
void printPrompting()
{SetConsoleTextAttribute(g_hConsoleOutput, 0xB);gotoxyWithFullwidth(26, 10);printf("■控制:");gotoxyWithFullwidth(27, 12);printf("□向左移动:← A 4");gotoxyWithFullwidth(27, 13);printf("□向右移动:→ D 6");gotoxyWithFullwidth(27, 14);printf("□向下移动:↓ S 2");gotoxyWithFullwidth(27, 15);printf("□顺时针转:↑ W 8");gotoxyWithFullwidth(27, 16);printf("□逆时针转:0");gotoxyWithFullwidth(27, 17);printf("□直接落地:空格");gotoxyWithFullwidth(27, 18);printf("□暂停游戏:回车");gotoxyWithFullwidth(25, 23);printf("■By:muchunfeng");
}// =============================================================================
// 运行游戏
void runGame(TetrisManager *manager, TetrisControl *control)
{clock_t clockLast, clockNow;clockLast = clock(); // 计时printTetrisPool(manager, control); // 显示游戏池while (!manager->dead) // 没挂{while (_kbhit()) // 有键按下{keydownControl(manager, control, _getch()); // 处理按键}if (!control->pause) // 未暂停{clockNow = clock(); // 计时// 两次记时的间隔超过0.45秒if (clockNow - clockLast > 0.45F * CLOCKS_PER_SEC){clockLast = clockNow;keydownControl(manager, control, 80); // 方块往下移}}}
}// =============================================================================
// 再来一次
bool ifPlayAgain()
{int ch;SetConsoleTextAttribute(g_hConsoleOutput, 0xF0);gotoxyWithFullwidth(15, 10);printf("游戏结束");gotoxyWithFullwidth(13, 11);printf("按Y重玩,按N退出");do{ch = _getch();if (ch == 'Y' || ch == 'y'){return true;}else if (ch == 'N' || ch == 'n'){return false;}} while (1);
}
end
相关文章:

俄罗斯方块的代码实现
文章目录 首先是头文件的引入部分接下来是一些预处理指令接下来定义了两个结构体:接下来是全局变量g_hConsoleOutput,用于存储控制台输出句柄。之后是一系列函数的声明最后是main函数源码 首先是头文件的引入部分 包括stdio.h、string.h、stdlib.h、tim…...

出海企业哪种组网方案更省事?
对于出海企业而言,建立跨地区的数据传输和协同工作至关重要,以提升运营效率。因此,网络构建变得迫在眉睫。通过构建企业组网,企业能够加强与海外分支、客户和合作伙伴之间的联系,加速海外业务的发展。 然而,…...

triton编译学习
一 流程 Triton-MLIR: 从DSL到PTX - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/671434808Superjomns blog | OpenAI/Triton MLIR 迁移工作简介https://superjom...
源码知识付费系统,在线教学平台需要优化什么?
在线教育关于广大的关注者而言属于快捷度非常高的传达途径,尤其是白日没有过多时间的上班族或学习繁忙的学生,均能够通过可靠的在线教育完结自己的目的。如此巨大的市场潜力使得以在线教育为主的公司数量呈现出直线上升的趋势,很多的在线教育…...

后端常用技能:解决java项目前后端传输数据中文出现乱码、问号问题
0. 问题背景 最近做一个解析数据的小工具,本地运行时都正常,发布到服务器上后在导出文件数据时发现中文全部变成了问号,特此记录下问题解决的思路和过程 1. 环境 java 1.8 springboot 2.6.13 额外引入了fastjson,commons-csv等…...

SpringBoot中使用MongoDB
目录 搭建实体类 基本的增删改查操作 分页查询 使用MongoTemplate实现复杂的功能 引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> 在ap…...

【TS】入门
创建项目 vscode自动编译ts 生成配置文件 tsc --init 然后发现终端也改变了:...

Apache ECharts
Apache ECharts介绍: Apache ECharts 是一款基于 Javascript 的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表。 官网地址:https://echarts.apache.org/zh/index.html Apache ECh…...

超详细的胎教级Stable Diffusion使用教程(四)
这套课程分为五节课,会系统性的介绍sd的全部功能和实操案例,让你打下坚实牢靠的基础 一、为什么要学Stable Diffusion,它究竟有多强大? 二、三分钟教你装好Stable Diffusion 三、小白快速上手Stable Diffusion 四、Stable dif…...

串口属性中的BM延时计时器问题
如果使用程序修改则需要修改注册表对应位置如下 第一个示例(217) 第二个示例(219) 需要注意的事情是修改前必须点查看串口名称(例如上图是com5) 程序修改: 有没有办法以编程方式更改USB <…...

PyQt6--Python桌面开发(8.QPlainTextEdit纯文本控件)
QPlainTextEdit纯文本控件...

Java | Leetcode Java题解之第83题删除排序链表中的重复元素
题目: 题解: class Solution {public ListNode deleteDuplicates(ListNode head) {if (head null) {return head;}ListNode cur head;while (cur.next ! null) {if (cur.val cur.next.val) {cur.next cur.next.next;} else {cur cur.next;}}return…...

重生奇迹mu再生宝石怎么用有什么用
重生奇迹mu再生宝石有2个用处: 1、在玛雅哥布林处给380装备加PVP属性4追4以上的380级装备,守护宝石一颗,再生宝石一颗,成功得到PVP装备,失败宝石消失,装备无变化; 2、给非套装点强化属性用法跟祝福,灵魂,生命一样直接往装备上敲,成功得到随机强化属性一…...
pdf 文件版面分析--pdfplumber (python 文档解析提取)
pdfplumber 的特点 1、它是一个纯 python 第三方库,适合 python 3.x 版本 2、它用来查看pdf各类信息,能有效提取文本、表格 3、它不支持修改或生成pdf,也不支持对pdf扫描件的处理 import glob import pdfplumber import re from collection…...

PostgreSQL的学习心得和知识总结(一百四十三)|深入理解PostgreSQL数据库之Support event trigger for logoff
目录结构 注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下: 1、参考书籍:《PostgreSQL数据库内核分析》 2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》 3、PostgreSQL数据库仓库…...

https免费证书获取
获取免费证书的网址: Certbot 1. 进入你的linux系统,先安装snapd, yum install snapd 2. 启动snapd service snapd start 3.安装 Certbot snap install --classic certbot 注意如下出现此错误时,需要先建立snap 软连接后&am…...

C语言 | Leetcode C语言题解之第74题搜索二维矩阵
题目: 题解: bool searchMatrix(int** matrix, int matrixSize, int* matrixColSize, int target) {int m matrixSize, n matrixColSize[0];int low 0, high m * n - 1;while (low < high) {int mid (high - low) / 2 low;int x matrix[mid /…...

杰发科技AC7840——软件Sent_HAL39X
0. 序 使用PWM模拟Sent测试下7840的软件sent功能。 参考链接:SENT协议应用笔记 - TechPlus汽车工坊的文章 - 知乎 SENT协议 1. Sent功能测试 使用提供的软件Sent代码在7840上测试,接收数据OK 2. 参考资料 3. 数据解析 我们个根据上述参考资料尝试解析…...

IOS 开发 - block 使用详解
1.Blobk的定义 block的写法相对难记,不必司机应被,只需要在xcode里打出"inlineBlock"--回车, 系统会自动帮你把基础版写法给你匹配出来 //Block的基础声明//等号""之前是blobk的声明,等号“”后面是block的实现/*returnType:返回类型(void、int、String *…...

BUU-[极客大挑战 2019]Http
考察点 信息收集 http构造请求数据包 题目 解题步骤 参考文章:https://zhuanlan.zhihu.com/p/367051798 查看源代码 发现有一个a标签,但是οnclick"return false"就是点击后不会去跳转到Secret.php的页面 所以我就自己拼接url http://no…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...

vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...

vulnyx Blogger writeup
信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面,gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress,说明目标所使用的cms是wordpress,访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...

免费数学几何作图web平台
光锐软件免费数学工具,maths,数学制图,数学作图,几何作图,几何,AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
前言: 双亲委派机制对于面试这块来说非常重要,在实际开发中也是经常遇见需要打破双亲委派的需求,今天我们一起来探索一下什么是双亲委派机制,在此之前我们先介绍一下类的加载器。 目录 编辑 前言: 类加载器 1. …...