一、摘 要?
???????本報告討論了中國象棋程序設計的關鍵技術和方法。首先介紹了中國象棋的棋盤制作,利用Qt中的一些繪畫類的函數來進行繪制。在創作中國象棋棋子方面,首先,我們先定義一下棋子類,將棋子中相同的部分進行打包,使用了循環和結構體定位的方式將棋子放在對應的位置上進行初始化。在棋子的走法方面,這一部分是課程設計的重要部分,在這一部分會將象棋的基本規則進行創建和完善,分別為七種棋子的走法進行封裝,讓棋盤的規則更加完善。這些內容為理解和設計高效的中國象棋程序提供了深入的技術基礎和方法指導,了解和掌握C++語言的基本語法和面向對象編程思想,同時通過實踐來加深對C++語言的理解和應用,了解和掌握C++語言的基本語法和面向對象編程思想,同時通過實踐來加深對C++語言的理解和應用。
二、問題分析
2.1 有關中國象棋的背景
???????中國象棋,又稱為“象棋”或“中國國際象棋”,是中國文化中最具代表性的棋類游戲之一,有著悠久的歷史和深厚的文化底蘊。以下是關于中國象棋背景的一些重要信息:
???????中國象棋的歷史和文化的發展:中國象棋起源于中國約兩千年前的春秋戰國時期,最初稱為“象戲”、“象棋”,是中國古代文化遺產之一。象棋在中國的發展不僅僅是一種游戲,更是文化的傳承和精神的表達。象棋的棋譜、名家棋局和賽事都被廣泛收集、傳播和研究;中國象棋的規則簡單:象棋以其簡單明了的規則而聞名,棋盤為九宮格,棋子包括將、士、象、車、馬、炮和兵,雙方分紅黑兩色,對于每一種棋子封裝一個屬于自己的象棋走法。
???????因此,由于上述兩種情況,中國象棋程序設計是有必要,在幫助了解中國的文化底蘊的同時,可以提升編程思維,編碼能力,進一步鞏固C++基礎。
2.2 中國象棋程序編程的目的
? ? ? ?通過這次課設,可以了解和掌握C++語言的基本語法和面向對象編程思想,進一步學習Qt,同時通過實踐來加深對C++語言的理解和應用。具體目標是實現一個簡單的中國象棋游戲,包括界面設計、程序邏輯實現和人機博弈功能。通過完成這個課設,可以提高自己的編程能力,能夠利用所學的基本知識和技能,解決簡單的面向對象程序設計問題,能夠熟練地使用C++的特點繼承,封裝,多態,加深對面向對象編程思想的理解,同時也為學習其他高級語言和開發其他復雜應用程序打下堅實的基礎。
2.3 中國象棋程序編程的意義
???????中國象棋程序編程的意義有很多:智能象棋程序可以作為教學工具,幫助人們學習象棋策略和戰術。此外,它們還可以提供高質量的游戲體驗,為象棋愛好者和學習者提供挑戰和娛樂。在課設中用到的技術也可以進行分享,象棋程序設計中的算法和技術不僅局限于象棋本身,還可以應用于其他領域,文化傳承與推廣:象棋作為中國傳統文化的重要組成部分,通過智能象棋程序的設計和推廣,可以增加公眾對象棋的興趣和了解,促進文化傳承和推廣。在對于這個未使用到的博弈算法,為人機版本的象棋奠定了基礎,同時對于棋類游戲,可以優先考慮博弈算法。
???????綜上所述,中國象棋程序設計不僅在技術和科學研究上具有重要意義,還在教育、娛樂和文化傳承方面發揮著重要作用,為人工智能技術的發展和應用提供了寶貴的實驗平臺和應用場景。
2.4 中國象棋程序設計的功能需求分析
???????在中國象棋程序設計中,總共分為四個部分:棋盤的繪制,棋子的擺放,棋子的移動規則,簡易的單機游戲。
???????在棋盤繪制方面中,因為棋盤整體是一個矩形,擁有10條豎線和9條橫線,最后還有兩個九宮格,需要創建一個board類,在這個類中利用一些有關繪畫的知識點進行棋盤的繪制。最后在主函數中將這個窗口進行顯示即可。
???????在棋子的擺放方面中,因為棋子的種類一共有7種,棋子的個數一共有32個,所以需要建立一個棋子類在這個棋子類中,可以將一些棋子相同的部分放在一起,最后利用結構體將棋子的坐標和種類進行初始化,完成棋子的擺放。
???????在棋子的移動規則方面中,在棋子的移動,必然需要使用鼠標點擊事件和鼠標釋放事件,在下象棋中,由于最后是鼠標釋放,所以在這個程序中,使用的是鼠標釋放事件。將鼠標可以控制棋子的移動,最后在棋子的移動中,需要將棋子的移動進行設置,防止作弊。根據中國象棋的棋子移動規則:
1、帥(將):帥(將)是棋中的首腦,是雙方竭力爭奪的目標。它只能在九宮之內活動,可上可下,可左可右,每次走動只能按豎線或橫線走動一格。帥與將不能在同一直線上直接對面,否則走方判負。
2、仕(士):仕(士)是將(帥)的貼身保鏢,它也只能在九宮內走動。它的行棋路徑只有九宮內的四條斜線。
3、相(象):相(象)的主要作用是防守,保護自己的帥(將)。它的走法是每次循對角線走兩格,俗稱“象飛田”。相(象)的活動范圍限于河界以內的本方陣地,不能過河,且如果它走的田字中央有一個棋子,就不能走,俗稱“塞象眼”。
4、車:車在象棋中威力最大,無論橫線、豎線均可行走,只要無子阻攔,步數不受限制。因此,一車可以控制十七個點,故有“一車十子寒”之稱。
5、炮:炮在不吃子的時候,移動與車完全相同。當吃子時,己方和對方的棋子中間必須間隔1個棋子(無論對方或己方棋子),炮是象棋中唯一可以越子的棋種。
6、馬:馬走動的方法是一直一斜,即先橫著或直著走一格,然后再斜著走一個對角線,俗稱“馬走日”。馬一次可走的選擇點可以達到四周的八個點,故有“八面威風”之說。如果在要去的方向有別的棋子擋住,馬就無法走過去,俗稱“蹩馬腿”。
7、兵(卒):兵(卒)在未過河前,只能向前一步步走,過河以后,除不能后退外,允許左右移動,但也只能一次一步,即使這樣,兵(卒)的威力也大大增強,故有“過河的卒子頂半個車”之說。
三、總體設計
3.1 界面設計
???????在界面設計中,一共有兩個主要的頁面,一個開始頁面,我們需要一個按鈕,點擊這個按鈕就可以進入游戲的主界面中;另一個頁面是中國象棋游戲進行頁面,我們需要使用這個頁面進行游戲,具體流程圖如圖所示。
???????在開始頁面設計中,需要創建出一個窗口類,在頁面中添加一個點擊按鈕,點擊這個按鈕,可以進行象棋的游戲界面。在這個窗口啟動之前,先使用qt設計者類中創建一個新的窗口以便于進入游戲,之后就是象棋的界面,之后需要使用painter類中的函數進行繪制,在board類中創建一個painterEvevt函數,在這個函數進行繪畫設計。
2.2 類的設計
???????中國象棋項目總共需要設計三個主要類,一個類是棋盤類,一個類是棋子類,一個是開始結束頁面類:
???????在棋盤類中,需要進行棋子的初始化,棋盤的繪制,棋子的繪制,判斷棋子是否可以移動(棋子的移動規則),判斷棋子是否重合。這個類的主要功能是:進行棋盤的繪制,然后根據棋子類中的棋子的位置進行棋子的繪制,在這個類中使用鼠標釋放事件使棋子可以按照玩家想點擊在哪里就點擊在哪里,但是還是要有一定的約束,因此在這個類中實現了棋子的移動規則用來約束玩家的移動,最后判斷棋子是否重合,因為如果棋子重合,需要將該棋子進行釋放,表示吃掉了該棋子。
???????在棋子類中,將棋子的相同屬性進行創建,有添加了初始化棋子的函數和將棋子的姓名和棋子進行對應的函數,這個類的主要功能是:列出棋子的種類,將棋子進行初始化,然后將這個棋子進行擺放在其對應的位置。
???????在開始結束頁面類中,在開始頁面中創建一個對話框類,在對話框內創建兩個點擊點擊按鈕,然后點擊開始按鈕就可以進入中國象棋游戲界面中,點擊退出游戲按鈕就可以退出游戲。在結束頁面中,當任何一方的將被吃掉后就會彈出一個消息框,在消息框匯總顯示勝利的字樣。這個類中的主要功能就是:點擊按鈕就可以進入游戲頁面,完善一下程序的界面。
???????這些類的關系是:在開始頁面中,點擊進入游戲頁面的端口中,然后進入游戲中;調用棋盤類,讓棋盤類進行游戲界面的繪制;之后在棋盤類中,需要調用棋子類,將棋子進行初始化,利用棋盤類中的鼠標釋放事件進行游戲。當游戲結束之后,程序會彈出一個勝利的窗口。
四、界面設計
4.1 界面的詳細設計
???????在開始頁面設計中,需要在項目中建立一個新的對話框類,選擇Qt設計師界面類進行創建,選擇界面模板為:Dialog without Buttons,填寫類名,在把其添加到項目中。之后,在設計界面中dialog.ui中拖入一個 Push Button,將其上的文本改為“進入主窗口”。
???????利用信號槽機制將點擊事件和按鈕進行關聯。在其屬性窗口中將其 objectName?改為 enterBtn,在下面的 Signals and slots editor?中進行信號和槽的關聯,其中,Sender(發送者) 設為 enterBtn,Signal(信號) 設為 clicked(),Receive?(接收者)設為 myDlg,Slot(槽)設為 accept()。這樣就實現了單擊這個按鈕使這個對話框關閉并發出 Accepted?信號的功能,如圖3.1所示。下面利用這個信號將其按鈕與游戲界面進行關聯。
???????其函數的代碼如下,將開始界面類與游戲界面進行關聯,進行判斷,當有鼠標點擊事件發生,需要進行判斷,然后將游戲界面類進行顯示;如果判斷沒有鼠標點擊事件發生,則不會進入主窗口,整個程序結束運行。
QApplication app(argc, argv);Dialog dia1; // 創建一個進入窗口SingleGame board; // 創建一個棋盤,即游戲界面if(dia1.exec() == QDialog::Accepted)// 進行判斷,利用 Accepted 信號判斷 enterBtn 是否被按下{board.show(); ?// 如果判斷成功,則進行顯示棋盤return app.exec(); ?// 程序一直執行,直到主窗口關閉}else return 0; ?// 如果沒被按下,則不會進入主窗口,整個程序結束運行
???????在游戲界面設計中,需要根據這個象棋棋盤的樣式進行設計。由于在Qt中,一般有關的繪畫的函數都在paintEvent函數,所以在棋盤的繪制中,大部分函數都是在paintEvent函數中實現。在這個函數中,首先創建一個畫家,初始化棋盤格子的大小為d。然后開始繪畫出棋盤的本體,利用drawLine函數進行畫出10條豎線和9條橫線,在這個規程中,需要解決一些bug,因為在棋盤中有一個楚河漢界分界線。所以在進行豎線的繪畫中,需要控制中間的7條豎線不能越過這個分界線,其主要代碼如下:
QPainter paint(this);// 創建一個畫家paint.drawLine(QPoint(d, i * d), QPoint(9 * d, i * d));??// 循環10次if(i == 1 || i == 9)paint.drawLine(QPoint(i * d, d), QPoint(i * d, 10 * d));else{paint.drawLine(QPoint(i * d, d), QPoint(i * d, 5 * d));paint.drawLine(QPoint(i * d, 6 * d), QPoint(i * d, 10 * d));}
???????之后創建出九宮格,還是利用這個drawLine函數創建出九宮格,找出這個點的坐標,然后將其進行連接,其主要代碼如下:
// 畫出九宮格// 還是用直線來進行畫出九宮格paint.drawLine(QPoint(4 * d, 1 * d), QPoint(6 * d, 3 * d));paint.drawLine(QPoint(6 * d, 1 * d), QPoint(4 *d, 3 * d));// 進行平移paint.drawLine(QPoint(4 * d, 8 * d), QPoint(6 * d, 10 * d));paint.drawLine(QPoint(6 * d, 8 * d), QPoint(4 * d, 10 * d));
???????對于棋盤中的準星,還是利用drawLine函數進行繪制,也是主要是要找對點的位置,需要進行排布點的位置,其主要代碼如下:
// 畫出棋盤中的準星paint.drawLine(QPoint(d + 35, 2 * d + 25), QPoint(d + 35, 2 * d + 35));paint.drawLine(QPoint(d + 35, 2 * d + 35), QPoint(d + 25, 2 * d + 35));paint.drawLine(QPoint(d + 35, 2 * d + 45), QPoint(d + 25, 2 * d + 45));paint.drawLine(QPoint(d + 35, 2 * d + 45), QPoint(d + 35, 2 * d + 55));
???????在楚河漢界中,使用drawText函數進行在棋盤中寫字,在棋盤的分界線上寫出“楚河”和“漢界”幾個字,然后棋盤更加豐富。使用setFont函數對棋盤的文字的字體進行修改,對于文字的大小進行修改,以及文字的粗細也可以進行修改,注意在這個文字中需要進行定位將文字放置與于正確的位置。
// 繪制出楚河漢界的文字paint.setFont(QFont("SimSun", _r, 700)); // 設置文字的相關樣式paint.drawText(2 * d, 6 * d - 8, QStringLiteral("楚河"));paint.drawText(7 * d - 10, 6 * d - 8, QStringLiteral("漢界"));
3.2 類的詳細設計
???????中國象棋中最主要的兩個類為:棋盤類和棋子類。這兩個類進行結合,進行象棋游戲主題進行設置。
???????在棋子類中,需要創建的屬性為:棋子的橫坐標,棋子的縱坐標,棋子的類型,棋子是否已經死亡,棋子是否是紅色的,棋子的id。因為在象棋中,棋子的種類只有7種,需要將棋子的種類更加的進行維護,所以在棋子類中創建了棋子的聯合體。
???????在棋子類中,需要創建的行為有:將棋子的姓名和id進行綁定的函數,將棋子進行初始化的函數,將棋子的紅黑方的坐標進行計算,該棋子類的聲明代碼如下:??
??Stone();~Stone();QString name();void rotate();void init(int id);// 棋子的類型enum TYPE{CHE, MA, PAO, BING, JIANG, SHI, XIANG};// 棋子的類int _row;int _col;TYPE _type;bool _dead;bool _red;int _id;
???????在將棋子的姓名和id進行綁定的函數中,需要根據棋子中type屬性進行綁定,所以使用swatch語句一一進行對應。在將棋子進行初始化的函數中,需要將棋子在棋盤中對應的坐標進行寫入一個結構體中,方便利用循環進行初始化棋子。在計算棋子的紅黑方的坐標函數中,需要使用一些對應的數學關系來進行計算,該棋子類函數的主要代碼如下:
QString Stone::name(){switch(this->_type){case CHE:return "車";case MA:return "馬";case PAO:return "炮";case BING:return "兵";case JIANG:return "將";case SHI:return "士";case XIANG:return "相";}return "錯誤";}
???????在棋盤類中,需要創建的屬性有:維護棋子的數組,被選中的棋子的id,棋子是否需要變紅,棋子的半徑。
???????在棋盤類中,需要創建的行為有:將棋盤的行列值轉為坐標點的函數,繪制棋子函數,鼠標釋放事件,繪制一個棋盤的函數,判斷棋子是否可以移動的函數,判斷棋子是夠重合的函數,將棋子的坐標點轉換為行列值的函數,返回棋盤行列對應的像素坐標的函數。
???????在這些函數中,需要進行一一詳細地介紹:將棋盤的行列值轉換為坐標點的函數中,這個函數的主要功能是判斷鼠標點擊的是哪一個棋子,然后返回棋子對應的坐標。
???????該函數的詳細設計為:在這個函數中,首先傳入獲取到的鼠標的位置,之后,一個思路是循環遍歷每一個棋盤上的點,進行計算,根據點與點之間的距離公式計算出鼠標點擊的距離與以坐標為圓心的園的半徑的比較,如果小于棋子的半徑,則返回true,以及棋子的坐標;如果遍歷完之后也沒有,則返回false。這個思路是比較慢的,但是代碼是比較容易實現。還有一個高效的算術,在獲取到鼠標的行列值之后,直接進行除法運算,算出該行列值在主窗口中的位置,然后在與棋盤中的坐標進行比較,計算出該坐標。該函數的實現代碼如下:
for(row = 0; row <= 9; row++){for(col = 0; col <= 8; col++){QPoint c = center(row, col);int dx = c.x() - pt.x();int dy = c.y() - pt.y();int dist = dx * dx + dy * dy;if(dist < _r * _r){return true;}}}
???????在返回棋盤行列對應的像素坐標的函數中,該函數的功能是:通過棋子的id進行將棋子的行列值計算出來,利用函數重載將調用接口進行簡化,在這個函數中需要根據坐標計算出行列值。
???????在繪制棋子函數中,需要將繪制棋盤的畫家通過引用的方式床底過去,因為在這個項目中繪制圖像,基本都在paintEvent函數中,通過引用傳遞是為了讓畫家是同一個。該函數的主要功能是:繪畫出棋子,對于已經死亡的棋子不進行繪制。
???????該函數的詳細設計是:在這個函數中,首先進行判斷棋子是否已經死亡,如果棋子已經死亡,則退出該函數;如果棋子沒有死亡,則繼續進行繪制。將傳入的id通過center函數的轉換,將棋子的坐標得出。然后以這個坐標為圓心進行繪制棋子。在繪制棋子的過程中,需要設置棋子的紅黑顏色,通過棋子類中的屬性來進行判斷是用紅筆畫圓,還是使用黑筆畫圓。根據棋子的類型,將棋子的名字寫入,并設置字體的樣式使字體更加好看。最后需要將選中的棋子的顏色進行更換,讓其顯示出來。其函數的代碼如下:
if(_s[id]._dead) return;QPoint c = center(id);QRect rect = QRect(c.x() - _r, c.y() - _r, _r * 2, _r * 2);if(id == _selectid)painter.setBrush(QBrush(Qt::gray)); // 將被點擊的棋子的顏色改變elsepainter.setBrush(QBrush(Qt::yellow));painter.setPen(Qt::black);painter.drawEllipse(center(id), _r, _r);??// 畫一個圓圈if(_s[id]._red){painter.setPen(Qt::red);}// 設置字體painter.setFont(QFont("SimSun", _r, 700));painter.drawText(rect, _s[id].name(), QTextOption(Qt::AlignCenter));
???????在鼠標釋放事件的函數中,該函數的主要功能是:點擊象棋,之后將被選中象棋的圖像進行改變,判斷出棋子是否是同一方的函數,如果不是同一方的函數,就可以進行移動棋子,如果是同一方的棋子,則將點擊的棋子進行轉移。
???????該函數的詳細設計是:首先通過鼠標類型的變量,獲取到鼠標點擊的坐標。判斷該坐標是否在棋盤中,并同時將鼠標點擊的坐標計算出來。如果鼠標不在棋盤中,則退出函數,如果鼠標在棋盤中,則需要進行遍歷這個棋子,看是哪一個棋子被選中,然后將id賦值給clickid。然后將clickid賦值給selectid,在這期間,需要將這次點擊的是紅方還是黑房進行判斷。最后判斷出可以移動棋子,則進行移動棋子,如果在移動棋子中,移動到棋子重合,在吃掉該棋子。然后將棋子的紅黑雙方進行更換,最后要記得使用update()更新畫面,其代碼如下:
QPoint pt = event->pos();bool bRet = getRowCol(pt, row, col);if(bRet == false) ?return ;for(i = 0; i < 32; i++){if(_s[i]._row == row && _s[i]._col == col && _s[i]._dead == false){break; // 說明選中了}}if(i < 32){clickid = i;}// 判斷是黑方還是紅方if(_s[clickid]._red)qDebug("這是紅方的棋子");elseqDebug("這是黑方的棋子");if(_selectid == -1){if(clickid != -1){if(_bRedTurn == _s[clickid]._red)_selectid = clickid;update(); // 將這個棋子顯示出來}}else{if(canMove(_selectid, row, col, clickid)){// 移動棋子_s[_selectid]._row = row;_s[_selectid]._col = col;if(clickid != -1){_s[clickid]._dead = true;}_selectid = -1;_bRedTurn = !_bRedTurn;update();}}
???????判斷棋子是否可以移動的函數,在這個函數中,需要創建出7個相同的函數進行約束棋子的走法。該種類型的函數的主要功能是指定棋子移動的規則。
???????該函數的詳細設計是:如果移動的顏色和killed的顏色相同,則將棋子的選擇更換,否則返回false。其中設置一些swatch語句,用于將棋子的移動規則進行返回。其代碼如下:
qDebug() << _s[moveid].name();AAif(_s[moveid]._red == _s[killed]._red)// 如果移動的顏色和killed的顏色相同{// 換選擇_selectid = killed;update();return false;}
???????七種棋子的移動規則在前面已經詳細地介紹了,現在需要一一根據移動的規則將代碼設計出來:
???????首先設計一些將的移動代碼:在將的移動代碼中,需要先進行判斷,如果是紅方,則不能出去九宮格內,其坐標和黑方的坐標不一樣,由表示的代碼不同,所以需要使用不同的代碼進行。將的走法每次只能走一格,且不能斜著走,所以移動的坐標和原本所在的坐標的差值只能是0和1,或者是1和0,其詳細的代碼如下:
if(_s[moveid]._red){if(row > 2) return false;}else{if(row < 7) return false;}if(col < 3) return false;if(col > 5) return false;// 只能走一步int dr = _s[moveid]._row - row;int dc = _s[moveid]._col - col;int d = abs(dr) * 10 + abs(dc);if(d == 1 || d == 10)return true;return false;
???????士的走法和將的走法基本一樣,只是士是斜著走的,而將是橫著走和豎著走的。因此,只需將移動的坐標和原本所在的坐標的差值只能是1和1,或者是-1和1,或者是1和-1,或者是-1和-1。其詳細的代碼如下:
if(_s[moveid]._red){if(row > 2) return false;}else{if(row < 7) return false;}if(col < 3) return false;if(col > 5) return false;// 只能走一步int dr = _s[moveid]._row - row;int dc = _s[moveid]._col - col;int d = abs(dr) * 10 + abs(dc);if(d == 11)return true;return false;
???????象的走法設計可以分為三步,首先象不能過河:在紅方和黑方中的限制的坐標不一樣;其次象是走田字,所以移動的坐標和原本所在的坐標的差值只能是2和2,或者是-2和2,或者是2和-2,或者是-2和-2。最后,如果有棋子在象眼處,則不能進行移動,需要在象眼處判斷是否有棋子存在,其代碼如下:
int rEye = (_s[moveid]._row + row) / 2;int cEye = (_s[moveid]._col + col) / 2;if(GetStoneId(rEye, cEye) != -1)return false;// 不能出界if(_s[moveid]._red){if(row > 4) return false;}else{if(row < 5) return false;}int dr = _s[moveid]._row - row;int dc = _s[moveid]._col - col;int d = abs(dr) * 10 + abs(dc);if(d != 22)return false;return true;
???????馬的移動規則有兩條:首先馬走日,所以移動的坐標和原本所在的坐標的差值只能是2和1,或者是-2和1,或者是1和-2,或者是-1和-2等。因此,其絕對值相加為21,或者12。最后,如果在馬腿處有棋子存在的話,則馬不能移動,需要在馬腿處判斷是否有棋子存在,其代碼如下:
int dr = _s[moveid]._row - row;int dc = _s[moveid]._col - col;int d = abs(dr) * 10 + abs(dc);if(d != 12 && d != 21)return false;int cd = col + _s[moveid]._col / 2;int rd = row + _s[moveid]._row / 2;int row1 = _s[moveid]._row;int col1 = _s[moveid]._col;if(d == 12){if(GetStoneId(row1, (col+col1)/2) != -1)return false;}else{if(GetStoneId((row+row1)/2, col1) != -1)return false;}// 不能出界if(row < 0) return false;if(row > 9) return false;if(col < 0) return false;if(col > 8) return false;return true;
???????炮的移動規則是比較麻煩的,在炮的移動過程中,只能橫跨0個或1個棋子,所以需要判斷出在炮的移動路徑上存在棋子的個數,如果存在兩個及兩個以上的棋子,不能移動炮,反之則可以。先獲取到所要移動的棋子的水平路線和垂直路線中的所有棋子,存儲在一個map中,然后遍歷map,返回其存儲的值是多少,如果是0或者1則可以移動,否則不可以進行移動,其詳細代碼如下:
int ret = getStoneCountAtLine(row, col, _s[moveid]._row, _s[moveid]._col);if(killed != -1){if(ret == 1) return true;}else{if(ret == 0) return true;}return false;
???????車的移動規則更加簡單,需要借助于炮中的思路,先獲取到所要移動的棋子的水平路線和垂直路線中的所有棋子,存儲在一個map中,然后遍歷map,返回其存儲的值是多少,如果是0則可以移動,否則不可以進行移動,其詳細代碼如下:
int ret = getStoneCountAtLine(row1, col1, row, col);if(ret == 0)??return true;return false;
???????兵的走棋規則分為兩個部分:在未出自己方的分界線時,兵只能向前走。需要將向左向右向后走的路線給封鎖,在這一部分中,兵每次只能走一格,所以可以復用將行走的代碼,因此移動的坐標和原本所在的坐標的差值只能是0和1,或者是1和0。在進入到對方的棋盤中,兵可以進行向左向右向前走,其代碼如下:
if(row < 0) return false;if(row > 9) return false;if(col < 0) return false;if(col > 8) return false;if(_s[moveid]._red) // 如果是紅方{if(row <= 4){if(dr <= 0 && dc == 0) return true;}else{if(d == 1 || d == 10){if(dr <= 0) return true;}}}else // 如果是黑方的{if(row >= 5){if(dr >= 0 && dc == 0) return true;}else{if(d == 1 || d == 10){if(dr >= 0) return true;}}}return false;
???????在判斷棋子是夠重合的函數中,只需遍歷32個棋子,看是否存在棋子和所選擇的棋子重合并且不能是死亡狀態,如果存在則返回該棋子對應的id,否則返回-1。
五、系統測試
5.1 主頁面測試
???????當我們啟動Qt程序之后,首先映入眼簾的就是一個主頁面,主頁面中有兩個按鈕,一個按鈕是進入象棋游戲頁面,一個按鈕是退出象棋游戲頁面。點擊這兩個按鈕分別有不同的事件發生,在點擊“進入象棋游戲”按鈕會直接跳轉到象棋的頁面;在點擊“退出象棋游戲”按鈕會關閉頁面,如圖4.1所示;并在控制臺輸出退出代碼0,如圖4.2所示。
4.2 游戲頁面測試
???????在進入游戲頁面之后,我們發現有紅方和黑方。中國象棋的規則是紅方先下,然后黑方才能下棋。在之后就是紅方下一次,黑方下一次。通過點擊棋子,在控制臺中會發生點擊的是什么顏色的棋子來判斷只能點擊紅色方棋子才能選中,測試結果如圖4.3所示。
???????之后,就是檢驗各種棋子的移動規則,在這里我們使用將棋子原本坐標和棋子要移動的坐標進行打印出來,根據棋子的移動規則約束,我們可以進行驗證棋子的移動是否正確,如果棋子的移動不正確,則在棋盤中,該棋子不會進行移動;而如果對應上棋子的移動規則,則棋子可以進行移動。通過在控制臺中打印出現在移動的是哪個棋子,棋子沒有移動前的坐標和想要棋子移動的坐標進行判斷,測試結果如圖4.4所示。
????????游戲結束時,該程序會彈出一個“勝利”的消息對話框,當有一方的將被吃掉后,該程序就會彈出該消息對話框,測試結果如圖4.5所示。
六、總結?
???????在完成該課設之后,學習到了很多,學習了如何使用Qt來創建多個窗口,并進行繪制圖像,如何使用信號槽機制將按鈕和窗口結合起來,如何創建一個消息對話框,也學到了如何使用函數將鼠標點擊事件和窗口進行交互。但是這個程序中,還是有很多不足的點:沒有引入Alpha-Beta算法,沒有實現人機對站;沒有實現悔棋功能,沒有實現網絡服務,進行聯網操作。因此,在之后的學習中,還是要學習更多的內容,在之后進行重新對該程序進行修改,以至于可以完成上述各項內容。這個小型項目的完成幫助進行了更深層次的了解項目的組成,為之后的項目的學習奠定了基礎。?