1. 程式人生 > >使用Qt開發中國象棋(五):走棋

使用Qt開發中國象棋(五):走棋

        在整個遊戲中,走棋是最複雜的部分,也是最麻煩的。開發這個程式,大概花了三分之一的時間在這個上面。在這個遊戲中,走棋是通過滑鼠點選事件來完成的,當然也可以通過拖動滑鼠事件來弄。假設我們自己先走,整個走棋的邏輯如下:

        (1)點選滑鼠。

        (2)ChessBoard類呼叫mousePressEvent並激發doMove訊號。在該事件處理函式中,我們只處理滑鼠左鍵單擊事件。

void ChessBoard::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        int row = 0;
        int column = 0;
        getPixmapIndex(event->pos().x(), event->pos().y(), row, column);

        int sq = getChessmanIndex(row, column, fliped);
        emit doMove(sq);
    }
}

        (3)呼叫MainWindow的doMove槽。如果該局未結束,則可以走棋。此外還會根據遊戲的模式和走棋方進行判斷。

void MainWindow::doMove(int index)
{
    if (chessHandler->getCurrentTurn() == g_gameSettings.getCompetitorSide() &&
        g_gameSettings.getGameType() != COMPITITOR_HUMAN)
    {
        return;
    }

    if (chessHandler->getGameResult() == -1)
    {
        chessHandler->doMove(index);
    }
}

        (4)呼叫邏輯層ChessHandle的doMove方法。該方法包括死棋檢測,重複局面檢測,走法合理性判斷,生成走棋字串,更新zobrist值等等。激發refreshGame訊號。裡面的更多細節,限於篇幅,就不列舉了,後面會介紹箇中細節。

void ChessHandler::doMove(int index)
{
    assert(index >= 0x33 && index <= 0xcb);
    int fromPos = SRC(currentMoveInfo.move);
    int toPos = DST(currentMoveInfo.move);

    if (fromPos == index || toPos == index)
    {
        return;
    }

    bool legal = false;
    if (currentTurn == RED)
    {
        legal = redDoMove(index);
    }
    else
    {
        legal = blackDoMove(index);
    }

    if (legal)
    {
        if (SRC(currentMoveInfo.move) > 0 && DST(currentMoveInfo.move) > 0)
        {
            applyMove();

            if (g_gameSettings.getGameType() == COMPITITOR_MACHINE)
            {
                //電腦走棋
                computerMove();
            }
        }
        else
        {
            //傳送網路訊息
            if (g_gameSettings.getGameType() == COMPITITOR_NETWORK)
            {
                sendMoveInfoMsg();
            }

            emit refreshGame(EVENT_UPDATE_MOVE);
        }
    }
    else
    {
        if (currentMoveInfo.movingChessman > 0)
        {
            emit refreshGame(EVENT_ILLEGAL_MOVE);
        }
    }
}

        (5)MainWindow中呼叫ProcessEvent,根據不同的引數進行不同的處理。如走棋合法會呼叫processUpdateMoveEvent,在該方法中會更新著法列表,顯示走棋的路跡,更新某些按鈕的狀態。否則會呼叫processIllegalMoveEvent,播放提示音。

void MainWindow::processUpdateMoveEvent()
{
    MoveInfo info = chessHandler->getCurrentMoveInfo();
    int gameResult = chessHandler->getGameResult();

    //如果移動了完整的一步,則需要先更新整個棋盤
    if (SRC(info.move) > 0 && DST(info.move) > 0)
    {
        chessBoard->loadPixmap(chessHandler->getChessman());
        addToStepList(info);
        if (gameResult == -1 && g_gameSettings.getStepTime() > 0)
        {
            stepOverCond.wakeAll();
        }
    }

    if (isSameSide(lastMoveInfo.movingChessman, info.movingChessman))
    {
        chessBoard->showMoveRoute(lastMoveInfo.movingChessman, lastMoveInfo.move, false);
    }
    chessBoard->showMoveRoute(info.movingChessman, info.move, true);
    chessBoard->update();

    playTipSound(info, gameResult);

    if (gameResult != -1)
    {
        if (gameResult != 0)
        {
            updateGeneralDisplay(gameResult);
        }

        showResult(gameResult);
    }

    lastMoveInfo = info;
    gameOver = gameResult != -1;
    ui->actionUndo->setEnabled(chessHandler->getLstMoveInfo().size() > 0);
}

void MainWindow::processIllegalMoveEvent()
{
    QSound::play(AUDIO_ILLEGAL);
}
        整個走棋基本就是這個邏輯,也不是很複雜。中國象棋真正複雜的地方是機器走棋的演算法,在這個遊戲中,沒有涉及到這麼複雜的演算法。曾經以為自己堅持不下來,畢竟要花兩百多個小時的時間,還是很需要耐心的。心情浮躁的話,很可能堅持不下來。當我硬著頭皮做完走棋的功能,發現已經完成了一半,如果半途而廢的話,前面的努力就全白費了。想到這裡,便一鼓作氣的完成了剩下的工作。雖說做這個東西沒什麼用,但至少也算是考驗一下自己的意志和耐性吧。個人感覺這個東西太需要耐心了,不能太浮躁。
原始碼下載連結:http://download.csdn.net/detail/zxywd/9172917