C++入门系列博客七 俄罗斯方块小游戏制作_俄罗斯方块英文

时间:2021年05月10日 01:41:32

作者:AceTan,转载请标明出处!

俄罗斯方块游戏可谓童年经典,遥想当年拿着那种掌机,玩一下午的俄罗斯方块,是多么惬意和悠闲的事情啊,满满地都是回忆啊(等等,是不是无意之间暴露了什么……)。今天带大家来实现这款小游戏,也是对前面博客所讲内容的一个综合实践。 效果图如下:

俄罗斯方块小游戏0x00 游戏开发##

先扯一些没用的,一款游戏一般由游戏策划、游戏程序员和美术人员来共同完成。游戏开发的主流语言还是C/C++。你平时可以写一些小游戏来提高你的C/C++的水平。现代大型游戏多在游戏引擎基础之上开发,目前主流的游戏引擎有U3D、UE4和CE3等等。各大游戏引擎的优缺点我们也不做讨论,唯一的共同点就是他们都非常复杂。游戏引擎开发是一项极具挑战的工作,牛叉的游戏引擎一般由团队共同合作完成(国产电视剧《微微一笑很倾城》 男主角貌似自己开发了一款游戏引擎,呵呵)。关于游戏引擎的更多知识可以读一下这本书—《游戏引擎构架》。嗯,你没猜错,笔者就是国内某游戏公司的程序猿。

回归正题,这款简单的俄罗斯方块游戏肯定不基于任何游戏引擎啦,甚至它不使用任何渲染库。我们来写一个控制台版的小游戏。

0x01 如何下手##

前面提到过,一款游戏的制作一般由游戏策划、游戏程序员和美术来共同完成。其中,游戏策划一般负责游戏的玩法、规则、界面、数值等设计,美术人员负责模型、动画、原画,插图和游戏整体风格的把握等。游戏程序员负责实现游戏策划所提的需求。那么,这款小游戏我们也可以从这几个方面入手:

游戏的规则是什么?

游戏的界面应该是什么样子的,计分面板、说明面板放在哪里?

如何控制游戏(按键控制)?

游戏的整体风格应该是什么样子?

上面的这几个问题解决了,就可以交给程序员去搞了。

0x02 程序设计##

接到策划的需求后,程序如何设计呢?通过需求分析,仔细查看策划人员给的设计图(例如上面的效果图),你可以很容易得出以下结论:这是一个在Windows平台下跑的一个控制台游戏。显然,你可能需要设计一个Console类和Window类(其中Console类是Window类的成员)。这两个类应该具有如下的能力:

控制窗口的标题,窗口大小,缓冲区大小,光标等

完全的控制输出的能力,包括但不限于文字的位置,颜色,前景色和背景色。

有了这些信息,你就可以Google和百度一下相关的API了,看哪些是已经有的,哪些需要自己设计的。比如,你就可以查到以下的一些函数:

GetStdHandle() // 获得句柄

SetConsoleCursorInfo() // 设置光标信息

SetConsoleWindowInfo() // 设置窗口信息

SetConsoleScreenBufferSize() //设置窗口缓冲区大小

SetConsoleTitle() //设置标题

WriteConsoleOutputCharacter() 和 WriteConsoleOutputAttribute() //控制输出的函数

其中,WriteConsoleOutputCharacter()和WriteConsoleOutputAttribute()函数你也许并不熟悉,这就需要你查看相关文档,弄懂这两个函数了,因为这两个函数至关重要,承担了游戏的打印(渲染)任务。

简单的查一下,很快就能得到该函数的原型和相关参数说明:

