QT:完整的人機五子棋設計(一)
1、前言
QT Creator5.9.9
近段時間學習了QT的一些設計基礎,忍不住設計了個五子棋小遊戲專案進行實戰,從最開始的建立,到最後的整個遊戲安裝包,經過磕磕絆絆,最終結果還算滿意。當然作為新手菜鳥,肯定存在一些問題,如果你恰好看到這篇文章,若有看到不當的地方,歡迎提及。
先來看下游戲介面整體效果:
實現的功能有:與電腦對弈(簡單的AI操作)、每步15秒倒計時、玩家資訊顯示、下棋等的提示音、悔棋,重新開始,退出按鍵功能、簡易的資訊提示、介面拖動
2、實現步驟
1、先設計一個棋盤類,即棋盤部件,能夠展現棋盤,滑鼠移動提示下棋點,點選下棋等功能,並位該類提供必要的介面。
2、設計整個遊戲視窗,通過QT設計模式,進行ui設計,如標題 、玩家資訊、按鍵位置、棋盤位置等,為這些功能提供部件支援和佈局,然後在程式碼當中對每個部件進行處理。
3、設計遊戲執行的邏輯,如開始遊戲、遊戲先後手、對弈過程、結果判定、悔棋、重新開始與退出等功能。
4、對遊戲介面進行美化,如新增背景,圖片資訊,修改按鍵樣式,在最後去除視窗邊框。
5、設計圓圈進度條部件類,為遊戲對弈新增進度條時間提示。
6、新增系統提示音,如開始遊戲聲音、落子聲音、結束聲音。
7、升級電腦AI下棋演算法。
8、為工程新增動態庫支援,使用inno setup軟體打包成一個安裝包程式。
2.1棋盤的實現
2.1.1建立部件
整一個棋盤就是一個視窗部件,直接建立一個app專案繼承於QWidget控制元件,此處不使用ui設計,以便最終可以得到該部件的類檔案,方便移植到總視窗中。
工程建立完成後,就得到一個視窗啦!
2.1.2定義棋盤各類邊界和資訊
此處定義的棋盤為15x15大小,此大小使用巨集定義或定義成變數,後邊會經常使用。還需要定義多個間隔變數,如棋盤與邊界的距離、棋格的大小,這樣後期直接初始化中可以隨意修改而不影響棋盤架構。定義列舉enum ChessType {noChess, blackChess, whiteChess};代表棋子型別,定義一塊ChessType型別的二維陣列空間chessInfo[15][15];該空間代表當前棋盤上存在的棋子資訊。在建構函式中為定義的變數賦初值,然後根據下圖示的變數資訊設定視窗的大小,並固定視窗大小。
2.1.3繪製棋盤
QWidget部件具有繪畫事件virtual void paintEvent(QPaintEvent *event),是個虛擬函式,我們的類是由QWidget類繼承而來,能夠對繪畫事件進行重寫,當發生重新繪製或者更新視窗等繪製事件就會響應。
2.1.3.1重寫介面
類定義中新增:
1 protected: 2 void paintEvent(QPaintEvent *e); 3
介面實現內容:
A、定義一個畫家。
B、定義一隻畫筆,給畫筆設定樣式(顏色、線條寬度、線條型別),並把畫筆給畫家。
C、呼叫畫家的drawPixmap方法繪製棋盤背景圖片。
D、通用畫家的drawLine方法依次畫橫豎各15條線組成棋盤網格。
E、檢視當前棋盤上棋子資訊,當存在棋子時,以網格座標為準推導繪製位置,繪製相應的棋子。
1View Codevoid ChessBoard::paintEvent(QPaintEvent *e) 2 { 3 QPixmap chessBoardPic(QString(":/images/chessBoard.jpg")); 4 QPixmap blackChessPic(QString(":/images/black.png")); 5 QPixmap whiteChessPic(QString(":/images/white.png")); 6 QPainter painter(this); 7 QPen pen; 8 pen.setColor(Qt::black); 9 pen.setWidth(2); 10 pen.setStyle(Qt::SolidLine); 11 painter.setPen(pen); 12 painter.drawPixmap(0, 0, this->width(), this->height(), chessBoardPic); 13 14 for(int i = 0; i < chequerNumOfLine; i++) { 15 for(int j = 0; j < chequerNumOfLine; j++) { 16 painter.drawLine(topW, topH + j * chequerSide, 17 topW + (chequerNumOfLine-1) * chequerSide, topH + j * chequerSide); 18 painter.drawLine(topW + j * chequerSide, topH, 19 topW + j * chequerSide, topH + (chequerNumOfLine-1) * chequerSide); 20 } 21 } 22 for(int i = 0; i < chequerNumOfLine; i++) { 23 for(int j = 0; j < chequerNumOfLine; j++) { 24 if (chessInfo[i][j] == blackChess) { 25 painter.drawPixmap(topW + i * chequerSide - chessSide/2, topH + j * chequerSide - chessSide/2, 26 chessSide, chessSide, blackChessPic); 27 } 28 if (chessInfo[i][j] == whiteChess) { 29 painter.drawPixmap(topW + i * chequerSide - chessSide/2, topH + j * chequerSide - chessSide/2, 30 chessSide, chessSide, whiteChessPic); 31 } 32 } 33 } 34 //this->update(); 35 }
2.1.3.2新增資原始檔
在我們專案當中,需要用到許多的圖片檔案,需要把圖片放置到工程資料夾下,然後為工程新增資原始檔,就能通過相對路徑的方式呼叫檔案。
新增資原始檔方法:
新建一個資原始檔夾(檔案->新建)。
然後工程目錄下會生成資原始檔夾
選擇紅框中的.qr檔案,又鍵選中Open in Editor進入編輯模式新增檔案。
資原始檔呼叫時的相對路徑是以:開頭的,或著當不識別時,可以在以qrc:開頭。
當我們paintEvent事件完成後,此時我們通過修改初始化時棋盤資訊的內容就可以看到棋子的顯示啦。
2.1.4滑鼠捕獲
我們下棋是用滑鼠點選,當滑鼠移動到下棋點的時候,需要讓滑鼠改變樣式(此處由箭頭變成手指),來框定是否是下棋點。具體方法是重寫滑鼠移動事件virtual void mouseMoveEvent(QMouseEvent *event)。當我們移動滑鼠的時候就會產生該事件。滑鼠移動事件預設是需要按住滑鼠才會產生事件的, 所以需要修改相關屬性。
開啟滑鼠的跟隨屬性:
this->setMouseTracking(true); // 在初始化時,開啟滑鼠跟蹤,則在視窗上只要移動滑鼠就 //會產生滑鼠移動事件。
程式碼實現:
void ChessBoard::mouseMoveEvent(QMouseEvent *e)
{
// 去除邊界無效跟蹤
if ((e->x() < (topW - rangeCenter) || e->x() > (topW * 2 + chequerSide * (chequerNumOfLine-1) - rangeCenter)) ||
e->y() < (topH - rangeCenter) || e->y() > (topH * 2 + chequerSide * (chequerNumOfLine-1) - rangeCenter) ) {
this->setCursor(Qt::ArrowCursor);
dropChessEnable = false;
return;
}
// 根據每個落棋點改變游標格式
int inChequerSideX = (e->x() - topW) % chequerSide;
int inChequerSideY = (e->y() - topH) % chequerSide;
dropW = (e->x() - topW) / chequerSide;
dropH = (e->y() - topH) / chequerSide;
if ((inChequerSideX < rangeCenter || inChequerSideX > (chequerSide - rangeCenter)) &&
(inChequerSideY < rangeCenter || inChequerSideY > (chequerSide - rangeCenter))) {
this->setCursor(Qt::PointingHandCursor);
dropChessEnable = true;
// 落點位置進一格
if (inChequerSideX > (chequerSide - rangeCenter)) {
dropW++;
}
if (inChequerSideY > (chequerSide - rangeCenter)) {
dropH++;
}
} else {
this->setCursor(Qt::ArrowCursor);
dropChessEnable = false;
}
}
2.1.5下棋操作
通過2.1.4步的操作,我們已經能得到是否可以下棋的標誌dropChessEnable和當前下棋的實時網格座標(dropW,dropH)。那麼同理的,滑鼠按鍵按下也有對應的事件函式能夠過載--
virtual void mousePressEvent(QMouseEvent *event)。
當我們被允許下棋的時候,根據當前實時網格座標,直接根據當前的棋子型別(黑/白),直接修改棋盤資訊chessInfo[dropW][ dropH]內容給即可。每次下完後判斷當前是否五連。
程式碼實現:
void ChessBoard::mousePressEvent(QMouseEvent *e)
{
if (dropChessEnable == false || dropedFlag == false) {
return;
}
if(e->button() == Qt::LeftButton) { // 左鍵按下
if (chessInfo[dropW][dropH] == noChess) {
chessInfo[dropW][dropH] = currentChess;
emit chessDroped(dropW, dropH); // 發射落棋點訊號,為悔棋而用
dropedFlag = false;
if(isGameOver(dropW, dropH)) {
emit boardGameOver(); // 發射五連結束訊號
}
this->update();
}
}
}
到此,我們的棋盤就實現了,能夠顯示並且下棋。當然要想用到主視窗當中,為保證棋盤的封裝性,還需要提供各種介面供主視窗呼叫,例如獲取和修改棋盤資訊,獲取棋盤的相關規格資訊,修改一些需要處理的標誌位……;還需要新增下棋和遊戲結束判定的訊號供給主視窗的槽函式響應。