简介
项目名称:C++/C语言编程扫雷游戏
开发环境:devc++
优化级别:自动
C++标准:ISOC++11
外部库:EGE
测试
源码
自己在之前放假时写的,码风和现在差别有一点大。
mine sweeping.cpp
#include "subsidiary mine sweeping.h"
int main()
{
start();//初始化EGE窗口
HideWindow();//隐藏控制台
game_main();//程序主体
}
subsidiary mine sweeping.h
#include <iostream>
#include <pthread.h>
#include <time.h>
#include <stdio.h>
#include "graphics.h"
using namespace std;
#define WIDTH 10
//EGE窗口的宽
#define HIGH 10
//EGE窗口的高
#define COUNT 10
//总雷数
#define FROMTO 25
//一个格子的尺寸
pthread_t di;
//进程ID型变量(全局变量)
int mine[WIDTH+2][HIGH+2],show[WIDTH+2][HIGH+2],count;
//雷区数组,点击(展示)数组
time_t tim;
//计时开始的时间
PIMAGE png[20],number[11];
//图片素材数组,数字图片素材数组
long long win_r=-1;
//最佳纪录
bool forkon=false;
//绘画进程是否须要存在的标志
/*
void Music(char* name);
//播放音乐
void HideWindow();
//隐藏控制台
void start();
//初始化EGE窗口
int ChangeSize(int width,int high,PIMAGE& png);
//改变图片大小
int GetPicture(PIMAGE& png,char* name);
//读取图片并保存在图片指针png内
int GetPicture_number(PIMAGE png[]);
//读取数字的图片
int GetPictures(PIMAGE png[]);
//依次读取图片并保存在相应的图片指针里
int get_digit_capacity(int ch);
//计算一个数的位数
void draw_number(int id,PIMAGE numbe[],int args,int x,int y,bool greenon);
//画数字
time_t time_start();
//获取当前时间
int time_count(time_t s);
//计算从一个时间到现在过了几秒
void time_draw(time_t s,int x,int y);
//画时间
void draw(int show[WIDTH+2][HIGH+2],PIMAGE png[],time_t tim);
//画图函数(游戏中)
void *cmd(void* args);
//进程函数(指针)(不断画图)
pthread_t make_fork();
//制造进程函数
bool get_mouse(int *x,int *y,int show[WIDTH+2][HIGH+2],bool right);
//获取鼠标坐标函数(show数组坐标姓形式)
void win_draw(int show[WIDTH+2][HIGH+2],int mine[WIDTH+2][HIGH+2],PIMAGE png[]);
//游戏结束时的画图
bool win(int show[WIDTH+2][HIGH+2],int mine[WIDTH+2][HIGH+2]);
//判断输赢
void New(int a[WIDTH+2][HIGH+2]);
//初始化数组
void fix_up(int a[HIGH+2][WIDTH+2],int xx,int yy);
//布置雷
int get_count(int show[WIDTH+2][HIGH+2]);
//获取空格子数
int get_mine_count(int mine[WIDTH+2][HIGH+2],int x,int y);
//获取一个位置附近有几颗雷
int find_mine(int mine[WIDTH+2][HIGH+2],int show[WIDTH+2][HIGH+2],int x,int y);
//当点击一个格子时的具体运算
int cpu(int mine[WIDTH+2][HIGH+2],int show[WIDTH+2][HIGH+2],int x,int y,int count);
//进行一次完整的运算
int get_mines(int show[WIDTH+2][HIGH+2],int mine[WIDTH+2][HIGH+2]);
//获取剩余雷数
int help_first(int show[WIDTH+2][HIGH+2],int mine[WIDTH+2][HIGH+2]);
//如果剩下的空位都是雷 则自动插旗
int help_second(int show[WIDTH+2][HIGH+2],int mine[WIDTH+2][HIGH+2],int count);
//如果所有雷都被排完了,则自动点开剩下的空格子
int help(int show[WIDTH+2][HIGH+2],int mine[WIDTH+2][HIGH+2],int count);
//系统自动操作函数
int game_play(PIMAGE png[]);
//游戏主体函数
void game_choose_draw(PIMAGE png[]);
//游戏菜单页面绘画
int game_choose(PIMAGE png[]);
//游戏菜单函数
int game_main();
//整个游戏函数
*/
#include "subsidiary mine sweeping.cpp"
subsidiary mine sweeping.cpp
void Music(char* name)//文件绝对路径
{
//播放音乐
PlaySound(name,NULL,SND_ASYNC);
return ;
}
void HideWindow()
{
//隐藏控制台
ShowWindow(GetConsoleWindow(),SW_HIDE);
}
void start()
{
initgraph(WIDTH*FROMTO,(HIGH+1)*FROMTO,INIT_RENDERMANUAL);//初始化窗口(x,y),并设置为手动渲染模式
//即须有delay_fps()之类的函数才会输出缓冲帧
setbkcolor(EGERGB(255,255,255));//设置背景RGB颜色为255,255,255,即白色
setcaption("扫雷");//设置窗口标题
return ;
}
int ChangeSize(int width,int high,PIMAGE& png)//改变图片大小
{
if(png==NULL)
return -1;
else if(width==getwidth(png)&&getheight(png))
return 0;
//判断图片指针是否为空和是否需要改变大小
PIMAGE png2=newimage(width,high);//定义一个规定大小的空图片指针
putimage(png2,0,0,width,high,png,0,0,getwidth(png),getheight(png));
//将图片输出到空指针内,并且改变大小
delimage(png);
//销毁原指针的图片数据
png=png2;
//用原指针指向修改大小后的指针数据
return 0;
}
int GetPicture(PIMAGE& png,char* name)//图片指针,文件绝对路径
{
//读取图片并保存在图片指针png内
png=newimage();//申请内存
int eorry=getimage(png,name);
//读取图片,获取其状态 :成功、失败
if(eorry!=0)return -1;
//判断图片是否获取成功
return 0;
}
int GetPicture_number(PIMAGE png[])
{
//读取数字的图片
char s[100];
int id[10],t=0;
for(int i=0;i<=9;i++)
{
//读取1-9的数字的图片
sprintf(s,"../pictures/number%d.png",i);
id[i]=GetPicture(png[i],s);
ChangeSize(FROMTO,FROMTO,png[i]);
}
sprintf(s,"../pictures/green.png");
id[10]=GetPicture(png[10],s);
ChangeSize(FROMTO*WIDTH,FROMTO,png[10]);
//读取数字背景图
for(int i=0;i<=10;i++)
{
//判断读取是否成功
if(id[i]!=0)
{
t=-1;
//如果不成功
//弹出弹窗
sprintf(s,"eorry picture(number%d)",i);
MessageBox(NULL,s,"",MB_OK);
}
}
return t;
}
int GetPictures(PIMAGE png[])//获取一个图片指针数组的地址
{
char s[100];
int id[100],t=0;
for(int i=0;i<=13;i++)id[i]=0;
for(int i=1;i<=8;i++)
{
//依次读取图片并保存在相应的图片指针里
sprintf(s,"../pictures/%d.jpg",i);//组合出图片路径
id[i]=GetPicture(png[i],s);//读取数字图片
ChangeSize(FROMTO,FROMTO,png[i]);//改变图片大小
}
sprintf(s,"../pictures/Empty lattice.jpg");//组合出图片路径
id[9]=GetPicture(png[9],s);//读取空格子图片
ChangeSize(FROMTO,FROMTO,png[9]);//改变图片大小
sprintf(s,"../pictures/flag.jpg");
id[10]=GetPicture(png[10],s);//读取已插旗格子图片
ChangeSize(FROMTO,FROMTO,png[10]);//改变图片大小
sprintf(s,"../pictures/empty.jpg");//组合出图片路径
id[0]=GetPicture(png[0],s);//读取点击后空格子图片
ChangeSize(FROMTO,FROMTO,png[0]);//改变图片大小
sprintf(s,"../pictures/mine.jpg");//组合出图片路径
id[11]=GetPicture(png[11],s);//读取游戏结束时已插旗有雷格图片
ChangeSize(FROMTO,FROMTO,png[11]);//改变图片大小
sprintf(s,"../pictures/Exploding mine.jpg");//组合出图片路径
id[12]=GetPicture(png[12],s);//读取游戏结束时未插旗有雷格图片
ChangeSize(FROMTO,FROMTO,png[12]);//改变图片大小
sprintf(s,"../pictures/Menu image.png");//组合出图片路径
id[13]=GetPicture(png[13],s);//读取开始的菜单图片
ChangeSize(FROMTO*WIDTH,FROMTO*HIGH,png[13]);//改变图片大小
sprintf(s,"../pictures/Best Record.png");//组合出图片路径
id[14]=GetPicture(png[14],s);//读取开始的菜单图片
ChangeSize(FROMTO*WIDTH,FROMTO*1,png[14]);//改变图片大小
for(int i=0;i<=13;i++)
{
if(id[i]!=0)sprintf(s,"eorry picture %d",i),MessageBox(NULL,s,"",MB_OK),t=-1;
}
return t;
}
int get_digit_capacity(int ch)
{
//计算一个数的位数
if(ch==0)return 0;//如果传入的数为零,即此次递归是最后一次递归
else return 1+get_digit_capacity(ch/10);//否则,ch除以10,继续递归调用
}
void draw_number(int id,PIMAGE numbe[],int args,int x,int y,bool greenon)
{
//画数字
int ch;
//如果须要画背景图则画
if(greenon==true)putimage(x,y,numbe[10]);
for(int i=get_digit_capacity(id)-1;i>=0;i--)
{
//对ch进行拆数字,并输出相应的数字图片
//从最后画起,因为是从个位开始拆的
ch=id%10;
id/=10;
putimage(x+i*FROMTO,y,numbe[ch]);
}
return ;
}
time_t time_start()
{
//获取当前时间
return time(NULL);
}
int time_count(time_t s)
{
//计算从一个时间到现在过了几秒
if(difftime(time(NULL),s)>=100000000||difftime(time(NULL),s)<0)
{
//如果过去的时间大于100000000
//则重新开始计时
tim=time_start();
//并重新获取过去了的时间
}
return difftime(time(NULL),s);
}
void time_draw(time_t s,int x,int y)
{
//画时间
int ch=time_count(s);
//获取过去的时间
draw_number(ch,number,get_digit_capacity(ch),0,0,true);
//利用画数字函数画出时间
return ;
}
void draw(int show[WIDTH+2][HIGH+2],PIMAGE png[],time_t tim)
{
//画图函数(游戏中)
cleardevice();//清屏
time_draw(tim,0,0);
for(int o=1;o<=WIDTH;o++)
{
for(int i=1;i<=HIGH;i++)
{
//遍历展示(即show)标记数组
switch(show[i][o])
{
//判断各种格子情况,相应输出图片
case 0:putimage((o-1)*FROMTO,i*FROMTO,png[0]);break;
case -1:putimage((o-1)*FROMTO,i*FROMTO,png[9]);break;
case -2:putimage((o-1)*FROMTO,i*FROMTO,png[10]);break;
case 1:putimage((o-1)*FROMTO,i*FROMTO,png[1]);break;
case 2:putimage((o-1)*FROMTO,i*FROMTO,png[2]);break;
case 3:putimage((o-1)*FROMTO,i*FROMTO,png[3]);break;
case 4:putimage((o-1)*FROMTO,i*FROMTO,png[4]);break;
case 5:putimage((o-1)*FROMTO,i*FROMTO,png[5]);break;
case 6:putimage((o-1)*FROMTO,i*FROMTO,png[6]);break;
case 7:putimage((o-1)*FROMTO,i*FROMTO,png[7]);break;
case 8:putimage((o-1)*FROMTO,i*FROMTO,png[8]);break;
}
}
}
return ;
}
void *cmd(void* args)
{
//进程函数(指针)(不断画图)
for(;forkon==true;delay_fps(60))
{
//如果需要结束该进程则退出
draw(show,png,tim);
}
//结束进程
pthread_exit(NULL);
}
pthread_t make_fork()
{
//制造进程函数
pthread_t di;
//进程ID型指针
forkon=true;
//将进程指针设为需要保留
int ch=pthread_create(&di,NULL,cmd,NULL);
//创造进程,获取返回值
if(ch!=0)MessageBox(NULL,"eorry","",MB_OK);
//如果返回值不正常,则弹出错误的窗口
return di;
//返回进程ID
}
bool get_mouse(int *x,int *y,int show[WIDTH+2][HIGH+2],bool right)
{
//获取鼠标坐标函数
char music[]="../music/Click on sound effects.wav";
mouse_msg mouse;//鼠标状态变量
bool t,ch=true;
*x=-1;
*y=-1;
//标记*x、*y指针的数值
//避免上一次的值被误以为已获取
while(*x<=0&&*x>WIDTH&&*y<=0&&*y>HIGH||show[*x][*y]==0||ch==true)
{
//判断获取的坐标所在的格子是否为空
//判断坐标是否有效
while(mousemsg())
{
//当有鼠标信息时
mouse=getmouse();
//获取鼠标信息
if((mouse.is_left()&&mouse.is_up()))
{
//当鼠标左键点击
Music(music);
//播放点击的音频
t=true;//标记是左键点击
ch=false;//标记点击过
if(right==true)
{
//如果设置为游戏中
//计算出对应的格子坐标
if(mouse.y%FROMTO==0)*x=mouse.y/FROMTO-1;
else *x=mouse.y/FROMTO;
if(mouse.x%FROMTO==0)*y=mouse.x/FROMTO;
else *y=mouse.x/FROMTO+1;
}
else
{
//否则则是在菜单页面
//直接返回坐标
*x=mouse.y;
*y=mouse.x;
return t;//返回鼠标点击状态
}
}
else if(mouse.is_right()&&mouse.is_up())
{
Music(music);
//播放点击音频
t=false;//标记是右键点击
ch=false;//标记点击过了
if(right==true)
{
//如果设置为游戏中
//计算出对应的格子坐标
if(mouse.y%FROMTO==0)*x=mouse.y/FROMTO-1;
else *x=mouse.y/FROMTO;
if(mouse.x%FROMTO==0)*y=mouse.x/FROMTO;
else *y=mouse.x/FROMTO+1;
}
else
{
//否则则是在菜单页面
//直接返回坐标
*x=mouse.y;
*y=mouse.x;
return t;//返回鼠标点击状态
}
}
}
}
return t;//返回鼠标点击状态
}
void win_draw(int show[WIDTH+2][HIGH+2],int mine[WIDTH+2][HIGH+2],PIMAGE png[])
{
//游戏结束时的画图
draw(show,png,tim);
//先正常画图
for(int o=1;o<=WIDTH;o++)
{
for(int i=1;i<=HIGH;i++)
{
//遍历雷区标记数组
if(mine[i][o]==1)
{
//如果该位置有雷
if(show[i][o]==-2)
{
//如果已插旗
putimage((o-1)*FROMTO,i*FROMTO,png[11]);
//画插旗雷
}
else
{
//如果未插旗
putimage((o-1)*FROMTO,i*FROMTO,png[12]);
//画红雷(爆炸雷)
}
}
}
}
delay_fps(60);
return ;
}
bool win(int show[WIDTH+2][HIGH+2],int mine[WIDTH+2][HIGH+2])
{
//判断输赢
bool t=true;//一开始标记为赢
cleardevice();//清屏
for(int o=1;o<=WIDTH;o++)
{
for(int i=1;i<=HIGH;i++)
{
//遍历雷区标记数组
if(mine[i][o]==1&&show[i][o]!=-2)
{
//如果该位置有雷
//如果未插旗
t=false;//标记为失败
break;
}
}
}
return t;
//返回输赢状态
}
void New(int a[WIDTH+2][HIGH+2])
{
//初始化数组
for(int i=0;i<HIGH+2;i++)
{
for(int o=0;o<WIDTH+2;o++)
{
//遍历数组
//便初始化为-1
a[i][o]=-1;
}
}
return ;
}
void fix_up(int a[HIGH+2][WIDTH+2],int xx,int yy)
{
//布置雷
int x,y;
srand(time(0));
//随机数种子
for(int i=0;i<COUNT;i++)
{
//布置COUNT次
do
{
x=rand()%WIDTH;
y=rand()%HIGH;
//随机抽取一个位置
}while(x>WIDTH||x<1||y>HIGH||y<1||a[x][y]!=-1||x==xx||y==yy);
//如果方位合法
//该位置为空
//与该位置不是第一次被玩家点击的位置
a[x][y]=1;
//则标记该位置为1
}
return ;
}
int get_count(int show[WIDTH+2][HIGH+2])
{
//获取空格子数
int count=0;
for(int i=1;i<=HIGH;i++)
{
for(int o=1;o<=WIDTH;o++)
{
//遍历标记数组
if(show[i][o]==-1)count++;
}
}
return count;
//返回雷数
}
int get_mine_count(int mine[WIDTH+2][HIGH+2],int x,int y)
{
//获取一个位置附近有几颗雷
int count=0;
for(int i=-1;i<=1;i++)
{
for(int o=-1;o<=1;o++)
{
//遍历八个方位
if(mine[i+x][o+y]==1)count++;
//如果标记数组是1
//则将雷数加1
}
}
//返回雷数
return count;
}
int find_mine(int mine[WIDTH+2][HIGH+2],int show[WIDTH+2][HIGH+2],int x,int y)
{
//当点击一个格子时的具体运算
if(x==0||y==0||x==WIDTH+1||y==HIGH+1||show[x][y]!=-1)return 0;
//如果该格子是边界格子(为系统展开到了这里)
//或改格子已经被点击(展开)并标记
//直接返回
if(mine[x][y]==1) return -1;
//如果该格子是雷
//返回-1
int shu=get_mine_count(mine,x,y);
//否则
//获取该格子附近的雷数
if(shu)
{
//如果该格子附近有雷
show[x][y]=shu;
//则标记该格子为雷数
}
else
{
//否则标记该格子雷数为0
show[x][y]=0;
for(int i=-1;i<=1;i++)
{
for(int o=-1;o<=1;o++)
{
//展开到该格子的上下左右四个格子
if(i!=0&&o!=0||i==0&&o==0)continue;
//如果是斜边的格子,跳过
//递归调用自己
find_mine(mine,show,x+i,y+o);
}
}
}
return 0;
//正常返回
}
int cpu(int mine[WIDTH+2][HIGH+2],int show[WIDTH+2][HIGH+2],int x,int y,int count)
{
//进行一次完整的运算
char s[]="../music/Clearing mines.wav";
bool mouse=get_mouse(&x,&y,show,true);
//获取被点击的数组格子
if(mouse==false)
{
//如果是右键点击
if(show[x][y]==-1&&count>0)
{
//如果点击的格子为空
//且旗帜数不为零
Music(s);
//播放插旗音效
if(mine[x][y]!=1)
{
//如果插旗错误
count=-1;
//将旗帜数标记为-1
return -1;
//返回-1
}
//将标记数组标记为-2(即插了旗)
show[x][y]=-2;
//旗帜数减1
count--;
}
}
else if(mouse==true)
{
//如果是左键点击
//调用运算函数对该格子进行标记
if(find_mine(mine,show,x,y)==-1)
{
//如果返回值为-1(即玩家点击到了雷)
count=-1;
//将雷数标记为-1
return -1;
//返回-1
}
}
return count;
//正常返回
//返回旗帜数
}
int get_mines(int show[WIDTH+2][HIGH+2],int mine[WIDTH+2][HIGH+2])
{
//获取剩余雷数
int ch=0;
//剩余雷数变量
for(int i=1;i<=HIGH;i++)
{
for(int o=1;o<=WIDTH;o++)
{
//遍历雷区数组
if(mine[i][o]==1)
{
//如果该位置是雷,且没排了,将剩余雷数加一
if(show[i][o]==-1)ch++;
}
}
}
return ch;
//返回雷数
}
int help_first(int show[WIDTH+2][HIGH+2],int mine[WIDTH+2][HIGH+2])
{
//如果剩下的空位都是雷
//则自动插旗
if(get_mines(show,mine)==get_count(show))
{
//如果剩下的空位都是雷
for(int i=1;i<=HIGH;i++)
{
for(int o=1;o<=WIDTH;o++)
{
//遍历show数组
if(show[i][o]==-1)
{
//如果该位置为空位
show[i][o]=-2;
//将其标记为插旗雷
}
}
}
return 0;
//返回剩下的空位都是雷
}
//返回剩下的空位不都是雷
return 1;
}
int help_second(int show[WIDTH+2][HIGH+2],int mine[WIDTH+2][HIGH+2],int count)
{
//如果所有雷都被排完了,则自动点开剩下的空格子
if(get_mines(show,mine)==0&&get_count(show)!=0)
{
//如果所有雷都被排完了
//且有空格子(否则会增加计算量)
//那么自动点完所有空格子
for(int i=1;i<=HIGH;i++)
{
for(int o=1;o<=WIDTH;o++)
{
//如果该格子是空格子
if(show[i][o]==-1)count=find_mine(mine,show,i,o);
//则系统调用点击格子函数自动点击格子
}
}
}
return count;
}
int help(int show[WIDTH+2][HIGH+2],int mine[WIDTH+2][HIGH+2],int count)
{
//系统自动操作函数
help_first(show,mine);
//如果剩下的空位都是雷 则自动插旗
count=help_second(show,mine,count);
//如果所有雷都被排完了,则自动点开剩下的空格子
return count;
//返回剩余雷数
}
int game_play(PIMAGE png[])
{
count=COUNT;
tim=time_start();
//游戏主体函数
char s[]="../music/defeat.wav",w[]="../music/victory.wav";
int x,y,ch;
long long r;
bool is=true;
New(mine);//初始化雷区数组
New(show);//初始化展示数组
di=make_fork();
get_mouse(&x,&y,show,true);//获取第一次点击
//此时布雷,以防玩家第一次点击就点到雷
fix_up(mine,x,y);
//调用运算函数标记数组
find_mine(mine,show,x,y);
do
{
//调用完整运算函数(从点击到标记)
count=cpu(mine,show,x,y,count);
if(count==-1)
{
//如果点击到了雷或插旗错误
is=false;
//标记跳出循环
}
//如果剩下的旗帜都是雷
//则系统直接帮助玩家插
count=help(show,mine,count);
//如果所有空格子都被点完了
//且所有雷都被旗帜标记了
//跳出循环
if(get_count(show)==0)break;
}while(is_run()&&is==true);
//判断输赢
r=time_count(tim);
is=win(show,mine);
//手动渲染
if(is==false)
{
//如果标记为失败
Music(s);
//播放雷炸的声音
//弹出对话框告诉玩家他失败了
forkon=false;
pthread_join(di,NULL);
win_draw(show,mine,png);
MessageBox(NULL,"游戏失败","游戏结果",MB_OK);
//返回-1(即失败)
return -1;
}
else
{
//播放胜利的音频
Music(w);
//弹出对话框提示玩家他成功了
forkon=false;
pthread_join(di,NULL);
win_draw(show,mine,png);
MessageBox(NULL,"游戏胜利","游戏结果",MB_OK);
//返回所用时(即胜利)
return r;
}
}
void game_choose_draw(PIMAGE png[])
{
//游戏菜单页面绘画
cleardevice();//清屏
while(forkon);//等待到游戏中绘画线程关闭
putimage(0,0,png[14]);//画“最高纪录”四个字的图片
putimage(0,FROMTO*1,png[13]);//画菜单的图片
//如果有产生过胜利记录,调用画数字画出最少用时
if(win_r!=-1)draw_number(win_r,number,get_digit_capacity(win_r),75,0,false);
//绘画菜单页面
//手动渲染
delay_fps(60);
return ;
}
int game_choose(PIMAGE png[])
{
//游戏菜单函数
int x,y;
bool t=true;
int show[HIGH+2][WIDTH+2];
while(t==true)
{
//绘画菜单页面
game_choose_draw(png);
//获取鼠标位置
get_mouse(&x,&y,show,false);
//如果玩家点击了(GO)字样
//即选择开始游戏
if(x>=65&&x<=150&&y>=100&&y<=150)
{
//返回1(开始游戏)
return 1;
}
/*
else if(x>=280&&x<=345&&y>=170&&y<=235)
{
return 0;
}
*/
}
}
int game_main()
{
//整个游戏函数
//初始化EGE窗口
start();
int ch=-1;
GetPictures(png);
//初始化图片素材
GetPicture_number(number);
//初始化数字的图片素材
while(is_run())
{
//重复循环至EGE窗口被关闭
//如果玩家当前产生记录
//且没有记录过任何纪录
//则直接纪录当前记录
if(win_r==-1&&ch!=-1)win_r=ch;
else if(ch!=-1&&ch<win_r)win_r=ch;
//否则玩家产生过记录
//如果玩家当前记录好于历史最好记录
//则纪录当前记录
//获取菜单页面玩家点击的结果
ch=game_choose(png);
//如果玩家点击了开始游戏
//则调用游戏主体函数
if(ch==1)ch=game_play(png);
}
return 0;
}
全套资源在主页的资源处。