本游戏是模仿经典游戏扫雷的玩法写成
前言
基于Qt的经典扫雷小程序;
一、软件描述
本游戏的所有代码都是基于Qt的编译环境工作;
简单演示一下程序效果:
二、基本框架
1.基类选择:MainWindow
MainWindow主要进行游戏各项操作;
MainWindow中通过对菜单栏的设置,得到想要的功能;例如新游戏,更改难度等;
MainWindow中通过PaintEvent绘图事件对游戏页面的绘制;例如游戏网格、数字等等;
MainWindow中通过mousePressEvent鼠标点击事件的重写,执行游戏功能;例如左击等;
通过构建类,构建第二页面,来选择新的游戏难度;
三、代码部分
1.类和函数的声明、头文件的声明 gamewindow.h
#ifndef GAMEWINDOW_H
#define GAMEWINDOW_H
#include "change_game.h"
#include <QDebug>
#include <QMainWindow>
#include <QMenu> //菜单头文件
#include <QMenuBar>
#include <QAction>
#include <QToolBar> //工作栏头文件
#include <QStatusBar> //状态栏头文件
#include <QDockWidget> //工作区头文件
#include <QLabel>
#include <QMessageBox>
#include <QTime>
#include <QPainter> //绘图方法
#include <QPen> //绘图工具、画笔
#include <QBrush> //绘图工具、画刷
#include <QColor>
#include <QEvent>
#include <QMouseEvent>
#include <QAction>
class GameWindow : public QMainWindow
{
Q_OBJECT
public:
GameWindow(int line = 16, int column = 16, int minenumber = 40,QWidget *parent = 0);
~GameWindow();
void Create(); //构建游戏板
void Restart(); //重新布雷
int NearFlag(int m, int n); //计算周围的旗子数
bool LClick(int x, int y); //左击
bool MidClick(int x, int y); //点击滑轮
bool RClick(int x, int y); //右击
bool Win(); //判断是否获胜
void Over(); //游戏结束
int** Map; //地图
int** flag; //插旗的地图 11为没插旗子 88为插了旗子
int MineNumber; //总雷数
int Line; //列数
int Column; //行数
int winflag; //判断胜利 1为失败 2为胜利
int RemainMine; //剩余雷数
bool first; //判断是否为第一次,防止直接踩雷
int offsetx = 5; //左右两侧留的空间
int offsety = 135; //上下两侧留的空间
int MineButton = 30; //一个按键模块的大小
void paintEvent(QPaintEvent *event); //画图事件
void mousePressEvent(QMouseEvent *event); //鼠标点击事件
QMenu *menugeme, *menuhelp, *menume;
QAction *actnew, *actchange, *actclose, *acthelp, *actme;
QLabel *lab1, *lab2;
/******************************菜单选项槽函数**************************************/
void actnew_fun();
void actchange_fun();
void actclose_fun();
void acthelp_fun();
void actme_fun();
};
#endif // GAMEWINDOW_H
2.对成员函数和槽函数的定义
构造函数、析构函数和绘图事件:
GameWindow::GameWindow(int line, int column, int minenumber, QWidget *parent)
: QMainWindow(parent),
Line(line),
Column(column),
MineNumber(minenumber)
{
setFixedSize(MineButton*Line+offsetx*2, MineButton*Column+offsety); //设置初始页面大小(普通难度);
/***********************构建程序窗口页面**************************************/
setWindowTitle("扫雷"); //设置软件名(窗口名)
setWindowIcon(QIcon(":/mine.png")); //设置窗口界面图标
/************************构造菜单项******************************************/
menugeme = menuBar()->addMenu("游戏"); //构建游戏菜单
menuhelp = menuBar()->addMenu("帮助");
menume = menuBar()->addMenu("关于");
actnew = new QAction("新游戏"); //构造菜单项
actnew->setShortcut(Qt::CTRL + Qt::Key_O); //设置快捷方式
actnew->setStatusTip("新游戏"); //设置状态栏的提升信息
actchange = new QAction("更改难度");
actchange->setShortcut(Qt::CTRL + Qt::Key_C);
actchange->setStatusTip("更改难度");
actclose = new QAction("关闭游戏");
actclose->setShortcut(Qt::CTRL + Qt::Key_Q);
actclose->setStatusTip("关闭游戏");
acthelp = new QAction("帮助");
acthelp->setStatusTip("帮助");
actme = new QAction("关于");
actme->setStatusTip("关于");
/******************************************添加菜单项***********************************/
menugeme->addAction(actnew); //添加菜单项
menugeme->addAction(actchange);
menugeme->addAction(actclose);
menuhelp->addAction(acthelp);
menume->addAction(actme);
/******************************************关联菜单信号与槽*****************************/
connect(actnew, &QAction::triggered, this, &GameWindow::actnew_fun);
connect(actchange, &QAction::triggered, this, &GameWindow::actchange_fun);
connect(actclose, &QAction::triggered, this, &GameWindow::actclose_fun);
connect(acthelp, &QAction::triggered, this, &GameWindow::acthelp_fun);
connect(actme, &QAction::triggered, this, &GameWindow::actme_fun);
/********************初始化二级指针,谨防段错误*************************/
Map = NULL;
flag = NULL;
/***********************创建棋盘*************************************/
Create();
}
GameWindow::~GameWindow()
{
}
/**********************************画图事件**********************************/
void GameWindow::paintEvent(QPaintEvent *event)
{
/*****************************初始化素材图片*******************************/
QPixmap nub0(":/0.png");
QPixmap nub1(":/1.png");
QPixmap nub2(":/2.png");
QPixmap nub3(":/3.png");
QPixmap nub4(":/4.png");
QPixmap nub5(":/5.png");
QPixmap nub6(":/6.png");
QPixmap nub7(":/7.png");
QPixmap nub8(":/8.png");
QPixmap my_new(":/new.png");
QPixmap mine(":/mine.png");
QPixmap mine_red(":/mine_red.png");
QPixmap my_flag(":/flag.png");
/***********************初始化画笔*******************************/
QPainter p(this);
QPen pen(Qt::LinearGradientPattern);
pen.setColor("gray");
pen.setWidth(2);
p.setPen(pen);
int x_start, y_start, x_end, y_end;
/*****************画横线**********************/
x_start = offsetx;
x_end = offsetx + Line*MineButton;
for(y_start=0;y_start <= Column;y_start++)
{
y_end = y_start*MineButton + offsety -5;
p.drawLine(QPoint(x_start, y_end), QPoint(x_end, y_end));
}
/*****************画竖线**********************/
y_start = offsety -5 ;
y_end = offsety - 5 + Column*MineButton;
for(x_start=0; x_start <= Line;x_start++)
{
x_end = x_start*MineButton + offsetx;
p.drawLine(QPoint(x_end, y_start), QPoint(x_end, y_end));
}
/************************************绘制初始棋盘*********************************************/
for(int i=0; i<Line; i++)
{
for(int j=0; j<Column; j++)
p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, my_new, 0, 0, 0, 0);
}
/*******************************绘制雷区***********************************/
for(int i=0; i<Line; i++)
{
for(int j=0; j<Column; j++)
{
if( Map[i][j] == 88){
p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, mine_red, 0, 0, 0, 0);
}else if(Map[i][j] == -1){
p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, mine, 0, 0, 0, 0);
}else if(Map[i][j] == 0){
p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub0, 0, 0, 0, 0);
}else if(Map[i][j] == 1){
p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub1, 0, 0, 0, 0);
}else if(Map[i][j] == 2){
p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub2, 0, 0, 0, 0);
}else if(Map[i][j] == 3){
p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub3, 0, 0, 0, 0);
}else if(Map[i][j] == 4){
p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub4, 0, 0, 0, 0);
}else if(Map[i][j] == 5){
p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub5, 0, 0, 0, 0);
}else if(Map[i][j] == 6){
p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub6, 0, 0, 0, 0);
}else if(Map[i][j] == 7){
p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub7, 0, 0, 0, 0);
}else if(Map[i][j] == 8){
p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub8, 0, 0, 0, 0);
}else{
continue;
}
}
}
/*********************************绘制插旗的情况************************************/
for(int i=0; i<Line; i++)
{
for(int j=0; j<Column; j++)
{
if(flag[i][j] == 88)
{
p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, my_flag, 0, 0, 0, 0);
}
}
}
/********************************绘制剩余雷数量提示框***********************************/
int remainMine = this->RemainMine;
if(remainMine < 0 )
remainMine = 0;
QPainter painter(this);
QFont font = painter.font();
font.setPixelSize(48);
pen.setColor("red");
painter.setPen(pen);
painter.setFont(font);
const QRect rectangle = QRect(5, 55, 300, 60);
QRect boundingRect;
painter.drawText(rectangle, 0, tr("雷:""%1").arg(remainMine), &boundingRect);
pen.setColor("gray");
painter.setPen(pen);
painter.drawRect(boundingRect.adjusted(0, 0, -pen.width(), -pen.width()));
}
重写鼠标点击事件:
/********************************重写鼠标点击事件************************************/
void GameWindow::mousePressEvent(QMouseEvent *event)
{
int px = event->x();
int py = event->y();
int x = (px - offsetx) / MineButton;
int y = (py - offsety + 5) / MineButton;
if(event->button()==(Qt::MidButton))
{
if(MidClick(x, y))
update();
}
else if(event->button()==Qt::LeftButton)
{
if(LClick(x,y))
update();
}
else if(event->button()==Qt::RightButton)
{
if(RClick(x,y))
update();
}
}
/*********************************点击鼠标左键********************************/
bool GameWindow::LClick(int x, int y)
{
if(x>=Line||y>=Column||x<0||y<0||Map[x][y]<10){ //越界或已打雷
return false;
}
if(flag[x][y] == 88){ //已经插旗就不能打开
return false;
}
if(winflag==1||winflag==2){
return false;
}
if(Map[x][y]>=101&&Map[x][y]<=108) //有数字
{
Map[x][y]-=100; //打开数字为1到8
first=false;
if(Win()) //判断是否胜利
winflag = 2;
Over();
return true;
}
if(Map[x][y]==100) //没有数字,递归打开周围的格子
{
Map[x][y] = 0; //打开为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]==66) //踩雷
{
int i,j;
if(first) //第一步就踩雷
{
RemainMine--; //消除当前位置的雷
Map[x][y]=100;
//改变周围的数字
for(i=-1;i<2;i++)
{
for(j=-1;j<2;j++)
{
if((i+x<Line)&&(j+y<Column)&&(i+x>=0)&&(j+y>=0)&&(i!=0||j!=0))
{
if(Map[i+x][j+y]>100)
Map[i+x][j+y]--;
else
Map[x][y]++;
}
}
}
first=false;
LClick(x,y); //消除雷后点开
return true;
}
Map[x][y] = 88;
for(i=0;i<Line;i++) //显示出雷和插错的旗子
{
for(j=0;j<Column;j++)
{
if(Map[i][j]==66)
Map[i][j]=-1; //-1为雷
}
}
winflag=1;
}
Over();
return true;
}
/************************************点击鼠标滑轮*********************************/
bool GameWindow::MidClick(int x, int y)
{
if(x>=Line||x<0||y>=Column||y<0||Map[x][y]>10) //越界或未打开
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);
}
if(Win())
winflag = 2;
Over();
return true;
}
/***************************************点击鼠标右键****************************************/
bool GameWindow::RClick(int x, int y)
{
if(Map[x][y] < 10) //若已经被点开就不能插旗
return false;
else {
if(flag[x][y]==11) //若未插旗就插旗
{
flag[x][y] = 88;
RemainMine--;
}else{ //若已插旗就拔掉
flag[x][y] = 11;
RemainMine++;
}
}
if(Win())
winflag = 2;
Over();
return true;
}
辅助判断函数:
/***********************判断周围有插了多少旗子*********************/
int GameWindow::NearFlag(int m, int n)
{
if(m>=Line||m<0||n>=Column||n<0) //越界
{
return -1;
}
int top=0;
int i,j;
for(i=-1;i<2;i++)
{
for(j=-1;j<2;j++)
{
if(m+i>=Line||n+j>=Column||m+i<0||n+j<0||flag[m+i][n+j]==11) //越界或不是旗子就跳过
continue;
top++;
}
}
return top;
}
/**************************************判断是否胜利**************************/
bool GameWindow::Win()
{
for(int i=0; i<Line; i++)
{
for(int j=0; j<Column; j++)
{
if(Map[i][j] == 66) //判断所有雷上是否插旗
{
if(flag[i][j] == 11)
return false;
}else if(Map[i][j]>9){ //判断所有数字是否已经点开
return false;
}
}
}
return true;
}
/*****************************************判断游戏是否结束**********************/
void GameWindow::Over()
{
if(winflag == 1){
QMessageBox::warning(this, "游戏结束", "失败");
}else if(winflag == 2){
QMessageBox::warning(this, "游戏结束", "扫雷成功");
}else {
return;
}
}
到此处基本功能已经实现,以下为附加功能函数;
构造菜单功能函数:
/***********************新游戏*************************/
void GameWindow::actnew_fun()
{
GameWindow *w;
w = new GameWindow(Line, Column, MineNumber);
this->close();
w->show();
}
/**************************选择新难度********************/
void GameWindow::actchange_fun()
{
change_game *G;
G = new change_game;
this->close();
G->show();
}
/**************************关闭游戏************************/
void GameWindow::actclose_fun()
{
if(QMessageBox::Ok == QMessageBox::warning(this, "结束游戏", "请确认是否退出游戏", QMessageBox::Ok | QMessageBox::Cancel))
this->close();
}
/*************************游戏帮助*************************/
void GameWindow::acthelp_fun()
{
QMessageBox msgBox;
msgBox.setText("该游戏为自制游戏,鼠标点击左键为翻开方块,鼠标点击右键为插旗,点击滑轮为探测,为了您的游戏体验,自定义列和行不得超过34*66,不得低于9*9,雷的数量不得超过25%");
msgBox.exec();
}
/**************************关于作者*****************************/
void GameWindow::actme_fun()
{
QMessageBox msgBox;
msgBox.setText("此作品为CSDN博主「澂瀄.」出品,查看代码可前往CSDN搜索博主");
msgBox.exec();
}
3.第二页面,只用于改变难度
第二页面效果展示:
功能代码:
/****************************第二页面 只用于改变难度********************************/
change_game::change_game(QWidget *parent) :
QWidget(parent),
ui(new Ui::change_game)
{
ui->setupUi(this);
}
change_game::~change_game()
{
delete ui;
}
void change_game::on_btn_1_clicked()
{
GameWindow *w;
w = new GameWindow(9, 9, 10); //简单难度
this->close();
w->show();
}
void change_game::on_btn_2_clicked()
{
GameWindow *w;
w = new GameWindow(16, 16, 40); //普通难度
this->close();
w->show();
}
void change_game::on_btn_3_clicked()
{
GameWindow *w;
w = new GameWindow(30, 16, 99); //困难难度
this->close();
w->show();
}
void change_game::on_btn_4_clicked()
{
GameWindow *w;
w = new GameWindow(66, 34, 462); //噩梦难度
this->close();
w->show();
}
void change_game::on_btn_5_clicked()
{ //自定义难度
int line = ui->edt_line->text().toInt();
int column = ui->edt_column->text().toInt();
int mine = ui->edt_mine->text().toInt();
int over = line*column;
if((line>=9) && (column>=9) && (line<=66) && (column<=34) && ((over/4) >= mine))
{
GameWindow *w;
w = new GameWindow(line, column, mine);
this->close();
w->show();
}else {
QMessageBox::warning(this, "警告" , "非法输入! 为了您的游戏体验,自定义行列不得超过66*34,不得低于9*9,雷的数量不得超过25%");
}
}
总结
以上就是对扫雷游戏的主要内容,本程序还有许多不足,大家感兴趣的可以广发奇想,写的帖子写得粗糙,希望对看帖子的你有所帮助,还希望各位大佬担待,诚心欢迎各位批评指正。