1. 程式人生 > 實用技巧 >QT:完整的人機五子棋設計(三)執行

QT:完整的人機五子棋設計(三)執行

QT Creator5.9.9

2.3遊戲進行的過程

整個遊戲博弈過程用定義的狀態來判定執行流程。enum RunState {NO_RUNNING, START, RUNNING, GAME_OVER,RESTART,EXIT};

2.3.1整體流程

遊戲執行整體就是一個互奕的過程,當我們執行開始時,就進入開始狀態中迴圈互奕,互奕時當重新開始或判定勝負,就會退出互奕過程。當重新開始,又會進入互奕,此時開始按鈕會被遮蔽。需要新增一個短期的延時,不然會一直佔用資源,造成視窗崩潰。

2.3.2互奕過程

通過一個當前回合變數改變回合,電腦下棋使用AI(初步隨機下棋),下棋前判斷對方是否提出悔棋請求,若有請求,需要對上一回合做回退處理;下完棋後判斷是否五連;如果該回合未結束,則修改回合變數,更換回合;人下棋使用滑鼠點選,回合到來進入等待迴圈,滑鼠按下後,根據自定義條件跳出迴圈,然後根據當前回合棋子型別判斷你是否五連結束遊戲;如果該回合未結束,則修改回合變數,更換回合。

下棋時,棋子的顯示,都是通過修改二維陣列中的棋盤資訊,然後根據資訊重新整理來顯示的。

滑鼠按下的操作,請看前面的隨筆,棋盤的製作過程,其主要是通過滑鼠事件來監控。

要想驗證悔棋過程,還需要在AI下棋前增加一個延時。

2.3.3按鍵功能實現

點選ui檔案進入設計模式,在要實現功能的按鍵上右鍵=>轉到槽……,選擇如下圖所示的clicked()點選事件,確認後在程式碼中會自動建立一個槽函式介面,當滑鼠點選按鈕時,就會執行該函式。

  1 void FiveChess::on_startBtn_clicked()
  2 {
  3 
  4 }

按鍵的遮蔽設定:

startBtn是自己修改的目標名

  1  ui->startBtn->setEnabled(false
); // 修改為true即可開啟

2.3.4AI演算法(隨機位置下棋)

主要要獲取一個隨機的空閒的棋盤座標。

初始化中定義一個隨機計數器qsrand(time(0)); 然後通過多次呼叫qrand()取得隨機數,最後得到一個符合範圍的空閒棋盤座標,修改該棋盤座標資訊,即完成了下棋操作。

所需標頭檔案:#include <QTime>

  1 bool FiveChess::computerDropAI()
  2 {
  3     int i, j;
  4     while(1) {
  5         i = qrand() % chessboard->getchequerNumOfLine();
  6
j = qrand() % chessboard->getchequerNumOfLine(); 7 if (chessboard->getChessInfo(i, j) == ChessBoard::noChess) { 8 break; 9 } 10 } 11 chessboard->setChessInfo(i, j, computerChess); 12 lastChessX = i; 13 lastChessY = j; 14 chessboard->update(); 15 if (chessboard->isGameOver(i, j)) { 16 return true; 17 } 18 return false; 19 }

2.3.5五子相連判斷與處理

如下圖所示,每下一個棋子,有四條直線上的結果需要判斷;

已知的資訊有下棋點的棋盤座標、當前棋子型別。

以落棋點為原點,建立座標軸,就有8個方向上的結果需要考慮。

先考慮獲取單一方向上的連子數,然後獲取直線上連子數,最後獲取所有的結果,判斷五連。

單一方向上的連子數處理如下:

polarityW、polarityY為座標軸上的極性,用來遞進棋盤座標。

x、y:為落棋點棋盤座標。

返回單一方向上相同型別的棋子數。

  1 int ChessBoard::chessNumcalPart(int polarityW, int polarityY, int x, int y)
  2 {
  3     int val = 0;
  4     int W;
  5     int Y;
  6     dropW = x;
  7     dropH = y;
  8     // 當前座標 dropW dropH
  9     for (int i = 1; i < 5; i++) {
 10         W = dropW + i * polarityW;
 11         Y = dropH + i * polarityY;
 12         if (W < 0 || Y < 0 || W >= 15 || Y >= 15) {
 13             break;
 14         }
 15         if(chessInfo[W][Y] == currentChess) {
 16              val++;
 17         } else {
 18             break;
 19         }
 20     }
 21     return val;
 22 }

直線上的棋子數處理

分別取直線上的兩個方向的結果,最終判斷是否五連。注意兩組極性的入參需要是在一條直線上的變化。

  1 bool ChessBoard::chessNumcalAll(int polarityW1, int polarityY1, int polarityW2, int polarityY2, int x, int y)
  2 {
  3     int val = 1;
  4     val += chessNumcalPart(polarityW1, polarityY1, x, y);
  5     val += chessNumcalPart(polarityW2, polarityY2, x, y);
  6     if(val >= 5) {
  7         return true;
  8     }
  9     return false;
 10 }

結果判定:

  1 bool ChessBoard::isGameOver(int x, int y)
  2 {
  3     bool result = false;
  4     // 判斷行, 引數為判斷方向上變化的極性
  5     result |= chessNumcalAll(-1, 0, 1, 0, x, y);
  6     // 判斷列
  7     result |= chessNumcalAll(0, -1, 0, 1, x, y);
  8     // 判斷反斜線
  9     result |= chessNumcalAll(-1, 1, 1, -1, x, y);
 10     // 判斷正斜線
 11     result |= chessNumcalAll(-1, -1, 1, 1, x, y);
 12     return result;
 13 }

ChessBoard::isGameOver(iint, int)是棋盤類的方法,由於電腦和人下棋時刻不是在一塊,電腦在互奕回合中判斷結果,人在棋盤落子時判斷結果,但是最終遊戲結束時的處理是由主視窗統一處理的,所以把遊戲結束的處理函式定義主視窗類中的槽函式,而棋盤類中自定義結束訊號;電腦獲勝時可以直接呼叫槽函式,人獲勝時,發射結束訊號;如此電腦和人獲勝都是同一函式處理。

槽函式實現:

  1 // 定義
  2 class FiveChess : public QWidget
  3 {
  4 	……
  5 private slots:
  6 	 void GameOver();
  7 	……
  8  9 // 實現
 10 void FiveChess::GameOver()
 11 {
 12     gameOverFlag = true;
 13     qDebug() << "game is over!!!";
 14     resultLabel->setVisible(true);
 15     timeLine->stop();
 16 
 17     if (currentRound == computer) {
 18         GameOverPrompt(computer);
 19         ui->msgLabel->setText("電腦獲勝!");
 20         if (computerChess == ChessBoard::blackChess) {
 21            qDebug() << "win is computer by black chess!!";
 22         } else {
 23            qDebug() << "win is computer by white chess!!";
 24         }
 25     } else {
 26         GameOverPrompt(player);
 27         ui->msgLabel->setText("獲得勝利!");
 28         if (playerChess == ChessBoard::blackChess) {
 29            qDebug() << "win is player by black chess!!";
 30         } else {
 31            qDebug() << "win is player by white chess!!";
 32         }
 33     }
 34 
 35     state = GAME_OVER;
 36     soundList->setCurrentIndex(3);
 37     music->play();
 38 }

棋盤類訊號定義:

  1 class ChessBoard : public QWidget
  2 {
  3     ……
  4 signals:
  5     void boardGameOver();
  6     ……
  7  8 

訊號不需要實現具體過程,只需在合適的地方發射訊號即可。

訊號發射:emit 訊號名(); // 如果有引數,需要帶入實參

  1 if(isGameOver(dropW, dropH))
  2 {
  3     emit boardGameOver();
  4 }

2.3.6延時函式

  1 void FiveChess::myMSleep(unsigned int msec)
  2 {
  3     QTime _Timer = QTime::currentTime().addMSecs(msec);
  4     while( QTime::currentTime() < _Timer )
  5         QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
  6 }