版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、<p><b> 1 引言</b></p><p><b> 1.1 五子棋介紹</b></p><p> 五子棋是起源于中國(guó)古代的傳統(tǒng)黑白棋種之一?,F(xiàn)代五子棋日文稱之為“連珠”,英譯為“Renju”,英文稱之為“Gobang”或“FIR”(Five in a Row的縮寫),亦有“連五子”、“五子連”、“串珠”、“五目”、“五目
2、碰”、“五格”等多種稱謂。</p><p> 五子棋不僅能增強(qiáng)思維能力,提高智力,而且富含哲理,有助于修身養(yǎng)性。五子棋既有現(xiàn)代休閑的明顯特征“短、平、快”,又有古典哲學(xué)的高深學(xué)問“陰陽易理”;它既有簡(jiǎn)單易學(xué)的特性,為人民群眾所喜聞樂見,又有深?yuàn)W的技巧和高水平的國(guó)際性比賽;它的棋文化源淵流長(zhǎng),具有東方的神秘和西方的直觀;既有“場(chǎng)”的概念,亦有“點(diǎn)”的連接。它是中西文化的交流點(diǎn),是古今哲理的結(jié)晶。</p>
3、;<p><b> 1.2 開發(fā)背景</b></p><p> 當(dāng)前網(wǎng)絡(luò)上流傳的五子棋游戲功能并不盡善盡美,其中最主要的問題就是人機(jī)對(duì)戰(zhàn)和網(wǎng)絡(luò)對(duì)戰(zhàn)不能夠一起實(shí)現(xiàn),所以我決定開發(fā)[1]一個(gè)既能夠人機(jī)對(duì)戰(zhàn),又能夠進(jìn)行網(wǎng)絡(luò)對(duì)戰(zhàn)的五子棋系統(tǒng)。</p><p> 1.3 開發(fā)環(huán)境及運(yùn)行環(huán)境</p><p> 1.3.1 開發(fā)環(huán)境&l
4、t;/p><p> Intel® Pentium® 4 2.0GHz,512M內(nèi)存,80G硬盤</p><p> Microsoft® Windows? 2000 Professional</p><p> Microsoft® Visual C++ 6.0</p><p> Microsoft
5、174; Developer Network for Visual Studio.NET 2003</p><p> Visual Assist X 10.1.1301.0</p><p> 1.3.2 運(yùn)行環(huán)境</p><p> Intel® Pentium® 2及以上處理器,32M以上內(nèi)存,4G以上硬盤</p><p
6、> Microsoft® Windows? 9X/NT操作系統(tǒng)</p><p> 800*600或以上的屏幕分辨率</p><p><b> 2 軟件架構(gòu)</b></p><p> 軟件的總體架構(gòu)如圖2.1:</p><p><b> 圖2.1 軟件架構(gòu)</b></p
7、><p> 考慮到整個(gè)的下棋過程(無論對(duì)方是電腦抑或其他網(wǎng)絡(luò)玩家)可以分為:己方落子、等待對(duì)方落子、對(duì)方落子、設(shè)置己方棋盤數(shù)據(jù)這一系列過程,因此一人游戲類、二人游戲類和棋盤類之間的關(guān)系參考了AbstractFactory(抽象工廠)模式,以實(shí)現(xiàn)對(duì)兩個(gè)不同模塊進(jìn)行一般化的控制。[2]</p><p><b> 2.1 棋盤類</b></p><p&g
8、t; 整個(gè)架構(gòu)的核心部分,類名為CTable。封裝了棋盤的各種可能用到的功能[3],如保存棋盤數(shù)據(jù)、初始化、判斷勝負(fù)等。用戶操作主界面,主界面與CTable進(jìn)行交互來完成對(duì)游戲的操作。</p><p><b> 2.2 游戲模式類</b></p><p> 用來管理人機(jī)對(duì)弈/網(wǎng)絡(luò)對(duì)弈兩種游戲模式,類名為CGame。CGame是一個(gè)抽象類,經(jīng)由它派生出一人游戲類C
9、OneGame和網(wǎng)絡(luò)游戲類CTwoGame,如圖2.2:</p><p> 圖2.2 CGame類派生關(guān)系</p><p> 這樣,CTable類就可以通過一個(gè)CGame類的指針[4],在游戲初始化的時(shí)候根據(jù)具體游戲模式的要求實(shí)例化COneGame或CTwoGame類的對(duì)象;然后利用多態(tài)性[5],使用CGame類提供的公有接口就可以完成不同游戲模式下的不同功能了。</p>
10、<p> 3 棋盤類——CTable</p><p> 3.1 主要成員變量說明</p><p> 3.1.1 網(wǎng)絡(luò)連接標(biāo)志——m_bConnected</p><p> 用來表示當(dāng)前網(wǎng)絡(luò)連接的情況,在網(wǎng)絡(luò)對(duì)弈游戲模式下客戶端連接服務(wù)器的時(shí)候用來判斷是否連接成功;事實(shí)上,它也是區(qū)分當(dāng)前游戲模式的唯一標(biāo)志。</p><p>
11、 3.1.2 棋盤等待標(biāo)志——m_bWait與m_bOldWait</p><p> 由于在玩家落子后需要等待對(duì)方落子,m_bWait標(biāo)志就用來標(biāo)識(shí)棋盤的等待狀態(tài)。當(dāng)m_bWait為TRUE時(shí),是不允許玩家落子的。</p><p> 在網(wǎng)絡(luò)對(duì)弈模式下,玩家之間需要互相發(fā)送諸如悔棋、和棋這一類的請(qǐng)求消息,在發(fā)送請(qǐng)求后等待對(duì)方回應(yīng)時(shí),也是不允許落子的,所以需要將m_bWait標(biāo)志置為TR
12、UE。在收到對(duì)方回應(yīng)后,需要恢復(fù)原有的棋盤等待狀態(tài),所以需要另外一個(gè)變量在發(fā)送請(qǐng)求之前保存棋盤的等待狀態(tài)做恢復(fù)之用,也就是m_bOldWait。</p><p> 等待標(biāo)志的設(shè)置,由成員函數(shù)SetWait和RestoreWait完成。</p><p> 3.1.3 網(wǎng)絡(luò)套接字——m_sock和m_conn</p><p> 在網(wǎng)絡(luò)對(duì)弈游戲模式下,需要用到這兩個(gè)
13、套接字對(duì)象。其中m_sock對(duì)象用于做服務(wù)器時(shí)的監(jiān)聽之用,m_conn用于網(wǎng)絡(luò)連接的傳輸。</p><p> 3.1.4 棋盤數(shù)據(jù)——m_data</p><p> 這是一個(gè)15*15的二位數(shù)組,用來保存當(dāng)前棋盤的落子數(shù)據(jù)。其中對(duì)于每個(gè)成員來說,0表示落黑子,1表示落白子,-1表示無子。</p><p> 3.1.5 游戲模式指針——m_pGame</p
14、><p> 這個(gè)CGame類的對(duì)象指針是CTable類的核心內(nèi)容。它所指向的對(duì)象實(shí)體決定了CTable在執(zhí)行一件事情時(shí)候的不同行為,具體的內(nèi)容請(qǐng)參見“游戲模式”一節(jié)。</p><p> 3.2 主要成員函數(shù)說明</p><p> 3.2.1 套接字的回調(diào)處理——Accept、Connect、Receive</p><p> 本程序的套接字
15、派生自MFC的CAsyncSocket類[6],CTable的這三個(gè)成員函數(shù)就分別提供了對(duì)套接字[7]回調(diào)事件OnAccept、OnConnect、OnReceive的實(shí)際處理,其中尤以Receive成員函數(shù)重要,它之中包含了對(duì)所有網(wǎng)絡(luò)消息(參見“消息機(jī)制”一節(jié))的分發(fā)處理。</p><p> 3.2.2 清空棋盤——Clear</p><p> 在每一局游戲開始的時(shí)候都需要調(diào)用這個(gè)函
16、數(shù)將棋盤清空,也就是棋盤的初始化工作。在這個(gè)函數(shù)中,主要發(fā)生了這么幾件事情:</p><p> 將m_data中每一個(gè)落子位都置為無子狀態(tài)(-1)。</p><p> 按照傳入的參數(shù)設(shè)置棋盤等待標(biāo)志m_bWait,以供先、后手的不同情況之用。</p><p> 使用delete將m_pGame指針?biāo)赶虻脑杏螒蚰J綄?duì)象從堆上刪除。</p>&l
17、t;p> 3.2.3 繪制棋子——Draw</p><p> 這無疑是很重要的一個(gè)函數(shù),它根據(jù)參數(shù)給定的坐標(biāo)和顏色繪制棋子。繪制的詳細(xì)過程如下:</p><p> 將給定的棋盤坐標(biāo)換算為繪圖的像素坐標(biāo)。</p><p> 根據(jù)坐標(biāo)繪制棋子位圖。</p><p> 如果先前曾下過棋子,則利用R2_NOTXORPEN將上一個(gè)繪制棋
18、子上的最后落子指示矩形擦除。</p><p> 在剛繪制完成的棋子四周繪制最后落子指示矩形。</p><p> 3.2.4 左鍵消息——OnLButtonUp</p><p> 作為棋盤唯一響應(yīng)的左鍵消息,也需要做不少的工作:</p><p> 如果棋盤等待標(biāo)志m_bWait為TRUE,則直接發(fā)出警告聲音并返回,即禁止落子。</
19、p><p> 如果點(diǎn)擊時(shí)的鼠標(biāo)坐標(biāo)在合法坐標(biāo)(0, 0)~(14, 14)之外,亦禁止落子。</p><p> 如果走的步數(shù)大于1步,方才允許悔棋。</p><p> 進(jìn)行勝利判斷,如勝利則修改UI狀態(tài)并增加勝利數(shù)的統(tǒng)計(jì)。</p><p> 如未勝利,則向?qū)Ψ桨l(fā)送已經(jīng)落子的消息。</p><p> 落子完畢,將m
20、_bWait標(biāo)志置為TRUE,開始等待對(duì)方回應(yīng)。</p><p> 3.2.5 繪制棋盤——OnPaint</p><p> 每當(dāng)WM_PAINT消息觸發(fā)時(shí),都需要對(duì)棋盤進(jìn)行重繪。OnPaint作為響應(yīng)繪制消息的消息處理函數(shù)使用了雙緩沖技術(shù),減少了多次繪圖可能導(dǎo)致的圖像閃爍問題。這個(gè)函數(shù)主要完成了以下工作:</p><p> 裝載棋盤位圖并進(jìn)行繪制。</
21、p><p> 根據(jù)棋盤數(shù)據(jù)繪制棋子。</p><p> 繪制最后落子指示矩形。</p><p> 3.2.6 對(duì)方落子完畢——Over</p><p> 在對(duì)方落子之后,仍然需要做一些判斷工作,這些工作與OnLButtonUp中的類似,在此不再贅述。</p><p> 3.2.7 設(shè)置游戲模式——SetGameM
22、ode</p><p> 這個(gè)函數(shù)通過傳入的游戲模式參數(shù)對(duì)m_pGame指針進(jìn)行了初始化,代碼如下:</p><p> void CTable::SetGameMode( int nGameMode )</p><p><b> {</b></p><p> if ( 1 == nGameMode )</p
23、><p> m_pGame = new COneGame( this );</p><p><b> else</b></p><p> m_pGame = new CTwoGame( this );</p><p> m_pGame->Init();</p><p><b>
24、 }</b></p><p> 這之后,就可以利用OO的繼承和多態(tài)特點(diǎn)[8]來使m_pGame指針使用相同的調(diào)用來完成不同的工作了,事實(shí)上,COneGame::Init和CTwoGame::Init都是不同的。</p><p> 3.2.8 勝負(fù)的判斷——Win</p><p> 這是游戲中一個(gè)極其重要的算法,用來判斷當(dāng)前棋盤的形勢(shì)是哪一方獲勝。
25、其詳細(xì)內(nèi)容請(qǐng)參見“主要算法”一節(jié)。</p><p> 4 游戲模式類——CGame</p><p> 這個(gè)類負(fù)責(zé)對(duì)游戲模式進(jìn)行管理,以及在不同的游戲模式下對(duì)不同的用戶行為進(jìn)行不同的響應(yīng)。由于并不需要CGame本身進(jìn)行響應(yīng),所以將其設(shè)計(jì)為了一個(gè)純虛類[9],它的定義如下:</p><p> class CGame</p><p><
26、b> {</b></p><p> protected:</p><p> CTable *m_pTable;</p><p><b> public:</b></p><p><b> // 落子步驟</b></p><p> list<
27、 STEP > m_StepList;</p><p><b> public:</b></p><p><b> // 構(gòu)造函數(shù)</b></p><p> CGame( CTable *pTable ) : m_pTable( pTable ) {}</p><p><b>
28、 // 析構(gòu)函數(shù)</b></p><p> virtual ~CGame();</p><p> // 初始化工作,不同的游戲方式初始化也不一樣</p><p> virtual void Init() = 0;</p><p> // 處理勝利后的情況,CTwoGame需要改寫此函數(shù)完成善后工作</p>
29、<p> virtual void Win( const STEP& stepSend );</p><p><b> // 發(fā)送己方落子</b></p><p> virtual void SendStep( const STEP& stepSend ) = 0;</p><p><b> // 接
30、收對(duì)方消息</b></p><p> virtual void ReceiveMsg( MSGSTRUCT *pMsg ) = 0;</p><p><b> // 發(fā)送悔棋請(qǐng)求</b></p><p> virtual void Back() = 0;</p><p><b> };<
31、;/b></p><p> 4.1 主要成員變量說明</p><p> 4.1.1 棋盤指針——m_pTable</p><p> 由于在游戲中需要對(duì)棋盤以及棋盤的父窗口——主對(duì)話框進(jìn)行操作及UI狀態(tài)設(shè)置,故為CGame類設(shè)置了這個(gè)成員。當(dāng)對(duì)主對(duì)話框進(jìn)行操作時(shí),可以使用m_pTable->GetParent()得到它的窗口指針。</p>
32、<p> 4.1.2 落子步驟——m_StepList</p><p> 一個(gè)好的棋類程序必須要考慮到的功能就是它的悔棋功能,所以需要為游戲類設(shè)置一個(gè)落子步驟的列表。由于人機(jī)對(duì)弈和網(wǎng)絡(luò)對(duì)弈中都需要這個(gè)功能,故將這個(gè)成員直接設(shè)置到基類CGame中。另外,考慮到使用的簡(jiǎn)便性,這個(gè)成員使用了C++標(biāo)準(zhǔn)模板庫[10](Standard Template Library,STL)中的std::list,而
33、不是MFC的CList。</p><p> 4.2 主要成員函數(shù)說明</p><p> 4.2.1 悔棋操作——Back</p><p> 在不同的游戲模式下,悔棋的行為是不一樣的。</p><p> 人機(jī)對(duì)弈模式下,計(jì)算機(jī)是完全允許玩家悔棋的,但是出于對(duì)程序負(fù)荷的考慮(此原因請(qǐng)參見“幾點(diǎn)補(bǔ)充說明”一節(jié)),只允許玩家悔當(dāng)前的兩步棋(計(jì)
34、算機(jī)一步,玩家一步)。</p><p> 雙人網(wǎng)絡(luò)對(duì)弈模式下,悔棋的過程為:首先由玩家向?qū)Ψ桨l(fā)送悔棋請(qǐng)求(悔棋消息),然后由對(duì)方?jīng)Q定是否允許玩家悔棋,在玩家得到對(duì)方的響應(yīng)消息(允許或者拒絕)之后,才進(jìn)行悔棋與否的操作。</p><p> 4.2.2 初始化操作——Init</p><p> 對(duì)于不同的游戲模式而言,也就有不同的初始化方式。對(duì)于人機(jī)對(duì)弈模式而言,
35、初始化操作包括以下幾個(gè)步驟:</p><p> 設(shè)置網(wǎng)絡(luò)連接狀態(tài)m_bConnected為FALSE。</p><p> 設(shè)置主界面計(jì)算機(jī)玩家的姓名。</p><p> 初始化所有的獲勝組合。</p><p> 如果是計(jì)算機(jī)先走,則占據(jù)天元(棋盤正中央)的位置。</p><p> 網(wǎng)絡(luò)對(duì)弈的初始化工作暫為空,
36、以供以后擴(kuò)展之用。</p><p> 4.2.3 接收來自對(duì)方的消息——ReceiveMsg</p><p> 這個(gè)成員函數(shù)由CTable棋盤類的Receive成員函數(shù)調(diào)用,用于接收來自對(duì)方的消息。對(duì)于人機(jī)對(duì)弈游戲模式來說,所能接收到的就僅僅是本地模擬的落子消息MSG_PUTSTEP;對(duì)于網(wǎng)絡(luò)對(duì)弈游戲模式來說,這個(gè)成員函數(shù)則負(fù)責(zé)從套接字讀取對(duì)方發(fā)過來的數(shù)據(jù),然后將這些數(shù)據(jù)解釋為自定義的
37、消息結(jié)構(gòu),并回到CTable::Receive來進(jìn)行處理。</p><p> 4.2.4 發(fā)送落子消息——SendStep</p><p> 在玩家落子結(jié)束后,要向?qū)Ψ桨l(fā)送自己落子的消息。對(duì)于不同的游戲模式,發(fā)送的目標(biāo)也不同:</p><p> 對(duì)于人機(jī)對(duì)弈游戲模式,將直接把落子的信息(坐標(biāo)、顏色)發(fā)送給COneGame類相應(yīng)的計(jì)算函數(shù)。</p>
38、<p> 對(duì)于網(wǎng)絡(luò)對(duì)弈游戲模式,將把落子消息發(fā)送給套接字,并由套接字轉(zhuǎn)發(fā)給對(duì)方。</p><p> 4.2.5 勝利后的處理——Win</p><p> 這個(gè)成員函數(shù)主要針對(duì)CTwoGame網(wǎng)絡(luò)對(duì)弈模式。在玩家贏得棋局后,這個(gè)函數(shù)仍然會(huì)調(diào)用SendStep將玩家所下的制勝落子步驟發(fā)送給對(duì)方玩家,然后對(duì)方的游戲端經(jīng)由CTable::Win來判定自己失敗。</p>
39、<p><b> 5 消息機(jī)制</b></p><p> Windows系統(tǒng)擁有自己的消息機(jī)制,在不同事件發(fā)生的時(shí)候,系統(tǒng)也可以提供不同的響應(yīng)方式[11]。五子棋程序也模仿Windows系統(tǒng)實(shí)現(xiàn)了自己的消息機(jī)制,主要為網(wǎng)絡(luò)對(duì)弈服務(wù),以響應(yīng)多種多樣的網(wǎng)絡(luò)消息。</p><p> 5.1 消息機(jī)制的架構(gòu)</p><p> 當(dāng)繼
40、承自CAsyncSocket的套接字類CFiveSocket收到消息時(shí),會(huì)觸發(fā)CFiveSocket::OnReceive事件[12],在這個(gè)事件中調(diào)用CTable::Receive,CTable::Receive開始按照自定義的消息格式接收套接字發(fā)送的數(shù)據(jù),并對(duì)不同的消息類型進(jìn)行分發(fā)處理。</p><p> 圖5.1 自定義的消息機(jī)制</p><p> 如圖5.1所示,當(dāng)CTable
41、獲得了來自網(wǎng)絡(luò)的消息之后,就可以使用一個(gè)switch結(jié)構(gòu)來進(jìn)行消息的分發(fā)了。</p><p> 5.2 各種消息說明</p><p> 網(wǎng)絡(luò)間傳遞的消息,都遵循以下一個(gè)結(jié)構(gòu)體的形式:</p><p> // 摘自Messages.h</p><p> typedef struct _tagMsgStruct {</p>
42、<p><b> // 消息ID</b></p><p> UINT uMsg;</p><p><b> // 落子信息</b></p><p><b> int x;</b></p><p><b> int y;</b></
43、p><p> int color;</p><p><b> // 消息內(nèi)容</b></p><p> TCHAR szMsg[128];</p><p> } MSGSTRUCT;</p><p> 隨著uMsg表示消息ID,x、y表示落子的坐標(biāo),color表示落子的顏色,szMsg隨著u
44、Msg的不同而有不同的含義。</p><p> 5.2.1 落子消息——MSG_PUTSTEP</p><p> 表明對(duì)方落下了一個(gè)棋子,其中x、y和color成員有效,szMsg成員無效。在人機(jī)對(duì)弈游戲模式下,亦會(huì)模擬發(fā)送此消息以達(dá)到程序模塊一般化的效果。</p><p> 5.2.2 悔棋消息——MSG_BACK</p><p>
45、 表明對(duì)方請(qǐng)求悔棋,除uMsg成員外其余成員皆無效。接到這個(gè)消息后,會(huì)彈出MessageBox詢問是否接受對(duì)方的請(qǐng)求(如圖5.2所示),并根據(jù)玩家的選擇回返MSG_AGREEBACK或MSG_REFUSEBACK消息。另外,在發(fā)送這個(gè)消息之后,主界面上的某些元素將不再響應(yīng)用戶的操作。</p><p><b> 圖5.2 請(qǐng)求悔棋</b></p><p> 5.2.
46、3 同意悔棋消息——MSG_AGREEBACK</p><p> 表明對(duì)方接受了玩家的悔棋請(qǐng)求,除uMsg成員外其余成員皆無效。接到這個(gè)消息后,將進(jìn)行正常的悔棋操作。</p><p> 5.2.4 拒絕悔棋消息——MSG_REFUSEBACK</p><p> 表明對(duì)方拒絕了玩家的悔棋請(qǐng)求(如圖5.3所示),除uMsg成員外其余成員皆無效。接到這個(gè)消息后,整個(gè)
47、界面將恢復(fù)發(fā)送悔棋請(qǐng)求前的狀態(tài)。</p><p><b> 圖5.3 拒絕悔棋</b></p><p> 5.2.5 和棋消息——MSG_DRAW</p><p> 表明對(duì)方請(qǐng)求和棋,除uMsg成員外其余成員皆無效。接到這個(gè)消息后,會(huì)彈出MessageBox詢問是否接受對(duì)方的請(qǐng)求(如圖5.4所示),并根據(jù)玩家的選擇回返MSG_AGREED
48、RAW或MSG_REFUSEDRAW消息。另外,在發(fā)送這個(gè)消息之后,主界面上的某些元素將不再響應(yīng)用戶的操作。</p><p><b> 圖5.4 請(qǐng)求和棋</b></p><p> 5.2.6 同意和棋消息——MSG_AGREEDRAW</p><p> 表明對(duì)方接受了玩家的和棋請(qǐng)求(如圖5.5所示),除uMsg成員外其余成員皆無效。接到
49、這個(gè)消息后,雙方和棋。</p><p><b> 圖5.5 同意和棋</b></p><p> 5.2.7 拒絕和棋消息——MSG_REFUSEDRAW</p><p> 表明對(duì)方拒絕了玩家的和棋請(qǐng)求(如圖5.6所示),除uMsg成員外其余成員皆無效。接到這個(gè)消息后,整個(gè)界面將恢復(fù)發(fā)送和棋請(qǐng)求前的狀態(tài)。</p><p&
50、gt;<b> 圖5.6 拒絕和棋</b></p><p> 5.2.8 認(rèn)輸消息——MSG_GIVEUP</p><p> 表明對(duì)方已經(jīng)投子認(rèn)輸(如圖5.7所示),除uMsg成員外其余成員皆無效。接到這個(gè)消息后,整個(gè)界面將轉(zhuǎn)換為勝利后的狀態(tài)。</p><p><b> 圖5.7 認(rèn)輸</b></p>
51、<p> 5.2.9 聊天消息——MSG_CHAT</p><p> 表明對(duì)方發(fā)送了一條聊天信息,szMsg表示對(duì)方的信息,其余成員無效。接到這個(gè)信息后,會(huì)將對(duì)方聊天的內(nèi)容顯示在主對(duì)話框的聊天記錄窗口內(nèi)。</p><p> 5.2.10 對(duì)方信息消息——MSG_INFORMATION</p><p> 用來獲取對(duì)方玩家的姓名,szMsg表示對(duì)方的
52、姓名,其余成員無效。在開始游戲的時(shí)候,由客戶端向服務(wù)端發(fā)送這條消息,服務(wù)端接到后設(shè)置對(duì)方的姓名,并將自己的姓名同樣用這條消息回發(fā)給客戶端。</p><p> 5.2.11 再次開局消息——MSG_PLAYAGAIN</p><p> 表明對(duì)方希望開始一局新的棋局,除uMsg成員外其余成員皆無效。接到這個(gè)消息后,會(huì)彈出MessageBox詢問是否接受對(duì)方的請(qǐng)求(如圖5.8所示),并根據(jù)玩
53、家的選擇回返MSG_AGREEAGAIN消息或直接斷開網(wǎng)絡(luò)。</p><p><b> 圖5.8 再次開局</b></p><p> 5.2.12 同意再次開局消息——MSG_AGREEAGAIN</p><p> 表明對(duì)方同意了再次開局的請(qǐng)求,除uMsg成員外其余成員皆無效。接到這個(gè)消息后,將開啟一局新游戲。</p>&l
54、t;p><b> 6 主要算法</b></p><p> 五子棋游戲中,有相當(dāng)?shù)钠撬惴ǖ牟糠帧o論是人機(jī)對(duì)弈,還是網(wǎng)絡(luò)對(duì)弈,都需要合理算法的支持,本節(jié)中將詳細(xì)介紹五子棋中使用的算法。[13]</p><p><b> 6.1 判斷勝負(fù)</b></p><p> 五子棋的勝負(fù),在于判斷棋盤上是否有一個(gè)點(diǎn),從
55、這個(gè)點(diǎn)開始的右、下、右下、左下四個(gè)方向是否有連續(xù)的五個(gè)同色棋子出現(xiàn),如圖6.1:</p><p> 圖6.1 判斷勝負(fù)方向</p><p> 這個(gè)算法也就是CTable的Win成員函數(shù)。從設(shè)計(jì)的思想上,需要它接受一個(gè)棋子顏色的參數(shù),然后返回一個(gè)布爾值,這個(gè)值來指示是否勝利,代碼如下:</p><p> BOOL CTable::Win( int color )
56、 const</p><p><b> {</b></p><p><b> int x, y;</b></p><p><b> // 判斷橫向</b></p><p> for ( y = 0; y < 15; y++ )</p><p&g
57、t;<b> {</b></p><p> for ( x = 0; x < 11; x++ )</p><p><b> {</b></p><p> if ( color == m_data[x][y] &&</p><p> color == m_data[x
58、+ 1][y] &&</p><p> color == m_data[x + 2][y] &&</p><p> color == m_data[x + 3][y] &&</p><p> color == m_data[x + 4][y] )</p><p><b> {&
59、lt;/b></p><p> return TRUE;</p><p><b> }</b></p><p><b> }</b></p><p><b> }</b></p><p><b> // 判斷縱向</b&g
60、t;</p><p> for ( y = 0; y < 11; y++ )</p><p><b> {</b></p><p> for ( x = 0; x < 15; x++ )</p><p><b> {</b></p><p> if (
61、color == m_data[x][y] &&</p><p> color == m_data[x][y + 1] &&</p><p> color == m_data[x][y + 2] &&</p><p> color == m_data[x][y + 3] &&</p>
62、<p> color == m_data[x][y + 4] )</p><p><b> {</b></p><p> return TRUE;</p><p><b> }</b></p><p><b> }</b></p><p
63、><b> }</b></p><p> // 判斷“\”方向</p><p> for ( y = 0; y < 11; y++ )</p><p><b> {</b></p><p> for ( x = 0; x < 11; x++ )</p>&l
64、t;p><b> {</b></p><p> if ( color == m_data[x][y] &&</p><p> color == m_data[x + 1][y + 1] &&</p><p> color == m_data[x + 2][y + 2] &&</
65、p><p> color == m_data[x + 3][y + 3] &&</p><p> color == m_data[x + 4][y + 4] )</p><p><b> {</b></p><p> return TRUE;</p><p><b>
66、 }</b></p><p><b> }</b></p><p><b> }</b></p><p> // 判斷“/”方向</p><p> for ( y = 0; y < 11; y++ )</p><p><b> {<
67、;/b></p><p> for ( x = 4; x < 15; x++ )</p><p><b> {</b></p><p> if ( color == m_data[x][y] &&</p><p> color == m_data[x - 1][y + 1] &
68、&</p><p> color == m_data[x - 2][y + 2] &&</p><p> color == m_data[x - 3][y + 3] &&</p><p> color == m_data[x - 4][y + 4] )</p><p><b> {&l
69、t;/b></p><p> return TRUE;</p><p><b> }</b></p><p><b> }</b></p><p><b> }</b></p><p> // 不滿足勝利條件</p><
70、;p> return FALSE;</p><p><b> }</b></p><p> 需要說明的一點(diǎn)是,由于這個(gè)算法所遵循的搜索順序是從左到右、自上而下,因此在每次循環(huán)的時(shí)候,都有一些坐標(biāo)無需納入考慮范圍。例如對(duì)于橫向判斷而言,由于右邊界所限,因而所有橫坐標(biāo)大于等于11的點(diǎn),都構(gòu)不成達(dá)到五子連的條件,所以橫坐標(biāo)的循環(huán)上界也就定為11,這樣也就提高了搜
71、索的速度。</p><p> 6.2 人機(jī)對(duì)弈算法</p><p> 人機(jī)對(duì)弈算法完全按照CGame基類定義的接口標(biāo)準(zhǔn),封裝在了COneGame派生類之中。下面將對(duì)這個(gè)算法進(jìn)行詳細(xì)地介紹。[14]</p><p> 6.2.1 獲勝組合</p><p> 獲勝組合是一個(gè)三維數(shù)組,它記錄了所有取勝的情況。也就是說,參考于CTable::
72、Win中的情況,對(duì)于每一個(gè)落子坐標(biāo),獲勝的組合一共有</p><p> 15 * 11 * 2 + 11 * 11 * 2 = 572種。</p><p> 而對(duì)于每個(gè)坐標(biāo)的獲勝組合,應(yīng)該設(shè)置一個(gè)[15][15][572]大小的三維數(shù)組。</p><p> 在擁有了這些獲勝組合之后,就可以參照每個(gè)坐標(biāo)的572種組合給自己的局面和玩家的局面進(jìn)行打分,也就是根據(jù)當(dāng)
73、前盤面中某一方所擁有的獲勝組合多少進(jìn)行權(quán)值的估算,給出最有利于自己的一步落子坐標(biāo)。</p><p> 由于是雙方對(duì)弈,所以游戲的雙方都需要一份獲勝組合,也就是:</p><p> bool m_Computer[15][15][572]; // 電腦獲勝組合</p><p> bool m_Player[15][15][572]; // 玩家獲勝組合</
74、p><p> 在每次游戲初始化(COneGame::Init)的時(shí)候,需要將每個(gè)坐標(biāo)下可能的獲勝組合都置為true。</p><p> 此外,還需要設(shè)置計(jì)算機(jī)和玩家在各個(gè)獲勝組合中所填入的棋子數(shù):</p><p> int m_Win[2][572];</p><p> 在初始化的時(shí)候,將每個(gè)棋子數(shù)置為0。</p><
75、p> 6.2.2 落子后處理</p><p> 每當(dāng)一方落子后,都需要作如下處理:</p><p> 如果己方此坐標(biāo)的獲勝組合仍為true,且仍有可能在此獲勝組合處添加棋子,則將此獲勝組合添加棋子數(shù)加1;</p><p> 如果對(duì)方此坐標(biāo)的獲勝組合仍為true,則將對(duì)方此坐標(biāo)的獲勝組合置為false,并將對(duì)方此獲勝組合添加棋子數(shù)置為-1(不可能靠此組合
76、獲勝)。</p><p> 以玩家落子為例,代碼為:</p><p> for ( i = 0; i < 572; i++ )</p><p><b> {</b></p><p><b> // 修改狀態(tài)變化</b></p><p> if ( m_Play
77、er[stepPut.x][stepPut.y][i] &&</p><p> m_Win[0][i] != -1 )</p><p> m_Win[0][i]++;</p><p> if ( m_Computer[stepPut.x][stepPut.y][i] )</p><p><b> {</
78、b></p><p> m_Computer[stepPut.x][stepPut.y][i] = false;</p><p> m_Win[1][i] = -1;</p><p><b> }</b></p><p><b> }</b></p><p>
79、 6.2.3 查找棋盤空位</p><p> 在計(jì)算機(jī)落子之前,需要查找棋盤的空位,所以需要一個(gè)SearchBlank成員函數(shù)完成此項(xiàng)工作,此函數(shù)需要進(jìn)行不重復(fù)的查找,也就是說,對(duì)已查找過的空位進(jìn)行標(biāo)記,并返回找到空位的坐標(biāo),其代碼如下:</p><p> bool COneGame::SearchBlank( int &i, int &j,</p>&l
80、t;p> int nowTable[][15] )</p><p><b> {</b></p><p><b> int x, y;</b></p><p> for ( x = 0; x < 15; x++ )</p><p><b> {</b>&l
81、t;/p><p> for ( y = 0; y < 15; y++ )</p><p><b> {</b></p><p> if ( nowTable[x][y] == -1 && nowTable[x][y] != 2 )</p><p><b> {</b><
82、;/p><p><b> i = x;</b></p><p><b> j = y;</b></p><p> return true;</p><p><b> }</b></p><p><b> }</b></p
83、><p><b> }</b></p><p> return false;</p><p><b> }</b></p><p> 6.2.4 落子打分</p><p> 找到空位后,需要對(duì)這個(gè)點(diǎn)的落子進(jìn)行打分,這個(gè)分?jǐn)?shù)也就是這個(gè)坐標(biāo)重要性的體現(xiàn),代碼如下:</
84、p><p> int COneGame::GiveScore( const STEP& stepPut )</p><p><b> {</b></p><p> int i, nScore = 0;</p><p> for ( i = 0; i < 572; i++ )</p><
85、;p><b> {</b></p><p> if ( m_pTable->GetColor() == stepPut.color )</p><p><b> {</b></p><p><b> // 玩家下</b></p><p> if ( m_P
86、layer[stepPut.x][stepPut.y][i] )</p><p><b> {</b></p><p> switch ( m_Win[0][i] )</p><p><b> {</b></p><p><b> case 1:</b></p&g
87、t;<p> nScore -= 5;</p><p><b> break;</b></p><p><b> case 2:</b></p><p> nScore -= 50;</p><p><b> break;</b></p>
88、<p><b> case 3:</b></p><p> nScore -= 500;</p><p><b> break;</b></p><p><b> case 4:</b></p><p> nScore -= 5000;</p>
89、<p><b> break;</b></p><p><b> default:</b></p><p><b> break;</b></p><p><b> }</b></p><p><b> }</b>
90、;</p><p><b> }</b></p><p><b> else</b></p><p><b> {</b></p><p><b> // 計(jì)算機(jī)下</b></p><p> if ( m_Computer
91、[stepPut.x][stepPut.y][i] )</p><p><b> {</b></p><p> switch ( m_Win[1][i] )</p><p><b> {</b></p><p><b> case 1:</b></p>&
92、lt;p> nScore += 5;</p><p><b> break;</b></p><p><b> case 2:</b></p><p> nScore += 50;</p><p><b> break;</b></p><p
93、><b> case 3:</b></p><p> nScore += 100;</p><p><b> break;</b></p><p><b> case 4:</b></p><p> nScore += 10000;</p><
94、;p><b> break;</b></p><p><b> default:</b></p><p><b> break;</b></p><p><b> }</b></p><p><b> }</b><
95、;/p><p><b> }</b></p><p><b> }</b></p><p> return nScore;</p><p><b> }</b></p><p> 如代碼所示,考慮到攻守兩方面的需要,所以將玩家落子給的分?jǐn)?shù)置為負(fù)值。
96、</p><p> 6.2.5 防守策略</p><p> 落子的考慮不單單要從進(jìn)攻考慮,還要從防守考慮。這一細(xì)節(jié)的實(shí)現(xiàn)其實(shí)就是讓計(jì)算機(jī)從玩家棋盤布局分析戰(zhàn)況,然后找出對(duì)玩家最有利的落子位置。整個(gè)過程如下:</p><p> for ( m = 0; m < 572; m++ )</p><p><b> {</
97、b></p><p> // 暫時(shí)更改玩家信息</p><p> if ( m_Player[i][j][m] )</p><p><b> {</b></p><p> temp1[n] = m;</p><p> m_Player[i][j][m] = false;</p
98、><p> temp2[n] = m_Win[0][m];</p><p> m_Win[0][m] = -1;</p><p><b> n++;</b></p><p><b> }</b></p><p><b> }</b></p&g
99、t;<p> ptempTable[i][j] = 0;</p><p><b> pi = i;</b></p><p><b> pj = j;</b></p><p> while ( SearchBlank( i, j, ptempTable ) )</p><p>&
100、lt;b> {</b></p><p> ptempTable[i][j] = 2; // 標(biāo)記已被查找</p><p> step.color = m_pTable->GetColor();</p><p> step.x = i;</p><p> step.y = j;</p><p
101、> ptemp = GiveScore( step );</p><p> if ( pscore > ptemp ) // 此時(shí)為玩家下子,運(yùn)用極小極大法時(shí)應(yīng)選取最小值</p><p> pscore = ptemp;</p><p><b> }</b></p><p> for ( m = 0
102、; m < n; m++ )</p><p><b> {</b></p><p><b> // 恢復(fù)玩家信息</b></p><p> m_Player[pi][pj][temp1[m]] = true;</p><p> m_Win[0][temp1[m]] = temp2[m]
103、;</p><p><b> }</b></p><p> 6.2.6 選取最佳落子</p><p> 在循環(huán)結(jié)束的時(shí)候,就可以根據(jù)攻、守兩方面的打分綜合地考慮落子位置了。代碼如下:</p><p> if ( ctemp + pscore > cscore )</p><p>&l
104、t;b> {</b></p><p> cscore = ctemp + pscore;</p><p> bestx = pi;</p><p> besty = pj;</p><p><b> }</b></p><p> 在這之后,重新改變一下棋盤的狀態(tài)(6.
105、2.2)即可。</p><p><b> 7 幾點(diǎn)補(bǔ)充說明</b></p><p> 考慮到程序的響應(yīng)速度,人機(jī)對(duì)弈算法只對(duì)玩家的棋子進(jìn)行了一步的推測(cè)。</p><p> 由于計(jì)算機(jī)在落子時(shí)選取的是得分最高的一步落子,所以如果玩家在開局的時(shí)候不改變落子步驟,那么將會(huì)獲得從頭至尾相同的棋局。</p><p> 考慮
106、到下棋同時(shí)還要聊天,所以并未對(duì)落子時(shí)間加入任何限制,同樣如果玩家離開游戲也不會(huì)判負(fù)。</p><p> 對(duì)于人機(jī)對(duì)弈的悔棋處理,由于這個(gè)算法的開銷相當(dāng)大,每一步落子都會(huì)存在不同的棋盤布局,所以實(shí)現(xiàn)從頭到尾的悔棋不是很現(xiàn)實(shí)(將會(huì)存在過多的空間保存棋盤布局),因而在人機(jī)對(duì)弈模式下,只允許玩家悔最近的兩步落子。</p><p><b> 8 心得體會(huì)</b></p
107、><p> 通過編寫這個(gè)程序,我體會(huì)最為深刻的一點(diǎn)是系統(tǒng)架構(gòu)和設(shè)計(jì)模式的重要性。即使是對(duì)于一個(gè)并不大的程序,代碼的組織都是非常重要的,因?yàn)檫@關(guān)系到日后的維護(hù)以及擴(kuò)展。這個(gè)游戲之中,有關(guān)網(wǎng)絡(luò)Socket編程或者博弈樹算法的知識(shí)都可以直接從無所不包的Internet上獲取,甚至可以直接獲得一個(gè)完整的五子棋人機(jī)對(duì)弈算法的源代碼級(jí)模塊。但是對(duì)于系統(tǒng)的架構(gòu),卻完全是自己的事情,幾千上萬行的代碼需要通過合適的方法組織起來,使程
108、序員編寫代碼更加有條理,更加符合軟件工程的標(biāo)準(zhǔn),這才是最重要的。</p><p> 在剛開始編寫這個(gè)程序的時(shí)候,我幼稚地認(rèn)為其中最重要的是博弈樹算法。但是頭一個(gè)月編寫程序的時(shí)候卻發(fā)現(xiàn)程序越寫越不容易維護(hù),可見是我走錯(cuò)了方向。后來我向公司真正的軟件設(shè)計(jì)人員及系統(tǒng)架構(gòu)師討教,他們告訴我:我們的先人早已為我們準(zhǔn)備好了各種精良可用的現(xiàn)成算法,我們所要做的就是直接“拿來主義”罷了;但是對(duì)于代碼的組織(也就是軟件的架構(gòu))才
109、是真正軟件工業(yè)的核心部分,因?yàn)檐浖聦?shí)上是直接和經(jīng)濟(jì)掛鉤的,因此我們必須在編寫代碼之前選擇一種最為合適的方法來組織這些代碼,否則我們將會(huì)失去更多的時(shí)間和金錢。[15]</p><p> 于是,我將以前寫的代碼全部刪除,認(rèn)真地思考了三天的時(shí)間。我也在這三天內(nèi)真正從一個(gè)學(xué)生程序員走入了軟件開發(fā)的大門,我開始發(fā)現(xiàn)其實(shí)軟件開發(fā)并不是純數(shù)學(xué)——正相反,數(shù)學(xué)只占了很小的一部分。它其實(shí)是一種哲學(xué),一種有著數(shù)學(xué)美感的哲學(xué)。&l
110、t;/p><p><b> 參考文獻(xiàn)</b></p><p> MSDN for Visual Studio 6.0</p><p> 設(shè)計(jì)模式——可復(fù)用面向?qū)ο筌浖幕A(chǔ),Erich Gamma/Richard Helm/Ralph Johnson/John Vlissides著,李英軍/馬曉星/蔡敏/劉建中 等譯,機(jī)械工業(yè)出版社</
111、p><p> 深入淺出MFC(第2版),侯俊杰著,華中科技大學(xué)出版社</p><p> A Beginner 's Guide to Pointers,Andrew Peace</p><p> 水煮多態(tài),titilima</p><p> Microsoft® Visual C++.NET 技術(shù)內(nèi)幕(第6版),Geor
112、ge Shepherd/David Kruglinski著,潘愛民譯,清華大學(xué)出版社</p><p> Visual C++網(wǎng)絡(luò)通信協(xié)議分析與應(yīng)用實(shí)現(xiàn),汪曉平/鐘軍 等編著,人民郵電出版社</p><p> C++編程思想,Bruce Eckel著,劉宗田/邢大紅/孫慧杰 等譯,機(jī)械工業(yè)出版社</p><p> 21天學(xué)通C++,Jesse Liberty著,
113、康博創(chuàng)作室譯,人民郵電出版社</p><p> C++標(biāo)準(zhǔn)程序庫,Nicolai M.Josuttis著,侯捷/孟巖 譯,華中科技大學(xué)出版社</p><p> Windows程序設(shè)計(jì),Charles Petzold著,北京博彥科技發(fā)展有限公司譯,北京大學(xué)出版社</p><p> Visual C++.NET網(wǎng)絡(luò)編程,易君 編著,中國(guó)鐵道出版社</p>
114、;<p><b> 博弈樹搜索</b></p><p> 五子棋的核心算法,蟈蟈俊.net</p><p> 道法自然,王詠武/王詠剛 著,電子工業(yè)出版社</p><p><b> 致謝</b></p><p> 感謝我的父母,沒有您們的包容和支持,就不會(huì)有我的今天。<
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 眾賞文庫僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- C++五子棋源代碼及畢業(yè)論文.doc
- (畢業(yè)設(shè)計(jì))c++五子棋源代碼及畢業(yè)論文
- 五子棋畢業(yè)論文
- 五子棋c++課程設(shè)計(jì)
- 五子棋c++課程設(shè)計(jì)
- 五子棋畢業(yè)論文-html開發(fā)五子棋的原型設(shè)計(jì)
- c++課程設(shè)計(jì)——五子棋
- 五子棋游戲設(shè)計(jì)畢業(yè)論文
- c++五子棋課程設(shè)計(jì)報(bào)告
- java五子棋游戲畢業(yè)論文
- 基于java的五子棋游戲的設(shè)計(jì)(源代碼+論文)
- 畢業(yè)論文——五子棋游戲設(shè)計(jì)
- java課程設(shè)計(jì)五子棋(附源代碼)
- c++課程設(shè)計(jì)--五子棋游戲
- 【精品】基于java的五子棋游戲的設(shè)計(jì)(源代碼+論文)
- 畢業(yè)論文---網(wǎng)絡(luò)五子棋游戲設(shè)計(jì)
- 【精品】基于java的五子棋游戲的設(shè)計(jì)(源代碼+論文)
- 基于visual c++的五子棋游戲畢業(yè)設(shè)計(jì)
- 畢業(yè)論文 基于android的五子棋設(shè)計(jì)
- 五子棋畢業(yè)論文--人工智能課題
評(píng)論
0/150
提交評(píng)論