计概大作业之同化棋

前言:

  唔,进了大学很久没有更新博客了...感觉自己的语言能力和代码理解能力也随着不写博客有一定的下降...

  最近看到室友们的计概大作业,同化棋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

 

  【未完待续...持续更新】
  笔者最近在忙线代的自习和作业,可能暂时没有时间更新呀...对不起啦~不过闲下来还会思考的...
posted @ 2017-12-06 00:16  诚叙  阅读(4155)  评论(8编辑  收藏  举报