扫雷目录
前言
扫雷程序主要应用了C++开发语言和QT框架,其中应用了大量的UI部件已经事件处理等,代码包中给了大量的注释,方便小伙伴阅读和理解
一、QT扫雷程序应用展示
二、使用步骤
1.项目说明
项目代码流程图
2.项目代码详解
地图类主要用于创建生产地图
mine.h
#ifndef MINE_H
#define MINE_H
/*功能:接受来自Mainwindow的输入,并响应
* 1.接收地图参数,生成地图
* 2.接收点击事件,实时响应
* */
class Mine
{
public:
Mine();
/* 函数:GenerateMap
* 功能:根据传入参数,生成二维数组地图
* Input:int raw, 行数
* int column, 列数
* int mineNum, 雷数
* Output:bool ret,返回是否成功生成地图
* */
bool GenerateMap(int in_raw, int in_column, int in_mineNum);
/* 函数:reGenerateMap
* 功能:重新生成地图,参数不变,所以不必传入。
* Input: NONE
* Output:NONE
* */
void reGenerateMap();
int getMineNumber();
unsigned char **map;
private:
int raw;//行数
int column;//列数
int mineNum;//雷数
/* 函数:MallocMemForMap
* 功能:为二维指针申请内存
* Input:int raw, 行数
* int column, 列数
* int mineNum, 雷数
* Output:bool ret,返回是否成功申请内存
* */
bool MallocMemForMap(int in_raw, int in_column, int in_mineNum);
/* 函数:InitMap
* 功能:在地图中埋雷,为每个方格填值
* Input: NONE
* Output:bool ret,返回是否完成埋雷与填值
* */
bool InitMap();
/* 函数:PrintMap
* 功能:打印地图
* Input: NONE
* Output:NONE
* */
void PrintMap();
};
#endif // MINE_H
mine.cpp
#include "mine.h"
#include "stdlib.h"
#include <time.h>
#include <iostream>
using namespace std;
Mine::Mine()
{
map = NULL;
}
bool Mine::GenerateMap(int in_raw, int in_column, int in_mineNum)
{
if(false == MallocMemForMap(in_raw, in_column, in_mineNum))
{
cout << "error: malloc memory failed!" << endl;
return false;
}
if(false == InitMap())
{
cout << "error: init map failed!" << endl;
return false;
}
return true;
}
void Mine::reGenerateMap()
{
InitMap();
}
int Mine::getMineNumber()
{
return this->mineNum;
}
bool Mine::MallocMemForMap(int in_raw, int in_column, int in_mineNum)
{
//非法入参
if(1 > in_raw || 1 > in_column || 1 > in_mineNum || in_mineNum > in_raw * in_column)
{
cout << "error: para error!" << endl;
return false;
}
if(NULL != map)
{
for(int i = 0; i < raw; i ++)
{
delete map[i];
}
delete map;
}
map = NULL;
raw = in_raw;
column = in_column;
mineNum = in_mineNum;
map = new unsigned char*[raw];
for(unsigned char j = 0; j < raw; j ++)
{
map[j] = new unsigned char[column];
}
return true;
}
bool Mine::InitMap()
{
int xpos, ypos, mineLeft = mineNum;
int xs, xe, ys, ye;//填值时使用的上下左右边界值
if(NULL == map)
{
cout << "error: null ptr!" << endl;
return false;
}
//初始化内存,全部填0
for(int i = 0; i < raw; i ++)
{
for(int j = 0; j < column; j ++)
{
map[i][j] = 0;
}
}
//埋雷---采用随机生成雷坐标的方式,雷使用符号'*'的ASCII
srand(time(NULL));//以当前时间为随机种子,保证随机性
while(0 != mineLeft)
{
xpos = rand() % raw;
ypos = rand() % column;
if(0 == map[xpos][ypos])
{
map[xpos][ypos] = '*';
mineLeft --;
}
continue;
}
//根据雷的分布填充其他方格数值
for(int i = 0; i < raw; i ++)
{
for(int j = 0; j < column; j ++)
{
if('*' == map[i][j])
{
//设定雷周围可遍历方格的区间
xs = (i - 1 >= 0) ? (i - 1) : i;
xe = (i + 1 < raw) ? (i + 1) : i;
ys = (j - 1 >= 0) ? (j - 1) : j;
ye = (j + 1 < column) ? (j + 1) : j;
for(xpos = xs; xpos <= xe ; xpos ++)
{
for(int ypos = ys; ypos <= ye; ypos ++)
{
if('*' == map[xpos][ypos])
{
continue;
}
map[xpos][ypos] ++;
}
}
}
}
}
return true;
}
void Mine::PrintMap()
{
for(int i = 0; i < raw; i ++)
{
for(int j = 0; j < column; j ++)
{
cout << (int)map[i][j] << "\t";
}
cout << endl;
}
}
主界面主要创建界面UI,控制窗口事件
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "mine.h"
#include <QTimer>
namespace Ui {
class MainWindow;
}
/*功能:与用户直接交互
* 1.等级切换外部接口
* 2.重新开始外部接口
* 3.纪录功能(待开发)
* 4.联机对战功能(待细化)
* */
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
/* 默认三个level的地图参数
* -----------------------------------------
* | level | raw | column | mineNum |
* -----------------------------------------
* | low-level | 9 | 9 | 10 |
* -----------------------------------------
* | intermediate | 16 | 16 | 40 |
* -----------------------------------------
* | advanced | 16 | 30 | 100 |
* -----------------------------------------
* */
unsigned int defaultMap[3][3] = {{9,9,10},
{16,16,40},
{16,30,100}};
/* 函数:ChangeLevel
* 功能:等级切换外部接口,根据传等级参数给Mine类用于生成地图
* Input:int raw, 行数
* int column, 列数
* int mineNum, 雷数
* Output:bool ret,返回等级是否切换成功
* */
bool ChangeLevel(int raw, int column, int mineNum);
/* 函数:Restart
* 功能:重新开始外部接口
* Input: NONE
* Output:bool ret,返回是否成功restart
* */
bool Restart();
/* 函数:paintEvent
* 功能:绘制,Qt虚函数,重写
* Input: QPaintEvent *
* Output:NONE
* */
void paintEvent(QPaintEvent *);
/* 函数:mousePressEvent
* 功能:鼠标点击事件处理,Qt虚函数,重写
* Input: QMouseEvent *
* Output:NONE
* */
void mousePressEvent(QMouseEvent *event);
/* 函数:MallocMemForMap_IsPushed
* 功能:为上层标记地图申请内存,指针为maindow的成员
* Input: 行、列
* Output:是否成功
* */
bool MallocMemForMap_IsPushed(int in_raw, int in_column);
/* 函数:InitMap_IsPushed
* 功能:为上层标记地图初始化,用0、1、2代表未访问过、访问过、插旗标志
* Input: 行、列
* Output:是否成功
* */
void InitMap_IsPushed();
/* 函数:GeneratePushedMap
* 功能:生成上层标记地图,调用MallocMemForMap_IsPushed、InitMap_IsPushed
* Input: 行、列
* Output:是否成功
* */
bool GeneratePushedMap(int in_raw, int in_column);
/* 函数:GenerateGlobalMap
* 功能:生成两级地图
* Input: 行、列、雷数
* Output:是否成功
* */
bool GenerateGlobalMap(int in_raw, int in_column, int in_mineNum);
/* 函数:changePosToIndex
* 功能:将鼠标的点击位置转化为地图坐标
* Input: x/y像素坐标
* Output:NONE
* */
void changePosToIndex(int *px, int *py);
void recursiveFreshBlock(int raw_pos, int col_pos);
void pressLeftRightButtonPorc(int raw_pos, int col_pos);
void pressLeftButtonProc(int raw_pos, int col_pos);
void pressRightButtonProc(int raw_pos, int col_pos);
//检查状态,标志置位处理
void JudgeIsOver();
Mine m;
//地图方格的起始位置偏移
int map_XOffset;
int map_YOffset;
//时间栏的起始位置偏移
int time_XOffset;
int time_YOffset;
//初始状态均置为0
struct StateFlag
{
bool IsOver;//游戏是否结束的状态,如果已经结束,置1,未结束置0。
bool IsStart;//游戏是否开始的状态,如果已经开始,置1,未开始置0.
bool result;//游戏结果,胜利置1,失败置0.
}STATE_FLAG;
private slots:
void on_actionExit_triggered();
void on_actionprimary_triggered();
void on_actionmedium_triggered();
void on_actionexpert_triggered();
void on_actionRestart_triggered();
void on_timeChange();
void on_actionVersion_triggered();
void on_actionAuthor_triggered();
private:
//上层地图标记,0---未点击过,1---点击过,2---插上了小旗
int **map_flag;
int raw;
int column;
//剩余雷的个数
int mine_Left;
//计时器
QTimer *timer;
int time_cost;
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <iostream>
#include <QPainter>
#include <QMouseEvent>
#include <QMessageBox>
using namespace std;
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//初始化各个成员变量。
STATE_FLAG.IsOver = 0;
STATE_FLAG.IsStart = 0;
STATE_FLAG.result = 0;
map_flag = NULL;
map_XOffset = 5;
map_YOffset = 64;
time_XOffset = 0;
time_YOffset = 27;
this->time_cost = 0;
this->timer = new QTimer(this);
connect(this->timer,SIGNAL(timeout()),this,SLOT(on_timeChange()));
/*start:
* 1.默认为初级,每个方格25*25,上方留出一定区域用于显示时间、剩余雷数等信息
* 2.初始话随机地图,和记录是否访问过的地图 */
setFixedSize(defaultMap[0][1] * 25 + map_XOffset * 2 , defaultMap[0][0] * 25 + map_YOffset + 5);
GenerateGlobalMap(defaultMap[0][0],defaultMap[0][1],defaultMap[0][2]);
/*end*/
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::paintEvent(QPaintEvent *)
{
QPixmap bmpblock;
QPixmap bmpdigital;
QPixmap bmpemoji;
if(false == bmpblock.load("../src/block.bmp") || false == bmpdigital.load("../src/digital_32_25.bmp") || false == bmpemoji.load("../src/emoji.bmp"))
{
cout << "error:load bmp failed!" << endl;
}
else
{
cout << "info:load bmp success!" << endl;
}
QPainter painter(this);
/*绘制格子的处理流程***************************************************************************Start*/
for(int i = 0; i < this->raw; i ++)
{
for(int j = 0; j < this->column; j ++)
{
//没有左击过的方格的绘制
if(0 == map_flag[i][j])
{
painter.drawPixmap(j * 25 + map_XOffset, i * 25 + this->map_YOffset, bmpblock, 9 * 25, 0, 25, 25);
}
//左击过的方格绘制
else if(1 == map_flag[i][j])
{
if(m.map[i][j] < 9)
{
painter.drawPixmap(j * 25 + map_XOffset, i * 25 + this->map_YOffset, bmpblock, m.map[i][j] * 25, 0, 25, 25);
}
else if(m.map[i][j] == '*')
{
painter.drawPixmap(j * 25 + map_XOffset, i * 25 + this->map_YOffset, bmpblock, 11 * 25, 0, 25, 25);
}
}
//插小旗的绘制
else if(2 == map_flag[i][j])
{
painter.drawPixmap(j * 25 + map_XOffset, i * 25 + this->map_YOffset, bmpblock, 12 * 25, 0, 25, 25);
}
//插错旗:3
else
{
painter.drawPixmap(j * 25 + map_XOffset, i * 25 + this->map_YOffset, bmpblock, 13 * 25, 0, 25, 25);
}
}
}
//游戏已结束
if(1 == STATE_FLAG.IsOver)
{
//游戏失败,所有未翻开的雷全部翻开
if(0 == STATE_FLAG.result)
{
for(int i = 0; i < this->raw; i ++)
{
for(int j = 0; j < this->column; j ++)
{
if(0 == map_flag[i][j] && m.map[i][j] == '*')
{
painter.drawPixmap(j * 25 + map_XOffset, i * 25 + this->map_YOffset, bmpblock, 10 * 25, 0, 25, 25);
}
}
}
// 表情切换为哭
painter.drawPixmap((this->column * 25 + 2 * map_XOffset) / 2 - 15, this->time_YOffset, bmpemoji, 2 * 31, 0, 31, 31);
}
//游戏胜利,切换表情,点击锁定
else
{
painter.drawPixmap((this->column * 25 + 2 * map_XOffset) / 2 - 15, this->time_YOffset, bmpemoji, 1 * 31, 0, 31, 31);
}
}
else
{
painter.drawPixmap((this->column * 25 + 2 * map_XOffset) / 2 - 15, this->time_YOffset, bmpemoji, 0, 0, 31, 31);
}
/*绘制格子的处理流程***************************************************************************End*/
/*绘制剩余雷数的统计处理流程********************************************************************Start*/
int gtemp,stemp,btemp,mine_leftTemp;
mine_leftTemp = mine_Left < 1 ? 0 : mine_Left;
btemp = mine_leftTemp / 100;
stemp = mine_leftTemp / 10 - btemp * 10;
gtemp = mine_leftTemp - btemp * 100 - stemp * 10;
cout << "mineLeftNum: " << this->mine_Left << " btemp: " << btemp << " stemp: " << stemp << " gtemp:" << gtemp << endl;
painter.drawPixmap(0 + map_XOffset, this->time_YOffset, bmpdigital, btemp * 25, 0, 25, 32);
painter.drawPixmap(25 + map_XOffset, this->time_YOffset, bmpdigital, stemp * 25, 0, 25, 32);
painter.drawPixmap(50 + map_XOffset, this->time_YOffset, bmpdigital, gtemp * 25, 0, 25, 32);
/*绘制剩余雷数的统计处理流程**********************************************************************End*/
/*绘制时间统计的处理流程************************************************************************Start*/
int time_temp = this->time_cost;
btemp = time_temp / 100;
stemp = time_temp / 10 - btemp * 10;
gtemp = time_temp - btemp * 100 - stemp * 10;
cout << "timecost: " << this->time_cost << " btemp: " << btemp << " stemp: " << stemp << " gtemp:" << gtemp << endl;
painter.drawPixmap(this->column * 25 + map_XOffset - 75, this->time_YOffset, bmpdigital, btemp * 25, 0, 25, 32);
painter.drawPixmap(this->column * 25 + map_XOffset - 50, this->time_YOffset, bmpdigital, stemp * 25, 0, 25, 32);
painter.drawPixmap(this->column * 25 + map_XOffset - 25, this->time_YOffset, bmpdigital, gtemp * 25, 0, 25, 32);
/*绘制时间统计的处理流程************************************************************************End*/
}
void MainWindow::mousePressEvent(QMouseEvent *event)
{
int ppx = event->x();
int ppy = event->y();
cout << "ppx: " << ppx << " ppy: " << ppy << endl;
cout << "xArea: " << map_XOffset << "," << 25 * this->column + map_XOffset << endl;
cout << "yArea: " << map_YOffset << "," << 25 * this->raw + map_YOffset << endl;
//如果点击位置不在方格区域内,点击表情进行处理,否则不处理
if(!((ppx > map_XOffset && ppx < 25 * this->column + map_XOffset) && (ppy > map_YOffset && ppy < this->raw * 25 + map_YOffset)))
{
int temp_x = (this->column * 25 + 2 * map_XOffset) / 2 - 15;
int temp_y = this->time_YOffset;
if(ppx >= temp_x && ppx <= temp_x + 31 && ppy >= temp_y && ppy <= temp_y + 31)
{
on_actionRestart_triggered();
}
//cout << "error: not in the correct area!" << endl;
return;
}
//如果游戏处于结束状态,不响应点击事件
if(1 == STATE_FLAG.IsOver)
{
return;
}
changePosToIndex(&ppx, &ppy);
if((Qt::LeftButton|Qt::RightButton) == event->buttons())
{
//如果游戏还没有开始,不响应左右双击事件
if(0 == STATE_FLAG.IsStart)
{
return;
}
pressLeftRightButtonPorc(ppy, ppx);
}
else if(Qt::LeftButton == event->button())
{
if(0 == STATE_FLAG.IsStart)
{
STATE_FLAG.IsStart = 1;
this->timer->start(1000);
}
pressLeftButtonProc(ppy, ppx);
}
else if(Qt::RightButton == event->button())
{
pressRightButtonProc(ppy, ppx);
}
//更新游戏状态
JudgeIsOver();
update();
}
bool MainWindow::MallocMemForMap_IsPushed(int in_raw, int in_column)
{
if(1 > in_raw || 1 > in_column)
{
cout << "error: malloc mem for map_IsPushed failed !" << endl;
return false;
}
if(NULL != map_flag)
{
for(int i = 0; i < raw ;i ++)
{
delete map_flag[i];
}
delete map_flag;
map_flag = NULL;
}
raw = in_raw;
column = in_column;
map_flag = new int*[raw];
for(int j = 0; j < raw; j ++)
{
map_flag[j] = new int[column];
}
return true;
}
void MainWindow::InitMap_IsPushed()
{
for(int i = 0 ;i < raw ;i ++)
{
for(int j = 0; j < column; j ++)
{
map_flag[i][j] = 0;
}
}
}
bool MainWindow::GeneratePushedMap(int in_raw, int in_column)
{
if(false == MallocMemForMap_IsPushed(in_raw, in_column))
{
cout << "error: malloc mem for map_IsPushed failed!!" << endl;
return false;
}
InitMap_IsPushed();
return true;
}
void MainWindow::changePosToIndex(int *px, int *py)
{
//进入此函数,由外部调用保证点击区域为方块区域
*px = (*px - map_XOffset) / 25;
*py = (*py - map_YOffset) / 25;
}
void MainWindow::recursiveFreshBlock(int raw_pos, int col_pos)
{
int raw_s, raw_e, col_s, col_e;//上下左右边界值
//设定周围可遍历方格的区间
raw_s = (raw_pos - 1 >= 0) ? (raw_pos - 1) : raw_pos;
raw_e = (raw_pos + 1 < raw) ? (raw_pos + 1) : raw_pos;
col_s = (col_pos - 1 >= 0) ? (col_pos - 1) : col_pos;
col_e = (col_pos + 1 < column) ? (col_pos + 1) : col_pos;
map_flag[raw_pos][col_pos] = 1;
for(int i = raw_s; i <= raw_e; i ++)
{
for(int j = col_s; j <= col_e; j ++)
{
if(i == raw_pos && j == col_pos)
{
continue;
}
if(0 == m.map[i][j] && 0 == map_flag[i][j])
{
recursiveFreshBlock(i, j);
}
else
{
map_flag[i][j] = 1;
}
}
}
}
void MainWindow::pressLeftRightButtonPorc(int raw_pos, int col_pos)
{
//先统计周围一圈的小旗个数,比较是否和此块数值相同
int cnt = 0;
int raw_s, raw_e, col_s, col_e;//上下左右边界值
//设定周围可遍历方格的区间
raw_s = (raw_pos - 1 >= 0) ? (raw_pos - 1) : raw_pos;
raw_e = (raw_pos + 1 < raw) ? (raw_pos + 1) : raw_pos;
col_s = (col_pos - 1 >= 0) ? (col_pos - 1) : col_pos;
col_e = (col_pos + 1 < column) ? (col_pos + 1) : col_pos;
for(int i = raw_s; i <= raw_e;i ++)
{
for(int j = col_s; j <= col_e; j ++)
{
if(2 == map_flag[i][j])
{
cnt ++;
}
}
}
if(cnt != m.map[raw_pos][col_pos])
{
return;
}
else
{
for(int i = raw_s; i <= raw_e;i ++)
{
for(int j = col_s; j <= col_e; j ++)
{
switch(map_flag[i][j])
{
case 2:
if('*' != m.map[i][j])
{
map_flag[i][j] = 3;
}
break;
case 0:
pressLeftButtonProc(i, j);
break;
default:
break;
}
}
}
}
}
void MainWindow::pressLeftButtonProc(int raw_pos, int col_pos)
{
if(1 == map_flag[raw_pos][col_pos] || 2 == map_flag[raw_pos][col_pos])
{
//已经点击的区域或已插上红旗的区域,不需要处理
return;
}
map_flag[raw_pos][col_pos] = 1;
if(0 == m.map[raw_pos][col_pos])
{
recursiveFreshBlock(raw_pos, col_pos);
}
}
void MainWindow::pressRightButtonProc(int raw_pos, int col_pos)
{
switch (map_flag[raw_pos][col_pos])
{
case 1:
return;
case 2:
map_flag[raw_pos][col_pos] = 0;
this->mine_Left ++;
break;
case 0:
map_flag[raw_pos][col_pos] = 2;
this->mine_Left --;
break;
default:
break;
}
}
void MainWindow::JudgeIsOver()
{
bool tempflag = 1;
for(int i = 0; i < this->raw; i ++)
{
for(int j = 0; j < this->column; j ++)
{
//如果点击到雷,游戏结束,结果判负
if(1 == map_flag[i][j] && '*' == m.map[i][j])
{
STATE_FLAG.IsOver = 1;
STATE_FLAG.result = 0;
this->timer->stop();
return;
}
//如果有非雷非0的区域没有被点击,游戏没有结束,继续检查。
if('*' != m.map[i][j] && 0 != m.map[i][j] && 0 == map_flag[i][j])
{
tempflag = 0;
}
}
}
if(tempflag)
{
//所有非雷非0格子都被点过,游戏结束,结果判胜
STATE_FLAG.IsOver = 1;
STATE_FLAG.result = 1;
this->timer->stop();
}
}
bool MainWindow::GenerateGlobalMap(int in_raw, int in_column, int in_mineNum)
{
mine_Left = in_mineNum;
//生成一层地图
if(false == m.GenerateMap(in_raw,in_column,in_mineNum))
{
cout << "error: generate map failed !!" << endl;
return false;
}
//生成二层地图
if(false == GeneratePushedMap(in_raw, in_column))
{
cout << "error: generate pushed map failed!!" << endl;
return false;
}
return true;
}
bool MainWindow::ChangeLevel(int in_raw, int in_column, int in_mineNum)
{
STATE_FLAG.IsOver = 0;
STATE_FLAG.IsStart = 0;
setFixedSize(in_column * 25 + map_XOffset * 2 , in_raw * 25 + map_YOffset + 5);
this->time_cost = 0;
this->timer->stop();
GenerateGlobalMap(in_raw, in_column, in_mineNum);
update();
return true;
}
void MainWindow::on_actionExit_triggered()
{
exit(0);
}
void MainWindow::on_actionprimary_triggered()
{
ChangeLevel(defaultMap[0][0], defaultMap[0][1], defaultMap[0][2]);
}
void MainWindow::on_actionmedium_triggered()
{
ChangeLevel(defaultMap[1][0], defaultMap[1][1], defaultMap[1][2]);
}
void MainWindow::on_actionexpert_triggered()
{
ChangeLevel(defaultMap[2][0], defaultMap[2][1], defaultMap[2][2]);
}
void MainWindow::on_actionRestart_triggered()
{
STATE_FLAG.IsOver= 0;
STATE_FLAG.IsStart = 0;
setFixedSize(column * 25 + map_XOffset * 2 , raw * 25 + map_YOffset + 5);
this->mine_Left = m.getMineNumber();
this->time_cost = 0;
this->timer->stop();
m.reGenerateMap();
InitMap_IsPushed();
update();
}
void MainWindow::on_timeChange()
{
this->time_cost ++;
cout << "@@time: " << this->time_cost << endl;
update();
}
void MainWindow::on_actionVersion_triggered()
{
QString title = "Version";
QString content = "Version:1.0\n基本功能开发完成\n\t\t\t\t2018.04.11";
QMessageBox::information(NULL, title, content, QMessageBox::Ok);
}
void MainWindow::on_actionAuthor_triggered()
{
QString title = "Author";
QString content = "\t这款扫雷是学习了一份扫雷开源代码后做的个人小练习,之后会在此基础上增加更多的扩展内容~欢迎各位同道中人交流联系~ ---不定期更新\n\nEmail:heqi_dlmuit@163.com\nQQ:292931255";
QMessageBox::information(NULL, title, content, QMessageBox::Ok);
}
总结
本项目主要用了C++和QT框架进行开发,对代码能力有一定的要求,小伙伴们可以把代码导入到自己的项目中进行开发和使用,还可以作为代码参考。