萌新也能写出的五子棋AI

· · 个人记录

萌新也能写出的五子棋AI

此五子棋,为无禁手五子棋简介

没错,这是一个十分简单的AI。即使你是个OI/五子棋萌新,只要你能够写代码,并且了解五子棋的规则,就能够写出这样有一定强度的AI。

首先,建立好一个棋盘。

你要写一个五子棋AI,就必须先有一个五子棋的棋盘。

我们可以定义一个类:


struct Gird{
    int G[17][17];//None:0 Player:1 Computer:2               //15*15的棋盘
    inline void init(){          //初始化棋盘
        memset(G,0,sizeof(G));
        for(int i=0;i<=16;++i)G[i][0]=3,G[i][16]=3;    //设置边界
        for(int i=0;i<=16;++i)G[0][i]=3,G[16][i]=3;
    }
    inline int check_row(int x,int y){       //判断是否是横着的5子赢了
        if(y+4>15)return 0;
        bool all=1;
        for(int i=x,j=y;j<y+5;++j)if(G[i][j]!=G[x][y])all=0;
        if(all==1 and G[x][y])return G[x][y];
        return 0;
    }
    inline int check_col(int x,int y){       //判断是否是竖着的5子赢了
        if(x+4>15)return 0;
        bool all=1;
        for(int i=x,j=y;i<x+5;++i)if(G[i][j]!=G[x][y])all=0;
        if(all==1 and G[x][y])return G[x][y];
        return 0;
    }
    inline int check_sla1(int x,int y){       //判断是否是斜向右下的5子赢了
        if(x+4>15 or y+4>15)return 0;
        bool all=1;
        for(int i=x,j=y;i<x+5,j<y+5;++i,++j)if(G[i][j]!=G[x][y])all=0;
        if(all==1 and G[x][y])return G[x][y];
        return 0;
    }
    inline int check_sla2(int x,int y){         //判断是否是斜向左下的5子赢了
        if(x+4>15 or y-4<1)return 0;
        bool all=1;
        for(int i=x,j=y;i<x+5,j>y-5;++i,--j)if(G[i][j]!=G[x][y])all=0;
        if(all==1 and G[x][y])return G[x][y];
        return 0;
    }
    inline int Count(){     //已经下了多少棋子
        int cnt=0;
        for(int i=1;i<=15;++i)for(int j=1;j<=15;++j)if(G[i][j]!=0)++cnt;
        return cnt;
    }
    inline bool Draw(){      //全部铺满了(和棋)
        return Count()==(15*15);
    } 
    inline int Win(){      //判断棋盘上是否由一个人已经赢了
        for(int i=1;i<=15;++i){
            for(int j=1;j<=15;++j){
                int T;
                T=check_row(i,j);
                if(T!=0)return T;
                T=check_col(i,j);
                if(T!=0)return T;
                T=check_sla1(i,j);
                if(T!=0)return T;
                T=check_sla2(i,j);
                if(T!=0)return T;
            }
        }
    }
    inline char Get(int x){                     //判断棋子的种类
        if(x==0)return '.';
        if(x==1)return 'X';
        if(x==2)return 'O';
    }
    inline void Print(int Turn){                        //输出棋盘
        system("cls");
        cout<<"Turn To: "<<Get(Turn)<<'\n';
        cout<<"   1 2 3 4 5 6 7 8 9 101112131415\n";
        for(int i=1;i<=15;++i){
            cout<<i<<" ";
            if(i<10)cout<<" ";
            for(int j=1;j<=15;++j)cout<<Get(G[i][j])<<" ";cout<<"\n";
        }
    }
};

然后,我们可以写一个人VS人的对局测试一下棋盘。


