github项目:https://github.com/Kamisama999/C_code
gitee项目:https://gitee.com/Kamisama_02/curriculum-design
大致效果
图 1 初始界面 图 2 胜利界面
框架结构
(1)MainWindow类
MainWindow主要负责建立人机交互界面,例如:绘制扫雷界面,对鼠标事件进行响应等。
MainWindow类通过信号和槽机制对使用者的操作进行响应和完成对应操作,使用者通过特定事件发出信号,由对应的槽函数进行接受,并实现相应操作。
MainWindow类通过PaintEvent进行绘图,调用了绘图事件相关的头文件,再添加图片资源文件,最后通过调用绘图函数将图片资源绘制在窗口上。
MainWindow类可以通过ui界面对窗口进行设置,可以增加菜单栏对游戏进行调整,改变游戏难度。
(2)MineMap游戏逻辑类
MineMap主要负责扫雷游戏核心逻辑的实现,例如:随机生成地图,鼠标的操作,判定游戏的胜负等。
类的实现:
class MineMap
{
public:
MineMap();
~MineMap();
void Create(); //布雷
void Create(int row, int column, int number);
void Restart(); //重新布雷
int NearFlag(int m, int n); //计算周围的旗子数
int** Map; //地图
int MineNumber; //雷数
int Row; //行
int Column; //列
bool RClick(int x, int y); //右击
bool LRClick(int x, int y); //左右同时按
bool LClick(int x, int y); //左击
bool Win(); //判断是否获胜
int winflag; //判断胜利 0为失败 2为胜利
int RemainMine; //剩余雷数
bool first; //判断是否为第一次,防止直接踩雷
int timer; //记录时间
};
游戏逻辑核心代码
1.生成地图空间
//创造Map空间
void MineMap::Create()
{
int i;
if(Map!=NULL) //删除原本地图
{
for(i=0;i<Row;i++)
{
delete Map[i];
}
delete Map;
Map=NULL;
}
Map=new int*[Row];
for(i=0;i<Row;i++)
{
Map[i]=new int[Column];
}
Restart(); //生成雷和数字
}
//自定义创造Map空间大小
void MineMap::Create(int row,int column,int number)
{
int i;
if(Map!=NULL) //删除原本地图
{
for(i=0;i<Row;i++)
{
delete Map[i];
}
delete Map;
Map=NULL;
}
Row=row; //自定义行列数和炸弹数
Column=column;
MineNumber=number;
Map=new int*[Row];
for(i=0;i<Row;i++)
{
Map[i]=new int[Column];
}
Restart(); //生成雷和数字
}
2.初始化模块
//生成雷和数字
void MineMap::Restart()
{
int i,j;
//初始化
for(i=0;i<Row;i++)
for(j=0;j<Column;j++)
Map[i][j] = 100; //100为空白
//随机布雷
srand(time(NULL));
for(i=0;i<MineNumber;i++)
{
int x=rand()%Row;
int y=rand()%Column;
if(Map[x][y]!=99)
Map[x][y]=99; //99为雷
else
i--; //如果已经布雷就重新来一次
}
//生成雷周围的数字
for(i=0;i<Row;i++)
{
for(j=0;j<Column;j++)
{
if(Map[i][j]==99)
{
int x,y;
for(x=-1;x<2;x++)
{
for(y=-1;y<2;y++)
{
// 越界或是雷就跳过
if(i+x>=Row||j+y>=Column||i+x<0||j+y<0||Map[i+x][j+y]==99)
{
continue;
}
Map[i+x][j+y]++; //101到109为数字
}
}
}
}
}
winflag=1;
RemainMine=MineNumber;
first=true;
timer=0;
}
3.鼠标左击模块
bool MineMap::LClick(int x,int y)
{
if(winflag==0||winflag==2)
return false;
//越界或已打开
if(x>=Row||y>=Column||x<0||y<0||Map[x][y]<=90)
return false;
//有数字
if(Map[x][y]>=101&&Map[x][y]<=108)
{
Map[x][y]-=100; //打开数字为1到8
first=false;
Win(); //判断是否胜利
return true;
}
//没有数字,递归打开周围的格子
if(Map[x][y]==100)
{
Map[x][y]-=100; //打开为0
LClick(x-1,y);
LClick(x+1,y);
LClick(x,y+1);
LClick(x,y-1);
LClick(x-1,y-1);
LClick(x+1,y-1);
LClick(x-1,y+1);
LClick(x+1,y+1);
}
//踩雷
if(Map[x][y]==99)
{
int i,j;
if(first) //第一步就踩雷
{
RemainMine--; //消除当前位置的雷
int t=0;
//改变周围的数字
for(i=-1;i<2;i++)
{
for(j=-1;j<2;j++)
{
if((i+x<Row)&&(j+y)<Column&&(i+x>=0)&&(j+y>=0)&&(x||y))
{
if(Map[i+x][j+y]>99)
Map[i+x][j+y]--;
if(Map[i+x][j+y]==99)
t++;
}
}
}
first=false;
Map[x][y]=100+t;
LClick(x,y); //消除雷后点开
return true;
}
//显示出雷和插错的旗子
for(i=0;i<Row;i++)
{
for(j=0;j<Column;j++)
{
if(Map[i][j]==99)
Map[i][j]=-1; //-1为雷
if(Map[i][j]>49&&Map[i][j]<60)
Map[i][j]=-2; //-2为插错的旗子
}
}
winflag=0;
}
return true;
}
4.鼠标右击模块
bool MineMap::RClick(int x,int y)
{
if(winflag==0||winflag==2)
return false;
if(x>=Row||x<0||y>=Column||y<0||Map[x][y]<40)
return false;
if(Map[x][y]>90) //未打开
{
Map[x][y]-=50;
RemainMine--;
}
else if(Map[x][y]>40&&Map[x][y]<60) //已插旗
{
RemainMine++;
Map[x][y]+=50;
}
return true;
}
5.左右键双击模块
//计算周围的旗子数
int MineMap::NearFlag(int x,int y)
{
if(x>=Row||x<0||y>=Column||y<0) //越界
{
return -1;
}
int n=0;
int i,j;
for(i=-1;i<2;i++)
{
for(j=-1;j<2;j++)
{
//越界或不是旗子就跳过
if(x+i>=Row||y+j>=Column||x+i<0||y+j<0||Map[x+i][y+j]>60||Map[x+i][y+j]<40)
continue;
n++;
}
}
return n;
}
//左右键同时按
bool MineMap::LRClick(int x,int y)
{
if(x>=Row||x<0||y>=Column||y<0||Map[x][y]>40) //越界或未打开
return false;
if(Map[x][y]==NearFlag(x,y)) //数字刚好和周围的旗子相等
{ //打开周围的格子
LClick(x-1,y);
LClick(x+1,y);
LClick(x,y+1);
LClick(x,y-1);
LClick(x-1,y-1);
LClick(x+1,y-1);
LClick(x-1,y+1);
LClick(x+1,y+1);
}
return true;
}
绘制人机交互界面
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle("扫雷");
mineMap.Create();
offsetx=5;
offsety=30;
setFixedSize(mineMap.Row*20+offsetx*2,mineMap.Column*20+offsety+46);
runtime=new QTimer(this);
connect(runtime,SIGNAL(timeout()),this,SLOT(on_sectime()));
}
MainWindow::~MainWindow()
{
delete ui;
}
//绘图事件
void MainWindow::paintEvent(QPaintEvent *)
{
QPixmap nub(":/item1.bmp");
QPixmap map(":/item2.bmp");
QPixmap fame(":/item3.bmp");
QPixmap face(":/item4.bmp");
QPainter painter(this);
//绘制上面的框架
painter.drawPixmap( 0+offsetx, 0+offsety, fame, 0, 0, 70, 40);
painter.drawPixmap(mineMap.Row * 10 - 20+offsetx, 0+offsety, fame, 80, 0, 40, 40);
painter.drawPixmap(mineMap.Row * 20 - 70+offsetx, 0+offsety, fame, 130, 0, 70, 40);
painter.drawPixmap(70+offsetx, 0+offsety, mineMap.Row * 10 - 90, 40, fame, 70, 0, 10, 40);
painter.drawPixmap(mineMap.Row * 10 + 20+offsetx, 0+offsety, mineMap.Row * 10 - 90, 40, fame, 70, 0, 10, 40);
painter.drawPixmap(mineMap.Row * 10 - 12+offsetx, 7+offsety, face, mineMap.winflag * 24, 0, 24, 24);
//绘制雷区
for(int i=0;i<mineMap.Row;i++)
{
for(int j=0;j<mineMap.Column;j++)
{
if(mineMap.Map[i][j]>=0&&mineMap.Map[i][j]<9)
{
painter.drawPixmap(i*20+offsetx,j*20+40+offsety,map,mineMap.Map[i][j]*20,0,20,20);
}
if(mineMap.Map[i][j]==-1)
painter.drawPixmap(i*20+offsetx,j*20+40+offsety,map,9*20,0,20,20);
if(mineMap.Map[i][j]>90)
painter.drawPixmap(i*20+offsetx,j*20+40+offsety,map,10*20,0,20,20);
if(mineMap.Map[i][j]>40&&mineMap.Map[i][j]<60)
painter.drawPixmap(i*20+offsetx,j*20+40+offsety,map,11*20,0,20,20);
if(mineMap.Map[i][j]==-2)
painter.drawPixmap(i*20+offsetx,j*20+40+offsety,map,12*20,0,20,20);
}
}
int remainMine=mineMap.RemainMine;
int remainTime=mineMap.timer;
//绘制剩余雷数
if(remainMine<0)
remainMine=0;
painter.drawPixmap(6+offsetx, 5+offsety, nub, remainMine / 100 * 20, 0, 20, 28);
if(remainMine>=100)
remainMine%=100;
painter.drawPixmap(26+offsetx, 5+offsety, nub, remainMine / 10 * 20, 0, 20, 28);
painter.drawPixmap(46+offsetx, 5+offsety, nub, remainMine % 10 * 20, 0, 20, 28);
//绘制时间
if(remainTime>=1000)
remainTime%=1000;
painter.drawPixmap(mineMap.Row*20-66+offsetx, 5+offsety, nub, remainTime / 100 * 20, 0, 20, 28);
if(remainTime>=100)
remainTime%=100;
painter.drawPixmap(mineMap.Row*20 - 46+offsetx, 5+offsety, nub, remainTime / 10 * 20, 0, 20, 28);
painter.drawPixmap(mineMap.Row*20 - 26+offsetx, 5+offsety, nub, remainTime % 10 * 20, 0, 20, 28);
}
//鼠标按下事件
void MainWindow::mousePressEvent(QMouseEvent *event)
{
int px=event->x()-offsetx;
int py=event->y() -offsety;
int x = (px) / 20;
int y = (py) / 20 - 2;
if(event->buttons()==(Qt::LeftButton|Qt::RightButton))
{
if(mineMap.LRClick(x, y))
update();
}
else if(event->button()==Qt::LeftButton)
{
//点击表情图标重新开始
if (px>mineMap.Row * 10 - 15 && px< mineMap.Row * 10 + 15 && py>4 && py < 34)
{
mineMap.Restart();
update();
}
if(mineMap.LClick(x,y))
{
if (!(runtime->isActive() ))
runtime->start(1000);
update();
}
if (mineMap.winflag==0 || mineMap.winflag == 2)
{
if (runtime->isActive() )
runtime->stop();
}
}
else if(event->button()==Qt::RightButton)
{
if(mineMap.RClick(x,y))
update();
}
}
PS:第一次写博客,希望多多包涵。图片资源上传有水印就不放了。
参考博客:https://blog.csdn.net/u013407923/article/details/50472635