扫雷游戏课程设计报告.docx
《扫雷游戏课程设计报告.docx》由会员分享,可在线阅读,更多相关《扫雷游戏课程设计报告.docx(23页珍藏版)》请在冰豆网上搜索。
![扫雷游戏课程设计报告.docx](https://file1.bdocx.com/fileroot1/2023-2/21/d9b24aa0-6fc4-49ff-b3a0-6178fbd09a6f/d9b24aa0-6fc4-49ff-b3a0-6178fbd09a6f1.gif)
扫雷游戏课程设计报告
(一)需求分析
题目:
32、实现一个N*M的扫雷游戏
设计要求:
能够实现一个N*M的扫雷游戏
a、能够打开一个方格(由于做的是静态显示,故在控制台上方格用‘—’代替),已打开的方格不能关闭
b、能够标记一个方格,标记方格的含义是对该方格有雷的预测(并不表示真的一定有雷)
c、能够给出游戏结果:
输、赢
d、N和M可由玩家自己设置
系统功能需求分析:
一个数字和一个雷(boom)。
你可以打开(open)一个方格,如果你打开的是一个boom,那么就失败;否则就会打开一个数字,该数字是位于[0,8]的一个整数,该数字表示其所有邻居方格所包含的雷数,应用该信息可以帮助你扫雷。
点击到了某区域发现其周围没有雷,那么显而易见应该点开周围的区域,拓展空白区域
(二)概要设计
由于知识储备不足,VC中的MFC应用程序又过于复杂,故退而求其次,不再采用动态显示和界面图形化,采用静态显示来实现扫雷游戏中的主要功能。
用键盘上的‘1’键代替鼠标左击,即打开一个方格查看其属性,已打开的方格不能在关闭;用键盘上的‘2’键代替鼠标右击,即标记一个方格,标记方格的含义是对该方格有雷的预测(并不表示真的一定有雷)
用键盘上的‘↑’‘↓’‘←’‘→’四个键来实现光标在控制台上的自由移动,
相当于用鼠标实现光标在图形界面的移动
游戏区域的高度与宽度和总雷数可由玩家自己设定
应题目要求设计了一个基类:
Base和一个继承类:
Game。
基类Base主要实现一些基本功能:
游戏结束时输出游戏的结果:
输赢;
返回控制台上光标的位置返回按下键时所对应的按键控制符
基类Base:
类名
成员类别
类型
成员名
描述
Base
方法
staticint
Output(constchar*)
在当前位置上输出一串字符
staticint
GotoXY(int,int);
取得控制台上光标的坐标位置并返回
staticint
GetKey();
等待按下键,并返回所对应的按键控制符
继承类Game是本程序的主要内容,也是实现扫雷游戏的关键部分。
主要实现的功能:
初始化图形界面,把游戏区域在控制台上显示出来;利用随机函数进行随机布雷,以保证玩家每次玩游戏时雷的分布位置均不同;得到一个坐标位置周围的雷数,并把数值返回;在一个坐标点上(x,y)点击,在该位置上显示其周围的雷数或拓展空白区域或失败;如果一个坐标点的周围没有雷,则拓展空白区域,并递归拓展;其中saolei()函数是类Game里的关键函数体,用来判断玩家按下了哪个键,并作出相应反应(上下左右四个方向的移动,打开一个方格,标记一个方格),并判断游戏的输与赢
继承类Game:
类名
成员类别
类型
成员名
描述
Game
属性
int
curX,curY
光标位置
int
poolWidth,poolHeight
游戏区域的高度与宽度
int
pool[GAME_MAX_HEIGHT+1][GAME_MAX_WIDTH+1]
用二维数组来存储雷的相关属性
conststaticint
GMARK_BOOM;
GMARK_EMPTY
GMARK_MARK;
雷的属性:
雷(*),空,标记(#)
方法
int
initpool(int,int,int)
初始布雷
Int
MoveCursor()
移动光标
int
huatu(int)
在控制台上把扫雷区域显示出来
int
tryopen(int,int)
在一个坐标点上(x,y)点击,在该位置上显示其周围的雷数或拓展空白区域或失败
int
shownum(int,int)
得到一个坐标位置周围的雷数,并把数值返回
int
tuozhan(int,int)
如果一个坐标点的周围没有雷,则拓展空白区域,并递归拓展
int
saolei()
扫雷并判断输赢
(三)详细设计
核心算法:
(1)布雷函数:
初始化时把数组里的值全部置为0,然后利用srand(),rand()随机机制产生随机数,分别对列和行取模,便产生了雷的随机位置。
但是布雷前,先要判断此随机位置是否已经布上了雷。
intGame:
:
initpool(intwidth,intheight,intnum)
{poolWidth=width;
poolHeight=height;if(num<0||num>=width*height||width>GAME_MAX_HEIGHT||width<=0||height<=0||height>GAME_MAX_HEIGHT)return1;
//初始是把游戏区域也即是数组里的值全都置为0
for(inty=0;y<=height+1;y++)
{for(intx=0;x<=width+1;x++)
{pool[y][x]=0;}
}
//利用伪随机函数进行随机布雷,以保证每次点击游戏时雷的分布位置不同
srand(time(NULL));
while(num!
=0){
intx=rand()%width+1;
inty=rand()%height+1;
if(pool[y][x]==0){
pool[y][x]=GMARK_BOOM;
num--;//num为设置的总雷数}
}
//初始化光标位置
curX=1;curY=1;
MoveCursor();
return0;
}
(2)打开方格,查看方格的属性
//在该位置上显示其周围的雷数或拓展空白区域或失败
intGame:
:
tryopen(intx,inty){
intm=0;
if(pool[y][x]&GMARK_BOOM)m=-1;
else{
intcount=shownum(x,y);
if(count==0)tuozhan(x,y);//拓展空白区域
elsepool[y][x]=count;}
returnm;}
(3)获得周围雷的数目
扫描其周围的所有相邻方格,记录雷数,并把值返回;扫描前要选择合理的起始点
intGame:
:
shownum(intx,inty){
intcount=0;
for(intY=-1;Y<=1;Y++)
for(intX=-1;X<=1;X++){
if(pool[y+Y][x+X]&GMARK_BOOM)
count++;}
returncount;}
(4)展拓空白区域
展拓原因:
当玩家点击的方块周围无雷时,此方块会被绘为空白,此时没有必要让玩家将其周围一一点开,应直接打开展拓条件:
周围雷数为零
函数inttuozhan(intx,inty)
参数:
x,y所点击的方块位置
算法:
递归
递归结束条件:
某一个方块不需要拓展就是因为其周围的雷数不是零
intGame:
:
tuozhan(intx,inty){
if((x>0&&x<=poolWidth)&&(y>0&&y<=poolHeight)&&(pool[y][x]==0)){intcount=shownum(x,y);
if(count==0){
pool[y][x]=GMARK_EMPTY;
for(intY=-1;Y<=1;Y++)
for(intX=-1;X<=1;X++)
{tuozhan(x+X,y+Y);}
}
elsepool[y][x]=count;}
return0;}
(5)在控制台上把扫雷区域显示出来
在还未按键时,调用huatu()函数把游戏界面在控制台上显示出来,即全部显示为方格。
以后每按键一次。
刷新一次界面
intGame:
:
huatu(intn=0)
{for(inty=1;y<=poolHeight;y++)
{Base:
:
GotoXY(1,y);
for(intx=1;x<=poolWidth;x++)
{if(pool[y][x]==0)
putchar('.');
elseif(pool[y][x]==GMARK_EMPTY)
putchar('');
elseif(pool[y][x]>0&&pool[y][x]<=8)
putchar('0'+pool[y][x]);
elseif(n==0&&(pool[y][x]&GMARK_MARK))
putchar('#');
elseif(pool[y][x]&GMARK_BOOM)
{if(n!
=0)putchar('*');
elseputchar('.');}
}
}
return0;}
(6)判断是否胜利
判断游戏是否胜利的条件在saolei()函数体内,在主函数里调用saolei()进行循环,直到戏赢了或输了才退出循环,并在控制台上输出结果;游戏赢了返回1,按下ESC键退出游戏返回-1,记为输,扫到雷也是返回-1
判断是否赢了游戏:
按下键‘1’后,对整个游戏区域进行检测,如果检测不到值为0的方格,即所有的雷已被扫出,则返回1,游戏胜利;按下ESC键退出游戏和扫到雷均为输,返回-1
if(Key==KEY_1)//判断是否赢了游戏
{inty=1;
for(;y<=poolHeight;++y)
{intx=1;
for(;x<=poolWidth;++x)
{if(pool[y][x]==0)break;
}
if(x<=poolWidth)break;}
if(!
(y<=poolHeight))
{m=1;
}
}
(四)调试分析和测试
(1)由玩家设置游戏区域的高度与宽度,并设置总雷数
按下回车键后界面如下:
按下键‘1’开始扫雷,扫雷过程部分界面如下:
…………
游戏输时显示的结果:
(2)直接按下ESC键时输掉游戏:
然后按下回车键后,直接按下ESC键,结果如下:
(3)为了使快速赢得游戏,减少雷的数目:
游戏运行过程部分界面如下:
按下键‘2’对怀疑有雷的区域进行标记
游戏胜利时显示的界面:
(五)源程序
附录源程序:
#include
#include
#include//包含伪随机数发生函数
#include//包含返回一个字符在控制台屏幕上坐标的文件
#include//包含getch()库函数:
从控制台读取一个字符,但不显示在屏幕上
//#define定义的一些宏变量
//键盘上上、下、左、右四个键和一些按键对应的ASCII编码
#defineKEY_UP0xE048
#defineKEY_DOWN0xE050
#defineKEY_LEFT0xE04B
#defineKEY_RIGHT0xE04D
#defineKEY_ESC0x001B
#defineKEY_1'1'
#defineKEY_2'2'
//设置游戏区域的最大长度与高度
#defineGAME_MAX_WIDTH100
#defineGAME_MAX_HEIGHT100
//游戏初始时在控制台上输出的游戏提示
#defineGAMETITLE"ArrowKey:
MoveCursorKEY1:
OpenKey2:
Mark"
//当游戏结束时输出的字符
#defineGAMEWIN"Congratulations!
YouWin!
Thankyouforplaying!
\n"
#defineGAMEOVER"Sorry,youlose!
thankyouforplaying!
\n"
#defineGAMEEND"PressESCtoexit\n"
//基类
//Baseclass
classBase{
public:
staticintOutput(constchar*);
staticintGotoXY(int,int);
staticintGetKey();
};
intBase:
:
GetKey()//等待按下键,并返回所对应的按键控制符
{intnkey=getch(),nk=0;
if(nkey>=128||nkey==0)nk=getch();//等待你按下任意键后,把该键字符所对应的ASCII符付给nk后,在执行下面语句
returnnk>0?
nkey*256+nk:
nkey;
}
intBase:
:
GotoXY(intx,inty)//取得控制台上光标的坐标位置并返回
{
COORDcd;
cd.X=x;cd.Y=y;
returnSetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),cd);
}
intBase:
:
Output(constchar*pstr)//在当前位置上输出一串字符
{
for(;*pstr;pstr++)putchar(*pstr);//函数向标准输出设备输出一个变量的字符形式
return0;
}
//继承
classGame:
publicBase
{
private:
intcurX,curY;
intpoolWidth,poolHeight;//雷区间的总高度与宽度,相当与height
intpool[GAME_MAX_HEIGHT+2][GAME_MAX_WIDTH+2];
public:
Game():
curX(0),curY(0){poolWidth=poolHeight=0;}
intinitpool(int,int,int);//初始化游戏区域,设置游戏区域的高度与宽度,并设置总雷数
intMoveCursor(){returnBase:
:
GotoXY(curX,curY);}
inthuatu(int);//在控制台上把扫雷区域显示出来
intshownum(int,int);//得到一个坐标位置周围的雷数,并把数值返回
inttryopen(int,int);//在一个坐标点上(x,y)点击,在该位置上显示其周围的雷数或拓展空白区域或失败
intsaolei();
private:
inttuozhan(int,int);//如果一个坐标点的周围没有雷,则拓展空白区域,并递归拓展
private:
conststaticintGMARK_BOOM;
conststaticintGMARK_EMPTY;
conststaticintGMARK_MARK;
};
constintGame:
:
GMARK_BOOM=0x10;//*所对应的ASCII编码,用*表示地雷
constintGame:
:
GMARK_EMPTY=0x100;//空白所对应的ASCII编码
constintGame:
:
GMARK_MARK=0x200;//#所对应的ASCII编码,用#表示作标记
intGame:
:
initpool(intwidth,intheight,intnum)
{poolWidth=width;
poolHeight=height;if(num<0||num>=width*height||width>GAME_MAX_HEIGHT||width<=0||height<=0||height>GAME_MAX_HEIGHT)return1;
//初始是把游戏区域也即是数组里的值全都置为0
for(inty=0;y<=height+1;y++)
{
for(intx=0;x<=width+1;x++)
{
pool[y][x]=0;
}
}
//利用伪随机函数进行随机布雷,以保证每次点击游戏时雷的分布位置不同
srand(time(NULL));
while(num!
=0){
intx=rand()%width+1;
inty=rand()%height+1;
if(pool[y][x]==0){
pool[y][x]=GMARK_BOOM;
num--;//num为设置的总雷数}
}
//初始化光标位置
curX=1;curY=1;
MoveCursor();
return0;
}
//在控制台上把扫雷区域显示出来
intGame:
:
huatu(intn=0)
{
for(inty=1;y<=poolHeight;y++)
{
Base:
:
GotoXY(1,y);
for(intx=1;x<=poolWidth;x++)
{
if(pool[y][x]==0)
putchar('.');
elseif(pool[y][x]==GMARK_EMPTY)
putchar('');
elseif(pool[y][x]>0&&pool[y][x]<=8)
putchar('0'+pool[y][x]);
elseif(n==0&&(pool[y][x]&GMARK_MARK))
putchar('#');
elseif(pool[y][x]&GMARK_BOOM)
{
if(n!
=0)putchar('*');
else
putchar('.');
}
}
}
return0;
}
//得到一个坐标位置周围的雷数,并把数值返回
intGame:
:
shownum(intx,inty){
intcount=0;
for(intY=-1;Y<=1;Y++)
for(intX=-1;X<=1;X++){
if(pool[y+Y][x+X]&GMARK_BOOM)
count++;}
returncount;
}
//在一个坐标点上(x,y)点击,在该位置上显示其周围的雷数或拓展空白区域或失败
intGame:
:
tryopen(intx,inty){
intm=0;
if(pool[y][x]&GMARK_BOOM)m=-1;
else{
intcount=shownum(x,y);
if(count==0)tuozhan(x,y);//拓展空白区域
elsepool[y][x]=count;
}
returnm;
}
//如果一个坐标点的周围没有雷,则拓展空白区域,并递归拓展
intGame:
:
tuozhan(intx,inty){
if((x>0&&x<=poolWidth)&&(y>0&&y<=poolHeight)&&(pool[y][x]==0)){
intcount=shownum(x,y);
if(count==0){
pool[y][x]=GMARK_EMPTY;
for(intY=-1;Y<=1;Y++)
for(intX=-1;X<=1;X++)
{tuozhan(x+X,y+Y);}
}
elsepool[y][x]=count;
}
return0;
}
intGame:
:
saolei()//游戏赢了返回1,按下ESC键退出游戏返回-1,记为输,扫到雷也是返回-1
{
intKey=Base:
:
GetKey();
intm=0,nArrow=0;
switch(Key)
{
caseKEY_UP:
{
if(curY>1)curY--;
nArrow=1;
}break;
caseKEY_DOWN:
{if(curYnArrow=1;
}break;
caseKEY_LEFT:
{if(curX>1)curX--;
nArrow=1;
}break;
caseKEY_RIGHT:
{
if(curXnArrow=1;
}break;
caseKEY_1:
{
m=tryopen(curX,curY);
}break;
caseKEY_2:
{
if((pool[curY][curX]
&~(GMARK_MARK|GMARK_BOOM))==0)
{
pool[curY][curX]^=GMARK_MARK;
}
}break;
caseKEY_ESC:
{
m=-1;
}break;
}
if(Key==KEY_1)//判断是否赢了游戏
{
inty=1;
for(;y<=poolHeight;++y)
{
intx=1;
for(;x<=poolWidth;++x)
{
if(pool[y][x]==0)break;
}
if(x<=poolWidth)break;
}
if(!
(y<=poolHeight))
{
m=1;
}
}
if(nArrow==0)
{
huatu();////如果按下了key_1,key_2键,则显示扫雷结果
}
MoveCursor();
returnm;
}
voidmain(){
intx,y,b;//定义扫雷区域的高与宽,并设置游戏中得总雷数
cout<<"宽度:
";
cin>>x;