人工智慧實現簡單的五子棋程式

BYSora發表於2019-04-04

我這個程式在QQ五子棋上實驗了一下,結果是黑棋先手全勝,白棋後手的勝率卻慘不忍睹,原因有下:
1、五子棋的先手是有優勢的,所以職業比賽裡都會有禁手來實現公平
2、水平有限,對局面的評估只有一小部分

主要思路就是博弈樹,程式向前看一步,對對手可能的走法進行評估,評估的策略主要有兩條:
1、掃描整個棋盤,判斷出現了多少個三子成線並且兩端未堵的情況,對方每出現一種該情況,評估大幅下降,因為這幾乎是必堵的,反之評估大幅上升,程式追求的就是這種情況;
2、判斷出現了多少個兩子成線的情況,這種情況的評估值比上一種要少
此外還有一個幾乎是沒用的判斷,是我從九宮格的評估方案上“拿”過來的,用兩種顏色的棋子分別填滿棋盤,判斷出現了多少個五子成龍的情況,不過很雞肋

#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <cstdio>
#include <set>
#include <math.h>
#include <algorithm>
#include <queue>
#define INF 0x3f3f3f3f
using namespace std;
int changex=8,changey=8;
struct Node
{
    int A[16][16];
    int f;
};
void mcopy(int des[16][16],int src[16][16])
{
    for(int i=1; i<=15; ++i)
        for(int j=1; j<=15; ++j)
            des[i][j]=src[i][j];
}
bool check(int flag,int tmp[16][16])
{
    int fflag;
    if(flag==0)
        fflag=1;
    else
        fflag=0;
    //棋譜1:四子連線,端點都在
    for(int i=1; i<=15; ++i) //水平方向
        for(int j=1; j<=10; ++j)
        {
            if(tmp[i][j]!=fflag&&tmp[i][j+1]==flag&&tmp[i][j+2]==flag&&tmp[i][j+3]==flag&&tmp[i][j+4]==flag&&tmp[i][j+5]!=fflag)
                return true;
        }
    for(int i=1; i<=10; ++i) //垂直方向
        for(int j=1; j<=15; ++j)
        {
            if(tmp[i][j]!=fflag&&tmp[i+1][j]==flag&&tmp[i+2][j]==flag&&tmp[i+3][j]==flag&&tmp[i+4][j]==flag&&tmp[i+5][j]!=fflag)
                return true;
        }
    for(int i=1; i<=10; ++i) //正斜方向
        for(int j=1; j<=10; ++j)
        {
            if(tmp[i][j]!=fflag&&tmp[i+1][j+1]==flag&&tmp[i+2][j+2]==flag&&tmp[i+3][j+3]==flag&&tmp[i+4][j+4]==flag&&tmp[i+5][j+5]!=fflag)
                return true;
        }
    for(int i=1; i<=10; ++i) //反斜方向
        for(int j=15; j>=6; --j)
        {
            if(tmp[i][j]!=fflag&&tmp[i+1][j-1]==flag&&tmp[i+2][j-2]==flag&&tmp[i+3][j-3]==flag&&tmp[i+4][j-4]==flag&&tmp[i+5][j-5]!=fflag)
                return true;
        }
    //棋譜2:最簡單的五子成型
    for(int i=1; i<=15; ++i) //水平方向
        for(int j=1; j<=11; ++j)
        {
            if(tmp[i][j]==flag&&tmp[i][j+1]==flag&&tmp[i][j+2]==flag&&tmp[i][j+3]==flag&&tmp[i][j+4]==flag)
                return true;
        }
    for(int i=1; i<=11; ++i) //垂直方向
        for(int j=1; j<=15; ++j)
        {
            if(tmp[i][j]==flag&&tmp[i+1][j]==flag&&tmp[i+2][j]==flag&&tmp[i+3][j]==flag&&tmp[i+4][j]==flag)
                return true;
        }
    for(int i=1; i<=11; ++i) //正斜方向
        for(int j=1; j<=11; ++j)
        {
            if(tmp[i][j]==flag&&tmp[i+1][j+1]==flag&&tmp[i+2][j+2]==flag&&tmp[i+3][j+3]==flag&&tmp[i+4][j+4]==flag)
                return true;
        }
    for(int i=1; i<=11; ++i) //反斜方向
        for(int j=15; j>=5; --j)
        {
            if(tmp[i][j]==flag&&tmp[i+1][j-1]==flag&&tmp[i+2][j-2]==flag&&tmp[i+3][j-3]==flag&&tmp[i+4][j-4]==flag)
                return true;
        }
    return false;
}
int h(int flag,int A[16][16])
{
    int tmp[16][16];
    mcopy(tmp,A);
    int fflag;
    if(flag==1)
        fflag=0;
    else
        fflag=1;
    int val=0;
    //出現沒有堵住的三子,對己方不利的評價要很高
    for(int i=1; i<=15; ++i) //水平方向
        for(int j=1; j<=11; ++j)
        {
            if(tmp[i][j]!=flag&&tmp[i][j+1]==fflag&&tmp[i][j+2]==fflag&&tmp[i][j+3]==fflag&&tmp[i][j+4]!=flag)
                val+= -1000;
            if(tmp[i][j]!=fflag&&tmp[i][j+1]==flag&&tmp[i][j+2]==flag&&tmp[i][j+3]==flag&&tmp[i][j+4]!=fflag)
                val+= 1000;
            /*if(tmp[i][j]!=flag&&tmp[i][j+1]==fflag&&tmp[i][j+2]!=flag&&tmp[i][j+3]==fflag&&tmp[i][j+4]!=flag)
                val+= -100;
            if(tmp[i][j]!=fflag&&tmp[i][j+1]==flag&&tmp[i][j+2]!=fflag&&tmp[i][j+3]==flag&&tmp[i][j+4]!=fflag)
                val+= 100;*/
        }
    for(int i=1; i<=11; ++i) //垂直方向
        for(int j=1; j<=15; ++j)
        {
            if(tmp[i][j]!=flag&&tmp[i+1][j]==fflag&&tmp[i+2][j]==fflag&&tmp[i+3][j]==fflag&&tmp[i+4][j]!=flag)
                val+= -1000;
            if(tmp[i][j]!=fflag&&tmp[i+1][j]==flag&&tmp[i+2][j]==flag&&tmp[i+3][j]==flag&&tmp[i+4][j]!=fflag)
                val+= 1000;
            /*if(tmp[i][j]!=flag&&tmp[i+1][j]==fflag&&tmp[i+2][j]!=flag&&tmp[i+3][j]==fflag&&tmp[i+4][j]!=flag)
                val+= -100;
            if(tmp[i][j]!=fflag&&tmp[i+1][j]==flag&&tmp[i+2][j]!=fflag&&tmp[i+3][j]==flag&&tmp[i+4][j]!=fflag)
                val+= 100;*/
        }
    for(int i=1; i<=11; ++i) //正斜方向
        for(int j=1; j<=11; ++j)
        {
            if(tmp[i][j]!=flag&&tmp[i+1][j+1]==fflag&&tmp[i+2][j+2]==fflag&&tmp[i+3][j+3]==fflag&&tmp[i+4][j+4]!=flag)
                val+= -1000;
            if(tmp[i][j]!=fflag&&tmp[i+1][j+1]==flag&&tmp[i+2][j+2]==flag&&tmp[i+3][j+3]==flag&&tmp[i+4][j+4]!=fflag)
                val+= 1000;
            /*if(tmp[i][j]!=flag&&tmp[i+1][j+1]==fflag&&tmp[i+2][j+2]!=flag&&tmp[i+3][j+3]==fflag&&tmp[i+4][j+4]!=flag)
                val+= -100;
            if(tmp[i][j]!=fflag&&tmp[i+1][j+1]==flag&&tmp[i+2][j+2]!=fflag&&tmp[i+3][j+3]==flag&&tmp[i+4][j+4]!=fflag)
                val+= 100;*/
        }
    for(int i=1; i<=11; ++i) //反斜方向
        for(int j=15; j>=5; --j)
        {
            if(tmp[i][j]!=flag&&tmp[i+1][j-1]==fflag&&tmp[i+2][j-2]==fflag&&tmp[i+3][j-3]==fflag&&tmp[i+4][j-4]!=flag)
                val+= -1000;
            if(tmp[i][j]!=fflag&&tmp[i+1][j-1]==flag&&tmp[i+2][j-2]==flag&&tmp[i+3][j-3]==flag&&tmp[i+4][j-4]!=fflag)
                val+= 1000;
            /*if(tmp[i][j]!=flag&&tmp[i+1][j-1]==fflag&&tmp[i+2][j-2]!=flag&&tmp[i+3][j-3]==fflag&&tmp[i+4][j-4]!=flag)
                val+= -100;
            if(tmp[i][j]!=fflag&&tmp[i+1][j-1]==flag&&tmp[i+2][j-2]!=fflag&&tmp[i+3][j-3]==flag&&tmp[i+4][j-4]!=fflag)
                val+= 100;*/
        }
    //出現未堵住的兩子,評價次之
    for(int i=1; i<=15; ++i) //水平方向
        for(int j=1; j<=12; ++j)
        {
            if(tmp[i][j]!=flag&&tmp[i][j+1]==fflag&&tmp[i][j+2]==fflag&&tmp[i][j+3]!=flag)
                val+= -100;
            if(tmp[i][j]!=fflag&&tmp[i][j+1]==flag&&tmp[i][j+2]==flag&&tmp[i][j+3]!=fflag)
                val+= 100;
        }
    for(int i=1; i<=12; ++i) //垂直方向
        for(int j=1; j<=15; ++j)
        {
            if(tmp[i][j]!=flag&&tmp[i+1][j]==fflag&&tmp[i+2][j]==fflag&&tmp[i+3][j]!=flag)
                val+= -100;
            if(tmp[i][j]!=fflag&&tmp[i+1][j]==flag&&tmp[i+2][j]==flag&&tmp[i+3][j]!=fflag)
                val+= 100;
        }
    for(int i=1; i<=12; ++i) //正斜方向
        for(int j=1; j<=12; ++j)
        {
            if(tmp[i][j]!=flag&&tmp[i+1][j+1]==fflag&&tmp[i+2][j+2]==fflag&&tmp[i+3][j+3]!=flag)
                val+= -100;
            if(tmp[i][j]!=fflag&&tmp[i+1][j+1]==flag&&tmp[i+2][j+2]==flag&&tmp[i+3][j+3]!=fflag)
                val+= 100;
        }
    for(int i=1; i<=12; ++i) //反斜方向
        for(int j=15; j>=4; --j)
        {
            if(tmp[i][j]!=flag&&tmp[i+1][j-1]==fflag&&tmp[i+2][j-2]==fflag&&tmp[i+3][j-3]!=flag)
                val+= -100;
            if(tmp[i][j]!=fflag&&tmp[i+1][j-1]==flag&&tmp[i+2][j-2]==flag&&tmp[i+3][j-3]!=fflag)
                val+= 100;
        }
    /*if(val!=0)
    {
        return val;
    }*/
    for(int i=1; i<=15; ++i)
        for(int j=1; j<=15; ++j)
            if(tmp[i][j]==8)
                tmp[i][j]=flag;
    int sum1=0;
    for(int i=1; i<=15; ++i) //水平方向
        for(int j=1; j<=11; ++j)
            if(tmp[i][j]==flag&&tmp[i][j+1]==flag&&tmp[i][j+2]==flag&&tmp[i][j+3]==flag&&tmp[i][j+4]==flag)
                sum1++;
    for(int i=1; i<=11; ++i) //垂直方向
        for(int j=1; j<=15; ++j)
            if(tmp[i][j]==flag&&tmp[i+1][j]==flag&&tmp[i+2][j]==flag&&tmp[i+3][j]==flag&&tmp[i+4][j]==flag)
                sum1++;
    for(int i=1; i<=11; ++i) //正斜方向
        for(int j=1; j<=11; ++j)
            if(tmp[i][j]==flag&&tmp[i+1][j+1]==flag&&tmp[i+2][j+2]==flag&&tmp[i+3][j+3]==flag&&tmp[i+4][j+4]==flag)
                sum1++;
    for(int i=1; i<=11; ++i) //反斜方向
        for(int j=15; j>=5; --j)
            if(tmp[i][j]==flag&&tmp[i+1][j-1]==flag&&tmp[i+2][j-2]==flag&&tmp[i+3][j-3]==flag&&tmp[i+4][j-4]==flag)
                sum1++;
    mcopy(tmp,A);
    for(int i=1; i<=15; ++i)
        for(int j=1; j<=15; ++j)
            if(tmp[i][j]==8)
                tmp[i][j]=fflag;
    int sum0=0;
    for(int i=1; i<=15; ++i) //水平方向
        for(int j=1; j<=11; ++j)
            if(tmp[i][j]==fflag&&tmp[i][j+1]==fflag&&tmp[i][j+2]==fflag&&tmp[i][j+3]==fflag&&tmp[i][j+4]==fflag)
                sum0++;
    for(int i=1; i<=11; ++i) //垂直方向
        for(int j=1; j<=15; ++j)
            if(tmp[i][j]==fflag&&tmp[i+1][j]==fflag&&tmp[i+2][j]==fflag&&tmp[i+3][j]==fflag&&tmp[i+4][j]==fflag)
                sum0++;
    for(int i=1; i<=11; ++i) //正斜方向
        for(int j=1; j<=11; ++j)
            if(tmp[i][j]==fflag&&tmp[i+1][j+1]==fflag&&tmp[i+2][j+2]==fflag&&tmp[i+3][j+3]==fflag&&tmp[i+4][j+4]==fflag)
                sum0++;
    for(int i=1; i<=11; ++i) //反斜方向
        for(int j=15; j>=5; --j)
            if(tmp[i][j]==fflag&&tmp[i+1][j-1]==fflag&&tmp[i+2][j-2]==fflag&&tmp[i+3][j-3]==fflag&&tmp[i+4][j-4]==fflag)
                sum0++;
    return val+sum1-sum0;
}
int move1(int A[16][16])
{
    int tmp[16][16],num,ans=INF;
    for(int i=1; i<=15; ++i)
        for(int j=1; j<=15; ++j)
            if(A[i][j]==8)
            {
                mcopy(tmp,A);
                tmp[i][j]=0; //對手下
                if(check(0,tmp))
                    num=-INF;
                else
                    num=h(1,tmp);
                //cout<<num<<endl;
                ans=min(ans,num);
            }
    return ans;
}
int move0(int A[16][16])
{
    int tmp[16][16],num,ans=INF;
    for(int i=1; i<=15; ++i)
        for(int j=1; j<=15; ++j)
            if(A[i][j]==8)
            {
                mcopy(tmp,A);
                tmp[i][j]=1; //對手下
                if(check(1,tmp))
                    num=-INF;
                else
                    num=h(0,tmp);
                //cout<<num<<endl;
                ans=min(ans,num);
            }
    return ans;
}
void solve1(Node &node)
{
    int tmp[16][16],f,ans=-INF,anst[16][16]= {};
    for(int i=1; i<=15; ++i)
        for(int j=1; j<=15; ++j)
            if(node.A[i][j]==8)
            {
                mcopy(tmp,node.A);
                tmp[i][j]=1; //自己下
                if(check(1,tmp))
                {
                    f=move1(tmp);
                    if(f!=-INF)
                        f=INF;
                }
                else
                    f=move1(tmp);
                //cout<<f<<endl;
                if(f>ans)
                {
                    //cout<<f<<endl;
                    ans=f;
                    mcopy(anst,tmp);
                }
            }
    for(int i=1; i<=15; ++i)
        for(int j=1; j<=15; ++j)
            if(node.A[i][j]!=anst[i][j])
            {
                changex=i;
                changey=j;
                break;
            }
    mcopy(node.A,anst);
    node.f=ans;
    cout<<ans<<endl;
}
void solve0(Node &node)
{
    int tmp[16][16],f,ans=-INF,anst[16][16]= {};
    for(int i=1; i<=15; ++i)
        for(int j=1; j<=15; ++j)
            if(node.A[i][j]==8)
            {
                mcopy(tmp,node.A);
                tmp[i][j]=0; //自己下
                if(check(0,tmp))
                {
                    f=move0(tmp);
                    if(f!=-INF)
                        f=INF;
                }
                else
                    f=move0(tmp);
                if(f>ans)
                {
                    //cout<<f<<endl;
                    ans=f;
                    mcopy(anst,tmp);
                }
            }
    for(int i=1; i<=15; ++i)
        for(int j=1; j<=15; ++j)
            if(node.A[i][j]!=anst[i][j])
            {
                changex=i;
                changey=j;
                break;
            }
    mcopy(node.A,anst);
    node.f=ans;
    cout<<ans<<endl;
}
void show(int A[16][16])
{
    int sum=0;
    for(int i=1; i<=15; ++i)
        for(int j=1; j<=15; ++j)
            sum+=A[i][j];
    if(sum==0)
        cout<<"無法繼續!"<<endl;
    else
    {
        for(int i=0; i<=15; ++i)
        {
            for(int j=0; j<=15; ++j)
            {
                if(i==0)
                {
                    cout<<j<<" ";
                    if(j==0)
                        cout<<" ";
                }
                else if(j==0)
                {
                    cout<<i<<" ";
                    if(i<10)
                        cout<<" ";
                }
                else if((i==4&&j==4)||(i==4&&j==12)||(i==12&&j==4)||(i==12&&j==12)||(i==changex&&j==changey))
                {
                    if(A[i][j]==8)
                        cout<<".<";
                    else
                        cout<<A[i][j]<<"<";
                }
                else
                {
                    if(A[i][j]==8)
                        cout<<". ";
                    else
                        cout<<A[i][j]<<" ";
                }
                if(j>9&&i!=0)
                    cout<<" ";
            }
            cout<<endl;
        }
        cout<<endl;
    }
}
int main()
{
    int flag=1,myturn,first=1;
    int i,j;
    Node node;
    for(int i=1; i<=15; ++i)
        for(int j=1; j<=15; ++j)
            node.A[i][j]=8;
    cout<<"程式執黑棋(1)or白棋(0)?";
    cin>>myturn;
    while(1)
    {
        if(flag)
        {
            if(flag==myturn)
            {
                if(first)//人工智慧第一次不會走中間,因為中間範圍的評價相同,所以第一步要強制下在中間
                {
                    node.A[8][8]=1;
                    first=0;
                }
                else
                    solve1(node);
            }
            else
            {
                cout<<"對方的選擇是?";
                cin>>i>>j;
                node.A[i][j]=1;
            }
            show(node.A);
            flag=0;
        }
        else
        {
            if(flag==myturn)
            {
                if(first)
                {
                    node.A[i-1][j-1]=0;
                    first=0;
                }
                else
                    solve0(node);
            }
            else
            {
                cout<<"對方的選擇是?";
                cin>>i>>j;
                node.A[i][j]=0;
            }
            show(node.A);
            flag=1;
        }
    }
    return 0;
}

相關文章