最近想着使用Qt实现一个简单版的扫雷。今天来给大家分享一下自己的实现思路。
实现效果
这是最后的实现效果
下载
源码地址:
gitee:https://gitee.com/chuzhongqaq/my-mine-map.git
类的介绍
#pragma once
class MineMap
{
public:
MineMap();
~MineMap();
void createMap();
void restartGame();
void createMap(int cow, int col, int minecount);
bool onLeftDown(int x, int y);
bool onRightDown(int x, int y);
int onLeftRightDown(int x, int y);
bool isWin();
void floatCoordinateRestore();
private:
int m_win;
int m_x;
int m_y;
int m_mineCount;
int** m_map;
int m_timer;
int m_mine;
friend class MainWindow;
int m_floatX;
int m_floatY;
};
这个类是地雷类,主要是利用一个二级指针来管理地雷。
当用户点击扫雷区后会在该类进行处理 ,并改变存储的值。
mainwindow
#pragma once
#include <QTimer>
#include "minemap.h"
#include <QtWidgets/QMainWindow>
#include "ui_mainwindow.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void init();
private:
Ui::MainWindowClass ui;
MineMap m_minemap;
int offsetx;
int offsety;
QTimer* m_time;
QTimer* m_floatTime;
protected:
void paintEvent(QPaintEvent*);
void mousePressEvent(QMouseEvent* e);//鼠标点击事件
public slots:
void onTimer();
void onFloatTimer();
void action_easy();
void action_intermediate();
void action_advance();
};
该类主要负责绘制操作
函数功能介绍
数字含义介绍
for (int i = 0; i < m_minemap.m_x; i++)
{
for (int j = 0; j < m_minemap.m_y; j++)
{
if (m_minemap.m_map[i][j] > 90)
painter.drawPixmap(i * 20 + offsetx, j * 20 + 40 + offsety, bmpmap, 200, 0, 20, 20);
if (m_minemap.m_map[i][j] >= 0 && m_minemap.m_map[i][j] <= 8)
painter.drawPixmap(i * 20 + offsetx, j * 20 + 40 + offsety, bmpmap, m_minemap.m_map[i][j] * 20, 0, 20, 20);
if (m_minemap.m_map[i][j] == -1)
painter.drawPixmap(i * 20 + offsetx, j * 20 + 40 + offsety, bmpmap, 180, 0, 20, 20);
if (m_minemap.m_map[i][j] == -2)
painter.drawPixmap(i * 20 + offsetx, j * 20 + 40 + offsety, bmpmap, 240, 0, 20, 20);
if (m_minemap.m_map[i][j] >= 49 && m_minemap.m_map[i][j] <= 58)
painter.drawPixmap(i * 20 + offsetx, j * 20 + 40 + offsety, bmpmap, 220, 0, 20, 20);
if (m_minemap.m_map[i][j] < -10)
painter.drawPixmap(i * 20 + offsetx, j * 20 + 40 + offsety, bmpmap, 0, 0, 20, 20);
}
}
处理规则:目前的规则是开始的时候99代表该区域为雷,其他部分为100-108,值-100代表该地方周围的雷数,例如101代表周围四周有一个雷。插旗后-50,即范围为50-58,-1代表该区域为雷,-2代表用户排的该区域的雷为错误的。由于存在双击操作,目前的双击操作如果该区域没有点开的话会有个弹起的操作,所以目前是减去120,即<-10的时候会显示弹起的图片。
左键操作事件
bool MineMap::onLeftDown(int x, int y)
{
if (m_win == 0 || m_win == 2)
return false;
if (x < 0 || x >= m_x || y < 0 || y >= m_y || (m_map[x][y] >= 0 && m_map[x][y] <= 8))
return false;
if (m_map[x][y] >= 51 && m_map[x][y] <= 58)
{
m_map[x][y] += 50;
m_mineCount++;
return true;
}
if (m_map[x][y] >= 101 && m_map[x][y] <= 108)
{
m_map[x][y] -= 100;
isWin();
return true;
}
if (m_map[x][y] == 100)
{
m_map[x][y] -= 100;
onLeftDown(x - 1, y - 1);
onLeftDown(x, y - 1);
onLeftDown(x + 1, y - 1);
onLeftDown(x - 1, y);
onLeftDown(x + 1, y);
onLeftDown(x - 1, y + 1);
onLeftDown(x, y + 1);
onLeftDown(x + 1, y + 1);
}
if (m_map[x][y] == 99)
{
//重新开始
m_map[x][y] = -2;
for (int i = 0; i < m_x; i++)
{
for (int j = 0; j < m_y; j++)
{
if (m_map[i][j] == 99)
{
m_map[i][j] = -1;
}
if (m_map[i][j] == 49)
{
m_map[i][j] = -1;
}
if (m_map[i][j] >= 50 && m_map[i][j] <= 58)
{
m_map[i][j] = -2;
}
}
}
m_win = 0;
}
return true;
}
点击区域后进行判断该区域是什么值,如果是大于100的话直接减去100,如果等于100的话说明该区域附件都不是雷,目前利用递归操作将四周都点开。
左右键同时点击事件
int MineMap::onLeftRightDown(int x, int y)
{
//必须是已经被点开的
if (x >= m_x || x < 0 || y >= m_y || y < 0 || m_map[x][y] >= 9 || m_map[x][y] < 0)
return 0;
if (m_win == 0 || m_win == 2)
return 0;
//99代表雷,周围8个区域必须被插对棋子旗子才能直接被填满
m_floatX = x;
m_floatY = y;
int count = 0;
int cows[8];
int cols[8];
memset(cows, -1, 8 * sizeof(int));
memset(cols, -1, 8 * sizeof(int));
for (int m = -1; m < 2; m++)
{
for (int n = -1; n < 2; n++)
{
int j = m + x;
int k = n + y;
//先判断是否都被点开或者被插上棋子
if (m_map[j][k] > 90 &&(j != x || k != y))
{
cows[count] = j;
cols[count] = k;
count++;
}
}
}
if (count == m_map[x][y])
{
for (int i = 0;; i++)
{
if (cows[i] == -1)
break;
m_map[cows[i]][cols[i]] -= 100;
if (m_map[cows[i]][cols[i]] == -1)
{
//重新开始
m_map[cows[i]][cols[i]] = -2;
for (int i = 0; i < m_x; i++)
{
for (int j = 0; j < m_y; j++)
{
if (m_map[i][j] == 99)
{
m_map[i][j] = -1;
}
if (m_map[i][j] == 49)
{
m_map[i][j] = -1;
}
if (m_map[i][j] >= 50 && m_map[i][j] <= 58)
{
m_map[i][j] = -2;
}
}
}
m_win = 0;
return 3;
}
}
return 1;
}
else
{
for (int m = -1; m < 2; m++)
{
for (int n = -1; n < 2; n++)
{
int j = m + x;
int k = n + y;
//先判断是否都被点开或者被插上棋子
if (m_map[j][k] > 90)
m_map[j][k] -= 120;
}
}
}
//如果周围有没有被点开或者没有被插上棋子的就进行浮动效果
return 2;
}
目前的操作是双击该区域的话会先进行判断该区域的雷数和为点开的数量是否相等,不相等的话只是弹出,相等的话会帮助用户点开,弹出的时间在主界面进行设置。
void MainWindow::onFloatTimer()
{
m_minemap.floatCoordinateRestore();
update();
m_floatTime->stop();
}