inline void Player_to_player(){
    int Now_Turn=1; //轮到谁了
    Load();
    system("cls");
    Gird gird;
    gird.init();
    for(;!gird.Win();){ //对局继续
        int x,y;
        gird.Print(Now_Turn);
        for(;;){
            cout<<"Input where your chess put: \n";
            cout<<"Line: ";cin>>x;cout<<'\n';
            cout<<"Column: ";cin>>y;cout<<'\n';
            if(x<=15 and y<=15 and x>=1 and y>=1 and !gird.G[x][y])break;
            cout<<"Wrong Input!\nPlease enter it again.\n";
        }
        gird.G[x][y]=Now_Turn;
        if(gird.Win()!=0){
            cout<<gird.Get(Now_Turn)<<" wins!\n";
            return;
        }
        Now_Turn=Next_Turn(Now_Turn);  //换人
    }
}

接下来就是核心的AI部分。

假设你没有任何的五子棋经验(像作者一样),你也可以想到一个最朴素的方法:

对于每一个新的局面,枚举下一个棋子放在了哪儿,使得放完后的局面的利益差最大。

我们定义利益为自己/敌人所有棋子的利益之和。

定义“活”为两头都为空的连续棋子。 如:..XXXX..

定义“死”为两头不是空的连续棋子。如:..OXXXO.

定义“冲”为一头为空,一头不为空的连续棋子。如:..OXXXX.

定义“空”为中间有一个断开、两头都为空的棋子。如:..XX.XX.

然后,对每一种状态设定一个参数,即为它带来的贡献。

每一种状态还有分两类:攻击时的,和防守时的。(这样可以防止盲目的攻击而不防守)

这个参数你不可能一下就确定,所以你需要在实战中不断的改善。

然后,我的AI采取的策略是“时候到了自然出手”。就是一开始先放手,到了中局将所有的攻击参数都乘上一个数。这样攻击的概率就会大一些了。

Code:

int zhihuo[2][6]={{0,5,25,84,15000000,15000000},{0,4,18,1500000,1500000,15000000}};  //0 先 1 后 
int xiehuo[2][6]={{0,5,30,96,15000000,15000000},{0,4,24,1500000,1500000,15000000}};
int zhichong[2][6]={{0,2,20,42,390,15000000},{0,3,15,152,1500000,15000000}};
int xiechong[2][6]={{0,3,25,63,410,15000000},{0,3,15,115,1500000,15000000}};
int zhisi[2][6]={{0,1,15,15,155,15000000},{0,1,15,45,116,15000000}};
int xiesi[2][6]={{0,1,15,18,120,15000000},{0,1,15,59,128,15000000}};
int zhikong[2][6]={{0,4,32,145,395,15000000},{0,5,35,1500000,15000000,15000000}};
int xiekong[2][6]={{0,3,36,155,411,15000000},{0,3,40,1500000,15000000,15000000}}; //left+right
//huo
inline bool henzhihuo(Gird gird,int x,int y,int k){               //分类讨论   gird:棋盘 x,y : 初始点的位置  k:连续的长度
    if(y+k>16)return 0;
    bool all=1;
    for(int i=x,j=y;j<y+k;++j)if(gird.G[i][j]!=gird.G[x][y])all=0;
    if(gird.G[x][y] and all and (gird.G[x][y-1]==0) and (gird.G[x][y+k]==0))return 1;
    return 0;
}
inline bool shuzhihuo(Gird gird,int x,int y,int k){
    if(x+k>16)return 0;
    bool all=1;
    for(int i=x,j=y;i<x+k;++i)if(gird.G[i][j]!=gird.G[x][y])all=0;
    if(gird.G[x][y] and all and (gird.G[x-1][y]==0) and (gird.G[x+k][y]==0))return 1;
    return 0;
}
inline bool xiaxiehuo(Gird gird,int x,int y,int k){ 
    if(y+k>16 or y+k>16)return 0;
    bool all=1;
    for(int i=x,j=y;i<x+k and j<y+k;++i,++j)if(gird.G[i][j]!=gird.G[x][y])all=0;
    if(gird.G[x][y] and all and (gird.G[x-1][y-1]==0) and (gird.G[x+k][y+k]==0))return 1;
    return 0;
}
inline bool shangxiehuo(Gird gird,int x,int y,int k){
    if(x+k>16 or y-k<0)return 0;
    bool all=1;
    for(int i=x,j=y;i<x+k and j>y-k;++i,--j)if(gird.G[i][j]!=gird.G[x][y])all=0;
    if(gird.G[x][y] and all and (gird.G[x-1][y+1]==0) and (gird.G[x+k][y-k]==0))return 1;
    return 0;
}
inline bool iszhihuo(Gird gird,int x,int y,int k){
    return henzhihuo(gird,x,y,k) or shuzhihuo(gird,x,y,k);
}
inline bool isxiehuo(Gird gird,int x,int y,int k){
    return shangxiehuo(gird,x,y,k) or xiaxiehuo(gird,x,y,k);
}