// 函数原型:BOOL WriteConsoleOutputCharacter( // 在指定位置处插入指定数量的字符HANDLE hConsoleOutput, // 句柄LPCTSTR lpCharacter, // 字符串DWORD nLength, // 字符个数COORD dwWriteCoord, // 起始位置LPDWORD lpNumberOfCharsWritten // 已写个数);/* 参数简介:hConsoleOutput:控制台输出句柄,通过调用GetStdHandle函数获得HANDLE hnd;hnd=GetStdHandle(STD_INPUT_HANDLE);lpCharacter:要输出的字符串nLength:输出长度dwWriteCoord:起始位置pNumberOfCharsWritten:已写个数,通常置为NULL其中,COORD是个结构体变量类型*/typedef struct _COORD {SHORT X;SHORT Y;} COORD;

上面这个是来自百度百科。其实更权威的说明应该查询MSDN,例如WriteConsoleOutputAttribute的传送门。 MSDN上对这个函数讲解的非常详细,也非常权威,前提是你有阅读英文文献的能力。还有就是在VS里打出这个函数名,然后按F12直接查看这个函数的原型,根据参数的命名,大概了解一下这个函数。

设计这个小游戏剩下的就是游戏的逻辑了。我们设计Tetris类来进行游戏的逻辑控制。我们还需要设计一个数据结构来表示方块。单个方块如何表示呢?通过我们队游戏规则的了解和对图形的观察,我们可以使用4*4的矩阵来表示一个方块。例如:

单个方块的表示

我们使用一个四维数组表示所有的方块。 diamonds[x][y][4][4],其中x表示有几种方块,y表示这种方块有几种变形,[4][4]表示这个方块。

0x03 工程结构##

这个小游戏很简单,没有那么多模块。现在列一下这个工程的结构,并做简要说明。其中.h头文件为声明,定义在对应的.cpp文件中。

Console 控制台类

GameDefine 定义游戏的一些常量。

StringUtil 字符串工具类

Tetris 俄罗斯方块类

Window 窗体类

其中的字符串工具类,最后并没有用到。

0x04 code##

懒得放github上了,直接上代码了。代码注释还是比较详尽的,应该能看得懂。

Talk is Cheap, show you the code.

Console.h文件:

//--------------------------------------------------------------------// 文件名:Console.h// 内容:控制台类// 说明:控制台类的一些声明// 创建日期:2016年9月6日//--------------------------------------------------------------------#pragma once// 保证该文件只被包含一次#include #include // 使用windows系统下的东西需要引入的头文件class Console{friend class Window;public:/// \brief 初始化控制台/// \param caption 控制台标题/// \param coordinate 控制台的高和宽void Init(const wchar_t* caption, COORD coordinate);public:HANDLE m_hStdInput;// 标准输入句柄private:HANDLE m_hStdOutput;// 标准输出句柄COORDm_coord;// 位置信息(x,y)};

Console.cpp文件

#include "Console.h"#ifndef INVALID_RETURN_VOID#define INVALID_RETURN_VOID(condition) if((condition)) {return;}#endif// 一些常量的定义const DWORD CURSOR_SIZE = 25;const SHORT SMALL_RECT_TOP = 0;const SHORT SMALL_RECT_LEFT = 0;/// \brief 打开控制台/// \param caption 控制台标题/// \param coordinate 控制台的高和宽void Console::Init(const wchar_t* caption, COORD coordinate){// 如果所给坐标不合法,则直接退出INVALID_RETURN_VOID(coordinate.X = rect.Bottom&& rect.Left < 0&& rect.Right > console.m_coord.X&& rect.Top > console.m_coord.Y);m_pConsole = &console;m_rect = rect;}/// \brief 输出信息/// \param str 要输出的字符串/// \param coordinate 位置信息 x, y/// \param color 颜色/// \param len 字符串长度void Window::Output(const char* str, COORD coordinate, WORD color, size_t len){// 先检测位置信息是否合法INVALID_RETURN_VOID(coordinate.X < 0|| coordinate.Y < 0|| coordinate.X > (m_rect.Right - m_rect.Left)|| coordinate.Y > (m_rect.Bottom - m_rect.Top));COORD coord = {m_rect.Left + coordinate.X, m_rect.Top + coordinate.Y};DWORD num = 0;WORD colorArray[2] = { color, color };// 字符串转换for (const char* p = str; len != 0 && *p != 0; --len, ++p, ++coord.X){// 需要换行if (coord.X >= m_rect.Right){coord.X = m_rect.Left + coordinate.X;++coord.Y;INVALID_RETURN_VOID(coord.Y >= m_rect.Bottom);}// 单字节字符if (*p > 0){WriteConsoleOutputCharacterA(m_pConsole->m_hStdOutput, p, 1, coord, &num);INVALID_RETURN_VOID(num != 1);WriteConsoleOutputAttribute(m_pConsole->m_hStdOutput, colorArray, 1, coord, &num);INVALID_RETURN_VOID(num != 1);}// 双字节字符else{INVALID_RETURN_VOID( len < 2 || *(p + 1) == 0 || (coord.X + 1) >= m_rect.Right);WriteConsoleOutputCharacterA(m_pConsole->m_hStdOutput, p, 2, coord, &num);INVALID_RETURN_VOID(num != 2);WriteConsoleOutputAttribute(m_pConsole->m_hStdOutput, colorArray, 2, coord, &num);INVALID_RETURN_VOID(num != 2);--len;++p; ++coord.X;}}}

