基于C语言设计的俄罗斯方块小游戏(VS2017运行)

(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下划线。

06. ScrollConsoleScreenBuffer函数

在屏幕缓冲区中移动数据块。通过指定剪切矩形可以限制移动的效果,因此剪切矩形外部的控制台屏幕缓冲区的内容不会改变。

函数声明:

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.

 

23. INPUT_RECORD结构

描述控制台输入缓冲区中的输入事件。可以使用ReadConsoleInputPeekConsoleInput函数从输入缓冲区读取这些记录,也可以使用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成员中的事件记录。

该成员可以是以下值之一。

含义
FOCUS_EVENT 0x0010事件成员包含一个FOCUS_EVENT_RECORD结构。这些事件在内部使用,应该被忽略。
KEY_EVENT 0x0001事件成员包含一个KEY_EVENT_RECORD结构有关键盘事件的信息。
MENU_EVENT 0x0008事件成员包含一个MENU_EVENT_RECORD结构。这些事件在内部使用,应该被忽略。
MOUSE_EVENT 0x0002所述事件构件包含MOUSE_EVENT_RECORD结构用约鼠标移动或按键按压事件的信息。
WINDOW_BUFFER_SIZE_EVENT 0x0004事件成员包含一个WINDOW_BUFFER_SIZE_RECORD结构有关控制台屏幕缓冲区的新大小信息。

事件
事件信息。此成员的格式取决于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成员的高位字包含正值,则轮向前旋转,远离用户。否则,车轮向后旋转,朝向用户。

25. 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。

26. INPUT_RECORD结构

描述控制台输入缓冲区中的输入事件。可以使用ReadConsoleInputPeekConsoleInput函数从输入缓冲区读取这些记录,也可以使用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成员中的事件记录。

该成员可以是以下值之一。

含义
FOCUS_EVENT 0x0010事件成员包含一个FOCUS_EVENT_RECORD结构。这些事件在内部使用,应该被忽略。
KEY_EVENT 0x0001事件成员包含一个KEY_EVENT_RECORD结构有关键盘事件的信息。
MENU_EVENT 0x0008事件成员包含一个MENU_EVENT_RECORD结构。这些事件在内部使用,应该被忽略。
MOUSE_EVENT 0x0002所述事件构件包含MOUSE_EVENT_RECORD结构用约鼠标移动或按键按压事件的信息。
WINDOW_BUFFER_SIZE_EVENT 0x0004事件成员包含一个WINDOW_BUFFER_SIZE_RECORD结构有关控制台屏幕缓冲区的新大小信息。

事件
事件信息。此成员的格式取决于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×05X1鼠标按钮
VK_XBUTTON2 0×06X2鼠标按钮
**- **0×07未定义
VK_BACK0x08的BACKSPACE键
VK_TAB×09TAB键
-0x0A至0B保留的
VK_CLEAR0x0C清除键
VK_RETURN0X0D回车键
-为0x0E-0F未定义
VK_SHIFT为0x10SHIFT键
VK_CONTROL为0x11CTRL键
VK_MENU0×12ALT键
VK_PAUSE0×13暂停键
VK_CAPITAL0×14CAPS LOCK键
-0x1A的未定义
VK_ESCAPE0x1BESC键
VK_CONVERT为0x1CIME转换
VK_NONCONVERT0x1DIME非转换
VK_ACCEPT0X1EIME接受
VK_MODECHANGE为0x1FIME模式更改请求
VK_SPACE为0x20空格键
VK_PRIOR为0x21PAGE UP键
VK_NEXT为0x22PAGE DOWN键
VK_END0×23结束键
VK_HOME0X24Home键
VK_LEFT0x25左箭头键
VK_UP0×26向上箭头键
VK_RIGHT0×27右箭头键
VK_DOWN0×28向下箭头键
VK_SELECT0x29SELECT键
VK_PRINT0x2A打印键
VK_EXECUTE0x2B访问执行键
VK_SNAPSHOT0x2c上PRINT SCREEN键
VK_INSERT0x2DINS键
VK_DELETE0x2E之间DEL键
VK_HELP值为0x2F帮助键
的0x300键
0X311键
0x322键
0x333键
0x344键
0x355键
0x366键
0×377键
0x388键
0x399键
-0x3A-40未定义
的0x41关键
的0x42B键
0x43中C键
0×44D键
0×45E键
0×46F键
0X47G键
0x48H键
×49我关键
0x4AJ键
0x4BK键
0x4CL键
送出0x4dM键
0x4EN键
0x4F哦关键
为0x50P键
0x51Q键
0×52R键
0x53S键
0x54T键
0x55的U键
0x56储存V键
的0x57W键
将0x58X键
0×59Y键
0x5AZ键
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_F10x70F1键
VK_F20x71F2键
VK_F30x72F3键
VK_F40x73F4键
VK_F50x74F5键
VK_F60x75F6键
VK_F70x76F7键
VK_F80x77F8键
VK_F90x78F9键
VK_F100x79的F10键
VK_F110x7AF11键
VK_F120x7BF12键
VK_F130x7CF13键
VK_F140x7DF14键
VK_F15的0x7EF15键
VK_F160x7F的F16键
VK_F170x80的F17键
VK_F180×81F18键
VK_F19为0x82F19键
VK_F200×83F20键
VK_F21的0x84F21键
VK_F220x85F22键
VK_F230x86可以F23键
VK_F2487H的F24键
-0x88-8F未分配
VK_NUMLOCK的0x90NUM LOCK键
VK_SCROLL0x91滚动锁定键
0x92-96OEM特定
-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保留的
0xE1OEM特定
VK_OEM_1020xE2RT 102键键盘上的尖括号键或反斜杠键
0xE3-E4OEM特定
VK_PROCESSKEY为0xE5IME PROCESS键
0xE6OEM特定
VK_PACKET0xE7用于传递Unicode字符,就像它们是击键一样。VK_PACKET键是用于非键盘输入方法的32位虚拟键值的低位字。有关更多信息,请参阅KEYBDINPUTSendInputWM_KEYDOWNWM_KEYUP中的备注
-0xE8未分配
0xE9-F5OEM特定
VK_ATTN0xF6关键
VK_CRSEL0xF7CrSel密钥
VK_EXSEL0xF8的ExSel密钥
VK_EREOF0xF9擦除EOF键
VK_PLAY0xFA回应播放键
VK_ZOOM0xFB的才能缩放键
VK_NONAME0xFC有保留的
VK_PA10xFDPA1键
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; //文件指针及时置空
}

 

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值