您的位置:首页 > 其它

扫雷程序实现

2009-07-08 17:13 225 查看
最近用空闲时间写了一个扫雷程序,感觉有一些收获,利用中午时间写一下总结。







编写整个程序的过程如下:
逻辑处理。
这里我主要写了一个bomb类来实现这些东西。
类bomb的变量、函数定义如下:
class bomb
{
public:
bomb(void); ////////////构造函数
void InitmineZone(int *, int, int*, int); /////////////初始化雷区
void Sortmine(int*, int);/////////排列
bool isSame(int *, int, int);//////判断随机数异同
void outCome(int*,int*,int, int);///判断是否踩雷
void showNer(int*,int*,int,int);///显示周围的值
int flagNum( int*viewZone_,int len_,int point_ );//////求指定方块周围被标识旗帜的方块数量

void Init();////////////////初始化
void Destroy();
public:
int length;///////////// 雷区的长宽
int point;////////////用户指向的方格
int *mineZone;////////初始化生成的数据存放于此
int *viewZone;///////用来显示的信息
int *mine; ///// 雷的位置
int pointout;
int mineNum;////////雷的数量
int unPointNum;/////还剩多少雷
~bomb(void);
};

下面重点介绍几个重要的变量涵义:

mineZone是一个数组就是游戏开始的时候用来存储每一个小方块的信息
mine也是一个数组用来储存游戏的雷的位置
mineNum 就是雷的数量
程序开始的时候首先由用户输入mineNum,然后产生nimeNum个不相同的随机数,实现代码如下:
for(int i=0; i<mineNum; i++)
{
srand((unsigned)time(NULL)); //////////用时间做种子产生随机数
int temp= rand() % 80;
if( i > 0)
while(isSame(mine, mineNum, temp))
{ temp = rand() % 80;
}
mine[i] = temp;
mineZone[(mine[i])] = 9;
}
这段代码中srand((unsigned)time(NULL))使用时间做种子产生随机数,如果没有它的话,每次运行程序产生的随机数都相同。譬如说第一次打开程序产生的随机数是1,2,3,4,5.那么关了程序再产生的随机数也是1,2,3,4,5。
接下来就是每产生一个随机数如果跟前面的随机数相同的话就再产生一次,直到产生mineNum个不同的随机数。
程序接下来根据mine中的数产生mineZone中各个位置的数,mineZone中有0,1,2,3,4,5,6,7,8,9 这么多的状态,0就是周围没有雷,1就是周围有一个雷,以此类推,9就表示这个方格是雷。
Sortmine(mine, mineNum);将产生的随机数从小到大排列
InitmineZone(mineZone, length, mine, mineNum);完成雷区数据
主要看看InitmineZone(mineZone, length, mine, mineNum)的内容
我实现的方式是扫描mine里面的数,一个一个来,它周围的方格都加1。如果周围有是9不用加,其他的都加1。
当扫描完之后就可以得到mineZone中每一个位置的值了。



这是隐藏在表面的现象的mineZone,用来控制整个游戏的数组。那么还有一个用来给玩家看的数组。
这是我用int *viewZone; viewZone储存给用户看的信息。一开始都是-1
for(int b=0; b< length*length; b++)
{
viewZone[b] = -1;
}
-1对应显示的图片是



,当用户按下一个健,如果它正好对应9,那么就是雷了,游戏结束9对应的图片是



。如果按下的是1-8那么显示1-8对应的图片


















如果按下的是0,那么应该执行的操作就是显示0周围的数看下图



如果我们按下的健,那么这显示应该如:



,如果0周围有0那么应该就是在显示周围那个0的周围。



如果不管按下那个0都应该显示如:



这里我实现的方法是一个递归函数如果周围是0那么应该在显示0的周围,这里有一点需要注意到的就是应该提防两个0的互相递归



这里不论按下黑色的0还是红色或蓝色的0都应该要显示周围的数,如果按下黑色的0,那么不仅要显示黑色0的周围还要显示红色0的周围。而红色0不仅要显示自己的周围还要显示黑色0的周围,和蓝色0的周围,如果不加区别就会造成死循环,所以这里在黑色0的显示红色0之后我将其mineZone的值赋为-2,用来加以区别。
showNer(viewZone_ ,mineZone_ ,len_ ,point_);用来显示数,在showNer()里面会递归调用showNer();
if(viewZone_[point_] == -3)
return;

mineZone_[point_] = -2;
viewZone_[point_] = -2;//////////////////-2为已显示的模块

if(point_ == 0)
{ if( viewZone_[point_+1] != 9 && viewZone_[point_+1] != -3)
viewZone_[point_+1] = mineZone_[point_+1];
if( mineZone_[point_+1] == 0 )
showNer(viewZone_,mineZone_,len_, point_+1);
if( viewZone_[point_+len_] != 9 && viewZone_[point_+len_] !=-3)
viewZone_[point_+len_] = mineZone_[point_+len_];
if( mineZone_[point_+len_] == 0 )
showNer(viewZone_,mineZone_,len_, point_+len_);

if( viewZone_[point_+1+len_] != 9 &&viewZone_[point_+1+len_] !=-3)
viewZone_[point_+len_+1] = mineZone_[point_+len_+1];
if( mineZone_[point_+len_+1] == 0 )
showNer(viewZone_,mineZone_,len_, point_+len_+1);
}
到这里逻辑上的算法已经全面实现了。接下来就介绍界面的实现。