Tetris.h文件

//--------------------------------------------------------------------// 文件名:Tetris.h// 内容:俄罗斯方块类// 说明:// 创建日期:2016年9月6日//--------------------------------------------------------------------#pragma once#include "Console.h"#include "Window.h"#include "GameDefine.h"class Tetris{public:/// \brief 构造函数/// \param console 控制台/// \param coordinate 控制台的高和宽Tetris(Console& console, COORD coordinate);/// \brief 初始化游戏/// \param keys 按键/// \param keyDesc 按键描述/// \param frequency 声效频率/// \param duration 延续时间void Init(int keys[KeyNum], char keyDesc[KeyNum][5], DWORD frequency, DWORD duration);/// \brief 是否正在运行游戏bool IsRun();/// \brief 获取当前等级int GetLevel() const;/// \brief 方块下落bool Fall();/// \brief 消息处理/// \param key 按键/// \return 游戏结束返回falsebool MessageProc(const Cmd cmd);private:/// \brief 声效void VoiceBeep();/// \brief 绘制得分void DrawScoreLevel();/// \brief 绘制下一个将要出现的图形void DrawNext();/// \brief 绘制游戏结束界面void DrawGameOver();/// \brief 绘制颜色void Draw(WORD color);/// \brief 给定的是否可行bool IsFit(int x, int y, int c, int z);/// \brief 消除行void RemoveRow();/// \brief 旋转(逆时针)void MoveTrans();/// \brief 向左移动void MoveLeft();/// \brief 向右移动void MoveRight();/// \brief 向下移动/// \return 0: 游戏结束; -1:触底; 1:没有触底int MoveDown();/// \brief 下落到底bool FallToBottom();private:char bg[GAME_HIGHT * GAME_WIDTH + 1];char bk[DIAMONDS_TYPES][DIAMONDS_TRANS][DIAMONDS_IFNO_ROW][DIAMONDS_IFNO_COL];private:// 声效频率DWORD m_voiceFrequency;// 延续时间DWORD m_voiceDuration;// 控制按键int m_keys[KeyNum];// 控制按键的描述char m_keyDesc[KeyNum][5];// 游戏是否结束bool m_gameover;// 游戏暂停bool m_pause;// 游戏声效开关bool m_voice;// 游戏得分int m_score;// 游戏速度int m_speed;// 游戏数据(实际方块的存放数据)char m_data[ROWS][COLS];// 下一个方块int m_next;// 位置(x, y)int m_x, m_y;// 当前方块int m_currentDiamonds;// 当前方向int m_currentDir;// 窗口Window win;};

Tetris.cpp文件

