計概大做業之同化棋

前言:html

  唔,進了大學好久沒有更新博客了...感受本身的語言能力和代碼理解能力也隨着不寫博客有必定的降低...算法

  最近看到室友們的計概大做業,同化棋AI。以爲挺有意思,因而也想着試一試,同時開一篇博客來記錄本身思考的過程。數組

 

STEP1app

  首先咱們須要對同化棋有必定的瞭解dom

  初始佈置爲雙方各將兩枚棋子放在最外的對角格。
  玩家必須輪流移動一枚己子到一個空棋位,該棋位能夠是 鄰近八格(包括對角相鄰的格)之一,或相隔一格的次鄰十六格之一。移動的新棋位,會使 鄰近的全部敵棋如黑白棋同樣變成己方。若是棋子移到的是鄰接八格,會有一顆新己棋出如今原先棋位。
  沒法行棋需棄權[就輸了]。當兩方都沒法行棋時,遊戲結束。以最多子者勝。
  
  其中黃色和綠色都是能夠移動的位置,可是若是移動到綠色,本來中間的棋子會消失,而若是在黃色區域,本來中間的棋子不會消失
  然後面的變顏色說的鄰近,也是黃色區域部分的全部棋子會變成放入中間的棋子的顏色。
 
瞭解完了規則,那麼咱們就能夠利用程序實現棋盤的基本操做了
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循環的棋盤模式ide

 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算法的: http://www.javashuo.com/article/p-eaeltecf-gm.html
  我也用本身的話來說講本身理解的這兩個算法吧:
  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剪枝是一個剪孫子的算法,也就是期待的只能把兒子的後繼變成一個很小的數,可是兒子的數目也是比較多的,這裏咱們就要考慮先搜哪些兒子更容易找到一個更好的解了。
  筆者目前的想法是:一、先用一次多步貪心獲得一個局部最優,再使用alpha-beta剪枝算法來剪枝。【這樣的複雜度會有些大,可是咱們控制層數的話由於和後面的算法是一個加法關係應該是頗有可行性的】
           二、但願兒子的拓展順序可以有一個一層的小貪心來決定順序。也就是走了一步以後獲得的子的數目排個序。這樣的操做是一個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

 

  【未完待續...持續更新】
  筆者最近在忙線代的自習和做業,可能暫時沒有時間更新呀...對不起啦~不過閒下來還會思考的...
相關文章
相關標籤/搜索