基于win32控制台应用程序的双人俄罗斯方块小游戏
1. 课题概述
1.1 课题目标和主要内容
使用visual studio 2015在win32控制台应用程序下用多线程实现双人同时进行俄罗斯方块的桌面游戏。最终将要完成的效果如图1.1所示,左右共两片工作区,也是游戏的主题;工作区旁边是记录当前游戏的级别和分数以及下一个掉落方块类别,根据工作区的不同各自通过键盘上的’w’, ’a’, ’ s’, ’d’和’↑’, ’←’, ’↓’, ’→’来控制方块的旋转、下移、左移、右移。
1.2 系统的主要功能
- 能够同时两个人进行俄罗斯方块的对战
- 每个俄罗斯方块具备基本的旋转、左移、右移和下落操作
- 当其中一个俄罗斯方块消掉一行时,另一个俄罗斯方块将增加一行
- 能够调节方块的下落速度
- 能够保存玩家的历史最高分
- 双重模式供玩家选择
- 能够根据模式的不同播放音乐
2. 系统设计
2.1 系统总体框架
俄罗斯方块游戏的设计思路遵循MVC设计模式,该模式是一种使用Model-View-Controller(模型-视图-控制器)设计创建应用程序的模式。MVC框架图如图2.1所示。
- Model(模型):表示应用程序的数据的存取
- View(视图):显示界面,根据Model提供的不同数据,显示不同的界面
- Controller(控制器):处理用户界面的交互,以及向模型发送更新的数据
MVC分层有助于管理复杂的应用程序,这样可以在一段时间内专门关注一个方面。同时也让应用程序的测试更加容易,并且简化了分组开发。不同的开发人员可同时开发视图、控制器逻辑和业务逻辑。
对于俄罗斯方块游戏来说,需要的设计步骤如下:
- 设计Model。即工作区和方块的存储结构用来方便地存储数据
- 设计View。即如何根据数据显示不同的界面
- 设计Controller。即如何用键盘控制数据的变化
2.2 系统详细设计
2.2.1 模块划分图及描述
- 初始化模块:完成初始界面的显示,以及数据的初始化。该函数封装了GetStdHandle()控制台API
- 基本的显示函数:在制定位置显示指定颜色的字符串
BOOL textout(HANDLE hOutput, //控制台句柄
int x,int y, //显示坐标
WORD wColors[], //显示颜色
int nColors, //颜色数量
LPTSTR lpszString); //显示的字符内容
- 方块的显示和擦除:完成在指定位置显示/擦除指定颜色、形状的方块。
- 方块显示:在x,y坐标位置显示长和宽为w和h,颜色为wColors的方块,方块形状由数组a表示,整型数p、q确定工作区位置。
void DrawBlocks(int a[], int w, int h, int x, int y, WORD wColors[], int nColors, int p, int q);
- 方块擦除:擦除x,y,y坐标位置的长和宽为w和h,颜色为wColors的方块,方块形状由数组a表示,整型数p、q确定工作区位置。
void ClearSquare(int a[], int w, int h, int x, int y, int p, int q);
- 方块旋转:完成每种方块4个方向的旋转。
void Turn(int a[][4], int w, int h, int *x, int y, int MAP[][MAPW]);
- 判断方块是否能够下落。
bool IsAvailable(int a[], int x, int y, int w, int h, int MAP[][MAPW]);
- 方块满一行后消行:
- 参数说明:在工作区row位置的一行,然后将该行之上的方块下移。其中数组m表示工作区,整型数p、q确定工作区位置。
void DeleteLine(int m[][MAPW], int row, int p, int q);
- 给另一工作区加行:
参数说明:在另一工作区,将工作区方块整体上移,然后在最底下一行位置增加一行。其中数组m表示工作区,整型数p、q确定工作区位置。
void AddLine(int m[][MAPW], int row, int p, int q);
- 获取历史最高分:在程序所在路径下生成一个MaxScore.txt文件用来记录最高分。
int GetMaxScore();
- 整体清屏
参数说明:默认为0,整体清空界面,参数值为1时清空左工作区内方块,参数值为2时清空右工作区内的方块。
void ClearBlank(int i=0);
- 判断重新开始还是结束游戏
void Change();
- 游戏说明
void GameRule();
- 难度选择
void LevelChoose();
- 正经模式:按照要求实现双人多线程俄罗斯方块,能够完成相关操作。
void DoubleMode();
- 地狱模式:改变背景音乐风格,随机确定初始下落位置。其他同正经模式。
void HeheMode();
- 模式选择:选择正经模式、地狱模式、游戏说明还是退出游戏。
void ModeAlter();
- 游戏结束:退出游戏时界面,打印GAME OVER。
void GameOver();
- 线程处理函数:左右工作区分别对应线程0和线程1
DWORD WINAPI ThreadFunc0(LPVOID lpParam);
DWORD WINAPI ThreadFunc(LPVOID lpParam);
2.2.2 程序流程图及描述
按照面向过程的程序设计思想,自顶向下逐步求精的方法,首先需要根据基本功能进行模块划分,设计程序流程。俄罗斯方块游戏的基本的运行模式就是根据用户的按键,显示、翻转、移动不同的方块,因此其流程结构详见附件系统主流程1,系统主流程2,键盘处理流程。
2.2.3 存储结构、内存分配
- 工作区数据存储:游戏中可划分出两个工作区,在两个区域内方块根据操作进行移动、翻转,用两个两位数组来分别保存两个工作区中每个位置信息点的信息。程序中选用工作区的大小是12*20,由常量MAPW和MAPH表示。
# define MAPW 12 //地图的宽度
# define MAPH 20 //地图的高度
- 方块数据存储:俄罗斯方块有各种不同的形状,基本形状有7种,每一种形状采用4*4的数组来存储,分别使用三维数组来存储。
int b[7][4][4] = { { { 1 },{ 1,1,1 } },{ { 0,2 },{ 2,2,2 } },{ { 3,3 },{ 0,3,3 } },{ { 0,0,4 },{ 4,4,4 } },{ { 0,5,5 },{ 5,5 } },{ { 6,6,6,6 } },{ { 7,7 },{ 7,7 } }};
3. 程序运行结果分析
3.1 输出显示方式
用Drawblock函数更新方块,配合Textout函数更新游戏参数。
欢迎界面
模式选择界面
正经双人模式界面
3.2 操作流程
程序开始后,进入模式选择界面,可选择游戏说明,或直接开始游戏,模式选择完毕后则选择游戏难度,之后正式开始进行游戏。游戏中中可任意按空格键暂停游戏,或任意按回车键返回模式选择界面,以及按ESC键直接退出游戏。
3.3 运行效果
整体来说程序相对流畅,基本符合要求,各种界面之间切换自然,衔接不会有太多延迟感,音乐循环播放也没有感觉到切换的卡顿。
4. 总结
4.1 课题的难点和关键点
4.1.1 线程同步
本程序中使用双线程,难点在于两个线程彼此同步调配,二者均同从键盘按键中获取键值进行相应操作,为避免二者相互冲突,故引进临界区变量,当某一线程读取键值时锁定使另一线程暂时无法读取,由于临界区锁定时间短,人难以察觉出细微变化,故不影响整体速度。
4.1.2 增加一行
当某一玩家成功消行时,对另一玩家实行加行操作。难点在于重绘上移后的工作区。仿照消行函数Deleteline()的逻辑,从上向下逐行扫描更新。一方面,由于不同于消行操作,加行时的绘制起始点行列,须认真计算,否则容易出现绘制异常;另一方面,由于绘制方块函数Drawblock()逻辑原因,对于工作区内块值为零的区域,不执行任何操作,故当该工作区内方块整体上移时,对于某些排列空隙有可能上移后被填满,所以对于块值为零区域,单独调用Textout()函数在其区域打印char[3]的空格进行擦除,但同时造成下落方块的暂时消失,可以在循环按键处理中更新下落方块,使由于擦除造成的方块闪烁感消失。
本程序中设定由于对方消行所增加的新行不在具有能够消除的属性,故要增加两个整型数分别记录增加的行,方便在调用消行函数时消去的行不在惩罚增加行之中。
4.1.3 线程结束
当游戏一方到达顶部时,游戏结束后重新开始需要将原有两个线程结束掉,该问题属于程序设计逻辑问题。解决思想如下:
当一方到达顶部时,设置该对应线程结束完成标志ok1(如线程0)和设置结束另一线程命令标志flag2,之后return结束该线程;当另一线程接收结束命令flag2后,设置该对应线程结束完成标志ok2,之后return结束该线程;
当满足两个线程结束标志ok1和ok2时将退出当前游戏模式,跳转到模式选择模式。
4.2 本课题的评价
从最初的一个子线程配合一个主线程,到现在的两个相似的子线程交叉调度;
从最初的单人模式,到现在正经双人和地狱双人模式;从只能游戏一次,到现在任意返回、任意退出、无限循环开始。可以说本课题完成费了不少时间精力,完成相对满意。但程序仍有一些不够完善的地方,例如长条形方块下落时再不足旋转空间处旋转造成旋转出工作区外、两个线程对键盘读取键值的处理不够理想,容易引起漏键的现象。希望随着编程能力的提高将来能够更好地将这些问题优化好,做成一个成熟应用程序。
4.3 心得体会
通过此次程序设计实践,一方面,对win32控制台应用程序有了新的认识,不在仅局限使用标准输入输出流cin/cout进行界面交互,通过使用API 函数可以使编程编得更加方便,增加对句柄的熟悉度;另一方面,学习了多线程编程的一些相关概念及函数调用,在debug时明白了线程之间切换的大概过程,让我debug能力得到了很好地提高。对于此类程序编写时,要注意其可扩展性,方便后期修改,模块要区分的清除很重要的一点就是,程序设计时,一定要整理后各个模块以及各个模块间衔接的逻辑关系,尽可能地避免逻辑错误。
5. 参考文献
[1] 张宏,黄小诚. VC++语言多线程编程及其实现[J]. 科技信息(科学教研). 2008(07)
[2] 韩玉坤,介龙梅. 浅谈多线程编程技术及方法[J]. 电脑学习. 2010(02)
[3] 胡吉全,李明星. 基于VC多线程技术的优化设计方法[J]. 交通与计算机. 2005(02)
[4] 王日宏. 基于VC的Win32多线程同步问题[J]. 计算机系统应用. 2004(07)