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即可開啟
退出對話框:
為工程添加會話框,新建->
修改類名
默認添加到工程中
設計繪畫UI如下:
添加功能:
ui中分別添加兩個按鍵,並右鍵=>轉到槽……,添加槽函數
void ExitShow::on_confirmBtn_clicked() { reject(); // 確認 } void ExitShow::on_cancelBtn_clicked() { accept(); // 取消 }
會話類代碼中,填充背景和提示信息
void ExitShow::paintEvent(QPaintEvent *e) { QPainter painter(this); QFont font; QPixmap exitShowPic(QString(":/images/backGround5.png")); painter.drawPixmap(0,0, this->width(),this->height(),exitShowPic); painter.drawRect(0,0,this->width()-1,this->height()-1); font = ui->confirmBtn->font(); painter.setFont(font); QRect textRect(0,0,this->width(), this->height()*2/3); painter.drawText(textRect, Qt::AlignCenter, "你確定要退出嗎?"); }
在主窗口退出按鍵中,槽函數實現如下:
定義一個自定義的會話框實例,然后設置為模態模式(模態模式只能處理當前窗口,主窗口無效),進行顯示后進入d.exec()循環當中,當會話框中的按鍵被按下,觸發reject()或accpt()時,退出循環,當是確認時,觸發reject(), exec()退出值是QDialog::Rejected。
void FiveChess::on_exitBtn_clicked() { ExitShow d(this); d.setModal(true); d.show(); if (d.exec() == QDialog::Rejected) { state = EXIT; // 退出游戲過程,結束循環 qApp->quit(); // 退出app,程序中存在死循環,會造成后台無法退出 } }
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 }