//chong
inline bool henzhichong(Gird gird,int x,int y,int k){
    if(y+k>16)return 0;
    bool all=1;
    for(int i=x,j=y;j<y+k;++j)if(gird.G[i][j]!=gird.G[x][y])all=0;
    if(gird.G[x][y] and all and ((gird.G[x][y-1]==0)+(gird.G[x][y+k]==0))==1)return 1;
    return 0;
}
inline bool shuzhichong(Gird gird,int x,int y,int k){
    if(x+k>16)return 0;
    bool all=1;
    for(int i=x,j=y;i<x+k;++i)if(gird.G[i][j]!=gird.G[x][y])all=0;
    if(gird.G[x][y] and all and ((gird.G[x-1][y]==0)+(gird.G[x+k][y]==0))==1)return 1;
    return 0;
}
inline bool xiaxiechong(Gird gird,int x,int y,int k){
    if(y+k>16 or y+k>16)return 0;
    bool all=1;
    for(int i=x,j=y;i<x+k and j<y+k;++i,++j)if(gird.G[i][j]!=gird.G[x][y])all=0;
    if(gird.G[x][y] and all and ((gird.G[x-1][y-1]==0)+(gird.G[x+k][y+k]==0))==1)return 1;
    return 0;
}
inline bool shangxiechong(Gird gird,int x,int y,int k){
    if(x+k>16 or y-k<0)return 0;
    bool all=1;
    for(int i=x,j=y;i<x+k and j>y-k;++i,--j)if(gird.G[i][j]!=gird.G[x][y])all=0;
    if(gird.G[x][y] and all and ((gird.G[x-1][y+1]==0)+(gird.G[x+k][y-k]==0))==1)return 1;
    return 0;
}
inline bool iszhichong(Gird gird,int x,int y,int k){
    return henzhichong(gird,x,y,k) or shuzhichong(gird,x,y,k);
}
inline bool isxiechong(Gird gird,int x,int y,int k){
    return shangxiechong(gird,x,y,k) or xiaxiechong(gird,x,y,k);
}