界面实现
界面说到底就是图片的处理。

首先是程序的建立,这里我是用mfc建的sdi单文档,修改菜单,工具条就不多说了。
游戏一开始就是给bomb里面的变量赋值了。
然后就画图



画图就是在OnDraw()里面画。具体就不多说了。画bitmap的方式大概就是如下:

CDC *pdc = GetDC();//////// 取得画笔
CDC memDC;//定义一个兼容DC
memDC.CreateCompatibleDC(pdc);//创建DC
bitmap[0].DeleteObject();
bitmap[0].LoadBitmap(IDB_BITMAP1);//装入DDB
CBitmap* pbmpOld=memDC.SelectObject(&bitmap[0]);//保存原有DDB,并选入新DDB入DC
pdc->BitBlt(x,y,x1,y1,&memDC,0,0,SRCCOPY);

需要注意的是bitmap[0]在loadBitmap的时候要下deleteObject(),不然程序会出错。

接下来讲鼠标的操作
首先鼠标动作的区域应该限制在雷区



,这里的计算是
p.x > 20*mine.length || p.y > 20*mine.length ,20是图片的变长



,如果鼠标的CPoint的x,y有一个大于边长乘以20,就不作用。

如果在雷区,鼠标的左键点下,那么它所在的区域显示的图片应该从



变为



。从程序如何实现计算x,y画图呢?
鼠标按下时会传会当前鼠标的位置用CPoint point来表示



如图假如鼠标点击的是很色的点,那么在画图的时候我们应该转换成白色的点来画图。
p.x = point.x - point.x%20;//////计算位图左边位
p.y = point.y - point.y%20;/////计算位图上边位
pdc->BitBlt(p.x,p.y,p.x+20,p.y+20,&memDC,0,0,SRCCOPY);将



画为



,表示按下。
如果在鼠标按下就应该随着鼠标的point不断计算x,y当x或y不用按下的时候那么在新位置画



,原来的位置就画为



if(point.x > 20*mine.length || point.y > 20*mine.length )
{
return;
}//////////////鼠标在雷区外
else
{

if(isLeftdown && (this->point.x-this->point.x%20 != point.x-point.x%20||this->point.y-this->point.y%20 != point.y-point.y%20))/////////////////左鼠标按下的拖动
{
CDC *pdc = GetDC();
CDC memDC;//定义一个兼容DC
memDC.CreateCompatibleDC(pdc);//创建DC
bitmap[0].DeleteObject();
bitmap[0].LoadBitmap(IDB_BITMAP1);//装入DDB
CBitmap* pbmpOld=memDC.SelectObject(&bitmap[0]);//保存原有DDB,并选入新DDB入DC
pdc->BitBlt(this->point.x-this->point.x%20,this->point.y-this->point.y%20,this->point.x-this->point.x%20+20,this->point.y-this->point.y%20+20,&memDC,0,0,SRCCOPY);
bitmap[1].DeleteObject();
bitmap[1].LoadBitmap(IDB_BITMAP2);
memDC.SelectObject(&bitmap[1]);
pdc->BitBlt(point.x-point.x%20,point.y-point.y%20,point.x-point.x%20+20,point.y-point.y%20+20,&memDC,0,0,SRCCOPY);
this->point = point;
}
}

当鼠标左健按下释放程序根据鼠标的位置计算对应mineZone的位置
这里我是这样写的
p.x=point.x –point.x%20; p.y=point.y-point.y%20;
int I = p.y/20 * length + p.x/20;
这样i就是方格对应与mineZone的位置,如果mineZone[i] 改好为9 那么不好意思游戏结束。如果是1-8就显示1-8的图片,0的画就按逻辑中的方式处理。不做多说。

鼠标邮件按下的话,就是标识为雷,图片显示为



,viewZone设为-3。同时未unpointNum减1



减1个了。

接下来讲左右健都按下的处理。这里有两个变量

bool isLeftdown;/////左键按下
bool isRightdown;////右键按下
用来标识左键和右键是否按下,如果左键按下 isLeftdow = 1, 释放左键就为0 。
如果 isLeftDown,和isRightdown都为1,那么表示两个健都按下,如果按在已经显示的数字上如



,那么它周围未显示的方格也就是viewZone为-1的方格应该都画为



,释放后如果刚好



周的方格



的数量刚好是3个,那么就判断显示了。
int flagNum( int*viewZone_,int len_,int point_ );//////求指定方块周围被标识旗帜的方块数量
我用这个来返回一个方格周围的-3的总数。

到这里程序已经基本有模有样了。在每次左键释放和右键释放判断一下是否已经达到胜利条件就可以了。

for(int i=0; i< mine.mineNum;i++)
{
if(mine.viewZone[mine.mine[i]] != -3)
return 0;
}
for(int i=0 ; i<mine.length*mine.length ;i++)
{
if(mine.viewZone[i]== -1)
return 0;
}
return 1;

还有一些零碎的东西如时间计算那些就不多讲了。

源代码已经上传到csdn,读者可以下载,这里因为编辑的问题导致图片无法显示,读者可以通过下载源代码,里面有更详细的说明。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: