計概大作業之同化棋


前言:

  唔,進了大學很久沒有更新博客了...感覺自己的語言能力和代碼理解能力也隨着不寫博客有一定的下降...

  最近看到室友們的計概大作業,同化棋AI。覺得挺有意思,於是也想着試一試,同時開一篇博客來記錄自己思考的過程。

 

STEP1

  首先我們需要對同化棋有一定的了解

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

 

  設計一個程序打出棋盤也是十分容易的。當然為了美觀性,我可以來介紹一下如何輸出一個優美的棋盤。
  【如何輸出一個優美的棋盤】
  .一開始我覺得這個沒有什么關系...這是我的【棋盤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");
}
View Code

  上面便是寫的代碼,可以先畫出來再寫成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比較好理解,就是雙方分別希望自己的值最大、希望對方的值最小。在這個算法中,只有一方會得分,這一方希望自己的利益最大,而另一方的決策會希望這一方得分越少越好。
  那么我們來討論一個節點的后繼是怎么影響這一點的:
  如果這個節點是希望得分者的點,那么這個點的最小得分應該是會因為尋找更多的后繼而不減的。【這里之所以稱作最小得分是假設后面還不知道的情況,所以如果我只通過現在已知的這些點得到的目前最大值應該是理論上的一個下界】,那么我的下界會因為更多后繼的搜尋而慢慢變大(或者不變)
  反過來,如果這個點是阻礙得分方的節點,那么這個點的上界應該會因為尋找更多的后繼而不增的。也就是隨着搜索的后繼更多我更容易選到一個更差的解。
  那么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;
}
View Code
  當然筆者平時下棋的時候發現每次其實能走的只有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 }
View Code

 

  【未完待續...持續更新】
  筆者最近在忙線代的自習和作業,可能暫時沒有時間更新呀...對不起啦~不過閑下來還會思考的...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM