扫雷开发
1.前期准备
1.1工具:VC,easyx
基础知识:循环,数组,函数
1.2雷区地图绘制
我们做一个9行9列的二维数组,0和-1代表有雷和无雷
这个数组最好是全局,这样很多函数都可以访问得到(注意全局数组自动初始化为0)
需要的头文件和宏定义如下:
#include<stdio.h>
#include<graphics.h>
#define ROW 9
#define COL 9
#define MINE_WID 40
int mine[ROW][COL];
1.3打印 printMap()函数
先做点简单的工作,写一个输出数组的函数,方便后面调用
void printMap()
{
for(int i=0;i<ROW;i++)
{
for(int j=0;j<COL;j++)
{
printf("%3d",mine[i][j]);
}
printf("\n");
}
}
main()调用一下
int main()
{
printMap();
return 0;
}
资源文件有数字标记,空白格子,红旗,地图,以及地雷,复制进入工程的当前目录
图片定义一个数组,作为全局变量,这个数组可被各个函数访问修改
IMAGE img[12];
1.4初始化
void gameInit()
{
loadimage(&img[0],"./0.png",MINE_WID,MINE_WID);
loadimage(&img[1],"./1.png",MINE_WID,MINE_WID);
loadimage(&img[2],"./2.png",MINE_WID,MINE_WID);
loadimage(&img[3],"./3.png",MINE_WID,MINE_WID);
}
void gameInit()
{
char temp[20]=" ";
for(int i=0;i<12;i++)
{
sprintf(temp,"%d.jpg",i);
loadimage(&img[i],temp,MINE_WID,MINE_WID);
}
}
资源加载完了,我们开始绘制图片(贴图)
void gameDraw()
{
for(int i=0;i<ROW;i++)
{
for(int j=0;j<COL;j++)
{
if(mine[i][j]==0)
{
putimage(i*MINE_WID,j*MINE_WID,&img[0]);
}
}
}
}
现在主函数调用一下
int main()
{
initgraph(MINE_WID*ROW,MINE_WID*COL,1);
gameInit();
gameDraw();
printMap();
system("pause");
return 0;
}
1.5开始布雷
雷的数量做一个宏定义,方便后期修改
#define MAX_MINE 9 //雷先少布置一点 方便分析
void gameInit()
{
int x,y;
for(int m=0;m<MAX_MINE;)
{
x=rand()%9;
y=rand()%9;
if(mine[x][y]==0)
{
mine[x][y]=-1;
m++;
}
}
char temp[20]=" ";
for(int i=0;i<12;i++)
{
sprintf(temp,"%d.jpg",i);
loadimage(&img[i],temp,MINE_WID,MINE_WID);
}
}
布雷之后运行看一下
多次运行,发现真随机的雷区发生变化,由于我们在gameDraw()绘图函数里面只定义了当数值为0,没有雷的时候绘制空白砖块,所以当数值为-1的时候,需要绘制地雷
1.6绘制地雷
void gameDraw()
{
for(int i=0;i<ROW;i++)
{
for(int j=0;j<COL;j++)
{
if(mine[i][j]==0)
{
putimage(i*MINE_WID,j*MINE_WID,&img[0]);
}
else if(mine[i][j]==-1)
{
putimage(i*MINE_WID,j*MINE_WID,&img[9]);
}
}
}
}
对比一下数据,行列是颠倒的,将绘图函数里面新增两个变量,对应起来
void gameDraw()
{
for(int i=0;i<ROW;i++)
{
for(int j=0;j<COL;j++)
{
int x=j*MINE_WID;
int y=i*MINE_WID;
if(mine[i][j]==0)
{
putimage(x,y,&img[0]);
}
else
{
putimage(x,y,&img[9]);
}
}
}
}
1.7布雷的数字
现在雷区有了,那么一个数字代表周围有多少雷,那么有一个雷,周围数字全部+1
注意,数字进行累加,但是不要把自己也是雷区的数字-1也给变化了,在gameInit()里面进行更改
void gameInit()
{
int x,y;
srand((unsigned int)time(0));
for(int m=0;m<MAX_MINE;)
{
x=rand()%9;
y=rand()%9;
if(mine[x][y]==0)
{
mine[x][y]=-1;
m++;
}
}
for(int a=0;a<ROW;a++)
{
for(int b=0;b<COL;b++)
{
if(mine[a][b]==-1)
{
for(int k=a-1;k<=a+1;k++)
{
for(int q=b-1;q<=b+1;q++)
{
if(mine[k][q]!=-1)
{
mine[k][q]++;
}
}
}
}
}
}
char temp[20]=" ";
for(int i=0;i<12;i++)
{
sprintf(temp,"%d.jpg",i);
loadimage(&img[i],temp,MINE_WID,MINE_WID);
}
}
尝试输出一下,程序可能会假死,可能不会,取决于编译器对数组上下限±1的严格程度。
问题出在哪里?思考一下!
1.8雷区在边界,-1,+1操作会越界,内存会溢出,访问不到!
小技巧:增加一个 ±1,作为辅助圈,在11*11格子里面的判断。
#include<stdio.h>
#include<graphics.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define MINE_WID 40
#define MAX_MINE 9
int mine[ROW+2][COL+2];
IMAGE img[12];
那么,0行,10行与0列,11列成为辅助圈
判断的数字增加的行列从a=1,到小于row+1(b=1到b<COL+1)
for(int a=1;a<ROW+1;a++)
{
for(int b=1;b<COL+1;b++)
{
if(mine[a][b]==-1)
{
for(int k=a-1;k<=a+1;k++)
{
for(int q=b-1;q<=b+1;q++)
{
if(mine[k][q]!=-1)
{
mine[k][q]++;
}
}
}
}
}
}
现在没有内存溢出的隐患了
我们先输出辅助区,printMap()里面的ROW、COL行列分别+2
void printMap()
{
for(int i=0;i<ROW+2;i++)
{
for(int j=0;j<COL+2;j++)
{
printf("%3d",mine[i][j]);
}
printf("\n");
}
}
但是辅助圈现在作为不越界访问帮忙计数,是不能布雷的
辅助圈可以有数字,但是不能有-1布雷,我们修改rand功能 从1到9布雷
x=rand()%9+1;
y=rand()%9+1;
void gameDraw()
{
for(int i=1;i<ROW+1;i++)
{
for(int j=1;j<COL+1;j++)
{
int x=(j-1)*MINE_WID;
int y=(i-1)*MINE_WID;
if(mine[i][j]==0)
{
putimage(x,y,&img[0]);
}
else if(mine[i][j]==-1)
{
putimage(x,y,&img[9]);
}
}
}
}
void printMap()
{
for(int i=0;i<ROW+2;i++)
{
for(int j=0;j<COL+2;j++)
{
printf("%3d",mine[i][j]);
}
printf("\n");
}
}
现在前后台就对应上了
1.9数字绘制
然后根据数组里面的数字,填充雷区标记数字,在drawMap()函数里面修改一下
void gameDraw()
{
for(int i=1;i<ROW+1;i++)
{
for(int j=1;j<COL+1;j++)
{
int x=(j-1)*MINE_WID;
int y=(i-1)*MINE_WID;
if(mine[i][j]>=0&&mine[i][j]<=8)
{
putimage(x,y,&img[mine[i][j]]);
}
else if(mine[i][j]==-1)
{
putimage(x,y,&img[9]);
}
}
}
}
运行调试多次,验证随机性,并观察数字与雷区是否对应正确
tips:这里我们更新一下,完整代码如下
#include<stdio.h>
#include<graphics.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define MINE_WID 40
#define MAX_MINE 9
int mine[ROW+2][COL+2];
IMAGE img[12];
void printMap()
{
for(int i=0;i<ROW+2;i++)
{
for(int j=0;j<COL+2;j++)
{
printf("%3d",mine[i][j]);
}
printf("\n");
}
}
void gameInit()
{
int x,y;
srand((unsigned int)time(0));
for(int m=0;m<MAX_MINE;)
{
x=rand()%9+1;
y=rand()%9+1;
if(mine[x][y]==0)
{
mine[x][y]=-1;
m++;
}
}
for(int a=1;a<ROW+1;a++)
{
for(int b=1;b<COL+1;b++)
{
if(mine[a][b]==-1)
{
for(int k=a-1;k<=a+1;k++)
{
for(int q=b-1;q<=b+1;q++)
{
if(mine[k][q]!=-1)
{
mine[k][q]++;
}
}
}
}
}
}
char temp[20]=" ";
for(int i=0;i<12;i++)
{
sprintf(temp,"%d.jpg",i);
loadimage(&img[i],temp,MINE_WID,MINE_WID);
}
}
void gameDraw()
{
for(int i=1;i<ROW+1;i++)
{
for(int j=1;j<COL+1;j++)
{
int x=(j-1)*MINE_WID;
int y=(i-1)*MINE_WID;
if(mine[i][j]>=0&&mine[i][j]<=8)
{
putimage(x,y,&img[mine[i][j]]);
}
else if(mine[i][j]==-1)
{
putimage(x,y,&img[9]);
}
}
}
}
int main()
{
initgraph(MINE_WID*ROW,MINE_WID*COL,1);
gameInit();
gameDraw();
printMap();
system("pause");
return 0;
}
2.游戏加密与交互操作
2.1加密
刚刚只是做好了数据系统,现在我们要做加密掩盖和交互,不然直接显示最终的结果了。
加密格子,把数组里面(数据区,不要包含辅助圈)每一个值+一个数,这里为20
for(int i=1;i<=ROW;i++)
{
for(int j=1;j<=COL;j++)
{
mine[i][j]+=20;
}
}
我们更新一下gameInit()函数,如果编译器对临时下标i,j检查严格,就不要在for循环里面反复int i=0,重复使用下标也可以。
void gameInit()
{
int x,y;
srand((unsigned int)time(0));
for(int m=0;m<MAX_MINE;)
{
x=rand()%9+1;
y=rand()%9+1;
if(mine[x][y]==0)
{
mine[x][y]=-1;
m++;
}
}
for(int a=1;a<ROW+1;a++)
{
for(int b=1;b<COL+1;b++)
{
if(mine[a][b]==-1)
{
for(int k=a-1;k<=a+1;k++)
{
for(int q=b-1;q<=b+1;q++)
{
if(mine[k][q]!=-1)
{
mine[k][q]++;
}
}
}
}
}
}
for(int i=1;i<=ROW;i++)
{
for(int j=1;j<=COL;j++)
{
mine[i][j]+=20;
}
}
char temp[20]=" ";
for(i=0;i<12;i++)
{
sprintf(temp,"%d.jpg",i);
loadimage(&img[i],temp,MINE_WID,MINE_WID);
}
}
加密之后,数据区+20,图没有贴了,未加密前数据最小是-1,最大是8
+20之后,最小是19,最大是28,增加分支判断,在这个区域覆盖掩盖图片(这个图片可以仍然为空白,为了掩饰效果,我们换一个不一样的图)
绘图函数更新
void gameDraw()
{
for(int i=1;i<ROW+1;i++)
{
for(int j=1;j<COL+1;j++)
{
int x=(j-1)*MINE_WID;
int y=(i-1)*MINE_WID;
if(mine[i][j]>=0&&mine[i][j]<=8)
{
putimage(x,y,&img[mine[i][j]]);
}
else if(mine[i][j]==-1)
{
putimage(x,y,&img[9]);
}
else if(mine[i][j]>=19&&mine[i][j]<=28)
{
putimage(x,y,&img[10]);
}
}
}
}
2.2鼠标操作
开头增加鼠标消息定义 MOUSEMSG msg; 任务:点击之后,加密后的数据解密(实现方法??) 点击之后,鼠标消息一般配合 无限循环反复贴图!
#include<stdio.h>
#include<graphics.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define MINE_WID 40
#define MAX_MINE 9
int mine[ROW+2][COL+2];
IMAGE img[12];
MOUSEMSG msg;
void gameMouse()
{
if(MouseHit())
{
msg=GetMouseMsg();
if(msg.uMsg==WM_LBUTTONDOWN)
{
mine[msg.y/MINE_WID+1][msg.x/MINE_WID+1]-=20;
}
else if(msg.uMsg==WM_RBUTTONDOWN)
{
}
}
}
现在我们在主函数调用试一下,要反复绘图,记得主函数中把点击操作与绘图操作放入死循环中
int main()
{
initgraph(MINE_WID*ROW,MINE_WID*COL,1);
gameInit();
while(1)
{
gameDraw();
gameMouse();
}
printMap();
system("pause");
return 0;
}
扫雷左键是做什么? 右键是做什么??
void gameMouse()
{
if(MouseHit())
{
msg=GetMouseMsg();
if(msg.uMsg==WM_LBUTTONDOWN)
{
mine[msg.y/MINE_WID+1][msg.x/MINE_WID+1]-=20;
}
else if(msg.uMsg==WM_RBUTTONDOWN)
{
if( mine[msg.y/MINE_WID+1][msg.x/MINE_WID+1]<30)
{
mine[msg.y/MINE_WID+1][msg.x/MINE_WID+1]+=20;
}
else
{
mine[msg.y/MINE_WID+1][msg.x/MINE_WID+1]-=20;
}
}
}
}
gameDraw()更新一下
void gameDraw()
{
for(int i=1;i<ROW+1;i++)
{
for(int j=1;j<COL+1;j++)
{
int x=(j-1)*MINE_WID;
int y=(i-1)*MINE_WID;
if(mine[i][j]>=0&&mine[i][j]<=8)
{
putimage(x,y,&img[mine[i][j]]);
}
else if(mine[i][j]==-1)
{
putimage(x,y,&img[9]);
}
else if(mine[i][j]>=19&&mine[i][j]<=28)
{
putimage(x,y,&img[10]);
}
else if(mine[i][j]>30)
{
putimage(x,y,&img[11]);
}
}
}
}
调试一下,验证红旗标记,数字标记,雷区标记,与红旗标记,取消红旗标记功能
2.3判断游戏结束与胜利
什么是输? 就是踩到雷了!踩雷之后,是点击事件发生,已经发生了数据解密,与-1进行比较
void gameWinorLose()
{
if(MouseHit())
{
msg=GetMouseMsg();
if(mine[msg.y/MINE_WID+1][msg.x/MINE_WID+1]==-1)
{
MessageBox(hwnd,"中雷了!","Game Over",MB_OK);
exit(0);
}
}
}
现在我们调用一下
int main()
{
initgraph(MINE_WID*ROW,MINE_WID*COL,1);
gameInit();
printMap();
while(1)
{
gameDraw();
gameMouse();
gameDraw();
gameWinorLose();
}
system("pause");
return 0;
}
当然游戏还有更完善的功能,比如点击空白区域,全部展开,我们需要更新一下代码,将鼠标坐标作为全局坐标方便传递参数,将鼠标消息的坐标赋值给openx,openy,代码会更简洁
#include<stdio.h>
#include<graphics.h>
#include<Windows.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define MINE_WID 40
#define MAX_MINE 9
int mine[ROW+2][COL+2];
int openx,openy;
IMAGE img[12];
MOUSEMSG msg;
HWND hwnd=NULL;
void gameMouse()
{
if(MouseHit())
{
msg=GetMouseMsg();
if(msg.uMsg==WM_LBUTTONDOWN)
{
openx=msg.y/MINE_WID+1;
openy=msg.x/MINE_WID+1;
mine[openx][openy]-=20;
}
else if(msg.uMsg==WM_RBUTTONDOWN)
{
if( mine[openx][openy]<30)
{
mine[openx][openy]+=20;
}
else
{
mine[openx][openy]-=20;
}
}
}
}
void gameWinorLose()
{
if(MouseHit())
{
msg=GetMouseMsg();
if(mine[openx][openy]==-1)
{
MessageBox(hwnd,"中雷了!","Game Over",MB_OK);
exit(0);
}
}
}
然后我们来设计打开空白全部展开的函数
void openNull(int x,int y)
{
if(mine[x][y]==0)
{
for (int i=x-1;i<=x+1;i++)
{
for(int j=y-1;j<=y+1;j++)
{
if(mine[i][j]==20)
{
mine[i][j]-=20;
openNull(i,j);
}
}
}
}
}
openNull()函数,在鼠标消息的左键点击判断里面调用,这样就一次展开所有空格,部分代码如下
if(msg.uMsg==WM_LBUTTONDOWN)
{
openx=msg.y/MINE_WID+1;
openy=msg.x/MINE_WID+1;
mine[openx][openy]-=20;
openNull(openx,openy);
}
3.结束完整代码(有兴趣的同学,可以加入胜利消息)
#include<stdio.h>
#include<graphics.h>
#include<Windows.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define MINE_WID 40
#define MAX_MINE 9
int mine[ROW+2][COL+2];
int openx,openy;
IMAGE img[12];
MOUSEMSG msg;
HWND hwnd=NULL;
void printMap()
{
for(int i=0;i<ROW+2;i++)
{
for(int j=0;j<COL+2;j++)
{
printf("%3d",mine[i][j]);
}
printf("\n");
}
}
void gameInit()
{
int x,y;
srand((unsigned int)time(0));
for(int m=0;m<MAX_MINE;)
{
x=rand()%9+1;
y=rand()%9+1;
if(mine[x][y]==0)
{
mine[x][y]=-1;
m++;
}
}
for(int a=1;a<ROW+1;a++)
{
for(int b=1;b<COL+1;b++)
{
if(mine[a][b]==-1)
{
for(int k=a-1;k<=a+1;k++)
{
for(int q=b-1;q<=b+1;q++)
{
if(mine[k][q]!=-1)
{
mine[k][q]++;
}
}
}
}
}
}
for(int i=1;i<=ROW;i++)
{
for(int j=1;j<=COL;j++)
{
mine[i][j]+=20;
}
}
char temp[20]=" ";
for(i=0;i<12;i++)
{
sprintf(temp,"%d.jpg",i);
loadimage(&img[i],temp,MINE_WID,MINE_WID);
}
}
void gameDraw()
{
for(int i=1;i<ROW+1;i++)
{
for(int j=1;j<COL+1;j++)
{
int x=(j-1)*MINE_WID;
int y=(i-1)*MINE_WID;
if(mine[i][j]>=0&&mine[i][j]<=8)
{
putimage(x,y,&img[mine[i][j]]);
}
else if(mine[i][j]==-1)
{
putimage(x,y,&img[9]);
}
else if(mine[i][j]>=19&&mine[i][j]<=28)
{
putimage(x,y,&img[10]);
}
else if(mine[i][j]>30)
{
putimage(x,y,&img[11]);
}
}
}
}
void openNull(int x,int y)
{
if(mine[x][y]==0)
{
for (int i=x-1;i<=x+1;i++)
{
for(int j=y-1;j<=y+1;j++)
{
if(mine[i][j]==20)
{
mine[i][j]-=20;
openNull(i,j);
}
}
}
}
}
void gameMouse()
{
if(MouseHit())
{
msg=GetMouseMsg();
if(msg.uMsg==WM_LBUTTONDOWN)
{
openx=msg.y/MINE_WID+1;
openy=msg.x/MINE_WID+1;
mine[openx][openy]-=20;
openNull(openx,openy);
}
else if(msg.uMsg==WM_RBUTTONDOWN)
{
if( mine[openx][openy]<30)
{
mine[openx][openy]+=20;
}
else
{
mine[openx][openy]-=20;
}
}
}
}
void gameWinorLose()
{
if(MouseHit())
{
msg=GetMouseMsg();
if(mine[openx][openy]==-1)
{
MessageBox(hwnd,"中雷了!","Game Over",MB_OK);
exit(0);
}
}
}
int main()
{
initgraph(MINE_WID*ROW,MINE_WID*COL,1);
gameInit();
printMap();
while(1)
{
gameDraw();
gameMouse();
gameDraw();
gameWinorLose();
}
system("pause");
return 0;
}
行减操作
mine[openx][openy]-=20;
openNull(openx,openy);
}
else if(msg.uMsg==WM_RBUTTONDOWN)
{
if( mine[openx][openy]<30)
{
mine[openx][openy]+=20;
}
else
{
mine[openx][openy]-=20;
}
}
}
}
void gameWinorLose()
{
if(MouseHit())
{
msg=GetMouseMsg();
if(mine[openx][openy]==-1)
{
MessageBox(hwnd,"中雷了!","Game Over",MB_OK);
exit(0);
}
}
}
int main()
{
initgraph(MINE_WID*ROW,MINE_WID*COL,1);
gameInit();
printMap();
while(1)
{
gameDraw();
gameMouse();
gameDraw();
gameWinorLose();
}
system("pause");
return 0;
}
|