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 }
按鍵的遮蔽設定:
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(); 6j = 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 }