前言:
唔,進了大學很久沒有更新博客了...感覺自己的語言能力和代碼理解能力也隨着不寫博客有一定的下降...
最近看到室友們的計概大作業,同化棋AI。覺得挺有意思,於是也想着試一試,同時開一篇博客來記錄自己思考的過程。
STEP1
首先我們需要對同化棋有一定的了解
初始布置為雙方各將兩枚棋子放在最外的對角格。
玩家必須輪流移動一枚己子到一個空棋位,該棋位可以是
鄰近八格(包括對角相鄰的格)之一,或相隔一格的次鄰十六格之一。移動的新棋位,會使
鄰近的所有敵棋如黑白棋一樣變成己方。如果棋子移到的是鄰接八格,會有一顆新己棋出現在原先棋位。
無法行棋需棄權[就輸了]。當兩方都無法行棋時,游戲結束。以最多子者勝。



其中黃色和綠色都是可以移動的位置,但是如果移動到綠色,原本中間的棋子會消失,而如果在黃色區域,原本中間的棋子不會消失
而后面的變顏色說的鄰近,也是黃色區域部分的所有棋子會變成放入中間的棋子的顏色。
了解完了規則,那么我們就可以利用程序實現棋盤的基本操作了
STEP2
支持的操作有:選中一個棋子,移動到一個位置,移動后的變化。
棋盤顯然的,最容易想到使用二維數組模擬。
1.選中一個棋子:考慮到目前只能使用控制台,那么我們可能需要輸入棋子的行和列,那么考慮到視覺感受,我們需要把棋盤也輸出。【怎么輸入一個優美的棋盤?】
在這個操作時,需要考慮這個位置是不是自己可以操作的棋子。
2.移動到一個位置:需要判斷移動的合法性,那么就是abs(x1-x2)<=2 abs(y1-y2)<=2,以及移動到的位置是否在棋盤內,是否為空地
3.移動后的基本變化:首先需要考慮移動距離,是在黃色區域還是綠色區域,這樣來考慮是否復制,然后將周圍有棋子的地方都染上自己的顏色。
如何判斷游戲是不是結束了呢?
1.棋盤放慢了比誰棋子多
2.某一方沒有子或者沒有可以移動的子了算輸
這樣就需要一個統計棋子數目的函數和一個判斷點是否能走的函數。
嗯嗯,這里可以提一下判斷是否能走的時候,我們很容易想到以前的跳馬問題類似的搜索,需要建立move_x[]和move_y[]數組,而這個題也可以這樣做,但是總共有24步,便可以使用for循環來初始化了...(同樣還有染色需要周圍的移動,也可以通過這個方法)
這里便是整個程序的初始化過程
View Code

1 void init(){ 2 map[1][7]=map[7][1]=1; 3 map[1][1]=map[7][7]=2; 4 int tmp=0; 5 for(int i=-2;i<=2;i++) 6 for(int j=-2;j<=2;j++) 7 if(!(i==0 && j==0)) tmp++,mvx[tmp]=i,mvy[tmp]=j; //mvx和mvy分別表示棋子移動時x方向上和y方向上的移動 8 tmp=0; 9 for(int i=-1;i<=1;i++) 10 for(int j=-1;j<=1;j++) 11 if(!(i==0 && j==0)) tmp++,arx[tmp]=i,ary[tmp]=j; //arx和ary分別表示染色時在x和y上的移動 12 }
設計一個程序打出棋盤也是十分容易的。當然為了美觀性,我可以來介紹一下如何輸出一個優美的棋盤。
【如何輸出一個優美的棋盤】
.一開始我覺得這個沒有什么關系...這是我的【棋盤1.0】

你看還是挺好看的嗎...良心的加上了你和電腦的棋子數目,而且 @ 和 # 顯然是OI里畫圖經常使用的符號嘛...
唔,當然控制台上看這個就丑陋了...

當時室友說你這個太丑了...他們的助教居然想到了使用制表符...唔,可是不知道制表符的編碼方式怎么辦呢?
哈,直接使用字符串...然后在word里找出這個制表符來復制就可以了!

好了這是我的word尋找過程...然后選定了兩個棋子——黑圈和白圈( 這兩個好像就在制表符下面一點點就能找到 )
哇,簡直良心制作人...
然后就誕生了我的【棋盤2.0】

dev文本效果還不錯的呢...當然最重要的是看控制台效果!(因為畢竟得在控制台下棋)

有沒有覺得十分好看!(相比之前那個hhh)順便mark一發我的貪心算法被我無情打敗的過程...

void print2(int map[][9]){ sum(map); printf(" YOU: %2d ┃COM: %2d\n",cnt[1],cnt[2]); printf(" ━━━━━━━━━━━━━━━━ \n"); printf(" 1 2 3 4 5 6 7 \n"); printf(" ┏━┳━┳━┳━┳━┳━┳━┓\n"); printf(" 1 ┃"); for(int i=1;i<=7;i++) if(map[1][i]==1) printf("●┃"); else if(map[1][i]==2) printf("○┃"); else printf(" ┃"); putchar('\n'); for(int i=2;i<=7;i++){ printf(" ┣━╋━╋━╋━╋━╋━╋━┫\n"); printf(" %d ┃",i); for(int j=1;j<=7;j++) if(map[i][j]==1) printf("●┃"); else if(map[i][j]==2) printf("○┃"); else printf(" ┃"); putchar('\n'); } printf(" ┗━┻━┻━┻━┻━┻━┻━┛\n"); }
上面便是寫的代碼,可以先畫出來再寫成for循環的棋盤模式
STEP3
既然要做一個良心游戲,那么還需要一個游戲界面:例如新游戲和存檔之類的...
新游戲還是十分簡單的,比較麻煩的有返回上層和讀取存檔。
【返回上層】因為我寫的界面比較簡陋,而且我相信玩家不會調戲游戲界面,所以如果返回上層我直接再次調用一次就可以了...
【讀取、存儲檔】因為我們的輸入是在控制台的,那么我們存檔又是一個文件操作,那么我們可以使用fscanf和fprintf的操作。
那么這兩個的基本使用方法和scanf()和print()幾乎一樣,只是他們需要一個文件指針FILE *fp;
當輸入的時候定義文件指針FILE *fp=fopen("rec.txt","r");
使用的時候fsanf(fp,"%d",&a);就可以啦...輸出也類似咯,那么每次存檔就在文件里輸出當前棋盤,讀取就在文件里輸入棋盤就可以啦...
STEP4
現在就是激動人心的AI時刻:
【貪心的思想】
當然,一開始我是沒有思路的,一開始跟手機上的app下的時候就會被吊起來打...唔,因為我使用的就是一步貪心,貪心能吃的子最多...至於這個子最多這種...有類似的自己的子最多、別人的子最少、以及自己的子減去別人的子最多等等等...
這樣的簡單貪心就不用說了...其實經過我后來下了很多次之后,我發現,每次如果我能跳進一個矩形中,那么我的收益非常大,而且別人一時間吞不掉我的,唔,抱着這種簡陋的想法,我打了一個貪心目的是能在我走了一步之后判斷我的棋子形成的最大矩陣能不能更大,就是在吃子相同的前提下進行第二步的關於最大矩陣面積的判斷。
再后來我還發現了基本上每次我贏的時候,棋盤中我幾乎所有的棋子都是聯通的 (只有角落上的幾個散落在旁邊),那么是不是可以用最大聯通塊來進行貪心呢?不過我就沒有嘗試了哈...因為我已經巧妙的想到了一種方法吊打自己的貪心AI...
然后不討論一步貪心了,很容易的我們會想到兩步貪心,即希望我走完一步之后,對手也走完一步之后,我的棋子還是占據優勢的,那么自然也會有三步貪心等等等等....
【搜索的思想】
我感覺搜索的思想也就從貪心的幾步貪心這里誕生了...
既然我能一直判斷到很多步之后的事情,那么我們是不是可以就直接搜索呢?當然想要搜索是要先挖掘很多性質的。
首先,很重要的一點,我之前是怎么走的已經不重要了,決定我下一步怎么走的就是當前的棋盤。
那么現在的搜索就應該是一棵樹,棋盤記錄了一個狀態,而狀態又會通過走來形成新的狀態,與其他樹不一樣的是,這種拓展有兩種拓展,分為我的選擇和對手的選擇,因為它們擴張的棋子不一樣,目的也不一樣。
這里便會涉及到一個博弈樹的概念。同時呢,也會涉及到一個經典Minimax算法,同時在這個算法研究的問題上也很容易用到一個叫做alpha-beta剪枝的算法...
幸運的發現了兩個比較好理解的博客:
介紹Minimax算法的:
https://www.cnblogs.com/zhuli19901106/p/3832592.html
介紹alpha-beta剪枝的:
http://blog.csdn.net/baixiaozhe/article/details/51872495
我也用自己的話來講講自己理解的這兩個算法吧:
Minimax比較好理解,就是雙方分別希望自己的值最大、希望對方的值最小。在這個算法中,只有一方會得分,這一方希望自己的利益最大,而另一方的決策會希望這一方得分越少越好。
那么我們來討論一個節點的后繼是怎么影響這一點的:
如果這個節點是希望得分者的點,那么這個點的最小得分應該是會因為尋找更多的后繼而不減的。【這里之所以稱作最小得分是假設后面還不知道的情況,所以如果我只通過現在已知的這些點得到的目前最大值應該是理論上的一個下界】,那么我的下界會因為更多后繼的搜尋而慢慢變大(或者不變)
反過來,如果這個點是阻礙得分方的節點,那么這個點的上界應該會因為尋找更多的后繼而不增的。也就是隨着搜索的后繼更多我更容易選到一個更差的解。
那么alpha-beta剪枝算法就是建立在這個上面的。
我們還要對剛才的想法再往下思考一層。
從希望得分者的角度出發:我的后繼我是希望找到一個比當前值更大的后繼的,對嗎?
但是我的后繼是由阻礙者來進行的,也就是說,我的后繼如果還沒有搜完就得到了一個比當前最優值要小或者相等的值,根據阻礙者的行動,如果想要完善這個后繼的話,只會讓我的后繼的上界越來越小,但是即使是當前的上界已經到達我的下界了,所以就沒有必要完善這個后繼了,因為我一定不會采用的。【忽然想到一個成語叫:“斷子絕孫”:因為我們的操作就是:斷掉這個兒子,停止這個兒子繼續延展出孫子】這樣就達到了減少搜索量的目的,是一個很有效的剪枝。
那么,從這個理論上的東西還要和我們的同化棋結合起來,分析這種算法的可行性。而這就是我們最艱巨的任務了。
首先,整個搜索的主體是這一棵博弈樹。我們應該想辦法把整個博弈樹的實現弄清楚。
先假定我是希望得分者,那么在我這一層便是白琪移動,在我的下一層是黑棋移動。
為了使用我們的剪枝,我們讓這個博弈樹深度優先生長。
而通過移動產生的后繼,為了測試復雜度需要進行一個估算:假設當前移動的子有n枚,剩余的空地<=48-n。那么粗略的估算的話:應該是<=n*(48-n)<=24*24=576,當然這種當前子碾壓另一種顏色的子的數目是很少出現的,希望的情況是兩種棋子差別不要太大,我打了一個程序:隨機10000個局面算后繼平均值,算了大約有10次,都發現后繼在(87,89)的這個區間。【下面是我測試用的代碼】

#include<cstdio> #include<cstring> #include<ctime> #include<cstdlib> #include<algorithm> using namespace std; int ans; int map[9][9],rec[9][9]; int mvx[25],mvy[25]; int move_list[25],top; //初始化函數 void init(){ int tmp=0; for(int i=-2;i<=2;i++) for(int j=-2;j<=2;j++) if(!(i==0 && j==0)) tmp++,mvx[tmp]=i,mvy[tmp]=j; //mvx和mvy分別表示棋子移動時x方向上和y方向上的移動 } int can_move(int x,int y){ top=0; int nx,ny; for(int i=1;i<=24;i++){ nx=x+mvx[i],ny=y+mvy[i]; if(nx>=1 && nx<=7 && ny>=1 && ny<=7 && !map[nx][ny]) move_list[++top]=i; } return top; } int count(){ int cnt=0,t=0; for(int i=1;i<=7;i++) for(int j=1;j<=7;j++) if(map[i][j]==2) cnt+=can_move(i,j); else if(map[i][j]==1) t=1; return cnt; } void random_map(){ for(int i=1;i<=7;i++) for(int j=1;j<=7;j++) map[i][j]=rand()%3; } int main(){ freopen("test2.out","w",stdout); long long sum=0; init(); srand(time(0)); for(int K=1;K<=10000;K++){ random_map(); sum+=count(); } printf("%lf",sum/10000.0); return 0; }
當然筆者平時下棋的時候發現每次其實能走的只有6步左右,當然是經過了肉眼貪心了之后[例如如果一個地方能復制過去,我就不跳過去],所以說事實上當我們的樹往下探的時候,期望的剪枝后繼會只剩下6左右。但是我們的alpha-beta剪枝是一個剪孫子的算法,也就是期待的只能把兒子的后繼變成一個很小的數,但是兒子的數目也是比較多的,這里我們就要考慮先搜哪些兒子更容易找到一個更好的解了。
筆者目前的想法是:1、先用一次多步貪心得到一個局部最優,再使用alpha-beta剪枝算法來剪枝。【這樣的復雜度會有些大,但是我們控制層數的話因為和后面的算法是一個加法關系應該是很有可行性的】
2、希望兒子的拓展順序能夠有一個一層的小貪心來決定順序。也就是走了一步之后得到的子的數目排個序。這樣的操作是一個nlogn的復雜度而且是加法,我覺得會是十分有效的。【或者優化2其實可能會悄悄地實現了優化1?】
然后上面其實一直忽略掉了一個東西,就是博弈樹上每個節點的利益,也就是我需要一個估價函數來判斷哪個棋盤好,哪個棋盤不好。這樣我們才能確定后繼的上界和下界。
但是這個估價函數卻不能單純的憑借哪一方的子多來判斷。它和棋子占據的位置也有着密切的關系。在這里我還是沒有想放棄我的最大矩陣的判斷,我認為矩陣是一種防御的姿態,因為只要的你的矩陣>=2*2無論對方怎么吞,得到你的棋子數都是<=3的。當然還有行動力也是一個棋盤升值的籌碼,如果行動力大,那么后繼多,好的機會也會多。那這些參數之間是一個怎樣的組合?我覺得是十分復雜的。
....嗯嗯,我打了一個簡單的棄坑啦...
因為明年我才選這個課hhh,死亡。
我在打程序的過程中,還發現了遞歸的特點,就是如果我要存一個棋盤的話,不一定要放在函數中定義,你可以按照層來分,然后放在全局變量中,因為使用深搜時,每一層在同時間最多只會出現一個,而且不管你在一個函數中調用幾次別的函數,每一層也最多只有一個。這樣下來我又想到了,為什么我們的程序會存在一個棧空間里。好吧,我一定是最后想這個問題的人....
最后附上我的代碼...大家不要抄哦

1 /* 2 Author : Robert_Yuan 3 */ 4 5 #include<cstdio> 6 #include<cstring> 7 #include<algorithm> 8 9 using namespace std; 10 11 const int INF=0x3f3f3f3f; 12 13 int map[9][9],TTT[9][9]; 14 int cnt[3]; 15 int mvx[25],mvy[25]; 16 int arx[9],ary[9]; 17 int move_list[25],top; 18 19 void init();//初始化 20 void menu();//操作界面 21 void sum();//統計棋子數目 22 void print(int map[][9]);//輸出棋盤 23 void print2(int map[][9]);//輸出一個好看的棋盤 24 void operation1();//單人游戲 25 void operation2();//雙人游戲 26 void Copy(); 27 void Save1();//存單人游戲檔 28 void Save2();//存雙人游戲檔 29 bool can_move(int x,int y); 30 void Search1(int Depth); 31 void Search2(int Depth); 32 33 int main(){ 34 //freopen("bug.in","r",stdin); 35 //freopen("game.out","w",stdout); 36 // init(); 37 menu(); 38 //operation1(); 39 } 40 41 void menu(){ 42 int ord1,ord2; 43 printf(" ━━━━━━━━━━━━━━━━━━━━━━━━ \n"); 44 printf("歡迎來到同化棋小游戲,您可以輸入數字來進行以下操作\n"); 45 printf(" ━━━━━━━━━━━━━━━━━━━━━━━━\n"); 46 printf(" 0.退出游戲\n"); 47 printf(" 1.單人游戲\n"); 48 printf(" 2.雙人游戲\n"); 49 scanf("%d",&ord1); 50 if(ord1==1){ 51 printf(" ━━━━━━━━━━━━━━━━━━━━━━━\n"); 52 printf(" 您來到了單人游戲,您可以輸入數字來進行以下操作\n"); 53 printf(" ━━━━━━━━━━━━━━━━━━━━━━━\n"); 54 printf(" 0.返回上層\n"); 55 printf(" 1.新游戲\n"); 56 printf(" 2.讀取存檔\n"); 57 scanf("%d",&ord2); 58 if(ord2==0) menu();//返回上層即再次遞歸 59 else{ 60 init(); 61 if(ord2==2){//讀取存檔 62 FILE *fp; 63 fp=fopen("rec1.txt","r"); 64 for(int i=1;i<=7;i++) 65 for(int j=1;j<=7;j++) 66 fscanf(fp,"%d",&map[i][j]); 67 } 68 operation1(); 69 } 70 } 71 else if(ord1==2){ 72 printf(" ━━━━━━━━━━━━━━━━━━━━━━━\n"); 73 printf(" 您來到了雙人游戲,您可以輸入數字來進行以下操作\n"); 74 printf(" ━━━━━━━━━━━━━━━━━━━━━━━\n"); 75 printf(" 0.返回上層\n"); 76 printf(" 1.新游戲\n"); 77 printf(" 2.讀取存檔\n"); 78 scanf("%d",&ord2); 79 if(ord2==0) menu(); 80 else{ 81 init(); 82 if(ord2==2){ 83 FILE *fp; 84 fp=fopen("rec2.txt","r"); 85 for(int i=1;i<=7;i++) 86 for(int j=1;j<=7;j++) 87 fscanf(fp,"%d",&map[i][j]); 88 } 89 operation2(); 90 } 91 } 92 else 93 return ; 94 } 95 96 //初始化函數 97 void init(){ 98 //初始化棋盤 99 map[1][7]=map[7][1]=1; 100 map[1][1]=map[7][7]=2; 101 //初始化移動和周圍數組 102 int tmp=0; 103 for(int i=-2;i<=2;i++) 104 for(int j=-2;j<=2;j++) 105 if(!(i==0 && j==0)) tmp++,mvx[tmp]=i,mvy[tmp]=j; //mvx和mvy分別表示棋子移動時x方向上和y方向上的移動 106 tmp=0; 107 for(int i=-1;i<=1;i++) 108 for(int j=-1;j<=1;j++) 109 if(!(i==0 && j==0)) tmp++,arx[tmp]=i,ary[tmp]=j; //arx和ary分別表示染色時在x和y上的移動 110 } 111 112 //統計棋盤內各種子的數目 113 void sum(int map[][9]){ 114 cnt[0]=cnt[1]=cnt[2]=0; 115 for(int i=1;i<=7;i++) 116 for(int j=1;j<=7;j++) 117 cnt[map[i][j]]++; 118 } 119 120 //輸出一個簡陋的棋盤(@和#組成) 121 void print(int map[][9]){ 122 sum(map); 123 printf("YOU:%2d COM:%2d\n",cnt[1],cnt[2]); 124 putchar(' '); 125 for(int i=1;i<=7;i++) 126 printf(" %d",i); 127 putchar('\n'); 128 for(int i=1;i<=7;i++){ 129 printf("%d",i); 130 for(int j=1;j<=7;j++){ 131 if(map[i][j]==1) printf(" @"); 132 else if(map[i][j]==2) printf(" #"); 133 else printf(" "); 134 } 135 putchar('\n'); 136 } 137 } 138 139 //輸出一個漂亮的棋盤,使用制表符 140 void print2(int map[][9]){ 141 sum(map); 142 printf(" ━━━━━━━━━━━━━━━━ \n"); 143 printf(" YOU: %2d ┃COM: %2d\n",cnt[1],cnt[2]); 144 printf(" ━━━━━━━━━━━━━━━━ \n"); 145 printf(" 1 2 3 4 5 6 7 \n"); 146 printf(" ┏━┳━┳━┳━┳━┳━┳━┓\n"); 147 printf(" 1 ┃"); 148 for(int i=1;i<=7;i++) 149 if(map[1][i]==1) printf("●┃"); 150 else if(map[1][i]==2) printf("○┃"); 151 else printf(" ┃"); 152 putchar('\n'); 153 for(int i=2;i<=7;i++){ 154 printf(" ┣━╋━╋━╋━╋━╋━╋━┫\n"); 155 printf(" %d ┃",i); 156 for(int j=1;j<=7;j++) 157 if(map[i][j]==1) printf("●┃"); 158 else if(map[i][j]==2) printf("○┃"); 159 else printf(" ┃"); 160 putchar('\n'); 161 } 162 printf(" ┗━┻━┻━┻━┻━┻━┻━┛\n"); 163 } 164 165 //判斷一個位置的琪是否還有位置可以移動 ,同時將可以移動的方向存在move_list中 166 bool can_move(int x,int y){ 167 top=0; 168 int nx,ny; 169 for(int i=1;i<=24;i++){ 170 nx=x+mvx[i],ny=y+mvy[i]; 171 if(nx>=1 && nx<=7 && ny>=1 && ny<=7 && map[nx][ny]==0) 172 move_list[++top]=i; 173 } 174 return top!=0; 175 } 176 177 //處理一個從(x1,y1)移動到(x2,y2)的t種棋子所產生的效果 178 void Deal_With(int x1,int y1,int x2,int y2,int t,int map[][9]){ 179 map[x1][y1]=t*(abs(x2-x1)<=1 && abs(y2-y1)<=1); 180 map[x2][y2]=t; 181 int nx,ny; 182 for(int i=1;i<=8;i++){ 183 nx=x2+arx[i],ny=y2+ary[i]; 184 if(map[nx][ny]) map[nx][ny]=t; 185 } 186 } 187 188 //單人游戲存檔 189 void Save1(){ 190 FILE *fp; 191 fp=fopen("rec1.txt","w"); 192 for(int i=1;i<=7;i++){ 193 for(int j=1;j<=7;j++) 194 fprintf(fp,"%d ",map[i][j]); 195 fprintf(fp,"\n"); 196 } 197 } 198 199 //雙人游戲存檔 200 void Save2(){ 201 FILE *fp; 202 fp=fopen("rec2.txt","w"); 203 for(int i=1;i<=7;i++){ 204 for(int j=1;j<=7;j++) 205 fprintf(fp,"%d ",map[i][j]); 206 fprintf(fp,"\n"); 207 } 208 } 209 210 //復制兩個棋盤 211 void Copy(int A[][9],int B[][9]){ 212 for(int i=1;i<=7;i++) 213 for(int j=1;j<=7;j++) 214 B[i][j]=A[i][j]; 215 } 216 217 //電腦0,使用黑棋子最多戰略 218 void COMPUTER0(){ 219 printf("現在是電腦0時間!"); 220 int BEST_I,BEST_J,BEST_NI,BEST_NJ,BEST=0; 221 for(int i=1;i<=7;i++) 222 for(int j=1;j<=7;j++) 223 if(map[i][j]==2){ 224 if(can_move(i,j)){ 225 for(int k=1;k<=top;k++){ 226 int nx=i+mvx[move_list[k]],ny=j+mvy[move_list[k]]; 227 Copy(map,TTT); 228 Deal_With(i,j,nx,ny,2,TTT); 229 sum(TTT); 230 if(cnt[2]>BEST) 231 BEST=cnt[2],BEST_I=i,BEST_J=j,BEST_NI=nx,BEST_NJ=ny; 232 } 233 } 234 } 235 Deal_With(BEST_I,BEST_J,BEST_NI,BEST_NJ,2,map); 236 printf("電腦把(%d,%d)移動到了(%d,%d):\n",BEST_I,BEST_J,BEST_NI,BEST_NJ); 237 } 238 239 //電腦1,使用黑棋減去白琪最多戰略 240 void COMPUTER1(){ 241 printf("現在是電腦1時間!"); 242 int BEST_I,BEST_J,BEST_NI,BEST_NJ,BEST=-49; 243 for(int i=1;i<=7;i++) 244 for(int j=1;j<=7;j++) 245 if(map[i][j]==2){ 246 if(can_move(i,j)){ 247 for(int k=1;k<=top;k++){ 248 int nx=i+mvx[move_list[k]],ny=j+mvy[move_list[k]]; 249 Copy(map,TTT); 250 Deal_With(i,j,nx,ny,2,TTT); 251 sum(TTT); 252 if(cnt[2]-cnt[1]>BEST) 253 BEST=cnt[2]-cnt[1],BEST_I=i,BEST_J=j,BEST_NI=nx,BEST_NJ=ny; 254 } 255 } 256 } 257 Deal_With(BEST_I,BEST_J,BEST_NI,BEST_NJ,2,map); 258 printf("電腦把(%d,%d)移動到了(%d,%d):\n",BEST_I,BEST_J,BEST_NI,BEST_NJ); 259 } 260 261 int height[9],ins[9],ht;//height和ht表示維護的單調棧和棧頂指針,ins表示每個位置能管到的最前面的位置 262 int Left[9][9];//Left表示統計這個位置往左邊最長延伸為多少 263 264 //返回兩個值中的最大值 265 int Max(int x,int y){ 266 return x>y?x:y; 267 } 268 269 int Min(int x,int y){ 270 return x<y?x:y; 271 } 272 273 //計算map中t的最大矩陣,使用單調棧 274 int calcu_matrix(int map[][9],int t){ 275 int ans=0; 276 for(int i=1;i<=7;i++) 277 for(int j=1;j<=7;j++){ 278 Left[i][j]=0; 279 if(map[i][j]==t) 280 Left[i][j]=Left[i][j-1]+1; 281 } 282 for(int i=1;i<=7;i++){ 283 ht=0; 284 for(int j=1;j<=8;j++){ 285 while(Left[height[ht]][i]>=Left[j][i] && ht>0){ 286 ans=Max(ans,Left[height[ht]][i]*(j-ins[height[ht]])); 287 ht--; 288 } 289 ins[j]=height[ht]+1; 290 ans=Max(Left[j][i]*(j-ins[j]+1),ans); 291 height[++ht]=j; 292 293 } 294 } 295 return ans; 296 } 297 298 //COM1優化:當黑子減白子相同時,可以通過最大矩陣進行第二次判斷 299 void COMPUTER2(){ 300 printf("現在是電腦2時間!"); 301 int BEST_I,BEST_J,BEST_NI,BEST_NJ,BEST=-49,BESTC=0; 302 for(int i=1;i<=7;i++) 303 for(int j=1;j<=7;j++) 304 if(map[i][j]==2){ 305 if(can_move(i,j)){ 306 for(int k=1;k<=top;k++){ 307 int nx=i+mvx[move_list[k]],ny=j+mvy[move_list[k]],nc; 308 Copy(map,TTT); 309 Deal_With(i,j,nx,ny,2,TTT); 310 nc=calcu_matrix(TTT,2); 311 sum(TTT); 312 if(cnt[2]-cnt[1]>BEST) 313 BEST=cnt[2]-cnt[1],BEST_I=i,BEST_J=j,BEST_NI=nx,BEST_NJ=ny,BESTC=nc; 314 else if(cnt[2]-cnt[1]==BEST && BESTC<nc){ 315 BEST_I=i,BEST_J=j,BEST_NI=nx,BEST_NJ=ny,BESTC=nc; 316 } 317 } 318 } 319 } 320 Deal_With(BEST_I,BEST_J,BEST_NI,BEST_NJ,2,map); 321 printf("電腦把(%d,%d)移動到了(%d,%d):\n",BEST_I,BEST_J,BEST_NI,BEST_NJ); 322 } 323 int move_steps(int x,int y){ 324 top=0; 325 int nx,ny; 326 for(int i=1;i<=24;i++){ 327 nx=x+mvx[i],ny=y+mvy[i]; 328 if(nx>=1 && nx<=7 && ny>=1 && ny<=7 && !map[nx][ny]) top++; 329 } 330 return top; 331 } 332 333 int Move_power(int t){ 334 int cnt=0; 335 for(int i=1;i<=7;i++) 336 for(int j=1;j<=7;j++) 337 if(map[i][j]==t) cnt+=move_steps(i,j); 338 return cnt; 339 } 340 341 int Judge(int map[][9]){ 342 sum(map); 343 int Main=cnt[2]-cnt[1],Other=calcu_matrix(map,2),Another; 344 Another=0; 345 //Another=Move_power(2); 346 return Main*100+Other+Another; 347 } 348 349 int rec[10][9][9],Inform[9][2]; 350 int Greed[25]; 351 352 int Calcu_Next(int i,int j,int k,int Depth){ 353 int nx=i+mvx[move_list[k]],ny=j+mvy[move_list[k]]; 354 Deal_With(i,j,nx,ny,map[i][j],map); 355 int ans=Judge(map); 356 Copy(rec[Depth],map); 357 return ans; 358 } 359 360 int MVL[10][25],TP[10]; 361 bool can_move2(int x,int y,int Depth){ 362 TP[Depth]=0; 363 int nx,ny; 364 for(int i=1;i<=24;i++){ 365 nx=x+mvx[i],ny=y+mvy[i]; 366 if(nx>=1 && nx<=7 && ny>=1 && ny<=7 && map[nx][ny]==0) 367 MVL[Depth][++TP[Depth]]=i; 368 } 369 return TP[Depth]!=0; 370 } 371 372 bool cmp1(const int &A,const int &B){ 373 return Greed[A]>Greed[B]; 374 } 375 bool cmp2(const int &A,const int &B){ 376 return Greed[A]<Greed[B]; 377 } 378 379 //假設電腦是得分者 380 void Search1(int Depth){ 381 if(Depth>3){ 382 Inform[Depth-1][1]=Min(Inform[Depth-1][1],Judge(map)); 383 return ; 384 } 385 Copy(map,rec[Depth]); 386 //如果是我(電腦)下 387 for(int i=1;i<=7;i++){ 388 for(int j=1;j<=7;j++) 389 if(map[i][j]==2 && can_move2(i,j,Depth)){ 390 for(int k=1;k<=TP[Depth];k++) 391 Greed[k]=Calcu_Next(i,j,k,Depth); 392 //把貪心中容易得分的先下 393 for(int k=1;k<=TP[Depth];k++) 394 for(int l=1;l<=TP[Depth]-k;l++) 395 if(Greed[l]<Greed[l+1]){ 396 int tmp=Greed[l];Greed[l]=Greed[l+1];Greed[l+1]=tmp; 397 tmp=MVL[Depth][l];MVL[Depth][l]=MVL[Depth][l+1];MVL[Depth][l+1]=tmp; 398 } 399 400 for(int k=1;k<=TP[Depth];k++){ 401 int nx=i+mvx[MVL[Depth][k]],ny=j+mvy[MVL[Depth][k]],Interest; 402 Deal_With(i,j,nx,ny,2,map); 403 Inform[Depth+1][0]=-INF; 404 Inform[Depth+1][1]=INF; 405 406 Search2(Depth+1); 407 Copy(rec[Depth],map); 408 if(Inform[Depth-1][1]<=Inform[Depth][0]) return; 409 410 } 411 } 412 } 413 Inform[Depth-1][1]=Min(Inform[Depth-1][1],Inform[Depth][0]); 414 } 415 416 void Search2(int Depth){ 417 if(Depth>3){ 418 Inform[Depth-1][0]=Max(Inform[Depth-1][0],Judge(map)); 419 return ; 420 } 421 Copy(map,rec[Depth]); 422 //如果是阻礙者下 423 for(int i=1;i<=7;i++){ 424 for(int j=1;j<=7;j++) 425 if(map[i][j]==1 && can_move2(i,j,Depth)){ 426 for(int k=1;k<=TP[Depth];k++) 427 Greed[k]=Calcu_Next(i,j,k,Depth); 428 //把貪心中難以得分的先下 429 for(int k=1;k<=TP[Depth];k++) 430 for(int l=1;l<=TP[Depth]-k;l++) 431 if(Greed[l]>Greed[l+1]){ 432 int tmp=Greed[l];Greed[l]=Greed[l+1];Greed[l+1]=tmp; 433 tmp=MVL[Depth][l];MVL[Depth][l]=MVL[Depth][l+1];MVL[Depth][l+1]=tmp; 434 } 435 436 for(int k=1;k<=TP[Depth];k++){ 437 int nx=i+mvx[MVL[Depth][k]],ny=j+mvy[MVL[Depth][k]],Interest; 438 Deal_With(i,j,nx,ny,1,map); 439 Inform[Depth+1][0]=-INF; 440 Inform[Depth+1][1]=INF; 441 442 Search1(Depth+1); 443 Copy(rec[Depth],map); 444 if(Inform[Depth-1][0]>=Inform[Depth][1]) return; 445 } 446 } 447 } 448 Inform[Depth-1][0]=Max(Inform[Depth-1][0],Inform[Depth][1]); 449 } 450 451 //這個就是搜索的過程了 452 void COMPUTER3(){ 453 int MIN=-INF; 454 int BEST_I,BEST_J,BEST_NI,BEST_NJ; 455 Copy(map,rec[0]); 456 Inform[0][0]=-INF; 457 Inform[0][1]=INF; 458 //如果是我(電腦)下 459 for(int i=1;i<=7;i++){ 460 for(int j=1;j<=7;j++) 461 if(map[i][j]==2 && can_move(i,j)){ 462 for(int k=1;k<=top;k++) 463 Greed[k]=Calcu_Next(i,j,k,0); 464 //把貪心中容易得分的先下 465 for(int k=1;k<=top;k++) 466 for(int l=1;l<=top-k;l++) 467 if(Greed[l]<Greed[l+1]){ 468 int tmp=Greed[l];Greed[l]=Greed[l+1];Greed[l+1]=tmp; 469 tmp=move_list[l];move_list[l]=move_list[l+1];move_list[l+1]=tmp; 470 } 471 472 for(int k=1;k<=top;k++){ 473 int nx=i+mvx[move_list[k]],ny=j+mvy[move_list[k]],Interest; 474 Deal_With(i,j,nx,ny,2,map); 475 Inform[1][0]=-INF; 476 Inform[1][1]=INF; 477 478 Search2(1); 479 Copy(rec[0],map); 480 if(Inform[0][0]>MIN){ 481 BEST_NI=nx;BEST_NJ=ny;BEST_I=i;BEST_J=j; 482 MIN=Inform[0][0]; 483 } 484 } 485 } 486 } 487 if(MIN==-INF){printf("不好意思它掛機了!");return;} 488 Deal_With(BEST_I,BEST_J,BEST_NI,BEST_NJ,2,map); 489 printf("電腦把(%d,%d)移動到了(%d,%d):\n",BEST_I,BEST_J,BEST_NI,BEST_NJ); 490 } 491 492 //單人游戲操作 493 void operation1(){ 494 int x1,y1,x2,y2; 495 while(true){ 496 print2(map); 497 if(!cnt[0]){ 498 if(cnt[1]>cnt[2]) printf("您贏了!Orz!"); 499 else printf("您差點就贏了!Orz!"); 500 return ; 501 } 502 int f1=false,f2=false; 503 for(int i=1;i<=7;i++) 504 for(int j=1;j<=7;j++) 505 if(map[i][j]==1 && !f1){ 506 if(can_move(i,j)) f1=true; 507 } 508 if(!f1) {printf("您差點就贏了!Orz!");return;} 509 int ord; 510 printf("您需要存檔嗎?如果需要請輸入1否則輸入0\n"); 511 scanf("%d",&ord); 512 if(ord) {printf("已存檔!\n");Save1();return ;} 513 bool Over=0; 514 while(true){ 515 printf("請輸入您需要移動的棋子的行和列\n"); 516 scanf("%d%d",&x1,&y1); 517 if(map[x1][y1]!=1) 518 printf("您在該位置沒有棋子!\n"); 519 else if(!can_move(x1,y1)) printf("您選擇的棋子無法移動!\n"); 520 else{ 521 while(true){ 522 printf("請輸入您需要移動的棋子到的位置:\n"); 523 scanf("%d%d",&x2,&y2); 524 if(abs(x2-x1)>2 || abs(y2-y1)>2 || (x1==x2 && y1==y2) || map[x2][y2]){ 525 printf("您的移動不合法!\n"); break; 526 } 527 else{ 528 Over=1; 529 break; 530 } 531 } 532 } 533 if(Over) break; 534 } 535 Deal_With(x1,y1,x2,y2,1,map); 536 print2(map); 537 for(int i=1;i<=7;i++) 538 for(int j=1;j<=7;j++) 539 if(map[i][j]==2 && !f2) 540 if(can_move(i,j)) f2=true; 541 if(!f2) {printf("您贏了!Orz!");return;} 542 if(!cnt[0]){ 543 if(cnt[1]>cnt[2]) printf("PLAYER1贏了!Orz!"); 544 else printf("PLAYER2贏了!Orz!"); 545 return ; 546 } 547 COMPUTER3(); 548 } 549 } 550 551 //雙人游戲操作 552 void operation2(){ 553 int x1,y1,x2,y2; 554 while(true){ 555 bool f1=false,f2=false; 556 print2(map); 557 for(int i=1;i<=7;i++) 558 for(int j=1;j<=7;j++) 559 if(map[i][j]==1 && !f1) 560 if(can_move(i,j)) f1=true; 561 if(!f1) {printf("PLAYER2贏了!Orz!");return;} 562 if(!cnt[0]){ 563 if(cnt[1]>cnt[2]) printf("PLAYER1贏了!Orz!"); 564 else printf("PLAYER2贏了!Orz!"); 565 return ; 566 } 567 568 int ord; 569 printf("您需要存檔嗎?如果需要請輸入1否則輸入0\n"); 570 scanf("%d",&ord); 571 if(ord) {printf("已存檔!\n");Save2();return ;} 572 573 bool Over=0; 574 while(true){ 575 printf("現在是PLAYER1操作!\n請輸入您需要移動的棋子的行和列\n"); 576 scanf("%d%d",&x1,&y1); 577 if(map[x1][y1]!=1) 578 printf("您在該位置沒有棋子!\n"); 579 else if(!can_move(x1,y1)) printf("您選擇的棋子無法移動!\n"); 580 else{ 581 while(true){ 582 printf("請輸入您需要移動的棋子到的位置:\n"); 583 scanf("%d%d",&x2,&y2); 584 if(abs(x2-x1)>2 || abs(y2-y1)>2 || (x1==x2 && y1==y2) || map[x2][y2]){ 585 printf("您的移動不合法!\n"); break; 586 } 587 else{ 588 Over=1; 589 break; 590 } 591 } 592 } 593 if(Over) break; 594 } 595 Deal_With(x1,y1,x2,y2,1,map); 596 print2(map); 597 for(int i=1;i<=7;i++) 598 for(int j=1;j<=7;j++) 599 if(map[i][j]==2 && !f2) 600 if(can_move(i,j)) f2=true; 601 if(!f2) {printf("PLAYER1贏了!Orz!");return;} 602 if(!cnt[0]){ 603 if(cnt[1]>cnt[2]) printf("PLAYER1贏了!Orz!"); 604 else printf("PLAYER2贏了!Orz!"); 605 return ; 606 } 607 608 Over=0; 609 while(true){ 610 printf("現在是PLAYER2操作!\n請輸入您需要移動的棋子的行和列\n"); 611 scanf("%d%d",&x1,&y1); 612 if(map[x1][y1]!=2) 613 printf("您在該位置沒有棋子!\n"); 614 else if(!can_move(x1,y1)) printf("您選擇的棋子無法移動!\n"); 615 else{ 616 while(true){ 617 printf("請輸入您需要移動的棋子到的位置:\n"); 618 scanf("%d%d",&x2,&y2); 619 if(abs(x2-x1)>2 || abs(y2-y1)>2 || (x1==x2 && y1==y2) || map[x2][y2]){ 620 printf("您的移動不合法!\n"); break; 621 } 622 else{ 623 Over=1; 624 break; 625 } 626 } 627 } 628 if(Over) break; 629 } 630 Deal_With(x1,y1,x2,y2,2,map); 631 632 } 633 }
【未完待續...持續更新】
筆者最近在忙線代的自習和作業,可能暫時沒有時間更新呀...對不起啦~不過閑下來還會思考的...