继续更新~ 原来csdn博客用底部的批量导入图片就不会失真
预览
步骤
项目结构
包括:游戏逻辑类,界面类,资源
1 定义游戏数据结构
// ------------ 全局变量 ------------ //
// 最大行和列数
const int MAX_ROW = 15;
const int MAX_COL = 20;
// 游戏可玩图片数量
const int MAX_ICON = 25;
// 游戏状态
enum GameStatus
{
PLAYING,
PAUSE,
WIN,
OVER
};
// 游戏难度,不同的方块数
const int kBasicNum = MAX_ROW * MAX_COL * 0.3;
const int kMediumNum = MAX_ROW * MAX_COL * 0.7;
const int kHardNum = MAX_ROW * MAX_COL;
enum GameLevel
{
BASIC,
MEDIUM,
HARD
};
// 用于绘制线段的连接点(其实应该统一用这个结构体的,代码有待优化)
struct PaintPoint
{
PaintPoint(int _x, int _y) : x(_x), y (_y) {}
int x;
int y;
};
// -------------------------------- //
一些状态,和结构体
2 创建游戏逻辑类
class GameModel
{
public:
GameModel();
virtual ~GameModel();
public:
void startGame(); // 开始游戏
void startGame(GameLevel level);
int *getGameMap(); // 获得地图
GameStatus checkGameStatus(); // 判断获得游戏状态,是否输赢
bool linkTwoTiles(int srcX, int srcY, int dstX,int dstY); // 连接起点和终点方块,连接是否成功
bool isFrozen(); // 判断是否已经成为了僵局
bool isWin(); // 检查游戏是否结束
int *getHint(); // 获得提示
std::vector<PaintPoint> paintPoints; // 用于绘制的点
// 游戏状态和难度
GameStatus gameStatus;
GameLevel gameLevel;
private:
// 游戏地图,存储方块,0表示消失,1-其他数字表示图片标号
int *gameMap;
// 游戏提示,存储2个点
int *hintArray;
// 判断起点到终点的方块是否可以连接消除
bool isCanLink(int srcX, int srcY, int dstX, int dstY);
bool canLinkDirectly(int srcX, int srcY, int dstX, int dstY);
bool canLinkWithOneCorner(int srcX, int srcY, int dstX, int dstY);
bool canLinkWithTwoCorner(int srcX, int srcY, int dstX, int dstY);
// 提示模式还是连接模式判断
bool isFrozenMode;
};
(1)开始游戏初始化
void GameModel::startGame(GameLevel level)
{
// 用C的方式初始化数组,怀旧一下~
gameMap = (int *)malloc(sizeof(int) * MAX_ROW * MAX_COL);
memset(gameMap, 0, MAX_ROW * MAX_COL);
for (int i = 0; i < MAX_ROW * MAX_COL; i++) // 必须这样赋初值,memset容易出问题
gameMap[i] = 0;
hintArray = (int *)malloc(sizeof(int) * 4);
memset(hintArray, 0, 4);
for (int i = 0; i < 4; i++)
hintArray[i] = -1;
gameStatus = PLAYING;
gameLevel = level;
int gameLevelNum;
switch (gameLevel)
{
case BASIC:
gameLevelNum = kBasicNum;
break;
case MEDIUM:
gameLevelNum = kMediumNum;
break;
case HARD:
gameLevelNum = kHardNum;
}
// 填充方块标号
int iconID = 0;
for(int i = 0; i < gameLevelNum; i += 2)
{
// 每次填充连着的两个,图片用尽了就循环
gameMap[i] = iconID % MAX_ICON + 1;
gameMap[i + 1] = iconID % MAX_ICON + 1;
iconID++;
}
// 打乱方块
srand((unsigned)time(0));
for(int i = 0; i < MAX_ROW * MAX_COL; i++)
{
int randomID = rand() % (MAX_ROW * MAX_COL);
std::swap(gameMap[i], gameMap[randomID]);
}
// 初始化判断模式
isFrozenMode = false;
// 初始化绘制点
paintPoints.clear();
}
- 随机布置方块
- 打乱方块
(3)连接方块判定
// 最重要的判断连接算法
bool GameModel::canLinkDirectly(int srcX, int srcY, int dstX, int dstY)
{
// 竖线
if (srcX == dstX)
{
if (srcY > dstY)
std::swap(srcY, dstY);
for (int y = srcY + 1; y < dstY; y++)
if (gameMap[MAX_COL * y + srcX])
return false;
if (!isFrozenMode) // 这里有坑,注意了
{
// 记录点和路线
PaintPoint p1(srcX, srcY), p2(dstX, dstY);
paintPoints.clear();
paintPoints.push_back(p1);
paintPoints.push_back(p2);
}
return true;
}
// 横线
if (srcY == dstY)
{
if (srcX > dstX)
std::swap(srcX, dstX);
for (int x = srcX + 1; x < dstX; x++)
if (gameMap[MAX_COL * srcY + x])
return false;
if (!isFrozenMode)
{
PaintPoint p1(srcX, srcY), p2(dstX, dstY);
paintPoints.clear();
paintPoints.push_back(p1);
paintPoints.push_back(p2);
}
return true;
}
return false;
}
bool GameModel::canLinkWithOneCorner(int srcX, int srcY, int dstX, int dstY)
{
if (srcX > dstX)
{
// 统一化,方便后续处理
std::swap(srcX, dstX);
std::swap(srcY, dstY);
}
// 先确定拐点,再确定直连线路,2种情况,4个点,每种情况逐个试,所以多个if顺序执行
if (dstY > srcY)
{
if (gameMap[srcY * MAX_COL + dstX] == 0)
{
// 右上角
if (canLinkDirectly(srcX, srcY, dstX, srcY) && canLinkDirectly(dstX, srcY, dstX, dstY))
{
// 只有连接模式才记录点
if (!isFrozenMode)
{
PaintPoint p1(srcX, srcY), p2(dstX, srcY), p3(dstX, dstY);
paintPoints.clear();
paintPoints.push_back(p1);
paintPoints.push_back(p2);
paintPoints.push_back(p3);
}
return true;
}
}
if (gameMap[dstY * MAX_COL + srcX] == 0)
{
// 左下角
if (canLinkDirectly(srcX, srcY, srcX, dstY) && canLinkDirectly(srcX, dstY, dstX, dstY))
{
if (!isFrozenMode)
{
PaintPoint p1(srcX, srcY), p2(srcX, dstY), p3(dstX, dstY);
paintPoints.clear();
paintPoints.push_back(p1);
paintPoints.push_back(p2);
paintPoints.push_back(p3);
}
return true;
}
}
}
else
{
if (gameMap[dstY * MAX_COL + srcX] == 0)
{
// 左上角
if (canLinkDirectly(srcX, srcY, srcX, dstY) && canLinkDirectly(srcX, dstY, dstX, dstY))
{
if (!isFrozenMode)
{
PaintPoint p1(srcX, srcY), p2(srcX, dstY), p3(dstX, dstY);
paintPoints.clear();
paintPoints.push_back(p1);
paintPoints.push_back(p2);
paintPoints.push_back(p3);
}
return true;
}
}
if (gameMap[srcY * MAX_COL + dstX] == 0)
{
// 右下角
if (canLinkDirectly(srcX, srcY, dstX, srcY) && canLinkDirectly(dstX, srcY, dstX, dstY))
{
if (!isFrozenMode)
{
PaintPoint p1(srcX, srcY), p2(dstX, srcY), p3(dstX, dstY);
paintPoints.clear();
paintPoints.push_back(p1);
paintPoints.push_back(p2);
paintPoints.push_back(p3);
}
return true;
}
}
}
return false;
}
bool GameModel::canLinkWithTwoCorner(int srcX, int srcY, int dstX, int dstY)
{
if (srcX > dstX)
{
// 统一化,方便后续处理
std::swap(srcX, dstX);
std::swap(srcY, dstY);
}
// 两种情况,横向垂线和竖向垂线,以src点作为基准遍历,双折线由直线和一个拐点的折线构成
// 常规情况
for (int y = 0; y < MAX_ROW; y++)
{
if (y != srcY && y != dstY)
{
if (gameMap[y * MAX_COL + srcX] == 0
&& canLinkDirectly(srcX, srcY, srcX, y)
&& canLinkWithOneCorner(srcX, y, dstX, dstY))
{
if (!isFrozenMode)
{
PaintPoint p1(srcX, srcY), p2(srcX, y), p3(dstX, y), p4(dstX, dstY);
paintPoints.clear();
paintPoints.push_back(p1);
paintPoints.push_back(p2);
paintPoints.push_back(p3);
paintPoints.push_back(p4);
}
return true;
}
}
}
for (int x = 0; x < MAX_COL; x++)
{
if (x != srcX && x != dstX)
{
if (gameMap[srcY * MAX_COL + x] == 0
&& canLinkDirectly(srcX, srcY, x, srcY)
&& canLinkWithOneCorner(x, srcY, dstX, dstY))
{
if (!isFrozenMode)
{
PaintPoint p1(srcX, srcY), p2(x, srcY), p3(x, dstY), p4(dstX, dstY);
paintPoints.clear();
paintPoints.push_back(p1);
paintPoints.push_back(p2);
paintPoints.push_back(p3);
paintPoints.push_back(p4);
}
return true;
}
}
}
// 边缘情况,从外边缘连接,注意方块不一定在边缘,(分开写便于记录路径)
if ((srcX == 0 || gameMap[srcY * MAX_COL + 0] == 0 && canLinkDirectly(srcX, srcY, 0, srcY))
&& (dstX == 0 || gameMap[dstY * MAX_COL + 0] == 0 && canLinkDirectly(0, dstY, dstX, dstY)))
{
// 左
if (!isFrozenMode)
{
PaintPoint p1(srcX, srcY), p2(-1, srcY), p3(-1, dstY), p4(dstX, dstY);
paintPoints.clear();
paintPoints.push_back(p1);
paintPoints.push_back(p2);
paintPoints.push_back(p3);
paintPoints.push_back(p4);
}
return true;
}
if ((srcX == MAX_COL - 1 || gameMap[srcY * MAX_COL + MAX_COL - 1] == 0 && canLinkDirectly(srcX, srcY, MAX_COL - 1, srcY))
&& (dstX == MAX_COL - 1 || gameMap[dstY * MAX_COL + MAX_COL - 1] == 0 && canLinkDirectly(MAX_COL - 1, dstY, dstX, dstY)))
{
// 右
if (!isFrozenMode)
{
PaintPoint p1(srcX, srcY), p2(MAX_COL, srcY), p3(MAX_COL, dstY), p4(dstX, dstY);
paintPoints.clear();
paintPoints.push_back(p1);
paintPoints.push_back(p2);
paintPoints.push_back(p3);
paintPoints.push_back(p4);
}
return true;
}
if ((srcY == 0 || gameMap[srcX] == 0 && canLinkDirectly(srcX, srcY, srcX, 0))
&& (dstY == 0 || gameMap[dstX] == 0 && canLinkDirectly(dstX, 0, dstX, dstY)))
{
// 上
if (!isFrozenMode)
{
PaintPoint p1(srcX, srcY), p2(srcX, -1), p3(dstX, -1), p4(dstX, dstY);
paintPoints.clear();
paintPoints.push_back(p1);
paintPoints.push_back(p2);
paintPoints.push_back(p3);
paintPoints.push_back(p4);
}
return true;
}
if ((srcY == MAX_ROW - 1 || gameMap[(MAX_ROW - 1) * MAX_COL + srcX] == 0 && canLinkDirectly(srcX, srcY, srcX, MAX_ROW - 1))
&& (dstY == MAX_ROW - 1 || gameMap[(MAX_ROW - 1) * MAX_COL + dstX] == 0 && canLinkDirectly(dstX, MAX_ROW - 1, dstX, dstY)))
{
// 下
if (!isFrozenMode)
{
PaintPoint p1(srcX, srcY), p2(srcX, MAX_ROW), p3(dstX, MAX_ROW), p4(dstX, dstY);
paintPoints.clear();
paintPoints.push_back(p1);
paintPoints.push_back(p2);
paintPoints.push_back(p3);
paintPoints.push_back(p4);
}
return true;
}
return false;
}
bool GameModel::isCanLink(int srcX, int srcY, int dstX, int dstY)
{
// 首先判断点击的两个方块不是同一个不是空,且方块相同
// 判断方块是否可以连,可用于实际的连接消除和提示消除
// x表示横向索引,y表示纵向索引,从0开始
// 分3种情况往下找,每一种都可以用前面简单情况组合找到一种情况可以连通就返回true,并选用这种连接情况
if (gameMap[srcY * MAX_COL + srcX] == 0 || gameMap[dstY * MAX_COL + dstX] == 0)
return false;
if (srcX == dstX && srcY == dstY)
return false;
if(gameMap[MAX_COL * srcY + srcX] != gameMap[MAX_COL * dstY + dstX])
return false;
// 情况1:横向或者竖向可以直线连通
if (canLinkDirectly(srcX, srcY, dstX, dstY))
return true;
// 情况2:一次拐弯可以连通
if (canLinkWithOneCorner(srcX, srcY, dstX, dstY))
return true;
// 情况3:两次拐弯可以连通
if (canLinkWithTwoCorner(srcX, srcY, dstX, dstY))
return true;
return false;
}
// 点击方块进行连接操作
bool GameModel::linkTwoTiles(int srcX, int srcY, int dstX, int dstY)
{
// 成功连接就返回true否则false用于GUI里面判断
if(isCanLink(srcX, srcY, dstX, dstY))
{
// 值重置
gameMap[MAX_COL * srcY + srcX] = 0;
gameMap[MAX_COL * dstY + dstX] = 0;
return true;
}
return false;
}
示意图:
方块连接判定是最重要的算法,主要分成直线连接、一个拐点折线、两个拐点折线,三种连接情况进行讨论,还要注意一些边缘情况。
(4)判断是否形成僵局
bool GameModel::isFrozen()
{
// 暴力法,所有方块两两判断是否可以连接
// 每次消除后做一次判断
// 其实在这个过程中记录提示
for (int i = 0; i < MAX_ROW * MAX_COL - 1; i++)
for( int j = i + 1; j < MAX_ROW * MAX_COL; j++)
{
int srcX = i % MAX_COL;
int srcY = i / MAX_COL;
int dstX = j % MAX_COL;
int dstY = j / MAX_COL;
// 只要能找到可以连接的就不为僵局
isFrozenMode = true;
if (isCanLink(srcX, srcY, dstX, dstY))
{
// 记录第一个可以连接的hint
hintArray[0] = srcX;
hintArray[1] = srcY;
hintArray[2] = dstX;
hintArray[3] = dstY;
isFrozenMode = false;
return false;
}
}
isFrozenMode = false;
return true;
}
- 该方法略暴力,可以采用dfs进行剪枝
- 在搜索过程中存储用于连接的提示
(5)判断输赢
bool GameModel::isWin()
{
for (int i = 0; i < MAX_ROW * MAX_COL; i++)
{
if (gameMap[i])
return false;
}
gameStatus = WIN;
return true;
}
4 游戏界面类
// 继承自button,存储坐标值
struct IconButton : QPushButton
{
public:
IconButton(QWidget *parent = Q_NULLPTR) :
QPushButton(parent),
xID(-1),
yID(-1)
{
}
int xID; // x 坐标
int yID; // y 坐标
};
class MainGameWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainGameWindow(QWidget *parent = 0);
virtual ~MainGameWindow();
virtual bool eventFilter(QObject *watched, QEvent *event); // 事件过滤
private:
Ui::MainGameWindow *ui;
GameModel *game; // 游戏模型
IconButton *imageButton[MAX_ROW * MAX_COL]; // 图片button数组
QTimer *gameTimer; // 游戏计时器
IconButton *preIcon, *curIcon; // 记录点击的icon
bool isLinking; // 维持一个连接状态的标志
QMediaPlayer *audioPlayer; // 音乐播放器
void initGame(GameLevel level); // 初始化游戏
private slots:
void onIconButtonPressed(); // icon点击到响应
void gameTimerEvent(); // 游戏计时回调
void handleLinkEffect(); // 实现连接效果
void on_hintBtn_clicked(); // 提示按钮
void on_robot_btn_clicked(); // 机器人自动刷
void createGameWithLevel(); // 选中难度开始
};
(1)开始游戏
void MainGameWindow::initGame(GameLevel level)
{
// 启动游戏
game = new GameModel;
game->startGame(level);
// 添加button
for(int i = 0; i < MAX_ROW * MAX_COL; i++)
{
imageButton[i] = new IconButton(this);
imageButton[i]->setGeometry(kLeftMargin + (i % MAX_COL) * kIconSize, kTopMargin + (i / MAX_COL) * kIconSize, kIconSize, kIconSize);
// 设置索引
imageButton[i]->xID = i % MAX_COL;
imageButton[i]->yID = i / MAX_COL;
imageButton[i]->show();
if (game->getGameMap()[i])
{
// 有方块就设置图片
QPixmap iconPix;
QString fileString;
fileString.sprintf(":/res/image/%d.png", game->getGameMap()[i]);
iconPix.load(fileString);
QIcon icon(iconPix);
imageButton[i]->setIcon(icon);
imageButton[i]->setIconSize(QSize(kIconSize, kIconSize));
// 添加按下的信号槽
connect(imageButton[i], SIGNAL(pressed()), this, SLOT(onIconButtonPressed()));
}
else
imageButton[i]->hide();
}
// 进度条
ui->timeBar->setMaximum(kGameTimeTotal);
ui->timeBar->setMinimum(0);
ui->timeBar->setValue(kGameTimeTotal);
// 游戏计时器
gameTimer = new QTimer(this);
connect(gameTimer, SIGNAL(timeout()), this, SLOT(gameTimerEvent()));
gameTimer->start(kGameTimerInterval);
// 连接状态值
isLinking = false;
// 播放背景音乐(QMediaPlayer只能播放绝对路径文件),确保res文件在程序执行文件目录里而不是开发目录
audioPlayer = new QMediaPlayer(this);
QString curDir = QCoreApplication::applicationDirPath(); // 这个api获取路径在不同系统下不一样,mac 下需要截取路径
QStringList sections = curDir.split(QRegExp("[/]"));
QString musicPath;
for (int i = 0; i < sections.size() - 3; i++)
musicPath += sections[i] + "/";
audioPlayer->setMedia(QUrl::fromLocalFile(musicPath + "res/sound/backgrand.mp3"));
audioPlayer->play();
}
- 根据游戏模型数据创建方块地图
- 初始化一些音频和定时器
(2)点击方块
void MainGameWindow::onIconButtonPressed()
{
// 如果当前有方块在连接,不能点击方块
// 因为涉及到多线,可能还要维护队列,有点复杂,就先这么简单处理一下
if (isLinking)
{
// 播放音效
QSound::play(":/res/sound/release.wav");
return;
}
// 记录当前点击的icon
curIcon = dynamic_cast<IconButton *>(sender());
if(!preIcon)
{
// 播放音效
QSound::play(":/res/sound/select.wav");
// 如果单击一个icon
curIcon->setStyleSheet(kIconClickedStyle);
preIcon = curIcon;
}
else
{
if(curIcon != preIcon)
{
// 如果不是同一个button就都标记,尝试连接
curIcon->setStyleSheet(kIconClickedStyle);
if(game->linkTwoTiles(preIcon->xID, preIcon->yID, curIcon->xID, curIcon->yID))
{
// 锁住当前状态
isLinking = true;
// 播放音效
QSound::play(":/res/sound/pair.wav");
// 重绘
update();
// 延迟后实现连接效果
QTimer::singleShot(kLinkTimerDelay, this, SLOT(handleLinkEffect()));
// 每次检查一下是否僵局
if (game->isFrozen())
QMessageBox::information(this, "oops", "dead game");
// 检查是否胜利
if (game->isWin())
QMessageBox::information(this, "great", "you win");
int *hints = game->getHint();
}
else
{
// 播放音效
QSound::play(":/res/sound/release.wav");
// 消除失败,恢复
preIcon->setStyleSheet(kIconReleasedStyle);
curIcon->setStyleSheet(kIconReleasedStyle);
// 指针置空,用于下次点击判断
preIcon = NULL;
curIcon = NULL;
}
}
else if(curIcon == preIcon)
{
// 播放音效
QSound::play(":/res/sound/release.wav");
preIcon->setStyleSheet(kIconReleasedStyle);
curIcon->setStyleSheet(kIconReleasedStyle);
preIcon = NULL;
curIcon = NULL;
}
}
}
点击方块,根据方块索引进行游戏逻辑的更新
(3)绘制连接线
bool MainGameWindow::eventFilter(QObject *watched, QEvent *event)
{
// 重绘时会调用,可以手动调用
if (event->type() == QEvent::Paint)
{
QPainter painter(ui->centralWidget);
QPen pen;
QColor color(rand() % 256, rand() % 256, rand() % 256);
pen.setColor(color);
pen.setWidth(5);
painter.setPen(pen);
QString str;
for (int i = 0; i < game->paintPoints.size(); i++)
{
PaintPoint p = game->paintPoints[i];
str += "x:" + QString::number(p.x) + "y:" + QString::number(p.y) + "->";
}
// qDebug() << str;
// 连接各点画线(注,qt中用标砖vector的size好像有点问题,需要类型转换,否则溢出)
for (int i = 0; i < int(game->paintPoints.size()) - 1; i++)
{
PaintPoint p1 = game->paintPoints[i];
PaintPoint p2 = game->paintPoints[i + 1];
// 拿到各button的坐标,注意边缘点坐标
QPoint btn_pos1;
QPoint btn_pos2;
// p1
if (p1.x == -1)
{
btn_pos1 = imageButton[p1.y * MAX_COL + 0]->pos();
btn_pos1 = QPoint(btn_pos1.x() - kIconSize, btn_pos1.y());
}
else if (p1.x == MAX_COL)
{
btn_pos1 = imageButton[p1.y * MAX_COL + MAX_COL - 1]->pos();
btn_pos1 = QPoint(btn_pos1.x() + kIconSize, btn_pos1.y());
}
else if (p1.y == -1)
{
btn_pos1 = imageButton[0 + p1.x]->pos();
btn_pos1 = QPoint(btn_pos1.x(), btn_pos1.y() - kIconSize);
}
else if (p1.y == MAX_ROW)
{
btn_pos1 = imageButton[(MAX_ROW - 1) * MAX_COL + p1.x]->pos();
btn_pos1 = QPoint(btn_pos1.x(), btn_pos1.y() + kIconSize);
}
else
btn_pos1 = imageButton[p1.y * MAX_COL + p1.x]->pos();
// p2
if (p2.x == -1)
{
btn_pos2 = imageButton[p2.y * MAX_COL + 0]->pos();
btn_pos2 = QPoint(btn_pos2.x() - kIconSize, btn_pos2.y());
}
else if (p2.x == MAX_COL)
{
btn_pos2 = imageButton[p2.y * MAX_COL + MAX_COL - 1]->pos();
btn_pos2 = QPoint(btn_pos2.x() + kIconSize, btn_pos2.y());
}
else if (p2.y == -1)
{
btn_pos2 = imageButton[0 + p2.x]->pos();
btn_pos2 = QPoint(btn_pos2.x(), btn_pos2.y() - kIconSize);
}
else if (p2.y == MAX_ROW)
{
btn_pos2 = imageButton[(MAX_ROW - 1) * MAX_COL + p2.x]->pos();
btn_pos2 = QPoint(btn_pos2.x(), btn_pos2.y() + kIconSize);
}
else
btn_pos2 = imageButton[p2.y * MAX_COL + p2.x]->pos();
// 中心点
QPoint pos1(btn_pos1.x() + kIconSize / 2, btn_pos1.y() + kIconSize / 2);
QPoint pos2(btn_pos2.x() + kIconSize / 2, btn_pos2.y() + kIconSize / 2);
painter.drawLine(pos1, pos2);
}
return true;
}
else
return QMainWindow::eventFilter(watched, event);
}
- 注意窗口的事件过滤和分发
- 绘制的时候要注意边界情况
(4)提示和自动逻辑
void MainGameWindow::on_robot_btn_clicked()
{
// 初始时不能自动玩
for (int i = 0; i < 4;i++)
if (game->getHint()[i] == -1)
return;
while (game->gameStatus == PLAYING)
{
// 连接生成提示
int srcX = game->getHint()[0];
int srcY = game->getHint()[1];
int dstX = game->getHint()[2];
int dstY = game->getHint()[3];
if(game->linkTwoTiles(srcX, srcY, dstX, dstY))
{
// 播放音效
// QSound::play(":/res/sound/pair.wav");
// 消除成功,隐藏掉
IconButton *icon1 = imageButton[srcY * MAX_COL + srcX];
IconButton *icon2 = imageButton[dstY * MAX_COL + dstX];
icon1->hide();
icon2->hide();
game->paintPoints.clear();
// 重绘
update();
// 检查是否胜利
if (game->isWin())
QMessageBox::information(this, "great", "you win");
// 每次检查一下是否僵局
if (game->isFrozen() && game->gameStatus == PLAYING)
QMessageBox::information(this, "oops", "dead game");
int *hints = game->getHint();
}
}
}
游戏内置上帝模式,可以根据提示显示连接的方块或者自动连接
截图
源码
csdn:
连连看
github:
连连看