五子棋(C++面向对象实现)
代碼鏈接:
https://github.com/zhouzg/FiveChess/tree/master
https://download.csdn.net/download/dreamlike_zzg/10948075
VS2017,控制臺(tái)輸出字符顯示棋盤。共定義的6個(gè)class,即棋子類(Chess)、棋盤類(ChessBoard)、棋手類(Player)、裁判類(Judge)、顯示類(Displayer)、游戲類(Game)。有人機(jī)對(duì)弈人人對(duì)弈兩種模式,時(shí)間原因AI下棋落子是隨機(jī)的。
序:
建國(guó)70年2月1日周五,寒假首flag告拔——以C++編作五子棋。上學(xué)期,求學(xué)于雁西湖畔,受教于楊LX大師,獲益匪淺,相見恨晚。作五子棋乃楊之令,然選課未果,雖遵令而學(xué)分猶不可得。當(dāng)是時(shí),期末眾考威逼咄咄,不得顧,遂妥,暫留坑。1月29日始填坑,醉坑三日有余,今日坑畢時(shí),如夢(mèng)初醒,方覺(jué)此乃吾獨(dú)作純軟project之首例,悲喜交加,不可斷絕。何喜之有?首例終竣,從無(wú)生有,質(zhì)變也。何悲之有?羞才疏學(xué)淺,恨竣工時(shí)晚,實(shí)愧也。感慨良多,情溢于言表,遂逐此文,以自勉。誠(chéng)求各路賢士不吝賜教,請(qǐng)灑潘江,各傾陸海。
正文
程序按面向?qū)ο蟮姆绞骄帉?#xff0c;共定義的6個(gè)class,即棋子類(Chess)、棋盤類(ChessBoard)、棋手類(Player)、裁判類(Judge)、顯示類(Displayer)、游戲類(Game)。其中棋手類作為基類派生出真人棋手類(Human)和電腦棋手類(AI)。由于本人寫五子棋的目的為體會(huì)C++的面向?qū)ο缶幊?#xff0c;再加上個(gè)人時(shí)間能力有限,所以沒(méi)有給AI什么策略,只讓其隨機(jī)落子。畢竟策略設(shè)計(jì)與面向?qū)ο缶幊剃P(guān)系不大。
接下來(lái)主要分享下本人關(guān)于這5個(gè)類設(shè)計(jì)的一點(diǎn)思考。在課上獲得了些體會(huì):C++比C語(yǔ)言難,不難在語(yǔ)法,而是難在對(duì)面向?qū)ο蟮睦斫夂皖惖脑O(shè)計(jì)。類的設(shè)計(jì)有些藝術(shù)的味道,設(shè)計(jì)沒(méi)有對(duì)錯(cuò)之分,但好的設(shè)計(jì)有很強(qiáng)的復(fù)用性,可以為以后的工作帶來(lái)便利,節(jié)省時(shí)間。楊大師說(shuō),省時(shí)間就是省錢!
下面僅是我的分類設(shè)計(jì),大家有不同想法歡迎留言討論。
Chess類
定義棋子,棋子包含顏色、位置。
ChessBoard類
定義棋盤,主要包括棋盤的大小、每個(gè)位置的狀態(tài)(是否有棋子)、添棋子和刪棋子操作,棋譜,顯示棋盤所需的字符。
Player類
定義棋手,主要包括棋手姓名、執(zhí)棋子的顏色、下棋操作。Human和AI作為其派生類。
Judge類
判斷落子棋子是否合法、是否有人獲勝、執(zhí)行悔棋操作。
Displayer類
負(fù)責(zé)所有的顯示任務(wù),包括顯示棋盤和提示信息。
Game類
負(fù)責(zé)整個(gè)游戲的流程。
接下來(lái)談一下設(shè)計(jì)類時(shí)讓我有些糾結(jié)的地方
“悔棋”由哪個(gè)類來(lái)負(fù)責(zé)?
?????? 這個(gè)是上課討論的一大焦點(diǎn),主要分為兩派:1.由棋手類負(fù)責(zé)。因?yàn)榛谄逭?qǐng)求是由棋手發(fā)起的,而且現(xiàn)實(shí)中把棋盤上的棋子拿掉也需要人來(lái)執(zhí)行。2.由裁判類負(fù)責(zé)。因?yàn)槠灞P的狀態(tài)變動(dòng)都需要裁判類判斷是否合法,而且悔棋需所有棋手都同意才行,有時(shí)不能悔棋,需要第三方調(diào)解。
?????? 個(gè)人觀點(diǎn):悔棋請(qǐng)求由玩家發(fā)起,撤回棋子由棋盤類負(fù)責(zé),而裁判類決定是否撤棋子。
首先本人不同意第一個(gè)觀點(diǎn),因?yàn)槌菲遄硬灰欢ǚ且婕覉?zhí)行。因?yàn)榛谄鍟r(shí)撤回的棋子是固定的,即最新下出的那步棋,這是由當(dāng)前棋局決定的,所以不需棋手來(lái)額外判斷。如果把該程序復(fù)用到多人棋類游戲中,由棋手類負(fù)責(zé)悔棋的話,悔棋時(shí)需要每個(gè)棋手設(shè)法獲取自己所有落子的順序,并輪流執(zhí)行撤棋操作,感覺(jué)好麻煩。
若想悔棋多次,則需要獲取棋盤中每個(gè)棋子的先后順序,也就是要維護(hù)一個(gè)棋譜,而棋譜和棋盤狀態(tài)均是棋盤類的成員,所以很自然就讓棋盤類來(lái)執(zhí)行撤棋操作。
悔棋需要所有玩家都同意才可執(zhí)行,所以需要第三方來(lái)協(xié)調(diào),這是裁判的職能。
這樣設(shè)計(jì)給人的感覺(jué)是我們的棋盤桌是“電動(dòng)”的,有個(gè)按鈕,按下則棋盤自動(dòng)把最近的那幾步棋撤走。而按鈕的掌控權(quán)在裁判手里,他會(huì)根據(jù)局勢(shì)判斷是否按下這個(gè)按鈕。
?????? 還有一點(diǎn),判斷悔棋是否可以執(zhí)行是在Judge的成員函數(shù)內(nèi)完成?還是在游戲進(jìn)行的流程中完成(即Game類中)?我是按照后者實(shí)現(xiàn)的。考慮到程序的復(fù)用性,我希望除Game類外的其他類的功能盡可能的獨(dú)立于游戲流程。這樣將程序修改為其他棋類時(shí),由于不同的棋局進(jìn)行流程可能不同,所以Game類的修改無(wú)法避免,但其他類的改動(dòng)會(huì)相對(duì)較小。
“棋手輪換”在哪里實(shí)現(xiàn)?
?????? 棋盤類游戲都是玩家輪流下出棋子。所以Player *current_player(指向當(dāng)前要出棋的玩家)所指向的對(duì)象要不斷改變。“棋手輪換”在哪里實(shí)現(xiàn)與“判斷悔棋是否可以執(zhí)行” 在哪里實(shí)現(xiàn)面臨的問(wèn)題相似。由Judge來(lái)決定當(dāng)前輪到誰(shuí)出棋似乎很合理,但是“輪到誰(shuí)”是游戲進(jìn)行的流程決定的,輪到誰(shuí)了就是誰(shuí),是很客觀的,無(wú)需裁決。五子棋流程簡(jiǎn)單,兩個(gè)玩家一替一次交換就行了,兩種方式實(shí)現(xiàn)起來(lái)都不困難,但對(duì)于較復(fù)雜的游戲來(lái)說(shuō),可能出現(xiàn)次序中途有變動(dòng)的情況(比如“大富翁”里強(qiáng)制休息一輪,“飛行棋”先到終點(diǎn)的玩家不必參加下一輪)。這時(shí)Judge想要判斷下一步輪到誰(shuí)了,還要從游戲進(jìn)行情況來(lái)判斷,需要許多描述游戲當(dāng)前狀況的參數(shù),直接在Game類的游戲流程里實(shí)現(xiàn)似乎更方便些。
為何需要Displayer類?
?????? 本程序是在控制臺(tái)里輸出,直接cout就行了,為何要搞個(gè)顯示類,再在Displayer的成員函數(shù)中使用cout?豈不多此一舉?這里是為了方便修改為其他顯示方式,修改時(shí)只需將成員函數(shù)中的cout替換就行了。
Player類的設(shè)計(jì)
?????? 棋手分為真人玩家(Human)和電腦玩家(AI),均從Player類中派生得到。Human與AI的區(qū)別有兩處:1. Human可以悔棋,AI不用2.落子方式不同,即成員函數(shù)GiveChess()不同。區(qū)別1容易處理,主要談?wù)剠^(qū)別2。Human出棋需要手動(dòng)輸入棋子坐標(biāo),需要傳參,即Human.GiveChess(Position p),而AI出棋不需要傳參,根據(jù)棋局便可決定落子位置,即AI.GiveChess( )或AI.GiveChess(ChessBoard board)。這就導(dǎo)致了兩個(gè)派生類存在同名不同參的成員函數(shù),給多態(tài)性的保證造成影響。
?????? Player類里將GiveChess定義為純虛函數(shù)virtual Chess GiveChess( ) = 0;在派生類中直接分別重寫Chess Human::GiveChess(Position p)和Chess AI::GiveChess( )的話,無(wú)法對(duì)基類虛函數(shù)覆蓋,只是將其隱藏。執(zhí)行下列語(yǔ)句必然error,
Player *ptr;
Human human;
AI ai;
ptr=&ai;
ptr->GiveChess();
ptr=&human;
ptr->GiveChess(position);
這個(gè)問(wèn)題卡了我好久,也讓我回想起了楊大師的一句話,意思是C++的類繼承機(jī)制使派生類能加能改不能刪,即可以增加成員,修改基類成員函數(shù),但是不能丟棄基類成員。就是有些基類成員對(duì)派生類無(wú)用,但派生類依然要將其保留下來(lái)。楊大師善于舉例子、打比方,士兵的坐騎由戰(zhàn)馬演變成了坦克,可以把戰(zhàn)馬看做基類,派生出了坦克類,從外表看坦克與馬差異巨大,但是把坦克的蓋子掀開,里面還“藏”了匹馬。我這里就是想把基類的GiveChess丟棄,增加不同參數(shù)的同名函數(shù)。好像扯遠(yuǎn)了,哈哈。
為了解決這個(gè)問(wèn)題,我把基類的虛函數(shù)這樣寫:
virtual Chess GiveChess(Position p = { -1,-1 }) = 0;
由于含有默認(rèn)參數(shù),調(diào)用AI::GiveChess( )時(shí)不需要傳入?yún)?shù),當(dāng)然隨便傳入一個(gè)Position參數(shù)也沒(méi)事兒,反正函數(shù)內(nèi)部用不到這個(gè)Position。這樣就從形式上解決了問(wèn)題,雖然連自己都感覺(jué)怪怪的。
我還征求了其他同學(xué)的看法,有人說(shuō)有時(shí)基類的源碼不能隨意修改,不能基類成員函數(shù)加默認(rèn)參數(shù)。給出的方案是把AI::GiveChess需要的參數(shù)(position p)中作為新的數(shù)據(jù)成員加入AI類,在加一個(gè)成員函數(shù)AI::SetPosition(Position p)負(fù)責(zé)修改position p的值。在調(diào)用AI::GiveChess( )前先調(diào)用一下下AI::SetPosition(Position p)就行了。但是這也存在問(wèn)題,ptr->SetPosition( )會(huì)報(bào)錯(cuò),因?yàn)榛惱餂](méi)有SetPosition成員函數(shù)。該方案還是不行。
同名不同參的函數(shù)如何實(shí)現(xiàn)多態(tài)?這個(gè)本人目前還沒(méi)有其他好的辦法。換個(gè)思路吧。也許我的類設(shè)計(jì)本事就有些問(wèn)題,非要C++去做他不善于做的事。那就改吧!睡前想到一個(gè)方案,干脆基類Player中寫兩個(gè)虛函數(shù):
??? virtual Chess GiveChess(Position p) = 0;
? ? virtual Chess GiveChess() = 0;
也不區(qū)分Human和AI類了,只派生一個(gè)Human_AI類,在Human_AI類里把兩個(gè)函數(shù)都重寫,需要真人就只調(diào)用GiveChess(Position p),需要電腦人就只調(diào)用GiveChess()。這樣設(shè)計(jì)也有其合理性:真人下棋時(shí)會(huì)需要電腦提示,相當(dāng)于讓電腦替自己下一步棋,所以真人棋手GiveChess()和GiveChess(Position p)都需要。
字?jǐn)?shù)逼近3K了,想說(shuō)的也差不多了,總結(jié)一下吧。這次寫五子棋的初衷已經(jīng)達(dá)到了,的確對(duì)面向?qū)ο缶幊逃辛顺醪降捏w會(huì)。面向?qū)ο笤O(shè)計(jì)分類可以抽象出相對(duì)獨(dú)立的模塊,有助于大型項(xiàng)目的維護(hù),也帶來(lái)了更強(qiáng)的復(fù)用性。就這個(gè)五子棋而言,用C語(yǔ)言按照面向過(guò)程方式寫或許更容易,甚至一個(gè)main函數(shù)就搞定了,但是復(fù)用性就要大打折扣了。但用C++,以后想要寫個(gè)其他什么棋類游戲,只需修改那6個(gè)類的部分成員就行了,整體框架基本不變。
另外,寫純軟程序感覺(jué)就是爽,定位導(dǎo)致出錯(cuò)的問(wèn)題所在比寫單片機(jī)程序快多了。不過(guò)過(guò)去寫的單片機(jī)程序也不是一點(diǎn)用都沒(méi),在寫游戲流程時(shí),跟寫單片機(jī)時(shí)似曾相識(shí),一些經(jīng)驗(yàn)同樣適用。
好了,就到這吧!文筆不好,贅述頗多,感謝您的耐心閱讀,謝謝!目前本人對(duì)C++的體會(huì)只有這些,可能很low,萌新入坑,在所難免,讓大佬們見笑了。懇請(qǐng)各路高手多多指點(diǎn)批評(píng)。
祝大家新年快樂(lè)!事事順心!
總結(jié)
以上是生活随笔為你收集整理的五子棋(C++面向对象实现)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: PHP面向对象——GD库实现图片水印和缩
- 下一篇: 重磅干货 | 五万字长文总结 C/C++