//si
inline bool henzhisi(Gird gird,int x,int y,int k){
    if(y+k>16)return 0;
    bool all=1;
    for(int i=x,j=y;j<y+k;++j)if(gird.G[i][j]!=gird.G[x][y])all=0;
    if(gird.G[x][y] and all and ((gird.G[x][y-1]==0)+(gird.G[x][y+k]==0))==2)return 1;
    return 0;
}
inline bool shuzhisi(Gird gird,int x,int y,int k){
    if(x+k>16)return 0;
    bool all=1;
    for(int i=x,j=y;i<x+k;++i)if(gird.G[i][j]!=gird.G[x][y])all=0;
    if(gird.G[x][y] and all and ((gird.G[x-1][y]==0)+(gird.G[x+k][y]==0))==2)return 1;
    return 0;
}
inline bool xiaxiesi(Gird gird,int x,int y,int k){
    if(y+k>16 or y+k>16)return 0;
    bool all=1;
    for(int i=x,j=y;i<x+k and j<y+k;++i,++j)if(gird.G[i][j]!=gird.G[x][y])all=0;
    if(gird.G[x][y] and all and ((gird.G[x-1][y-1]==0)+(gird.G[x+k][y+k]==0))==2)return 1;
    return 0;
}
inline bool shangxiesi(Gird gird,int x,int y,int k){
    if(x+k>16 or y-k<0)return 0;
    bool all=1;
    for(int i=x,j=y;i<x+k and j>y-k;++i,--j)if(gird.G[i][j]!=gird.G[x][y])all=0;
    if(gird.G[x][y] and all and ((gird.G[x-1][y+1]==0)+(gird.G[x+k][y-k]==0))==2)return 1;
    return 0;
}
inline bool iszhisi(Gird gird,int x,int y,int k){
    return henzhisi(gird,x,y,k) or shuzhisi(gird,x,y,k);
}
inline bool isxiesi(Gird gird,int x,int y,int k){
    return shangxiesi(gird,x,y,k) or xiaxiesi(gird,x,y,k);
}
//kong
inline bool henzhikong(Gird gird,int x,int y,int k){
    if(y+k>15)return 0;
    int all=2;
    for(int i=x,j=y;j<y+k+1;++j)if(gird.G[i][j]==3-gird.G[x][y])all=0;else if(gird.G[i][j]==0)--all;
    if(gird.G[x][y] and all==1 and ((gird.G[x][y-1]!=3-gird.G[x][y]) or (gird.G[x][y+k+1]!=3-gird.G[x][y])))return 1;
    return 0;
}
inline bool shuzhikong(Gird gird,int x,int y,int k){
    if(x+k>15)return 0;
    int all=2;
    for(int i=x,j=y;i<x+k+1;++i)if(gird.G[i][j]==3-gird.G[x][y])all=0;else if(gird.G[i][j]==0)--all;
    if(gird.G[x][y] and all==1 and ((gird.G[x-1][y]!=3-gird.G[x][y]) or (gird.G[x+k+1][y]!=3-gird.G[x][y])))return 1;
    return 0;
}
inline bool xiaxiekong(Gird gird,int x,int y,int k){
    if(x+k>15 or y+k>15)return 0;
    int all=2;
    for(int i=x,j=y;i<x+k+1 and j<y+k+1;++i,++j)if(gird.G[i][j]==3-gird.G[x][y])all=0;else if(gird.G[i][j]==0)--all;
    if(gird.G[x][y] and all==1 and ((gird.G[x-1][y-1]!=3-gird.G[x][y]) or (gird.G[x+k+1][y+k+1]!=3-gird.G[x][y])))return 1;
    return 0;
}
inline bool shangxiekong(Gird gird,int x,int y,int k){
    if(x+k>15 or y-k<1)return 0;
    int all=2;
    for(int i=x,j=y;i<x+k+1 and j>y-k-1;++i,--j)if(gird.G[i][j]==3-gird.G[x][y])all=0;else if(gird.G[i][j]==0)--all;
    if(gird.G[x][y] and all==1 and ((gird.G[x-1][y+1]!=3-gird.G[x][y]) or (gird.G[x+k+1][y-k-1]!=3-gird.G[x][y])))return 1;
    return 0;
}
inline bool iszhikong(Gird gird,int x,int y,int k){
    return henzhikong(gird,x,y,k) or shuzhikong(gird,x,y,k);
}
inline bool isxiekong(Gird gird,int x,int y,int k){
    return shangxiekong(gird,x,y,k) or xiaxiekong(gird,x,y,k);
}
inline pair<int,int> CountValue(Gird gird,pair<int,int>isxs){
    int val[3];val[0]=0,val[1]=0,val[2]=0;
    int xs[3];xs[1]=isxs.first;xs[2]=isxs.second;
    for(int i=1;i<=15;++i){
        for(int j=1;j<=15;++j){
            for(int k=1;k<=5;++k){
                if(gird.G[i][j]==0)continue;if(debug)cout<<i<<' '<<j<<' '<<k<<' '<<gird.G[i][j]<<'\n';         //养成debug的好习惯
                if(iszhihuo(gird,i,j,k))val[gird.G[i][j]]+=zhihuo[xs[gird.G[i][j]]][k];           //依次判断每一种局面
                if(isxiehuo(gird,i,j,k))val[gird.G[i][j]]+=xiehuo[xs[gird.G[i][j]]][k];
                if(iszhichong(gird,i,j,k))val[gird.G[i][j]]+=zhichong[xs[gird.G[i][j]]][k];
                if(isxiechong(gird,i,j,k))val[gird.G[i][j]]+=xiechong[xs[gird.G[i][j]]][k];
                if(iszhisi(gird,i,j,k))val[gird.G[i][j]]+=zhisi[xs[gird.G[i][j]]][k];
                if(isxiesi(gird,i,j,k))val[gird.G[i][j]]+=xiesi[xs[gird.G[i][j]]][k];
                if(iszhikong(gird,i,j,k))val[gird.G[i][j]]+=zhikong[xs[gird.G[i][j]]][k];
                if(isxiekong(gird,i,j,k))val[gird.G[i][j]]+=xiekong[xs[gird.G[i][j]]][k];
            }
        }
    }
    return mp(val[1],val[2]);
}
inline pair<int,int>GetCount(Gird gird,int xs){
    if(xs==1)return CountValue(gird,mp(0,1));
    else return CountValue(gird,mp(1,0));
}
inline pair<int,int>GoingToDie(Gird gird,int xs){
    if(xs==1)return CountValue(gird,mp(0,1));
    else return CountValue(gird,mp(1,0));
}
bool checked;
inline void Ck(Gird gird){
    int cnt=0;
    for(int i=1;i<=15;++i)for(int j=1;j<=15;++j)if(gird.G[i][j])++cnt;
    if(cnt>=18)checked=1;
}
inline void check(Gird gird){                        //该出手了
    if(checked)return;
    Ck(gird); 
    if(checked){                                 //
        for(int i=1;i<=5;++i){
            for(int j=0;j<2;++j){
                zhihuo[j][i]*=4;
                xiehuo[j][i]*=4;
                zhichong[j][i]*=4;
                xiechong[j][i]*=4;
                zhisi[j][i]*=4;
                xiesi[j][i]*=4;
                zhikong[j][i]*=4;
                xiekong[j][i]*=4;
            }
        }
    }
}

