這篇文章主要爲大家詳細介紹了C++語言設計實現五子棋,包括數據結構和對象設計及主函數調用實現,具有一定的參考價值,感興趣的小夥伴們可以參考一下
本文爲大家分享了C++五子棋的設計思路和設計實現,供大家參考,具體內容如下
算法思路:
在結束了對C++的學習之後,準備自己編制一些簡單的練習程序。目前初步設想是編制一個人機對戰的簡易五子棋軟件。 以下爲個人設計思考的過程。
首先,進行問題分析與設計。計劃實現的功能爲,開局選擇人機或雙人對戰,確定之後比賽開始。比賽結束後初始化棋盤,詢問是否繼續比賽或退出。後續可加入覆盤、悔棋等功能。整個過程中,涉及到了棋子和棋盤兩種對象,同時要加上人機對弈時的AI對象,即涉及到三個對象。
棋盤類的設計。
數據存儲:五子棋棋盤爲15*15的網格結構,即一共有225個節點,每個節點有橫豎座標各一,同時每個節點狀態有3種,黑,白,或者爲空。可考慮採用二維數組來存儲。每個節點三種狀態,適合採用枚舉(enum)類型。
-* 實現功能*:1. 棋盤對象應負責實時更新棋盤狀態並對外顯示,因此需要接收棋子對象的輸入,同時需要對外輸出。而棋盤對象不需要對棋子對象發送信息,所以可以設計棋盤類中更新棋盤狀態的函數接收棋子對象作爲形參。2. 同時,在每次走棋之後,棋盤對象都要及時更新棋盤信息,即輸出棋盤狀態。3. 而且,每次走棋之後更新輸出之前,應該對棋子走棋是否符合規則以及輸贏進行判定。若將規則判定單獨封裝爲一個對象,則不方便調用棋盤信息,故將規則判定設計爲棋盤類的成員函數,接收兩個形參,一個是棋盤當前的狀態,一個是即將走的下一步棋,即一個棋子對象。
設計棋子對象。棋子對象應包含兩種信息。一是棋子顏色,二是當前要走的棋子的位置座標,並保留對外輸出的接口。
接下來細化規則判定函數。
- 首先進行走棋規則判定。接收當前棋子位置信息後,判定該位置是否越界或爲空,若非空或者越界,則判定違規,拋擲異常,交付上級調用處理。
- 然後進行輸贏判定。按照一定順序對當前落子位置的相鄰元素進行同色判定並計數。當發現某條直線上同色棋子超過四枚,則判定當前走棋方獲勝 。判定過程中同樣需要注意是否越界。若均未構成五星連珠,則進入平局判定。
- 平局判定,遍歷棋盤,統計空位,若空位爲0,即棋盤已滿,判定爲平局。
接下來設計下棋AI。設計爲一個棋子類型的函數,即接收當前棋盤狀態和對方最後一次落棋,返回棋子對象類型。
對弈算法設計。
- 首先進行先後手判定:若棋盤爲空則直接落子(8,8),正中開局。
- 然後進行防守判定:針對對方上次落棋進行活棋檢測,在橫、豎、左斜、右斜四條直線上依次進行檢測。在任意方向檢測到四或活三,即可進行 封堵操作,給出所有可行的封堵位置。若未檢測到四或活三,則統計活二並給出所有可能的封堵位置。然後針對所有可能的封堵位置進行評分,選取分數最高的位置進行落子。若上述檢測均未找到防守點,則轉入進攻算法。
- 進攻算法:採用枚舉,即暴力破解的方法。遍歷整個棋盤的所有空位,並給出每個空位的評分,選取最高分進行落子。
- 活棋檢測算法:給定參照棋子,在四個方向上分別檢測。以橫向檢測爲例,設參照棋子座標爲(x,y),設定同色計數器count=1(計算同色棋子數目),設定封鎖端統計量lock=0,設定已判斷的方向統計judge=0。對x-1,判斷節點狀態,若同色計數器加1,繼續判斷x-2;若異色,則lock+1,judge+1,若judge=2,終止判斷,若judge<2,反向判斷x+1;若空白,judge+1,若judge=2,終止判斷,若judge<2,反向判斷。最後得到被封堵的端口數lock和同色數count。若lock=0,count=3或2,判定爲活3或活2。若lock=1,count=4,判定爲4,若lock=1,count=3,判定爲半3。但是在這種算法中,關於空白的判定存在着一些問題。用0代表空白,用+代表同色,-代表異色,則當出現下列情況時:-0++0-,-+++0-,事實上是死棋,而+0++0,+0+++-,實際上相當於活3或半4。爲此,需要對活2和半3的情況進行進一步篩選,即空白端應保證連續兩個空白。在活棋檢測過程中,如果遇到活3或者半4,則立即終止檢測,返回落子的可能位置。若沒有則記錄活2半3的防守位置,換方向檢測。最後返回一個棋子類的數組,包含所有建議的落子位置。若均無,則遍歷棋盤,統計所有空白位置,返回。
- 落子位置評分算法:對活棋檢測返回數組中的每個位置進行評分,即以該點爲己方參照點,進行活棋檢測,若有count=5,直接返回該落子位置。有活三或者半4,分數加20,有活2,分數加5,對角線有相鄰同色,分數+2,有異色,分數-2,橫豎有同色,分數+1,有異色,分數-1。最後排序,取最高分對應的落子,返回該落子。
接下來則是數據結構和對象設計及主函數調用實現:
類及類的實現
#define RENJU_H #include <iostream> #include <windows.h> #include <string> #define hor 7 #define ver 4 using namespace std; //用於記錄座標 struct position { int x; int y; position() { x = 0; y = 0; } position(int a,int b) { x = a; y = b; } }; //用於記錄棋子顏色和節點狀態 enum state { blank=0,black=1,white=2 }; //用於存儲棋局分析信息:未完賽,犯規,平局,黑方勝,白方勝 enum result { go_on,error,draw,black_win,white_win }; // 設置光標 void setpos(COORD a) { HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleCursorPosition(out, a); } // 設置光標 void setpos(int i, int j) { COORD pos = { i, j }; setpos(pos); } //繪圖函數,用於在指定座標輸出指定字符 inline void gps(int x,int y,char c) { setpos(y,x); cout<<c; } //繪圖函數,用於在指定座標輸出整數 inline void gps(int x,int y,int i) { setpos(y,x); if(i>=10) cout<<i; else cout<<0<<i; } //繪圖函數,用於在指定座標輸出字符串 inline void gps(int x,int y,string s) { setpos(y,x); cout<<s; } //繪圖函數,用於在給定座標打印棋盤中的一格 void tab(int x,int y,state str) { string s; switch (str) { case blank: s=" "; break; case black: s="黑"; break; case white: s="白"; break; default: break; } setpos(y,x); cout<<" ------ "; setpos(y,x+1); cout<<"| |"; setpos(y,x+2); cout<<"| "<<s<<" |"; setpos(y,x+3); cout<<"| |"; setpos(y,x+4); cout<<" ------ "; } //查找最大值 int MAX(const int *a,int n) { int max = a[0]; for(int i =1; i < n ;i++) { if(a[i] > max) max = a[i]; } return max; } //檢測是否符合勝利條件 //棋子類 class chess { public: inline chess(int x=0,int y=0,state c=blank) { point.x=x,point.y=y; color=c; }; inline chess(chess &ch) { point=ch.drop_point(); color=ch.get_color(); } ~chess(){}; struct position drop_point()const;//用於向外部提供棋子位置 inline state get_color() const { return color;}//提供棋子顏色信息 void set_point();//用於從外部獲取棋子位置 void set_point(int x,int y){ point.x=x,point.y=y;} void set_point(position p){ point.x=p.x,point.y=p.y;} void set_color(state c){ color=c;}//設置棋子顏色 private: position point; enum state color; }; position chess::drop_point()const { return point; } void chess::set_point() { if(color==black) { setpos(110,1); cout<<"請黑方輸入落子列數和行數,空格隔開:"; cin>>point.x>>point.y; while(point.x<1||point.x>15) { setpos(110,1); cout<<"列數輸入超出範圍,請重新輸入1~15之間數字 "; cin>>point.x; } while(point.y<1||point.y>15) { setpos(110,2); cout<<"行數輸入超出範圍,請重新輸入1~15之間數字 "; cin>>point.y; } } else if(color==white) { setpos(110,1); cout<<"請白方輸入落子列數和行數,空格隔開:"; cin>>point.x>>point.y; while(point.x<1||point.x>15) { setpos(110,1); cout<<"列數輸入超出範圍,請重新輸入1~15之間數字 "; cin>>point.x; } while(point.y<1||point.y>15) { setpos(110,2); cout<<"行數輸入超出範圍,請重新輸入1~15之間數字 "; cin>>point.y; } } point.x--; point.y--; } //棋盤類 class chessboard { public: chessboard() { for(int i=0;i<15;i++) for(int j=0;j<15;j++) { square[i][j]=blank; } } chessboard(chessboard *cb) { for(int i=0;i<15;i++) for(int j=0;j<15;j++) { square[i][j]=cb->viewboard(i,j); } } inline state viewboard(position p_c) const;//接收座標,返回該位置的狀態 inline state viewboard(int x,int y) const;//接收整數座標,返回該位置的狀態 void update(chess ch);//接收新棋子,更新棋盤狀態 void display()const;//向外輸出棋盤狀態 result judge(chess ch)const;//規則函數,判斷走棋是否犯規和輸贏 void clear()//清空棋盤 { for(int i=0;i<15;i++) for(int j=0;j<15;j++) { square[i][j]=blank; } } private: state square[15][15]; }; int check_five(chessboard bd,chess ch) { position ori=ch.drop_point(); int count=1;//計數器,統計同色個數 int sum[4]={0}; bool locked=0;//邏輯標記量,用來標記是否遇到了非同色節點 //水平方向檢測 for(int i=0,locked=0;i<5&&((ori.x-i)>=0)&&(!locked);i++)//終止循環條件:同色超過4個或觸碰到棋盤邊界或遇到非同色節點 if(ch.get_color()==bd.viewboard(ori.x-i,ori.y)) count++; else if(i!=0) locked=1; //sum[0]=count-1; for(int i=0,locked=0;i<5&&((ori.x+i)<=14)&&(!locked);i++)//終止循環條件:同色超過4個或觸碰到棋盤邊界或遇到非同色節點 if(ch.get_color()==bd.viewboard(ori.x+i,ori.y)) count++; else if(i!=0) locked=1; //sum[1]=count-sum[0]-2; sum[0]=count; if(count>=5) return count; //豎直方向檢測 count=1; for(int i=0,locked=0;i<5&&((ori.y-i)>=0)&&(!locked);i++)//終止循環條件:同色超過4個或觸碰到棋盤邊界或遇到非同色節點 if(ch.get_color()==bd.viewboard(ori.x,ori.y-i)) count++; else if(i!=0) locked=1; //sum[2]=count-1; for(int i=0,locked=0;i<5&&((ori.y+i)<=14)&&(!locked);i++)//終止循環條件:同色超過4個或觸碰到棋盤邊界或遇到非同色節點 if(ch.get_color()==bd.viewboard(ori.x,ori.y+i)) count++; else if(i!=0) locked=1; //sum[3]=count-sum[2]-2; sum[1]=count; if(count>=5) return count; //左上到右下斜向檢測 count=1; for(int i=0,locked=0;i<5&&((ori.y-i)>=0)&&((ori.x-i)>=0)&&(!locked);i++)//終止循環條件:同色超過4個或觸碰到棋盤邊界或遇到非同色節點 if(ch.get_color()==bd.viewboard(ori.x-i,ori.y-i)) count++; else if(i!=0) locked=1; //sum[4]=count-1; for(int i=0,locked=0;i<5&&((ori.x+i)<=14)&&((ori.y+i)<=14)&&(!locked);i++)//終止循環條件:同色超過4個或觸碰到棋盤邊界或遇到非同色節點 if(ch.get_color()==bd.viewboard(ori.x+i,ori.y+i)) count++; else if(i!=0) locked=1; //sum[5]=count-sum[4]-2; sum[2]=count; if(count>=5) return count; //左下到右上斜向檢測 count=1; for(int i=0,locked=0;i<5&&((ori.y+i)<=14)&&((ori.x-i)>=0)&&(!locked);i++)//終止循環條件:同色超過4個或觸碰到棋盤邊界或遇到非同色節點 if(ch.get_color()==bd.viewboard(ori.x-i,ori.y+i)) count++; else if(i!=0) locked=1; //sum[6]=count-1; for(int i=0,locked=0;i<5&&((ori.x+i)<=14)&&((ori.y-i)>=0)&&(!locked);i++)//終止循環條件:同色超過4個或觸碰到棋盤邊界或遇到非同色節點 if(ch.get_color()==bd.viewboard(ori.x+i,ori.y-i)) count++; else if(i!=0) locked=1; //sum[7]=count-sum[6]-2; sum[3]=count; if(count>=5) return count; return MAX(sum,4); } state chessboard::viewboard(position p_c) const { return square[p_c.x][p_c.y]; } state chessboard::viewboard(int x,int y) const { return square[x][y]; } void chessboard::update(chess ch) { position pos=ch.drop_point(); square[pos.x][pos.y]=ch.get_color(); } void chessboard::display()const { system("cls"); for(int i=0;i<15;i++)//打印列座標說明 { gps(0,6+i*hor,i+1); } for(int i=0;i<15;i++)//打印列座標說明 { gps(16*ver,6+i*hor,i+1); } for(int i=0;i<15;i++)//打印行座標說明 { gps(3+i*ver,1,i+1); } for(int i=0;i<15;i++)//打印行座標說明 { gps(3+i*ver,1+16*hor,i+1); } for(int i=0,j=0;i<15;i++) { for(j=0;j<15;j++) tab(1+i*ver,3+hor*j,square[j][i]); } cout<<endl; } result chessboard::judge(chess set)const { bool full=1; if(viewboard(set.drop_point())!=blank) { return error; } if(check_five(*this,set)>=5&&(set.get_color()==black)) return black_win; if(check_five(*this,set)>=5&&(set.get_color()==white)) return white_win; for(int i=0;i<15;i++) for(int j=0;j<15;j++) { if(square[i][j]==blank) full=0; } if(full==1) return draw; else return go_on; } #endif
主函數,應該再定義game類進行優化,不過設計的時候沒有考慮周全,還是按照C的思想寫了。
#include "ai.h" #include <stdlib.h> int main() { system("mode con cols=150 lines=150 "); system("color 27"); chessboard bd; chess now; result final; int mode; gps(5,40,"==============歡迎使用簡易五子棋!=============="); gps(10,50,">>>>輸入1或2進行模式選擇<<<<"); gps(11,50," <1> 雙人對戰 "); gps(12,50," <2> 人機對戰 "); do{ cout<<endl<<" input mode:"; cin>>mode; }while(mode != 1 && mode != 2); //雙人對戰 if (mode==1) { loop1: now.set_color(black);//執黑先行 bd.clear(); bd.display(); //初始化棋盤 setpos(110,0); cout<<"對局開始,黑旗先行"; //循環判斷上一次落子結果,收集下一次輸入,直到棋局出結果 do{ now.set_point();//輸入 final=bd.judge(now); //判斷是否違規 while(final==error)//犯規輸入則重新輸入 { system("cls"); bd.display(); setpos(110,10); cout<<"犯規(輸入座標已有棋子)!"; now.set_point(); final=bd.judge(now); } //正確落子後更新棋盤 bd.update(now); bd.display(); //反轉下一步走棋的顏色 if(now.get_color()==black) now.set_color(white); else now.set_color(black); }while(final==go_on); switch (final) { case go_on: break; case error: break; case draw: setpos(110,10); cout<<"平局:遊戲結束"; break; case black_win: setpos(110,10); cout<<"黑旗獲勝:遊戲結束"; break; case white_win: setpos(110,10); cout<<"白旗獲勝:遊戲結束"; break; default: break; } setpos(110,11); cout<<"是否繼續下一局?Y/N"; char flag; cin>>flag; if(flag == 'Y') goto loop1; } if(mode == 2) { chess ai_ch; system("cls"); gps(5,40,"==============歡迎使用簡易五子棋!=============="); gps(10,50,">>>>輸入1或2進行模式選擇<<<<"); gps(11,50," <1> 執黑先行 "); gps(12,50," <2> 執白後行 "); do{ cout<<endl<<" input mode:"; cin>>mode; }while(mode != 1 && mode != 2); if(mode == 1) { loop2: now.set_color(black);//執黑先行 bd.clear(); bd.display(); //初始化棋盤 Ai afago(bd,white); setpos(110,0); cout<<"對局開始,請您落子"; now.set_point(); bd.update(now); ai_ch.set_color(white); ai_ch.set_point(left(now.drop_point(),false)); bd.update(ai_ch); bd.display(); //循環判斷上一次落子結果,收集下一次輸入,直到棋局出結果 do{ now.set_point();//輸入 final=bd.judge(now); //判斷是否違規 while(final==error)//犯規輸入則重新輸入 { system("cls"); bd.display(); setpos(110,10); cout<<"犯規(輸入座標已有棋子)!"; now.set_point(); final=bd.judge(now); } //正確落子後更新棋盤 bd.update(now); if(final != black_win) { ai_ch=afago.set_chess(); final=bd.judge(ai_ch); bd.update(ai_ch); bd.display(); }else{bd.display();} }while(final==go_on); switch (final) { case go_on: break; case error: break; case draw: setpos(110,10); cout<<"平局:遊戲結束"; break; case black_win: setpos(110,10); cout<<"恭喜您棋藝高超,戰勝了AI:遊戲結束"; break; case white_win: setpos(110,10); cout<<"電腦獲勝,請繼續努力提高自己:遊戲結束"; break; default: break; } setpos(110,11); cout<<"是否繼續下一局?Y/N"; char flag; cin>>flag; if(flag=='Y') goto loop2; } if(mode == 2) { loop3: ai_ch.set_color(black);//AI執黑先行 now.set_color(white); bd.clear(); //初始化棋盤 Ai afago(bd,black); ai_ch.set_point(7,7); bd.update(ai_ch); bd.display(); setpos(110,0); cout<<"對局開始,請您落子"; //循環判斷上一次落子結果,收集下一次輸入,直到棋局出結果 do{ now.set_point();//輸入 final=bd.judge(now); //判斷是否違規 while(final==error)//犯規輸入則重新輸入 { system("cls"); bd.display(); setpos(110,10); cout<<"犯規(輸入座標已有棋子)!"; now.set_point(); final=bd.judge(now); } //正確落子後更新棋盤 bd.update(now); if(final != white_win) { ai_ch=afago.set_chess(); final=bd.judge(ai_ch); bd.update(ai_ch); bd.display(); }else{bd.display();} }while(final==go_on); switch (final) { case go_on: break; case error: break; case draw: setpos(110,10); cout<<"平局:遊戲結束"; break; case white_win: setpos(110,10); cout<<"恭喜您棋藝高超,戰勝了AI:遊戲結束"; break; case black_win: setpos(110,10); cout<<"電腦獲勝,請繼續努力提高自己:遊戲結束"; break; default: break; } setpos(110,11); cout<<"是否繼續下一局?Y/N"; char flag; cin>>flag; if(flag=='Y') goto loop3; } } return 0; }
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持神馬文庫。