#include "Tetris.h"#include #include /// \brief 构造函数/// \param console 控制台/// \param coordinate 控制台的高和宽Tetris::Tetris(Console & console, COORD coordinate){// 创建一个矩形SMALL_RECT rect = { coordinate.X, coordinate.Y, coordinate.X + GAME_WIDTH, coordinate.Y + GAME_HIGHT };// 初始化这个窗口win.Init(console, rect);}/// \brief 初始化游戏/// \param keys 按键/// \param keyDesc 按键描述/// \param frequency 声效频率/// \param duration 延续时间void Tetris::Init(int keys[KeyNum], char keyDesc[KeyNum][5], DWORD frequency, DWORD duration){// 初始化游戏的数据memcpy(m_keys, keys, sizeof(m_keys));memcpy(m_keyDesc, keyDesc, sizeof(m_keyDesc));memcpy(bk, Diamonds, sizeof(bk));memcpy(bg, Background, sizeof(bg));m_voiceFrequency = frequency;m_voiceDuration = duration;m_gameover = false;m_pause = true;m_voice = true;m_score = 0;m_speed = 0;// 方块数据部分置0memset(m_data, 0, sizeof(m_data));// 设置随机种子srand((unsigned)time(NULL));// 下一个方块m_next = rand() % DIAMONDS_TYPES;m_x = 4;m_y = 2;m_currentDiamonds = -1;m_currentDir = 0;COORD coord = { 0, 0 };win.Output(bg + 0, coord, COLOR_STILL, GAME_WIDTH);for (int i = 1; i < ROWS - 1; ++i){coord = { 0, (SHORT)i };win.Output(bg + GAME_WIDTH * i + 0, coord, COLOR_STILL, 2);coord = { 2, (SHORT)i };win.Output(bg + GAME_WIDTH * i + 2, coord, COLOR_BLANK, 22);coord = { 24, (SHORT)i };win.Output(bg + GAME_WIDTH * i + 24, coord, COLOR_STILL, 14);}coord = { 0, 20 };win.Output(bg + GAME_WIDTH * 20, coord, COLOR_STILL, GAME_WIDTH);for (int j = 0; j < KeyNum; ++j){coord = { 33, (SHORT)j + 7 };win.Output(m_keyDesc[j], coord, COLOR_STILL, 4);}// 绘制下一个将要出现的方块DrawNext();}/// \brief 是否正在运行游戏bool Tetris::IsRun(){return !m_gameover && !m_pause;}/// \brief 获取当前等级int Tetris::GetLevel() const{return m_speed;}/// \brief 方块下落bool Tetris::Fall(){return MessageProc(CMD_DOWN);}/// \brief 消息处理/// \param key 按键/// \return 游戏结束返回falsebool Tetris::MessageProc(const Cmd cmd){int const key = m_keys[cmd];// 游戏结束if (m_gameover){// 游戏重新开始if (m_keys[GameBegin] == key){Init(m_keys, m_keyDesc, m_voiceFrequency, m_voiceDuration);return true;}return false;}// 游戏暂停if (m_pause){// 游戏重新开始if (m_keys[GameBegin] == key){m_pause = false;if (m_currentDiamonds == -1){m_currentDiamonds = m_next;m_next = rand() % DIAMONDS_TYPES;DrawNext();}}else if (m_keys[GameVoice] == key){m_voice = !m_voice;}else{return true;}VoiceBeep();return true;}if (m_keys[GamePause] == key)// 按下暂停键{m_pause = true;}else if (m_keys[GameVoice] == key)// 按下声效键{m_voice = !m_voice;}else if (m_keys[Up] == key)// 按下变形键{MoveTrans();}else if (m_keys[Left] == key)// 按下方向左键{MoveLeft();}else if (m_keys[Right] == key)// 按下方向右键{MoveRight();}else if (m_keys[Down] == key)// 按下方向下键{if (0 == MoveDown()){return false;}}else if (m_keys[FallDown] == key)// 按下方块直接落地键{if (!FallToBottom()){return false;}}return true;}/// \brief 声效void Tetris::VoiceBeep(){if (m_voice){Beep(m_voiceFrequency, m_voiceDuration);}}/// \brief 绘制得分void Tetris::DrawScoreLevel(){char tmp[6];COORD coord = { 0, 0 };sprintf_s(tmp, "%05d", m_score);coord = {31, 19};win.Output(tmp, coord, COLOR_STILL, 5);sprintf_s(tmp, "%1d", m_speed);coord = { 28, 19 };win.Output(tmp, coord, COLOR_STILL, 1);}/// \brief 绘制下一个将要出现的图形void Tetris::DrawNext(){for (int i = 0; i < 2; ++i){for (int j = 0; j < 4; ++j){COORD coord = {28 + (SHORT)j * 2, 1 + (SHORT)i};char* tmp = bk[m_next][0][i][j] == 0 ? "" : "■";win.Output(tmp, coord, COLOR_STILL, 2);}}}/// \brief 绘制游戏结束界面void Tetris::DrawGameOver(){COORD coord = { 28, 1 };win.Output("游戏结束", coord, COLOR_STILL);coord = { 28, 2 };win.Output(" ", coord, COLOR_STILL);}/// \brief 绘制颜色void Tetris::Draw(WORD color){COORD coord = { 0, 0 };for (int i = 0; i < 4; ++i){if (m_y + i < 0 || m_y + i >= ROWS - 2){continue;}for (int j = 0; j < 4; ++j){if (bk[m_currentDiamonds][m_currentDir][i][j] == 1){coord = { SHORT(2 + m_x * 2 + j * 2), SHORT(1 + m_y + i) };win.Output("■", coord, color, 2);}}}}/// \brief 给定的是否可行bool Tetris::IsFit(int x, int y, int c, int z){for (int i = 0; i < 4; ++i){for (int j = 0; j < 4; ++j){if (bk[c][z][i][j] == 1){if (y + i < 0){continue;}if (y + i >= (ROWS - 2) || x + j < 0 || x + j >= (COLS - 2) || m_data[y + i][x + j] == 1){return false;}}}}return true;}/// \brief 消除行void Tetris::RemoveRow(){int lineCount = 0;COORD coord = { 0, 0 };for (int i = 0; i < (ROWS - 2); ++i){if (0 == memcmp(m_data[i], FULL_LINE, (COLS - 2))){++lineCount;for (int m = 0; m < (COLS - 2); ++m){for (int n = i; n > 1; --n){m_data[n][m] = m_data[n - 1][m];coord = {SHORT(2 + m * 2), SHORT(1 + n)};WORD color = m_data[n][m] == 1 ? COLOR_STILL : COLOR_BLANK;win.Output("■", coord, color, 2);}m_data[0][m] = 0;coord = { SHORT(2 + m * 2) , 1};win.Output("■", coord, COLOR_BLANK, 2);}}}char data[ROWS - 2][COLS - 2] = { 0 };if (lineCount == 0){return;}int score = 0;switch (lineCount){case 1:score = ONE_ROW_SCORE;break;case 2:score = TWO_ROWS_SCORE;break;case 3:score = THREE_ROWS_SCORE;break;case 4:score = FOUR_ROWS_SCORE;break;}m_score += score;if (score > MAX_SCORE){score = MAX_SCORE;}m_speed = score / SPEED_ADD_SCORE;DrawScoreLevel();}/// \brief 旋转(逆时针)void Tetris::MoveTrans(){if (IsFit(m_x, m_y, m_currentDiamonds, (m_currentDir + 1) % 4)){VoiceBeep();Draw(COLOR_BLANK);m_currentDir = (m_currentDir + 1) % 4;Draw(COLOR_MOVE);}}/// \brief 向左移动void Tetris::MoveLeft(){if (IsFit(m_x - 1, m_y, m_currentDiamonds, m_currentDir)){VoiceBeep();Draw(COLOR_BLANK);--m_x;Draw(COLOR_MOVE);}}/// \brief 向右移动void Tetris::MoveRight(){if (IsFit(m_x + 1, m_y, m_currentDiamonds, m_currentDir)){VoiceBeep();Draw(COLOR_BLANK);++m_x;Draw(COLOR_MOVE);}}/// \brief 向下移动/// \return 0: 游戏结束; -1:触底; 1:没有触底int Tetris::MoveDown(){if (IsFit(m_x, m_y + 1, m_currentDiamonds, m_currentDir)){VoiceBeep();Draw(COLOR_BLANK);++m_y;Draw(COLOR_MOVE);return 1;}// 触底了if (m_y != -2){Draw(COLOR_STILL);for (int i = 0; i < 4; ++i){if (m_y + i < 0){continue;}for (int j = 0; j < 4; ++j){if (bk[m_currentDiamonds][m_currentDir][i][j] == 1){m_data[m_y + i][m_x + j] = 1;}}}RemoveRow();m_x = 4;m_y = -2;m_currentDir = 0;m_currentDiamonds = m_next;m_next = rand() % DIAMONDS_TYPES;DrawNext();return -1;}// 游戏结束m_gameover = true;DrawGameOver();return 0;}/// \brief 下落到底bool Tetris::FallToBottom(){int r = MoveDown();while (r == 1){r = MoveDown();}return r == -1;}