AI部分:

const int sma=2;            //选择非最优解的概率(/32767)
const int smb=666;        //同为最优解,替换当前最优解的概率(/32767)
const int Beg=7;            //定义“开局”时放了多少棋子
inline bool Begin(Gird gird){  //是否是开局状态
    int cnt=0;
    for(int i=1;i<=15;++i)for(int j=1;j<=15;++j)if(gird.G[i][j]!=0)++cnt;
    return cnt<=Beg;
}
inline bool First(Gird gird){  //最开始(防止一开始乱放)
    int cnt=0;
    for(int i=1;i<=15;++i)for(int j=1;j<=15;++j)if(gird.G[i][j]!=0)++cnt;
    return cnt<=3;
}
inline Gird AI_esay(Gird gird){ 
    check(gird);
    pair<int,int>res;int mx=-2000000000;
    res.first=0;res.second=0;
    if(First(gird)){  //最开始
        for(int i=6;i<=10;++i){
            for(int j=6;j<=10;++j){        //限制放的位置
                if(!gird.G[i][j]){
                    gird.G[i][j]=2;
                    pair<int,int>T=GetCount(gird,2);  
                    if(gird.Win())return gird;
                    int delta=T.second-T.first;
                    if(T.second>1500000*(checked?4:1) and T.first<1500000)return gird;  //AI完全可以赢了
                    if(delta>mx){  //更新最优解
                        mx=delta;
                        res=mp(i,j);
                    }else if(delta==mx){
                        if(rand()<smb){
                            mx=delta;
                            res=mp(i,j);
                        }
                    }else{
                        if(rand()<sma){
                            mx=delta;
                            res=mp(i,j);
                        }
                    }
                    gird.G[i][j]=0;
                }
            }
        }
    }else if(Begin(gird)){
        //必须防守
        for(int i=1;i<=15;++i){
            for(int j=1;j<=15;++j){
                if(!gird.G[i][j]){
                    gird.G[i][j]=2;
                    if(gird.Win()){
                        return gird;
                    }
                    pair<int,int>T=GetCount(gird,2);
                    int delta=T.second-T.first;
                    if(T.first>=1500000 and T.second<15000000){   //不行,敌人的进攻来了,必须防守
                        gird.G[i][j]=0;
                        continue;
                    }
                    if(T.second>1500000*(checked?4:1) and T.first<1500000)return gird;
                    if(delta>mx){
                        mx=delta;
                        res=mp(i,j);
                    }else if(delta==mx){
                        if(rand()<smb){
                            mx=delta;
                            res=mp(i,j);
                        }
                    }else{
                        if(rand()<sma){
                            mx=delta;
                            res=mp(i,j);
                        }
                    }
                    gird.G[i][j]=0;
                }
            }
        }
        gird.G[res.first][res.second]=2;
        pair<int,int>P=GetCount(gird,2);
        if(P.second-P.first==mx){
            res=res;
        }else{  //不必防守,还是在中间挑地方放
            gird.G[res.first][res.second]=0;
            for(int i=6;i<=10;++i){
                for(int j=6;j<=10;++j){
                    if(!gird.G[i][j]){
                        gird.G[i][j]=2;
                        if(gird.Win()){
                            return gird;
                        }                       
                        pair<int,int>T=GetCount(gird,2);
                        int delta=T.second-T.first;
                        if(T.first>=1500000 and T.second<15000000){
                            gird.G[i][j]=0;
                            continue;
                        }
                        if(T.second>1500000*(checked?4:1) and T.first<1500000)return gird;
                        if(delta>mx){
                            mx=delta;
                            res=mp(i,j);
                        }else if(delta==mx){
                            if(rand()<smb){
                                mx=delta;
                                res=mp(i,j);
                            }
                        }else{
                            if(rand()<sma){
                                mx=delta;
                                res=mp(i,j);
                            }
                        }
                        gird.G[i][j]=0;
                    }
                }
            }
        }
    }else{  //非开局
        for(int i=1;i<=15;++i){
            for(int j=1;j<=15;++j){
                if(!gird.G[i][j]){
                    gird.G[i][j]=2;
                    if(gird.Win()){
                        return gird;
                    }                   
                    pair<int,int>T=GetCount(gird,2);
                    int delta=T.second-T.first;
                    if(T.first>=1500000 and T.second<15000000){
                        gird.G[i][j]=0;
                        continue;
                    }
                    if(T.second>1500000*(checked?4:1) and T.first<1500000)return gird;
                    if(delta>mx){
                        mx=delta;
                        res=mp(i,j);
                    }else if(delta==mx){
                        if(rand()<smb){
                            mx=delta;
                            res=mp(i,j);
                        }
                    }else{
                        if(rand()<sma){
                            mx=delta;
                            res=mp(i,j);
                        }
                    }
                    gird.G[i][j]=0;
                }
            }
        }
    }
    if(res.first==0 and res.second==0){   //必输,则随便挑一个最优的地方放了
        for(int i=1;i<=15;++i){
            for(int j=1;j<=15;++j){
                if(!gird.G[i][j]){
                    gird.G[i][j]=2;
                    if(gird.Win()){
                        return gird;
                    }
                    pair<int,int>T=GoingToDie(gird,2);
                    int delta=T.second-T.first;
                    if(T.second>1500000*(checked?4:1) and T.first<1500000)return gird;
                    if(delta>mx){
                        mx=delta;
                        res=mp(i,j);
                    }else if(delta==mx){
                        if(rand()<smb){
                            mx=delta;
                            res=mp(i,j);
                        }
                    }else{
                        if(rand()<sma){
                            mx=delta;
                            res=mp(i,j);
                        }
                    }
                    gird.G[i][j]=0;
                }
            }
        }
    }
    gird.G[res.first][res.second]=2;
    return gird;
} 

总结:

这个AI,没有用到任何的高深算法(如模拟退火,甚至连搜索都没有用),但是还是打过了百度上搜到的几个AI。

写这些AI,还是比写猪国杀之类的大模拟好多了,各位也去写一个吧!

整体代码(github,求star /cy)