(1)俄罗斯方块起源
? ? ? 《俄罗斯方块》(Tetris, 俄文:Тетрис)是一款由俄罗斯人阿列克谢·帕基特诺夫于1984年6月发明的休闲游戏。 ? ? ? ?该游戏曾经被多家公司代理过。经过多轮诉讼后,该游戏的代理权最终被任天堂获得。 任天堂对于俄罗斯方块来说意义重大,因为将它与GB搭配在一起后,获得了巨大的成功。 ? ? ?《俄罗斯方块》的基本规则是移动、旋转和摆放游戏自动输出的各种方块,使之排列成完整的一行或多行并且消除得分。
? ? ? ?游戏规则: 由小方块组成的不同形状的板块陆续从屏幕上方落下来,玩家通过调整板块的位置和方向,使它们在屏幕底部拼出完整的一条或几条。这些完整的横条会随即消失,给新落下来的板块腾出空间,与此同时,玩家得到分数奖励。没有被消除掉的方块不断堆积起来,一旦堆到屏幕顶端,玩家便告输,游戏结束。
 
?
?
程序在VS2017下运行通过测试:

?
(2)windows下控制台图像控制相关函数分析
因为接下来的俄罗斯方块源码里就需要用到部分函数,要深入理解俄罗斯方块游戏源码,需要先把窗口控制相关的函数搞清楚。
01. CONSOLE_CURSOR_INFO结构
包含有关控制台光标的信息。
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
dwSize 光标填充的字符单元格的百分比。该值介于1和100之间。光标外观会发生变化,从完全填充单元格到显示为单元格底部的水平线。
注意?虽然dwSize值通常介于1和100之间,但在某些情况下,可能会返回该范围之外的值。例如,如果在注册表中将CursorSize设置为0,则返回的dwSize值将为0。
bVisible 光标的可见性。如果光标可见,则此成员为TRUE。
02. GetConsoleCursorInfo函数
获取有关指定控制台屏幕缓冲区的光标大小和可见性的信息。
类型声明
BOOL WINAPI GetConsoleCursorInfo(
_In_ HANDLE hConsoleOutput,
_Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
功能:
获取光标相关信息
参数:
hConsoleOutput 控制台屏幕缓冲区的句柄。句柄必须具有GENERIC_READ访问权限。
lpConsoleCursorInfo 指向CONSOLE_CURSOR_INFO结构的指针,该结构接收有关控制台游标的信息。
返回值:
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError。
03. SetConsoleCursorInfo函数
设置指定控制台屏幕缓冲区的光标大小和可见性。
函数声明:
BOOL WINAPI SetConsoleCursorInfo(
_In_ HANDLE hConsoleOutput,
_In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
功能:
设置光标的属性
参数:
hConsoleOutput 控制台屏幕缓冲区的句柄。句柄必须具有GENERIC_READ访问权限。
lpConsoleCursorInfo 指向CONSOLE_CURSOR_INFO结构的指针,该结构为控制台屏幕缓冲区的游标提供新规范。
返回值:
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError。
04. SetConsoleCursorPosition函数
设置指定控制台屏幕缓冲区中的光标位置。
函数声明:
BOOL WINAPI SetConsoleCursorPosition(
_In_ HANDLE hConsoleOutput,
_In_ COORD dwCursorPosition
);
功能:
设置光标的位置
参数:
hConsoleOutput 控制台屏幕缓冲区的句柄。句柄必须具有GENERIC_READ访问权限。
dwCursorPosition 用于指定新的光标位置(以字符为单位)。坐标是屏幕缓冲区字符单元格的列和行。坐标必须位于控制台屏幕缓冲区的边界内。
返回值:
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError。
05. CHAR_INFO结构
指定Unicode或ANSI字符及其属性。控制台功能使用此结构来读取和写入控制台屏幕缓冲区。
类型声明
typedef struct _CHAR_INFO {
union {
WCHAR UnicodeChar;
CHAR AsciiChar;
} Char;
WORD Attributes;
} CHAR_INFO, *PCHAR_INFO;
UnicodeChar 屏幕缓冲区字符单元格的Unicode字符。
AsciiChar 屏幕缓冲区字符单元格的ANSI字符。
Attributes
字符属性。该成员可以是零或以下值的任意组合。
值 | 含义 |
---|
FOREGROUND_BLUE?0x0001 | 文字颜色包含蓝色。 | FOREGROUND_GREEN?0x0002 | 文字颜色包含绿色。 | FOREGROUND_RED?0x0004 | 文字颜色包含红色。 | FOREGROUND_INTENSITY?0x0008 | 文字颜色加强。 | BACKGROUND_BLUE?0x0010 | 背景颜色包含蓝色。 | BACKGROUND_GREEN?0x0020 | 背景颜色包含绿色。 | BACKGROUND_RED?0x0040 | 背景颜色包含红色。 | BACKGROUND_INTENSITY?0x0080 | 背景颜色加剧。 | COMMON_LVB_LEADING_BYTE?0x0100 | 前导字节。 | COMMON_LVB_TRAILING_BYTE?0x0200 | 尾随字节。 | COMMON_LVB_GRID_HORIZONTAL?0x0400 | 顶部水平 | COMMON_LVB_GRID_LVERTICAL?0x0800 | 左垂直。 | COMMON_LVB_GRID_RVERTICAL?0x1000 | 正确的垂直。 | COMMON_LVB_REVERSE_VIDEO?0x4000 | 反转前景和背景属性。 | COMMON_LVB_UNDERSCORE?0x8000 | 下划线。 |
在屏幕缓冲区中移动数据块。通过指定剪切矩形可以限制移动的效果,因此剪切矩形外部的控制台屏幕缓冲区的内容不会改变。
函数声明:
BOOL WINAPI ScrollConsoleScreenBuffer(
_In_ HANDLE hConsoleOutput,
_In_ const SMALL_RECT *lpScrollRectangle,
_In_opt_ const SMALL_RECT *lpClipRectangle,
_In_ COORD dwDestinationOrigin,
_In_ const CHAR_INFO *lpFill
);
功能:
在屏幕缓冲区中移动数据块
参数:
hConsoleOutput 控制台屏幕缓冲区的句柄。句柄必须具有GENERIC_READ访问权限。
lpScrollRectangle 指定要移动的控制台屏幕缓冲区矩形的左上角和右下角坐标。
lpClipRectangle 指定受滚动影响的控制台屏幕缓冲区矩形的左上角和右下角坐标。该指针可以为NULL。
dwDestinationOrigin 它以字符为单位指定lpScrollRectangle内容新位置的左上角。
lpFill 指向CHAR_INFO结构的指针,该结构指定在填充lpScrollRectangle和lpClipRectangle交集中的单元格时使用的字符和颜色属性,这些属性由于移动而保留为空。
返回值:
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError。
?
07. FillConsoleOutputAttribute函数
设置指定数量的字符单元格的字符属性,从屏幕缓冲区中的指定坐标开始。
函数声明:
设置指定数量的字符单元格的字符属性BOOL WINAPI FillConsoleOutputAttribute(
_In_ HANDLE hConsoleOutput,
_In_ WORD wAttribute,
_In_ DWORD nLength,
_In_ COORD dwWriteCoord,
_Out_ LPDWORD lpNumberOfAttrsWritten
);
功能:
设置指定数量的字符单元格的字符属性
参数:
hConsoleOutput 控制台屏幕缓冲区的句柄。句柄必须具有GENERIC_WRITE访问权限。
wAttribute 写入控制台屏幕缓冲区时要使用的属性。
nLength 要设置为指定颜色属性的字符单元格数。
dwWriteCoord 指定字符坐标的开始位置
lpNumberOfAttrsWritten 指向变量的指针,该变量接收实际设置了属性的字符单元格数。
返回值:
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError。
08. FillConsoleOutputCharacter函数
从指定的坐标开始,将一个字符按照指定的次数写入控制台屏幕缓冲区。
函数声明:
BOOL WINAPI FillConsoleOutputCharacter(
_In_ HANDLE hConsoleOutput,
_In_ TCHAR cCharacter,
_In_ DWORD nLength,
_In_ COORD dwWriteCoord,
_Out_ LPDWORD lpNumberOfCharsWritten
);
功能:
填充指定次数的字符
参数:
hConsoleOutput 控制台屏幕缓冲区的句柄。句柄必须具有GENERIC_WRITE访问权限。
cCharacter 要写入控制台屏幕缓冲区的字符。
nLength 应写入字符的字符单元格数。
dwWriteCoord 指定字符坐标到的字符是要被写入的开始位置。
lpNumberOfCharsWritten 指向变量的指针,该变量接收实际写入控制台屏幕缓冲区的字符数。
返回值:
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError。
?
09. WriteConsoleOutputCharacter函数
从指定位置开始,将多个字符复制到控制台屏幕缓冲区的连续单元格。
函数声明:
BOOL WINAPI WriteConsoleOutputCharacter(
_In_ HANDLE hConsoleOutput,
_In_ LPCTSTR lpCharacter,
_In_ DWORD nLength,
_In_ COORD dwWriteCoord,
_Out_ LPDWORD lpNumberOfCharsWritten
);
功能:
从指定位置开始,将多个字符复制到控制台屏幕缓冲区的连续单元格。
参数:
hConsoleOutput 控制台屏幕缓冲区的句柄。句柄必须具有GENERIC_WRITE访问权限。
lpCharacter 要写入控制台屏幕缓冲区的字符串。
nLength 要写入的字符数。
dwWriteCoord 起始位置
lpNumberOfCharsWritten 指向接收实际写入的字符数的变量的指针。
返回值:
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError。
?
10. GetConsoleTitle函数
获取当前控制台标题
函数声明
DWORD WINAPI GetConsoleTitle(
_Out_ LPTSTR lpConsoleTitle,
_In_ DWORD nSize
);
功能:
获取当前控制台标题
参数:
lpConsoleTitle 指向缓冲区的指针,该缓冲区接收包含标题的以null结尾的字符串。如果缓冲区太小而无法存储标题,则该函数将存储符合缓冲区的标题字符,以空终止符结束。
nSize 指向的缓冲区大小,以字符为单位。
返回值:
如果函数成功,则返回值是控制台窗口标题的长度(以字符为单位)。
如果函数失败,则返回值为零,GetLastError返回错误代码。
?
11. SetConsoleTitle函数
设置当前控制台窗口标题
函数声明:
BOOL WINAPI SetConsoleTitle(
_In_ LPCTSTR lpConsoleTitle
);
功能:
设置当前控制台窗口标题
参数:
lpConsoleTitle 要在控制台窗口的标题栏中显示的字符串。总大小必须小于64K。
返回值:
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError。
注意: 当进程终止时,系统将恢复原始控制台标题。
12. SetConsoleScreenBufferSize函数
设置指定控制台屏幕缓冲区的大小。
函数声明:
BOOL WINAPI SetConsoleScreenBufferSize(
_In_ HANDLE hConsoleOutput,
_In_ COORD dwSize
);
功能:
设置指定控制台屏幕缓冲区的大小。
参数:
hConsoleOutput 控制台屏幕缓冲区的句柄。句柄必须具有GENERIC_READ访问权限。
dwSize 指定控制台屏幕缓冲区的新的大小,在字符行和列。指定的宽度和高度不能小于控制台屏幕缓冲区窗口的宽度和高度。指定的尺寸也不能小于系统允许的最小尺寸。此最小值取决于控制台的当前字体大小(由用户选择)以及GetSystemMetrics函数返回的SM_CXMIN和SM_CYMIN值。
返回值:
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError。
?
13. SetConsoleWindowInfo函数
设置控制台屏幕缓冲区窗口的当前大小和位置。
函数声明:
BOOL WINAPI SetConsoleWindowInfo(
_In_ HANDLE hConsoleOutput,
_In_ BOOL bAbsolute,
_In_ const SMALL_RECT *lpConsoleWindow
);
功能:
设置控制台屏幕缓冲区窗口的当前大小和位置。
参数:
hConsoleOutput 控制台屏幕缓冲区的句柄。句柄必须具有GENERIC_READ访问权限。
bAbsolute 如果此参数为TRUE,则坐标指定窗口的新左上角和右下角。如果为FALSE,则坐标相对于当前窗口角坐标。
lpConsoleWindow 指向SMALL_RECT结构的指针,该结构指定窗口的新左上角和右下角。
返回值:
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError。
14. COORD结构体
定义控制台屏幕缓冲区中字符单元格的坐标。坐标系(0,0)的原点位于缓冲区的左上角。
类型声明
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
说明:
x: 水平坐标或列值。单位取决于函数调用。
y: 垂直坐标或行值。单位取决于函数调用。
15. SMALL_RECT结构
定义矩形的左上角和右下角的坐标。
类型声明
typedef struct _SMALL_RECT {
SHORT Left;
SHORT Top;
SHORT Right;
SHORT Bottom;
} SMALL_RECT;
说明:
Left: 矩形左上角的x坐标。
Top: 矩形左上角的y坐标。
Right: 矩形右下角的x坐标。
Bottom: 矩形右下角的y坐标。
16. CONSOLE_SCREEN_BUFFER_INFO结构体
包含有关控制台屏幕缓冲区的信息
类型声明
typedef struct _CONSOLE_SCREEN_BUFFER_INFO {
COORD dwSize;
COORD dwCursorPosition;
WORD wAttributes;
SMALL_RECT srWindow;
COORD dwMaximumWindowSize;
} CONSOLE_SCREEN_BUFFER_INFO;
说明:
dwSize: 包含控制台屏幕缓冲区的大小,以字符列和行为单位。
dwCursorPosition: 包含控制台屏幕缓冲区中光标的列和行坐标。
wAttributes: 字符属性
srWindow: 包含显示窗口左上角和右下角的控制台屏幕缓冲区坐标。
dwMaximumWindowSize: 它包含控制台窗口的最大大小,在字符列和行中,给定当前屏幕缓冲区大小和字体以及屏幕大小。
17. GetConsoleScreenBufferInfo函数
获取有关指定控制台屏幕缓冲区的信息。
函数声明:
BOOL WINAPI GetConsoleScreenBufferInfo(
_In_ HANDLE hConsoleOutput,
_Out_ PCONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo
);
功能:
获取有关指定控制台屏幕缓冲区的信息。
参数:
hConsoleOutput:控制台屏幕缓冲区的句柄。句柄必须具有GENERIC_READ访问权限。
lpConsoleScreenBufferInfo:
指向CONSOLE_SCREEN_BUFFER_INFO结构的指针,该结构接收控制台屏幕缓冲区信息。
返回值:
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError。
?
18. FillConsoleOutputCharacter 函数
从指定的坐标开始,将一个字符写入控制台屏幕缓冲区指定的次数。
函数声明:
BOOL WINAPI FillConsoleOutputCharacter(
_In_ HANDLE hConsoleOutput,
_In_ TCHAR cCharacter,
_In_ DWORD nLength,
_In_ COORD dwWriteCoord,
_Out_ LPDWORD lpNumberOfCharsWritten
);
参数:
hConsoleOutput 控制台屏幕缓冲区的句柄。句柄必须具有GENERIC_WRITE访问权限。
cCharacter 要写入控制台屏幕缓冲区的字符。
nLength 应写入字符的字符单元格数。
dwWriteCoord 指定字符坐标到的字符是要被写入的开始坐标。
lpNumberOfCharsWritten 指向变量的指针,该变量接收实际写入控制台屏幕缓冲区的字符数。
返回值:
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError。
?
19. 句柄
??句柄是Windows最常用的概念。它通常用来标识Windows资源(如菜单、图标、窗口等)和设备等对象。虽然可以把句柄理解为是一个指针变量类型,但它不是对象所在的地址指针,而是作为Windows系统内部表的索引值来使用的。
HANDLE在winnt.h中的声明为
typedef void *HANDLE;
? 从上面可以看出HANDLE是一种无类型指针,句柄是处理对象的一个接口,你可以通过句柄去操作程序中所涉及的对象。在windows中,句柄是和对象一一对应的32位无符号整数值,对象可以映射到唯一的句柄,
? 句柄也可以映射到唯一的对象windows需要向程序员提供必要地编程接口,在这些接口中,允许程序员访问,创建和销毁对象,但是,出于封装的考虑,windows并不想向程序员返回指针
? 如果作数据的话,句柄这种方式则允许你按自己的方式直接操作数据,但windows又不向你直接暴露数据。直接操作数据是程序员需要的,不暴露数据是windows所需要的,句柄封装方式实现了各取所需。
? “句柄”是Windows最常用的概念。它通常用来标识Windows资源(如菜单、图标、窗口等)和设备等对象。虽然可以把句柄理解为是一个指针变量类型,但它不是对象所在的地址指针,而是作为Windows系统内部表的索引值来使用的。
?
20. GetStdHandle函数
函数相关说明
HANDLE WINAPI GetStdHandle(_In_ DWORD nStdHandle);
功能:
获取指定标准设备的句柄(标准输入,标准输出或标准错误)
参数:
nStdHandle 标准设备。此参数可以是以下值之一。
STD_INPUT_HANDLE(DWORD)-10 标准输入设备。最初,这是控制台输入缓冲区
STD_OUTPUT_HANDLE(DWORD)-11 标准输出设备。最初,这是活动的控制台屏幕缓冲区
STD_ERROR_HANDLE(DWORD)-12 标准错误设备。最初,这是活动的控制台屏幕缓冲区
返回值:
如果函数成功,则返回值是指定设备的句柄,或者是先前调用SetStdHandle设置的重定向句柄。该句柄具有GENERIC_READ和GENERIC_WRITE访问权限,除非应用程序使用SetStdHandle设置具有较少访问权限的标准句柄。
如果函数失败,则返回值为INVALID_HANDLE_VALUE。要获取扩展错误信息,请调用GetLastError。
如果应用程序没有关联的标准句柄,例如在交互式桌面上运行的服务,并且未重定向它们,则返回值为NULL。
在默认情况下:
标准输入(stdin)----键盘
标准输出(stdout)----显示器(屏幕)
标准错误(stderr)----显示器(屏幕)
注意:标准输出句柄和标准错误句柄默认情况下都是对应的屏幕
?
?
21. CloseHandle函数
函数相关说明
BOOL CloseHandle(HANDLE hObject);
功能:
关闭一个打开的对象句柄
参数:
hObject 打开对象的有效句柄
返回值:
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用 GetLastError。
22. SetConsoleTextAttribute函数
设置控制台文本属性(颜色),可以设置前景色FOREGROUND(文本颜色)和背景色BACKGROUND
BOOL WINAPI SetConsoleTextAttribute(HANDLE hConsoleOutput, WORD wAttributes);
功能:
设置控制台文本属性(颜色)
参数:
hConsoleOutput: 控制台屏幕缓冲区的句柄。句柄必须具有GENERIC_READ访问权限。
wAttributes: 字符属性
返回值:
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError。
备注:要确定屏幕缓冲区的当前颜色属性,请调用GetConsoleScreenBufferInfo函数。
字符属性可以分为两类:颜色和DBCS。Wincon.h头文件中定义了以下属性。
属性 | 含义 |
---|
FOREGROUND_BLUE | 文字颜色包含蓝色。 | FOREGROUND_GREEN | 文字颜色包含绿色。 | FOREGROUND_RED | 文字颜色包含红色。 | FOREGROUND_INTENSITY | 文字颜色加强。 | BACKGROUND_BLUE | 背景颜色包含蓝色。 | BACKGROUND_GREEN | 背景颜色包含绿色。 | BACKGROUND_RED | 背景颜色包含红色。 | BACKGROUND_INTENSITY | 背景颜色加剧。 | COMMON_LVB_LEADING_BYTE | 前导字节。 | COMMON_LVB_TRAILING_BYTE | 尾随字节。 | COMMON_LVB_GRID_HORIZONTAL | 顶部水平。 | COMMON_LVB_GRID_LVERTICAL | 左垂直。 | COMMON_LVB_GRID_RVERTICAL | 正确的垂直。 | COMMON_LVB_REVERSE_VIDEO | 反转前景和背景属性。 | COMMON_LVB_UNDERSCORE | 下划线。 |
相关声明
//
// Attributes flags:
//
#define FOREGROUND_BLUE 0x0001 // text color contains blue.
#define FOREGROUND_GREEN 0x0002 // text color contains green.
#define FOREGROUND_RED 0x0004 // text color contains red.
#define FOREGROUND_INTENSITY 0x0008 // text color is intensified.
#define BACKGROUND_BLUE 0x0010 // background color contains blue.
#define BACKGROUND_GREEN 0x0020 // background color contains green.
#define BACKGROUND_RED 0x0040 // background color contains red.
#define BACKGROUND_INTENSITY 0x0080 // background color is intensified.
#define COMMON_LVB_LEADING_BYTE 0x0100 // Leading Byte of DBCS
#define COMMON_LVB_TRAILING_BYTE 0x0200 // Trailing Byte of DBCS
#define COMMON_LVB_GRID_HORIZONTAL 0x0400 // DBCS: Grid attribute: top horizontal.
#define COMMON_LVB_GRID_LVERTICAL 0x0800 // DBCS: Grid attribute: left vertical.
#define COMMON_LVB_GRID_RVERTICAL 0x1000 // DBCS: Grid attribute: right vertical.
#define COMMON_LVB_REVERSE_VIDEO 0x4000 // DBCS: Reverse fore/back ground attribute.
#define COMMON_LVB_UNDERSCORE 0x8000 // DBCS: Underscore.
#define COMMON_LVB_SBCSDBCS 0x0300 // SBCS or DBCS flag.
?
描述控制台输入缓冲区中的输入事件。可以使用ReadConsoleInput或PeekConsoleInput函数从输入缓冲区读取这些记录,也可以使用WriteConsoleInput函数将这些记录写入输入缓冲区。
类型声明:
typedef struct _INPUT_RECORD {
WORD EventType;
union {
KEY_EVENT_RECORD KeyEvent;
MOUSE_EVENT_RECORD MouseEvent;
WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
MENU_EVENT_RECORD MenuEvent;
FOCUS_EVENT_RECORD FocusEvent;
} Event;
} INPUT_RECORD;
EventType
输入事件类型的句柄和存储在Event成员中的事件记录。
该成员可以是以下值之一。
事件 事件信息。此成员的格式取决于EventType成员指定的事件类型。
24. MOUSE_EVENT_RECORD结构
描述控制台INPUT_RECORD结构中的鼠标输入事件。
类型声明:
typedef struct _MOUSE_EVENT_RECORD {
COORD dwMousePosition;
DWORD dwButtonState;
DWORD dwControlKeyState;
DWORD dwEventFlags;
} MOUSE_EVENT_RECORD;
成员说明
dwMousePosition 一个COORD结构,它根据控制台屏幕缓冲区的字符单元格坐标包含光标的位置。
dwButtonState 鼠标按钮的状态。最低有效位对应于最左边的鼠标按钮。下一个最低有效位对应于最右边的鼠标按钮。下一位表示从左到右的鼠标按钮。然后,这些位从左到右对应鼠标按钮。如果按下按钮,则位为1。
为前五个鼠标按钮定义了以下常量。
值 | 含义 |
---|
FROM_LEFT_1ST_BUTTON_PRESSED?0x0001 | 最左边的鼠标按钮。一般来说鼠标左键 | FROM_LEFT_2ND_BUTTON_PRESSED?0x0004 | 左起第二个按钮。一般来说是鼠标中键,就是滚轮键 | FROM_LEFT_3RD_BUTTON_PRESSED?0x0008 | 左起第三个按钮。 | FROM_LEFT_4TH_BUTTON_PRESSED?0x0010 | 左起第四个按钮。 | RIGHTMOST_BUTTON_PRESSED?0x0002 | 最右边的鼠标按钮。一般来说鼠标右键 |
dwControlKeyState 控制键的状态。该成员可以是以下一个或多个值。
值 | 含义 |
---|
CAPSLOCK_ON?0x0080 | 大写锁定被打开 | ENHANCED_KEY?0x0100 | 扩展键被按下 | LEFT_ALT_PRESSED?0x0002 | 按下左ALT键。 | LEFT_CTRL_PRESSED?0x0008 | 按下左CTRL键。 | NUMLOCK_ON?0x0020 | 数字锁定被打开 | RIGHT_ALT_PRESSED?0x0001 | 按下右ALT键。 | RIGHT_CTRL_PRESSED?0x0004 | 按下右CTRL键。 | SCROLLLOCK_ON?0x0040 | 滚动锁定被打开 | SHIFT_PRESSED?0x0010 | 按下SHIFT键。 |
dwEventFlags 鼠标事件的类型。如果此值为零,则表示正在按下或释放鼠标按钮。否则,此成员是以下值之一。
值 | 含义 |
---|
DOUBLE_CLICK?0x0002 | 双击的第二次单击(按下按钮)发生。第一次单击作为常规按钮事件返回。 | MOUSE_HWHEELED?0x0008 | 水平鼠标滚轮被移动了。如果dwButtonState成员的高位字包含正值,则轮子向右旋转。否则,车轮向左旋转。 | MOUSE_MOVED?0x0001 | 发生了鼠标位置的变化。 | MOUSE_WHEELED?0x0004 | 垂直鼠标滚轮被移动。如果dwButtonState成员的高位字包含正值,则轮向前旋转,远离用户。否则,车轮向后旋转,朝向用户。 |
从控制台输入缓冲区读取数据并将其从缓冲区中删除。
函数声明:
BOOL WINAPI ReadConsoleInput(
_In_ HANDLE hConsoleInput,
_Out_ PINPUT_RECORD lpBuffer,
_In_ DWORD nLength,
_Out_ LPDWORD lpNumberOfEventsRead
);
功能:
从控制台输入缓冲区读取数据并将其从缓冲区中删除。
参数:
hConsoleInput 控制台输入缓冲区的句柄。句柄必须具有GENERIC_READ访问权限。
lpBuffer 指向接收输入缓冲区数据的INPUT_RECORD结构数组的指针。
nLength 数组元素中lpBuffer参数 指向的数组大小。
lpNumberOfEventsRead 指向接收读取的输入记录数的变量的指针。
返回值:
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError。
26. INPUT_RECORD结构
描述控制台输入缓冲区中的输入事件。可以使用ReadConsoleInput或PeekConsoleInput函数从输入缓冲区读取这些记录,也可以使用WriteConsoleInput函数将这些记录写入输入缓冲区。
类型声明:
typedef struct _INPUT_RECORD {
WORD EventType;
union {
KEY_EVENT_RECORD KeyEvent;
MOUSE_EVENT_RECORD MouseEvent;
WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
MENU_EVENT_RECORD MenuEvent;
FOCUS_EVENT_RECORD FocusEvent;
} Event;
} INPUT_RECORD;
EventType
输入事件类型的句柄和存储在Event成员中的事件记录。
该成员可以是以下值之一。
事件 事件信息。此成员的格式取决于EventType成员指定的事件类型。
27. KEY_EVENT_RECORD结构
描述控制台INPUT_RECORD结构中的键盘输入事件。
typedef struct _KEY_EVENT_RECORD {
BOOL bKeyDown;
WORD wRepeatCount;
WORD wVirtualKeyCode;
WORD wVirtualScanCode;
union {
WCHAR UnicodeChar;
CHAR AsciiChar;
} uChar;
DWORD dwControlKeyState;
} KEY_EVENT_RECORD;
bKeyDown 如果按下该键,则该成员为TRUE。否则,此成员为FALSE(密钥已释放)。
wRepeatCount 重复计数,表示正在按住某个键。例如,当按下某个键时,您可能会获得五个事件,该成员等于1,一个事件的成员等于5,或者此成员的多个事件大于或等于1。
wVirtualKeyCode 虚拟键码识别在设备无关的方式给定的键。
wVirtualScanCode 给定键的虚拟扫描代码,表示键盘硬件生成的与设备相关的值。
uChar 以下成员的联盟。
UnicodeChar 翻译的Unicode字符。
AsciiChar 翻译的ASCII字符。
dwControlKeyState 控制键的状态。该成员可以是以下一个或多个值。
值 | 含义 |
---|
CAPSLOCK_ON?0x0080 | 大写锁定被打开 | ENHANCED_KEY?0x0100 | 扩展键被按下 | LEFT_ALT_PRESSED?0x0002 | 按下左ALT键。 | LEFT_CTRL_PRESSED?0x0008 | 按下左CTRL键。 | NUMLOCK_ON?0x0020 | 数字锁定被打开 | RIGHT_ALT_PRESSED?0x0001 | 按下右ALT键。 | RIGHT_CTRL_PRESSED?0x0004 | 按下右CTRL键。 | SCROLLLOCK_ON?0x0040 | 滚动锁定被打开 | SHIFT_PRESSED?0x0010 | 按下SHIFT键。 |
虚拟键值码表
下表显示了系统使用的虚拟键代码的符号常量名称,十六进制值以及鼠标或键盘等效项。代码按数字顺序列出。
恒/值 | 描述 |
---|
VK_LBUTTON?0×01 | 鼠标左键 | VK_RBUTTON?0×02 | 鼠标右键 | VK_CANCEL?0×03 | 控制中断处理 | VK_MBUTTON?0×04 | 鼠标中键(三键鼠标) | VK_XBUTTON1?0×05 | X1鼠标按钮 | VK_XBUTTON2?0×06 | X2鼠标按钮 | **- **0×07 | 未定义 | VK_BACK0x08的 | BACKSPACE键 | VK_TAB×09 | TAB键 | -0x0A至0B | 保留的 | VK_CLEAR0x0C | 清除键 | VK_RETURN0X0D | 回车键 | -为0x0E-0F | 未定义 | VK_SHIFT为0x10 | SHIFT键 | VK_CONTROL为0x11 | CTRL键 | VK_MENU0×12 | ALT键 | VK_PAUSE0×13 | 暂停键 | VK_CAPITAL0×14 | CAPS LOCK键 | -0x1A的 | 未定义 | VK_ESCAPE0x1B | ESC键 | VK_CONVERT为0x1C | IME转换 | VK_NONCONVERT0x1D | IME非转换 | VK_ACCEPT0X1E | IME接受 | VK_MODECHANGE为0x1F | IME模式更改请求 | VK_SPACE为0x20 | 空格键 | VK_PRIOR为0x21 | PAGE UP键 | VK_NEXT为0x22 | PAGE DOWN键 | VK_END0×23 | 结束键 | VK_HOME0X24 | Home键 | VK_LEFT0x25 | 左箭头键 | VK_UP0×26 | 向上箭头键 | VK_RIGHT0×27 | 右箭头键 | VK_DOWN0×28 | 向下箭头键 | VK_SELECT0x29 | SELECT键 | VK_PRINT0x2A | 打印键 | VK_EXECUTE0x2B访问 | 执行键 | VK_SNAPSHOT0x2c上 | PRINT SCREEN键 | VK_INSERT0x2D | INS键 | VK_DELETE0x2E之间 | DEL键 | VK_HELP值为0x2F | 帮助键 | 的0x30 | 0键 | 0X31 | 1键 | 0x32 | 2键 | 0x33 | 3键 | 0x34 | 4键 | 0x35 | 5键 | 0x36 | 6键 | 0×37 | 7键 | 0x38 | 8键 | 0x39 | 9键 | -0x3A-40 | 未定义 | 的0x41 | 关键 | 的0x42 | B键 | 0x43中 | C键 | 0×44 | D键 | 0×45 | E键 | 0×46 | F键 | 0X47 | G键 | 0x48 | H键 | ×49 | 我关键 | 0x4A | J键 | 0x4B | K键 | 0x4C | L键 | 送出0x4d | M键 | 0x4E | N键 | 0x4F | 哦关键 | 为0x50 | P键 | 0x51 | Q键 | 0×52 | R键 | 0x53 | S键 | 0x54 | T键 | 0x55的 | U键 | 0x56储存 | V键 | 的0x57 | W键 | 将0x58 | X键 | 0×59 | Y键 | 0x5A | Z键 | VK_LWIN0x5B | 左键Windows键(自然键盘) | VK_RWIN0x5C | 右键Windows键(自然键盘) | VK_APPS0x5D | 应用键(自然键盘) | -0x5E | 保留的 | VK_SLEEP0x5F的 | 电脑睡眠键 | VK_NUMPAD00X60 | 数字小键盘0键 | VK_NUMPAD10x61 | 数字小键盘1键 | VK_NUMPAD20X62 | 数字小键盘2键 | VK_NUMPAD30x63 | 数字小键盘3键 | VK_NUMPAD40x64 | 数字键盘4键 | VK_NUMPAD50x65 | 数字键盘5键 | VK_NUMPAD60x66 | 数字小键盘6键 | VK_NUMPAD70×67 | 数字小键盘7键 | VK_NUMPAD80x68 | 数字小键盘8键 | VK_NUMPAD90×69 | 数字小键盘9键 | VK_MULTIPLY的0x6A | 乘以键 | VK_ADD0x6B | 添加密钥 | VK_SEPARATOR0x6C | 分隔键 | VK_SUBTRACT0x6D | 减去关键 | VK_DECIMAL0x6E | 十进制键 | VK_DIVIDE0x6F | 划分密钥 | VK_F10x70 | F1键 | VK_F20x71 | F2键 | VK_F30x72 | F3键 | VK_F40x73 | F4键 | VK_F50x74 | F5键 | VK_F60x75 | F6键 | VK_F70x76 | F7键 | VK_F80x77 | F8键 | VK_F90x78 | F9键 | VK_F100x79的 | F10键 | VK_F110x7A | F11键 | VK_F120x7B | F12键 | VK_F130x7C | F13键 | VK_F140x7D | F14键 | VK_F15的0x7E | F15键 | VK_F160x7F的 | F16键 | VK_F170x80的 | F17键 | VK_F180×81 | F18键 | VK_F19为0x82 | F19键 | VK_F200×83 | F20键 | VK_F21的0x84 | F21键 | VK_F220x85 | F22键 | VK_F230x86可以 | F23键 | VK_F2487H的 | F24键 | -0x88-8F | 未分配 | VK_NUMLOCK的0x90 | NUM LOCK键 | VK_SCROLL0x91 | 滚动锁定键 | 0x92-96 | OEM特定 | -0x97-9F | 未分配 | VK_LSHIFT0XA0 | 左SHIFT键 | VK_RSHIFT0xA1 | 右SHIFT键 | VK_LCONTROL0xA2 | 左控制键 | VK_RCONTROL0xA3执行 | 右控制键 | VK_LMENU0xA4 | 左MENU键 | VK_RMENU的0xA5 | 右键MENU | VK_BROWSER_BACK0xA6 | 浏览器返回键 | VK_BROWSER_FORWARD0xA7 | 浏览器转发键 | VK_BROWSER_REFRESH0xA8 | 浏览器刷新键 | VK_BROWSER_STOP0xA9 | 浏览器停止键 | VK_BROWSER_SEARCH和0xAA | 浏览器搜索键 | VK_BROWSER_FAVORITES是0xAB | 浏览器收藏夹键 | VK_BROWSER_HOME0xAC | 浏览器开始和主页键 | VK_VOLUME_MUTE0xAD,将 | 音量静音键 | VK_VOLUME_DOWN0xAE | 降低音量键 | VK_VOLUME_UP0xAF执行 | 提高音量键 | VK_MEDIA_NEXT_TRACK0XB0 | 下一曲目键 | VK_MEDIA_PREV_TRACK0xB1 | 上一曲目键 | VK_MEDIA_STOP0xB2 | 停止媒体键 | VK_MEDIA_PLAY_PAUSE0xB3 | 播放/暂停媒体键 | VK_LAUNCH_MAIL0xB4 | 启动邮件密钥 | VK_LAUNCH_MEDIA_SELECT0xB5执行 | 选择媒体键 | VK_LAUNCH_APP10xB6 | 启动应用程序1键 | VK_LAUNCH_APP20xB7 | 启动应用程序2密钥 | -0xB8-B9 | 保留的 | VK_OEM_10xBA | 用于杂项字符; 它可以因键盘而异。 对于美国标准键盘,使用';:'键 | VK_OEM_PLUS为0xBB | 对于任何国家/地区,请使用“+”键 | VK_OEM_COMMA0xBC | 对于任何国家/地区,请使用“,”键 | VK_OEM_MINUS0xBD | 对于任何国家/地区,请使用“ - ”键 | VK_OEM_PERIOD0xBE | 对于任何国家/地区,'。' 键 | VK_OEM_2为0xBF | 用于杂项字符; 它可以因键盘而异。 对于美国标准键盘,'/?' 键 | VK_OEM_3将0xC0 | 用于杂项字符; 它可以因键盘而异。 对于美国标准键盘,使用'?'键 | -0xC1-D7 | 保留的 | -0xD8-DA | 未分配 | VK_OEM_4位于0xDB | 用于杂项字符; 它可以因键盘而异。 对于美国标准键盘,'[{'键 | VK_OEM_5的0xDC | 用于杂项字符; 它可以因键盘而异。 对于美国标准键盘,'\ |' 键 | VK_OEM_60xDD | 用于杂项字符; 它可以因键盘而异。 对于美国标准键盘,使用']}'键 | VK_OEM_7写0xDE | 用于杂项字符; 它可以因键盘而异。 对于美国标准键盘,“单引号/双引号”键 | VK_OEM_80xDF | 用于杂项字符; 它可以因键盘而异。 | -取0xE0 | 保留的 | 0xE1 | OEM特定 | VK_OEM_1020xE2 | RT 102键键盘上的尖括号键或反斜杠键 | 0xE3-E4 | OEM特定 | VK_PROCESSKEY为0xE5 | IME PROCESS键 | 0xE6 | OEM特定 | VK_PACKET0xE7 | 用于传递Unicode字符,就像它们是击键一样。VK_PACKET键是用于非键盘输入方法的32位虚拟键值的低位字。有关更多信息,请参阅KEYBDINPUT,SendInput,WM_KEYDOWN和WM_KEYUP中的备注 | -0xE8 | 未分配 | 0xE9-F5 | OEM特定 | VK_ATTN0xF6 | 关键 | VK_CRSEL0xF7 | CrSel密钥 | VK_EXSEL0xF8的 | ExSel密钥 | VK_EREOF0xF9 | 擦除EOF键 | VK_PLAY0xFA回应 | 播放键 | VK_ZOOM0xFB的才能 | 缩放键 | VK_NONAME0xFC有 | 保留的 | VK_PA10xFD | PA1键 | VK_OEM_CLEAR0xFE的 | 清除键 |
官方参考手册:?https://docs.microsoft.com/en-us/windows/desktop/inputdev/virtual-key-codes
28. ReadConsoleInput函数
从控制台输入缓冲区读取数据并将其从缓冲区中删除。
函数声明:
BOOL WINAPI ReadConsoleInput(
_In_ HANDLE hConsoleInput,
_Out_ PINPUT_RECORD lpBuffer,
_In_ DWORD nLength,
_Out_ LPDWORD lpNumberOfEventsRead
);
功能:
从控制台输入缓冲区读取数据并将其从缓冲区中删除。
参数:
hConsoleInput 控制台输入缓冲区的句柄。句柄必须具有GENERIC_READ访问权限。
lpBuffer 指向接收输入缓冲区数据的INPUT_RECORD结构数组的指针。
nLength 数组元素中lpBuffer参数 指向的数组大小。
lpNumberOfEventsRead 指向接收读取的输入记录数的变量的指针。
返回值:
如果函数成功,则返回值为非零值。
如果函数失败,则返回值为零。要获取扩展错误信息,请调用GetLastError。
官方参考网址:https://docs.microsoft.com/en-us/windows/console/readconsoleinput
(3)俄罗斯方块源码
#include <stdio.h>
#include <Windows.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#define ROW 29 //游戏区行数
#define COL 20 //游戏区列数
#define DOWN 80 //方向键:下
#define LEFT 75 //方向键:左
#define RIGHT 77 //方向键:右
#define SPACE 32 //空格键
#define ESC 27 //Esc键
struct Face
{
int data[ROW][COL + 10]; //用于标记指定位置是否有方块(1为有,0为无)
int color[ROW][COL + 10]; //用于记录指定位置的方块颜色编码
}face;
struct Block
{
int space[4][4];
}block[7][4]; //用于存储7种基本形状方块的各自的4种形态的信息,共28种
//隐藏光标
void HideCursor();
//光标跳转
void CursorJump(int x, int y);
//初始化界面
void InitInterface();
//初始化方块信息
void InitBlockInfo();
//颜色设置
void color(int num);
//画出方块
void DrawBlock(int shape, int form, int x, int y);
//空格覆盖
void DrawSpace(int shape, int form, int x, int y);
//合法性判断
int IsLegal(int shape, int form, int x, int y);
//判断得分与结束
int JudeFunc();
//游戏主体逻辑函数
void StartGame();
//从文件读取最高分
void ReadGrade();
//更新最高分到文件
void WriteGrade();
int max, grade; //全局变量
int main()
{
#pragma warning (disable:4996) //消除警告
max = 0, grade = 0; //初始化变量
system("title 经典游戏-俄罗斯方块(VS2017运行)"); //设置cmd窗口的名字
system("mode con lines=29 cols=60"); //设置cmd窗口的大小
HideCursor(); //隐藏光标
ReadGrade(); //从文件读取最高分到max变量
InitInterface(); //初始化界面
InitBlockInfo(); //初始化方块信息
srand((unsigned int)time(NULL)); //设置随机数生成的起点
StartGame(); //开始游戏
return 0;
}
//隐藏光标
void HideCursor()
{
CONSOLE_CURSOR_INFO curInfo; //定义光标信息的结构体变量
curInfo.dwSize = 1; //如果没赋值的话,隐藏光标无效
curInfo.bVisible = FALSE; //将光标设置为不可见
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄
SetConsoleCursorInfo(handle, &curInfo); //设置光标信息
}
//光标跳转
void CursorJump(int x, int y)
{
COORD pos; //定义光标位置的结构体变量
pos.X = x; //横坐标设置
pos.Y = y; //纵坐标设置
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄
SetConsoleCursorPosition(handle, pos); //设置光标位置
}
//初始化界面
void InitInterface()
{
color(7); //颜色设置为白色
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < COL + 10; j++)
{
if (j == 0 || j == COL - 1 || j == COL + 9)
{
face.data[i][j] = 1; //标记该位置有方块
CursorJump(2 * j, i);
printf("■");
}
else if (i == ROW - 1)
{
face.data[i][j] = 1; //标记该位置有方块
printf("■");
}
else
face.data[i][j] = 0; //标记该位置无方块
}
}
for (int i = COL; i < COL + 10; i++)
{
face.data[8][i] = 1; //标记该位置有方块
CursorJump(2 * i, 8);
printf("■");
}
CursorJump(2 * COL, 1);
printf("下一个方块:");
CursorJump(2 * COL + 4, ROW - 19);
printf("左移:←");
CursorJump(2 * COL + 4, ROW - 17);
printf("右移:→");
CursorJump(2 * COL + 4, ROW - 15);
printf("加速:↓");
CursorJump(2 * COL + 4, ROW - 13);
printf("旋转:空格");
CursorJump(2 * COL + 4, ROW - 11);
printf("暂停: S");
CursorJump(2 * COL + 4, ROW - 9);
printf("退出: Esc");
CursorJump(2 * COL + 4, ROW - 7);
printf("重新开始:R");
CursorJump(2 * COL + 4, ROW - 5);
printf("最高纪录:%d", max);
CursorJump(2 * COL + 4, ROW - 3);
printf("当前分数:%d", grade);
}
//初始化方块信息
void InitBlockInfo()
{
//“T”形
for (int i = 0; i <= 2; i++)
block[0][0].space[1][i] = 1;
block[0][0].space[2][1] = 1;
//“L”形
for (int i = 1; i <= 3; i++)
block[1][0].space[i][1] = 1;
block[1][0].space[3][2] = 1;
//“J”形
for (int i = 1; i <= 3; i++)
block[2][0].space[i][2] = 1;
block[2][0].space[3][1] = 1;
for (int i = 0; i <= 1; i++)
{
//“Z”形
block[3][0].space[1][i] = 1;
block[3][0].space[2][i + 1] = 1;
//“S”形
block[4][0].space[1][i + 1] = 1;
block[4][0].space[2][i] = 1;
//“O”形
block[5][0].space[1][i + 1] = 1;
block[5][0].space[2][i + 1] = 1;
}
//“I”形
for (int i = 0; i <= 3; i++)
block[6][0].space[i][1] = 1;
int temp[4][4];
for (int shape = 0; shape < 7; shape++) //7种形状
{
for (int form = 0; form < 3; form++) //4种形态(已经有了一种,这里每个还需增加3种)
{
//获取第form种形态
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
temp[i][j] = block[shape][form].space[i][j];
}
}
//将第form种形态顺时针旋转,得到第form+1种形态
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
block[shape][form + 1].space[i][j] = temp[3 - j][i];
}
}
}
}
}
//颜色设置
void color(int c)
{
switch (c)
{
case 0:
c = 13; //“T”形方块设置为紫色
break;
case 1:
case 2:
c = 12; //“L”形和“J”形方块设置为红色
break;
case 3:
case 4:
c = 10; //“Z”形和“S”形方块设置为绿色
break;
case 5:
c = 14; //“O”形方块设置为黄色
break;
case 6:
c = 11; //“I”形方块设置为浅蓝色
break;
default:
c = 7; //其他默认设置为白色
break;
}
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
//注:SetConsoleTextAttribute是一个API(应用程序编程接口)
}
//画出方块
void DrawBlock(int shape, int form, int x, int y)
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (block[shape][form].space[i][j] == 1) //如果该位置有方块
{
CursorJump(2 * (x + j), y + i); //光标跳转到指定位置
printf("■"); //输出方块
}
}
}
}
//空格覆盖
void DrawSpace(int shape, int form, int x, int y)
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (block[shape][form].space[i][j] == 1) //如果该位置有方块
{
CursorJump(2 * (x + j), y + i); //光标跳转到指定位置
printf(" "); //打印空格覆盖(两个空格)
}
}
}
}
//合法性判断
int IsLegal(int shape, int form, int x, int y)
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
//如果方块落下的位置本来就已经有方块了,则不合法
if ((block[shape][form].space[i][j] == 1) && (face.data[y + i][x + j] == 1))
return 0; //不合法
}
}
return 1; //合法
}
//判断得分与结束
int JudeFunc()
{
//判断是否得分
for (int i = ROW - 2; i > 4; i--)
{
int sum = 0; //记录第i行的方块个数
for (int j = 1; j < COL - 1; j++)
{
sum += face.data[i][j]; //统计第i行的方块个数
}
if (sum == 0) //该行没有方块,无需再判断其上的层次(无需再继续判断是否得分)
break; //跳出循环
if (sum == COL - 2) //该行全是方块,可得分
{
grade += 10; //满一行加10分
color(7); //颜色设置为白色
CursorJump(2 * COL + 4, ROW - 3); //光标跳转到显示当前分数的位置
printf("当前分数:%d", grade); //更新当前分数
for (int j = 1; j < COL - 1; j++) //清除得分行的方块信息
{
face.data[i][j] = 0; //该位置得分后被清除,标记为无方块
CursorJump(2 * j, i); //光标跳转到该位置
printf(" "); //打印空格覆盖(两个空格)
}
//把被清除行上面的行整体向下挪一格
for (int m = i; m > 1; m--)
{
sum = 0; //记录上一行的方块个数
for (int n = 1; n < COL - 1; n++)
{
sum += face.data[m - 1][n]; //统计上一行的方块个数
face.data[m][n] = face.data[m - 1][n]; //将上一行方块的标识移到下一行
face.color[m][n] = face.color[m - 1][n]; //将上一行方块的颜色编号移到下一行
if (face.data[m][n] == 1) //上一行移下来的是方块,打印方块
{
CursorJump(2 * n, m); //光标跳转到该位置
color(face.color[m][n]); //颜色设置为还方块的颜色
printf("■"); //打印方块
}
else //上一行移下来的是空格,打印空格
{
CursorJump(2 * n, m); //光标跳转到该位置
printf(" "); //打印空格(两个空格)
}
}
if (sum == 0) //上一行移下来的全是空格,无需再将上层的方块向下移动(移动结束)
return 1; //返回1,表示还需调用该函数进行判断(移动下来的可能还有满行)
}
}
}
//判断游戏是否结束
for (int j = 1; j < COL - 1; j++)
{
if (face.data[1][j] == 1) //顶层有方块存在(以第1行为顶层,不是第0行)
{
Sleep(1000); //留给玩家反应时间
system("cls"); //清空屏幕
color(7); //颜色设置为白色
CursorJump(2 * (COL / 3), ROW / 2 - 3);
if (grade > max)
{
printf("恭喜你打破最高记录,最高记录更新为%d", grade);
WriteGrade();
}
else if (grade == max)
{
printf("与最高记录持平,加油再创佳绩", grade);
}
else
{
printf("请继续加油,当前与最高记录相差%d", max - grade);
}
CursorJump(2 * (COL / 3), ROW / 2);
printf("GAME OVER");
while (1)
{
char ch;
CursorJump(2 * (COL / 3), ROW / 2 + 3);
printf("再来一局?(y/n):");
scanf("%c", &ch);
if (ch == 'y' || ch == 'Y')
{
system("cls");
main();
}
else if (ch == 'n' || ch == 'N')
{
CursorJump(2 * (COL / 3), ROW / 2 + 5);
exit(0);
}
else
{
CursorJump(2 * (COL / 3), ROW / 2 + 4);
printf("选择错误,请再次选择");
}
}
}
}
return 0; //判断结束,无需再调用该函数进行判断
}
//游戏主体逻辑函数
void StartGame()
{
int shape = rand() % 7, form = rand() % 4; //随机获取方块的形状和形态
while (1)
{
int t = 0;
int nextShape = rand() % 7, nextForm = rand() % 4; //随机获取下一个方块的形状和形态
int x = COL / 2 - 2, y = 0; //方块初始下落位置的横纵坐标
color(nextShape); //颜色设置为下一个方块的颜色
DrawBlock(nextShape, nextForm, COL + 3, 3); //将下一个方块显示在右上角
while (1)
{
color(shape); //颜色设置为当前正在下落的方块
DrawBlock(shape, form, x, y); //将该方块显示在初始下落位置
if (t == 0)
{
t = 15000; //这里t越小,方块下落越快(可以根据此设置游戏难度)
}
while (--t)
{
if (kbhit() != 0) //若键盘被敲击,则退出循环
break;
}
if (t == 0) //键盘未被敲击
{
if (IsLegal(shape, form, x, y + 1) == 0) //方块再下落就不合法了(已经到达底部)
{
//将当前方块的信息录入face当中
//face:记录界面的每个位置是否有方块,若有方块还需记录该位置方块的颜色。
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (block[shape][form].space[i][j] == 1)
{
face.data[y + i][x + j] = 1; //将该位置标记为有方块
face.color[y + i][x + j] = shape; //记录该方块的颜色数值
}
}
}
while (JudeFunc()); //判断此次方块下落是否得分以及游戏是否结束
break; //跳出当前死循环,准备进行下一个方块的下落
}
else //未到底部
{
DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置
y++; //纵坐标自增(下一次显示方块时就相当于下落了一格了)
}
}
else //键盘被敲击
{
char ch = getch(); //读取keycode
switch (ch)
{
case DOWN: //方向键:下
if (IsLegal(shape, form, x, y + 1) == 1) //判断方块向下移动一位后是否合法
{
//方块下落后合法才进行以下操作
DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置
y++; //纵坐标自增(下一次显示方块时就相当于下落了一格了)
}
break;
case LEFT: //方向键:左
if (IsLegal(shape, form, x - 1, y) == 1) //判断方块向左移动一位后是否合法
{
//方块左移后合法才进行以下操作
DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置
x--; //横坐标自减(下一次显示方块时就相当于左移了一格了)
}
break;
case RIGHT: //方向键:右
if (IsLegal(shape, form, x + 1, y) == 1) //判断方块向右移动一位后是否合法
{
//方块右移后合法才进行以下操作
DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置
x++; //横坐标自增(下一次显示方块时就相当于右移了一格了)
}
break;
case SPACE: //空格键
if (IsLegal(shape, (form + 1) % 4, x, y + 1) == 1) //判断方块旋转后是否合法
{
//方块旋转后合法才进行以下操作
DrawSpace(shape, form, x, y); //用空格覆盖当前方块所在位置
y++; //纵坐标自增(总不能原地旋转吧)
form = (form + 1) % 4; //方块的形态自增(下一次显示方块时就相当于旋转了)
}
break;
case ESC: //Esc键
system("cls"); //清空屏幕
color(7);
CursorJump(COL, ROW / 2);
printf(" 游戏结束 ");
CursorJump(COL, ROW / 2 + 2);
exit(0); //结束程序
case 's':
case 'S': //暂停
system("pause>nul"); //暂停(按任意键继续)
break;
case 'r':
case 'R': //重新开始
system("cls"); //清空屏幕
main(); //重新执行主函数
}
}
}
shape = nextShape, form = nextForm; //获取下一个方块的信息
DrawSpace(nextShape, nextForm, COL + 3, 3); //将右上角的方块信息用空格覆盖
}
}
//从文件读取最高分
void ReadGrade()
{
FILE* pf = fopen("俄罗斯方块最高得分记录.txt", "r"); //以只读方式打开文件
if (pf == NULL) //打开文件失败
{
pf = fopen("俄罗斯方块最高得分记录.txt", "w"); //以只写方式打开文件(文件不存在可以自动创建该文件)
fwrite(&grade, sizeof(int), 1, pf); //将max写入文件(此时max为0),即将最高历史得分初始化为0
}
fseek(pf, 0, SEEK_SET); //使文件指针pf指向文件开头
fread(&max, sizeof(int), 1, pf); //读取文件中的最高历史得分到max当中
fclose(pf); //关闭文件
pf = NULL; //文件指针及时置空
}
//更新最高分到文件
void WriteGrade()
{
FILE* pf = fopen("俄罗斯方块最高得分记录.txt", "w"); //以只写方式打开文件
if (pf == NULL) //打开文件失败
{
printf("保存最高得分记录失败\n");
exit(0);
}
fwrite(&grade, sizeof(int), 1, pf); //将本局游戏得分写入文件当中(更新最高历史得分)
fclose(pf); //关闭文件
pf = NULL; //文件指针及时置空
}
?
|