StringUtil.h文件

//--------------------------------------------------------------------// 文件名:StringUtil.h// 内容:字符串工具类// 说明:提供字符串操作的一些便捷工具类// 创建日期:2016年9月6日// 创建人:AceTan// 版权所有:AceTan//--------------------------------------------------------------------#pragma once#include #include #include // 字符串处理class StringUtil{public:// 字符串转换成宽字符串static const wchar_t* StringToWideStr(const char* info, wchar_t* buf,size_t size, long codepage = CP_UTF8);// 宽字符串转换成字符串static const char* WideStrToString(const wchar_t* info, char* buf,size_t size, long codepage = CP_UTF8);};

StringUtil.cpp文件

#include "StringUtil.h"#include // 字符串转换到宽字符串const wchar_t* StringUtil::StringToWideStr(const char* info, wchar_t* buf,size_t size, long codepage){if (NULL == info || NULL == buf || size < sizeof(wchar_t)){return L"";}const size_t len = size / sizeof(wchar_t);int res = MultiByteToWideChar(codepage, 0, info, -1, buf, int(len));if (res == 0){if (GetLastError() == ERROR_INSUFFICIENT_BUFFER){buf[len - 1] = 0;}else{buf[0] = 0;}}return buf;}// 宽字符串转换成字符串const char* StringUtil::WideStrToString(const wchar_t* info, char* buf,size_t size, long codepage){if (NULL == info || NULL == buf || size < sizeof(char)){return "";}int res = WideCharToMultiByte(codepage, 0, info, -1, buf, int(size),NULL, NULL);if (0 == res){if (GetLastError() == ERROR_INSUFFICIENT_BUFFER){buf[size - 1] = 0;}else{buf[0] = 0;}}return buf;}

