1. 程式人生 > >Qt小遊戲開發:連連看

Qt小遊戲開發:連連看

繼續更新~ 原來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;

};
3 遊戲邏輯
(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(); // 選中難度開始

};
5 遊戲互動邏輯

(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:連連看