Email: [email protected]
《俄罗斯方块》(俄语:Тетрис,英语:Tetris),是上世纪80年末期至90年代初期风靡全世界的电脑游戏,是落下型益智游戏的始祖,为苏联首个在美国发布的娱乐软件。该游戏最初是由阿列克谢·帕基特诺夫在苏联设计和编程,于1984年6月6日首次发布,当时帕基特诺夫正在苏联科学院电算中心工作。俄罗斯方块的英文名称(Tetris)是由希腊语的数字“四”前缀“tetra-”(所有落下方块皆由四块组成)和帕基特诺夫最喜欢的运动网球(“tennis”)拼接而成。本次实验使用VS2015-community软件的Windows程序设计平台,使用C++语言实现了基本的俄罗斯方块界面以及功能。
关键字:俄罗斯方块、C++、Windows程序设计
一个win32程序可以简单的划分为UI部分和代码两大部分。当我们编辑好这两部分后,再由RC(resource compiler)编译器将这两部分整合成一个EXE文件。程序代码不用说了,UI资源指的是一些如菜单、对话框、位图、鼠标指针、图标等,我们必须在一个.rc文件中描述它们。
Windows程序的运行是靠外部事件来驱动的,就是说,程序一直出于一个等待的状态,如果有一个事件发生,程序就会判断是什么事件,然后做出相应的处理。下面将结合俄罗斯方块的具体实现来介绍程序的具体框架。
windows程序的进入点是WinMain函数(类似于普通C++程序的Main函数),它有四个参数,形式如下:
int WINAPI wWinMain (HINSTANCE hInst,
HINSTANCE hPrevInst,
LPSTR lpCmdLine,
int nCmdShow );
参数说明:hInst 为当前实例句柄,Windows 环境下用于区别同一应用程序的不同实例;hPrevInst应用程序先前实例的句柄(如果有的话),否则为 NULL,可以用来确定当前实例是否为应用程序的第一个实例;lpCmdLine是以NULL结尾的命令行字符串长指针;nCmdShow指定窗口初始显示方式的整型常量(1 = 通常;7 = 最小化) 。
该函数的主要功能有初始化全局字符串、执行应用程序初始化以及主消息循环等。
设计定义窗口类,其实就是定义程序的主窗口,比如有没有标题栏,背景什么颜色,有没有边框,可不可以调整大小等。Tetris窗口类的定义如图1所示。
窗口类注册完成后,下一步就是创建窗口以及显示窗口,调用CreateWindow创建窗口,如果成功,会返回一个窗口的句柄,我们对这个窗口的操作都要用到这个句柄。什么是句柄呢?其实它就是一串数字,只是一个标识而已,内存中会存在各种资源,如图标、文本等,为了可以有效标识这些资源,每一个资源都有其唯一的标识符,这样,通过查找标识符,就可以知道某个资源存在于内存中哪一块地址中。窗口句柄就是类似于窗口指针的一个存在。
此外UpdateWindow函数用于窗口更新操作。
图2.创建程序主窗口
Windows操作系统是基于消息控制机制的,用户与系统之间的交互,程序与系统之间的交互,都是通过发送和接收消息来完成的。
我们都知道程序代码是不断向前执行的,为了能够持续与用户交互,程序定义了消息循环。这些消息主要包含了与用户交互消息,系统向应用程序发送的消息通知等,因为这些消息是不定时不断发送的,因此定义一个缓冲区来存放,这就是消息队列。
定义消息队后就应该处理消息队列中的信息。要取出一条消息,调用GetMessage函数。函数会传入一个MSG结构体的指针,当收到消息,会填充MSG结构体中的成员变量,这样我们就知道我们的应用程序收到什么消息了,直到GetMessage函数取不到消息,条件不成立,循环跳出,这时应用程序就退出。MSG的定义如图3。
TranslateMessage是用于转换按键信息的,因为键盘按下和弹起会发送WM_KEYDOWN和WM_KEYUP消息,但如果我们只想知道用户输了哪些字符,这个函数可以把这些消息转换为WM_CHAR消息,它表示的就是键盘按下的那个键的字符,如“A”,这样我们处理起来就更方便了。
DispatchMessage函数是必须调用的,它的功能就相当于一根传送带,每收到一条消息,DispatchMessage函数负责把消息传到WindowProc让我们的代码来处理,如果不调用这个函数,WindowProc就永远接收不到消息,程序自然也就无法做出消息响应了。
图3.消息循环机制
CALLBACK是一种函数调用习惯,被定义为__stdcall,说明此函数为回调函数,由系统自动调用的,当窗口接收到消息并DispatchMessage之后,系统就自动调用窗口函数WndPro函数了。注意窗口函数中消息的分支结构中default分支必须是return DefWindowProc ( hWnd, message, wParam,lParam );因为不论什么消息都必须被处理,DefWindowProc是windows内部预设的消息处理函数。
图4.消息响应机制
每个俄罗斯方块使用4*4的BOOL类型矩阵表示,总共有7种类型的俄罗斯方块分别编号为0~7。对于每一个4*4矩阵若取值为true表示该处有格子,反之取值为false表示该处没有格子。每次通过随机函数随机从7种俄罗斯方块中随机选取一种类型表示接下来会落下的俄罗斯方块。
值得注意的是本程序使用的俄罗斯方块变形函数是以方块左上角为旋转中心,每次顺时针旋转90。来实现的。其中g_curTetris全局变量表示当前方块。具体的实现函数如下:
图5.Tetris变形函数
程序界面主要分为三部分来绘制的——左边的游戏区主要是背景小方块的绘制以及其中的俄罗斯方块的绘制、右上部分的接下来的俄罗斯方块绘制以及右下部分的信息区域绘制。由于实现方法基本类似,因此在此着重介绍左边游戏区的绘制函数。
HPEN和HBRUSH分别声明一个画笔和画刷比较容易理解。Rectangle由Windows API提供的绘制矩形的函数五个参数依次表示当前dc(设备上下文)、起始点坐标、长、宽。其中引用MSDN解释DC如下:
一个DC是一个结构,它定义了一系列图形对象的集合以及它们相关的属性,以及影响输出效果的一些图形模式。这些图形对象包括一个画线的笔,一个填充和painting的画刷,一个用来向屏幕拷贝的位图,一个定义了一系列颜色集合的调色板,一个用来剪裁等操作的区域,一个做painting和drawing操作的路径。
图6.Tetris初始界面
通过定义窗口计时器控制俄罗斯方块的下落速度,并可动态调节计时器平率从而达到控制游戏难度的目的。计时器的实现函数是SetTimer,可以通过KillTimer函数销毁当前窗口计时器。Tetris的窗口计时器定义如下:
图7.定时器实现暂停、继续功能
俄罗斯方块是一款风靡全球的游戏,它的规则简单、玩法多样,30多年来深受全球玩家的喜爱,是我们每个人童年的美好回忆。本次借着程序设计大作业的机会结合Windows程序设计的相关知识将亲手实现了这款游戏,虽然最初看上去比较容易实现,但是完整实现后发现还是需要花费一定时间的。本程序的核心部分就是Windows窗口函数的定义、消息循环以及消息响应等等部分,通过这次大作业也加深了自己对于Windows程序设计相关知识的印象与理解。
另外关于上周展示时Tetris窗口抖动较为严重情况,经过实践发现极有可能与设备屏幕的分辨率有关系。在虚拟机上的XP环境下没有发现类似现象,在自己的PC上和实验室其他设备上(1920*1080分辨率下)抖动现象极少发生,具体原因尚不明确。
[1] 俄罗斯方块.(February 20, 2016). Retrieved October 28, 2016,from 维基百科:https://zh.wikipedia.org/wiki/%E4%BF%84%E7%BE%85%E6%96%AF%E6%96%B9%E5%A1%8A.
[2] 跟我一起玩Win32开发(2):完整的开发流程.(January13, 2013). Retrieved October 28, 2016,from http://blog.csdn.net/tcjiaan/article/details/8497535.