GameDefine.h文件

//--------------------------------------------------------------------// 文件名:GameDefine.h// 内容:游戏定义文件// 说明:定义游戏的一些常量,比如窗口大小等// 创建日期:2016年9月6日//--------------------------------------------------------------------#pragma once#include // 高度const SHORT GAME_HIGHT = 21;// 宽度const SHORT GAME_WIDTH = 38;// 方块的行数const SHORT ROWS = 21;// 方块的列数const SHORT COLS = 13;// 运动中的颜色const WORD COLOR_MOVE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY;// 固定不动的颜色const WORD COLOR_STILL = FOREGROUND_GREEN;// 空白处的颜色const WORD COLOR_BLANK = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;// 方块种类const unsigned int DIAMONDS_TYPES = 7;// 每个方块有几种变形const unsigned int DIAMONDS_TRANS = 4;// 表示单个方块的行数const unsigned int DIAMONDS_IFNO_ROW = 4;// 表示单个方块的列数const unsigned int DIAMONDS_IFNO_COL = 4;// 消除1行的得分const int ONE_ROW_SCORE = 100;// 消除2行的得分const int TWO_ROWS_SCORE = 300;// 消除3行的得分const int THREE_ROWS_SCORE = 700;// 消除4行的得分const int FOUR_ROWS_SCORE = 1500;// 最大分值const int MAX_SCORE = 99999;// 得分满,加一个速度const int SPEED_ADD_SCORE = 10000;// 默认声效频率const DWORD DEFAULT_FREQUENCY = 1760;// 默认声效延续时间const DWORD DEFAULT_DURATION = 20;// 超时下落const DWORD TIME_OUT = 1000;// 休眠间隔时间(毫秒)const int SLEEP_TIME = 200;// 游戏按键对应的索引enum KeyIndex{GameBegin = 0,// 游戏开始GamePause,// 游戏暂停GameVoice,// 游戏声效Up,// 方向键-上Left,// 方向键-左Right,// 方向键-右Down,// 方向键-下FallDown,// 方块直接落地KeyNum,// 按键总数};// 对应的键值(这个需要查表或者自己实验所得)enum KeyMap{KEY_ENTER = 13,KEY_F1 = 59,KEY_F2 = 60,KEY_UP = 72,KEY_LEFT = 75,KEY_RIGHT = 77,KEY_DOWN = 80,KEY_SPACE = 32,KEY_ESC = 27,};// 游戏操作定义enum Cmd{CMD_BEGIN,// 游戏开始CMD_PAUSE,// 游戏暂停CMD_VOICE,// 游戏声效CMD_ROTATE,// 方块变形CMD_LEFT,// 方块左移CMD_RIGHT,// 方块右移CMD_DOWN,// 方块下移CMD_SINK,// 方块沉底CMD_QUIT,// 游戏退出};// 某一行满了const char FULL_LINE[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };// 方块用一个4维数组表示:共7种不同方块,4种变形。每个方块用 4*4 表示。const char Diamonds[DIAMONDS_TYPES][DIAMONDS_TRANS][DIAMONDS_IFNO_ROW][DIAMONDS_IFNO_COL] ={{{ { 0,1,1,0 },{ 1,1,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },{ { 1,0,0,0 },{ 1,1,0,0 },{ 0,1,0,0 },{ 0,0,0,0 } },{ { 0,1,1,0 },{ 1,1,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },{ { 1,0,0,0 },{ 1,1,0,0 },{ 0,1,0,0 },{ 0,0,0,0 } }},{{ { 1,1,0,0 },{ 0,1,1,0 },{ 0,0,0,0 },{ 0,0,0,0 } },{ { 0,1,0,0 },{ 1,1,0,0 },{ 1,0,0,0 },{ 0,0,0,0 } },{ { 1,1,0,0 },{ 0,1,1,0 },{ 0,0,0,0 },{ 0,0,0,0 } },{ { 0,1,0,0 },{ 1,1,0,0 },{ 1,0,0,0 },{ 0,0,0,0 } }},{{ { 1,1,1,0 },{ 1,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },{ { 1,0,0,0 },{ 1,0,0,0 },{ 1,1,0,0 },{ 0,0,0,0 } },{ { 0,0,1,0 },{ 1,1,1,0 },{ 0,0,0,0 },{ 0,0,0,0 } },{ { 1,1,0,0 },{ 0,1,0,0 },{ 0,1,0,0 },{ 0,0,0,0 } }},{{ { 1,1,1,0 },{ 0,0,1,0 },{ 0,0,0,0 },{ 0,0,0,0 } },{ { 1,1,0,0 },{ 1,0,0,0 },{ 1,0,0,0 },{ 0,0,0,0 } },{ { 1,0,0,0 },{ 1,1,1,0 },{ 0,0,0,0 },{ 0,0,0,0 } },{ { 0,1,0,0 },{ 0,1,0,0 },{ 1,1,0,0 },{ 0,0,0,0 } }},{{ { 1,1,0,0 },{ 1,1,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },{ { 1,1,0,0 },{ 1,1,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },{ { 1,1,0,0 },{ 1,1,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },{ { 1,1,0,0 },{ 1,1,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } }},{{ { 0,1,0,0 },{ 1,1,1,0 },{ 0,0,0,0 },{ 0,0,0,0 } },{ { 0,1,0,0 },{ 1,1,0,0 },{ 0,1,0,0 },{ 0,0,0,0 } },{ { 1,1,1,0 },{ 0,1,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },{ { 1,0,0,0 },{ 1,1,0,0 },{ 1,0,0,0 },{ 0,0,0,0 } }},{{ { 1,1,1,1 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },{ { 1,0,0,0 },{ 1,0,0,0 },{ 1,0,0,0 },{ 1,0,0,0 } },{ { 1,1,1,1 },{ 0,0,0,0 },{ 0,0,0,0 },{ 0,0,0,0 } },{ { 1,0,0,0 },{ 1,0,0,0 },{ 1,0,0,0 },{ 1,0,0,0 } }}};// 游戏背景const char Background[GAME_HIGHT * GAME_WIDTH + 1] ="┏━━━━━━━━━━━┓┏━━━━┓""┃■■■■■■■■■■■┃┃┃""┃■■■■■■■■■■■┃┃┃""┃■■■■■■■■■■■┃┗━━━━┛""┃■■■■■■■■■■■┃""┃■■■■■■■■■■■┃ 退出= ESC""┃■■■■■■■■■■■┃""┃■■■■■■■■■■■┃ 开始=""┃■■■■■■■■■■■┃ 暂停=""┃■■■■■■■■■■■┃ 声效=""┃■■■■■■■■■■■┃ 变形=""┃■■■■■■■■■■■┃ 左移=""┃■■■■■■■■■■■┃ 右移=""┃■■■■■■■■■■■┃ 下移=""┃■■■■■■■■■■■┃ 落地=""┃■■■■■■■■■■■┃""┃■■■■■■■■■■■┃""┃■■■■■■■■■■■┃ 速度得分 ""┃■■■■■■■■■■■┃┏━━━━┓""┃■■■■■■■■■■■┃┃000000┃""┗━━━━━━━━━━━┛┗━━━━┛";// 游戏开始时的X坐标const unsigned int GameStartX = 38;// 游戏开始时的Y坐标const unsigned int GameStartY = 21;

main.cpp文件

#include "Console.h"#include "Window.h"#include "GameDefine.h"#include "Tetris.h"#include #include DWORD oldTime = 0;// 得到按键命令Cmd GetCmd(Tetris& tetris, Console& console){while (true){// 延时,减少CPU占用率Sleep(SLEEP_TIME);DWORD newTime = GetTickCount();// 超时下落if (newTime - oldTime > TIME_OUT){oldTime = newTime;return CMD_DOWN;}// 有按键if (_kbhit()){switch (_getch()){case KEY_ENTER:return CMD_BEGIN;case KEY_SPACE:return CMD_SINK;case KEY_ESC:return CMD_QUIT;case 0:case 0xE0:switch (_getch()){case KEY_F1:return CMD_PAUSE;case KEY_F2:return CMD_VOICE;case KEY_UP:return CMD_ROTATE;case KEY_LEFT:return CMD_LEFT;case KEY_RIGHT:return CMD_RIGHT;case KEY_DOWN:return CMD_DOWN;}}}if (tetris.IsRun() && tetris.GetLevel()