五子棋基本玩法-AI实现

版权声明:转载 请说明 https://blog.csdn.net/qq2512667/article/details/84478944

参考:http://game.onegreen.net/wzq/HTML/142336.html

对于正式接触五子棋时间不长的朋友来说,了解和掌握一些基本棋型的名称及其特点是非常重要的。不仅可以加深对棋的理解,更重要的是可以方便自己与其他棋友交流。

最常见的基本棋型大体有以下几种:连五,活四,冲四,活三,眠三,活二,眠二

①连五:顾名思义,五颗同色棋子连在一起,不需要多讲。
图2-1  

②活四:有两个连五点(即有两个点可以形成五),图中白点即为连五点。
稍微思考一下就能发现活四出现的时候,如果对方单纯过来防守的话,是已经无法阻止自己连五了。
图2-2  


③冲四:有一个连五点,如下面三图,均为冲四棋型。图中白点为连五点。
相对比活四来说,冲四的威胁性就小了很多,因为这个时候,对方只要跟着防守在那个唯一的连五点上,冲四就没法形成连五。
图2-3  图2-4  图2-5  


④活三:可以形成活四的三,如下图,代表两种最基本的活三棋型。图中白点为活四点。
活三棋型是我们进攻中最常见的一种,因为活三之后,如果对方不以理会,将可以下一手将活三变成活四,而我们知道活四是已经无法单纯防守住了。所以,当我们面对活三的时候,需要非常谨慎对待。在自己没有更好的进攻手段的情况下,需要对其进行防守,以防止其形成可怕的活四棋型。
图2-6  图2-7 

其中图2-7中间跳着一格的活三,也可以叫做跳活三。

⑤眠三只能够形成冲四的三,如下各图,分别代表最基础的六种眠三形状。图中白点代表冲四点。眠三的棋型与活三的棋型相比,危险系数下降不少,因为眠三棋型即使不去防守,下一手它也只能形成冲四,而对于单纯的冲四棋型,我们知道,是可以防守住的。
图2-8  图2-9  图2-10 

2-11 图2-12 图2-13 

如上所示,眠三的形状是很丰富的。对于初学者,在下棋过程中,很容易忽略不常见的眠三形状,例如图2-13所示的眠三。

有新手学了活三眠三后,会提出疑问,说活三也可以形成冲四啊,那岂不是也可以叫眠三?
会提出这个问题,说明对眠三定义看得不够仔细:眠三的的定义是,只能够形成冲四的三。而活三可以形成眠三,但也能够形成活四。

此外,在五子棋中,活四棋型比冲四棋型具有更大的优势,所以,我们在既能够形成活四又能够形成冲四时,会选择形成活四。

温馨提示:学会判断一个三到底是活三还是眠三是非常重要的。所以,需要好好体会。
后边禁手判断的时候也会有所应用。 

⑥活二:能够形成活三的二,如下图,是三种基本的活二棋型。图中白点为活三点。
活二棋型看起来似乎很无害,因为他下一手棋才能形成活三,等形成活三,我们再防守也不迟。但其实活二棋型是非常重要的,尤其是在开局阶段,我们形成较多的活二棋型的话,当我们将活二变成活三时,才能够令自己的活三绵绵不绝微风里,让对手防不胜防。
图2-14  图2-15  图2-16 

⑦眠二:能够形成眠三的二。图中四个为最基本的眠二棋型,细心且喜欢思考的同学会根据眠三介绍中的图2-13找到与下列四个基本眠二棋型都不一样的眠二。图中白点为眠三点。
图2-17 图2-18  
图2-19  图2-20 

由此 可以确定 所有的棋型,连五,活四,冲四,活三,眠三,活二,眠二

每一种棋型对应不同的分数,依次递减

在C#里 可以用 一个字典,提前存储起来

 public class ChessAI :MonoBehaviour
{//分数字典
    protected Dictionary<string, float> toScore = new Dictionary<string, float>();

    public ChessType mChessType = ChessType.Black;//表示下棋的类型
    protected float[,] score = new float[15, 15];//棋盘里 棋子对应的分数的二维数组,
    void Start()
    {
     //眠二
        toScore.Add("aa__", 100);
        toScore.Add("__aa", 100);
        toScore.Add("__a_a", 100);
        toScore.Add("_a__a", 100);
        toScore.Add("a__a", 100);
        toScore.Add("a__a_", 100);
        toScore.Add("a_a__", 100);

        //活二
        toScore.Add("__aa__", 500);
        toScore.Add("__aa_", 500);
        toScore.Add("_a_a_", 500);
        toScore.Add("_a__a_", 500);
        toScore.Add("_aa__", 500);

        //眠3
        //toScore.Add("__aaa", 1000);
        toScore.Add("a_a_a", 1000);
        toScore.Add("_aa_a", 1000);
        toScore.Add("a_aa_", 1000);
        toScore.Add("_a_aa", 1000);
        toScore.Add("aa_a_", 1000);
        toScore.Add("aa__a", 1000);
        toScore.Add("aaa__", 1000);



        toScore.Add("_aa_a_", 9000);//跳活三
        toScore.Add("_a_aa_", 9000);


        toScore.Add("_aaa_", 10000); //活三

        toScore.Add("a_aaaa", 15000);//冲四
        toScore.Add("aa_aa", 15000);
        toScore.Add("_aaaa", 15000);
        toScore.Add("aaa_a", 15000);
        toScore.Add("aaaa_", 15000);

        toScore.Add("_aaaa_", 100000);//活四

        toScore.Add("aaaaa",float.MaxValue);//连五

    }
 protected virtual  void FixedUpdate()
    {

        //下棋
        if (ChessBoard.Instance.turn==mChessType&& ChessBoard.Instance.timer>0.3f)
        {
            PlayerChess();
        }
        
     
    }
//设置分数
 public void SetScore(int[] pos)
    {
        score[pos[0], pos[1]] = 0;

        //黑棋 加入评分
        CheckOneLine(pos, new int[2] { 1, 0 }, 1);
        CheckOneLine(pos, new int[2] { 1, 1 }, 1);
        CheckOneLine(pos, new int[2] { 1, -1 }, 1);
        CheckOneLine(pos, new int[2] { 0, 1 }, 1);

        //白棋 加入评分
        CheckOneLine(pos, new int[2] { 1, 0 }, 2);
        CheckOneLine(pos, new int[2] { 1, 1 }, 2);
        CheckOneLine(pos, new int[2] { 1, -1 }, 2);
        CheckOneLine(pos, new int[2] { 0, 1 }, 2);
    }
  /// <summary>
    /// 用来检查一行里 棋子的分数,上左, 左斜右斜,是右偏移量决定的,这里分别对左边,和右边进行检测。
    /// </summary>
    /// <param name="pos">当前下棋的位置</param>
    /// <param name="offset">偏移量,0是x,1是y</param>
    /// <param name="chess">棋子类型</param>
public virtual void CheckOneLine(int[] pos, int[] offset, int chess)
    {

        bool LFirst = true, lStop=false, rStop = false;//LFirst 是否扫左边,LStop 左边停止
        int AllNum = 1;//记录一共扫了多少课棋子,扫到7课退出循环
         
        string str = "a";
        int ri = offset[0], rj = offset[1];//右边的棋子的位置  i是X位置, j是Y位置
        int li = -offset[0], lj = -offset[1];//左边的棋子的位置
        while (AllNum<7 && (!lStop||!rStop))
        {
            //左边
            if (LFirst)
            {
                //边界处理
                if ((pos[0] + li >= 0 && pos[0] + li < 15) &&
            pos[1] + lj >= 0 && pos[1] + lj < 15&&!lStop)
                {
                    //碰到 chess棋
                    if (ChessBoard.Instance.grids[pos[0] + li, pos[1] + lj] == chess)
                    {
                        AllNum++;
                        str = "a" + str;
                    }
                    else if (ChessBoard.Instance.grids[pos[0] + li, pos[1] + lj] == 0)
                    {
                        AllNum++;
                        //空位置
                        str = "_" + str;
                        //如果右边没 停止 就 往右边
                        if (!rStop)
                            LFirst = false;
                    }
                    else
                    {
                        lStop = true; //停左边, 遍历右边
                        if (!rStop)
                            LFirst = false;
                        //chess的对手棋
                    }

                    li -= offset[0]; //寻找下个 位置
                    lj -= offset[1];
                }
                else //出边界 停止  遍历右边
                {
                    lStop = true; //停左边, 遍历右边
                    if (!rStop)
                        LFirst = false;
                }
            }
            else  //右边
            {
                //边界处理
                if ((pos[0] + ri >= 0 && pos[0] + ri < 15) &&
            pos[1] + rj >= 0 && pos[1] + rj < 15 &&!LFirst )
                {
                    //碰到 chess棋
                    if (ChessBoard.Instance.grids[pos[0] + ri, pos[1] + rj] == chess)
                    {
                        AllNum++;
                        str += "a"  ;
                    }
                    else if (ChessBoard.Instance.grids[pos[0] + ri, pos[1] + rj] == 0)
                    {
                        AllNum++;
                        //空位置
                        str += "_"  ;
                        //如果右边没 停止 就 往右边
                        if (!lStop)
                            LFirst = true;
                    }
                    else
                    {
                        rStop = true; //停左边, 遍历右边
                        if (!lStop)
                            LFirst = true;
                        //chess的对手棋
                    }

                    ri += offset[0]; //寻找下个 位置
                    rj += offset[1];
                }
                else //出边界 停止  遍历右边
                {
                    rStop = true; //停左边, 遍历右边
                    if (!lStop)
                        LFirst = true;
                    //chess的对手棋
                }

            }
        }

        string cmpStr = "";

        foreach (var keyVar in toScore )
        {

                //因为 str遍历出来是7位 可能有多种情况,  遍历取出 所有情况里边 值大的
            if (str.Contains(keyVar.Key ))
            {
                if (cmpStr != "")
                {
                    if (toScore[keyVar.Key]>toScore[cmpStr] )
                    {
                        cmpStr = keyVar.Key;
                    }
                }
                else
                {//第一次
                    cmpStr = keyVar.Key;
                }
            }

        }

        if (cmpStr!="")
        {
            score[pos[0], pos[1]] += toScore[cmpStr];
        }

    }

    public  void PlayerChess()
    {
        if (ChessBoard.Instance.chessStack.Count == 0)
        {
            if (ChessBoard.Instance.PlayChess(new int[2] { 7, 7 }))
                ChessBoard.Instance.timer = 0;
            return;
        }

        float maxScore = 0;
        int[] maxPos = new int[2] { 0, 0 };
        //遍历棋盘里的棋子 寻找一个最佳的下棋位置,根据平方函数算出,
        for (int i = 0; i < 15; i++)
        {
            for (int j = 0; j < 15; j++)
            {
                if (ChessBoard.Instance.grids[i, j] == 0)
                {
                    SetScore(new int[2] { i, j });
                    if (score[i, j] >= maxScore)
                    {
                        maxPos[0] = i;
                        maxPos[1] = j;
                        maxScore = score[i, j];
                    }
                }
            }
        }

        if (ChessBoard.Instance.PlayChess(maxPos))
            ChessBoard.Instance.timer = 0;

    }
}

这里的a就代表 黑棋子,_代表空位置

扫棋的算法比较简单,这里以AI为黑子 为例。

先定义一个String变量 str="a", 表示落子,和一个变量Num来保存一共扫了多少个,

先从左边开始扫,遇到黑棋子,就让它 str="a"+str;左边扫,扫到这个棋子"a",肯定是在落子的左边,再让变量Num++

扫到左边为空的 情况,就让它str="_"+str;再让变量Num++,遇到白子或者边界,就开始扫右边,处理情况和扫左边类似,只不过在遇到黑子的时候,让字符 str+="a";因为扫到的黑子是在落子的右边的,因此,"a"在落子的右边,遇到空子也一样;

pos是当前落子的位置, offset是偏移量,用来应付不同的方法,上下左右,左斜右斜,chess是棋子类型,AI不仅要计算出自己的棋子的最佳落子位置,也要计算出对手的最佳落子位置,抢在对方之前落子。

这里棋盘是15*15的,ChessBoard.Instance.grids保存了所有的棋子,

CheckOneLine(int[] pos, int[] offset, int chess)    这里的pos 

public virtual void CheckOneLine(int[] pos, int[] offset, int chess)
    {
        bool LFirst = true, lStop=false, rStop = false;
        int AllNum = 1;
         
        string str = "a";
        int ri = offset[0], rj = offset[1];//右边  i是X位置, j是Y位置
        int li = -offset[0], lj = -offset[1];//左边
        while (AllNum<7 && (!lStop||!rStop))
        {
            //左边
            if (LFirst)
            {
                //边界处理
                if ((pos[0] + li >= 0 && pos[0] + li < 15) &&
            pos[1] + lj >= 0 && pos[1] + lj < 15&&!lStop)
                {
                    //碰到 chess棋
                    if (ChessBoard.Instance.grids[pos[0] + li, pos[1] + lj] == chess)
                    {
                        AllNum++;
                        str = "a" + str;
                    }
                    else if (ChessBoard.Instance.grids[pos[0] + li, pos[1] + lj] == 0)
                    {
                        AllNum++;
                        //空位置
                        str = "_" + str;
                        //如果右边没 停止 就 往右边
                        if (!rStop)
                            LFirst = false;
                    }
                    else
                    {
                        lStop = true; //停左边, 遍历右边
                        if (!rStop)
                            LFirst = false;
                        //chess的对手棋
                    }

                    li -= offset[0]; //寻找下个 位置
                    lj -= offset[1];
                }
                else //出边界 停止  遍历右边
                {
                    lStop = true; //停左边, 遍历右边
                    if (!rStop)
                        LFirst = false;
                }
            }
            else  //右边
            {
                //边界处理
                if ((pos[0] + ri >= 0 && pos[0] + ri < 15) &&
            pos[1] + rj >= 0 && pos[1] + rj < 15 &&!LFirst )
                {
                    //碰到 chess棋
                    if (ChessBoard.Instance.grids[pos[0] + ri, pos[1] + rj] == chess)
                    {
                        AllNum++;
                        str += "a"  ;
                    }
                    else if (ChessBoard.Instance.grids[pos[0] + ri, pos[1] + rj] == 0)
                    {
                        AllNum++;
                        //空位置
                        str += "_"  ;
                        //如果右边没 停止 就 往右边
                        if (!lStop)
                            LFirst = true;
                    }
                    else
                    {
                        rStop = true; //停左边, 遍历右边
                        if (!lStop)
                            LFirst = true;
                        //chess的对手棋
                    }

                    ri += offset[0]; //寻找下个 位置
                    rj += offset[1];
                }
                else //出边界 停止  遍历右边
                {
                    rStop = true; //停左边, 遍历右边
                    if (!lStop)
                        LFirst = true;
                    //chess的对手棋
                }

            }
        }

        string cmpStr = "";

        foreach (var keyVar in toScore )
        {

                //因为 str遍历出来是7位 可能有多种情况,  遍历取出 所有情况里边 值大的
            if (str.Contains(keyVar.Key ))
            {
                if (cmpStr != "")
                {
                    if (toScore[keyVar.Key]>toScore[cmpStr] )
                    {
                        cmpStr = keyVar.Key;
                    }
                }
                else
                {//第一次
                    cmpStr = keyVar.Key;
                }
            }

        }

        if (cmpStr!="")
        {
            score[pos[0], pos[1]] += toScore[cmpStr];
        }

    }
public class ChessBoard : MonoBehaviour
{
    public static ChessBoard Instance { get { return _instance; } }
    static ChessBoard _instance;
    public ChessType turn = ChessType.Black;
    public int[,] grids;//存 1 和2 ,表示黑棋和白棋
    public GameObject[] prefabs;
    public float timer = 0;
    public bool gameStart = true;

    public Stack< Transform> chessStack = new Stack<Transform>();//先入后出。
    // Use this for initialization
      Transform parent;
    private void Awake()
    {
        if (Instance==null)
        {
            _instance = this;
        }

        grids = new int[15, 15];

        parent = GameObject.Find("Parent").transform;
    }

    

    private void FixedUpdate()
    {
        timer += Time.deltaTime;
    }
    // Update is called once per frame
    void Update ()
    {

    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="pos">落子的位置</param>
    /// <returns></returns>
    public bool PlayChess(int[] pos)
    {
        if (!gameStart) { return false; }
        if (pos[0] < 0 || pos[0] > 14 || pos[1] < 0 || pos[1] > 14)
        {
            return false;
        }

        //判断 当前点是否为0  是否下过棋
        if (grids[pos[0], pos[1]] != 0) return false;

        //黑 是1
        if (turn == ChessType.Black)
        {//生成黑棋
           GameObject go=  Instantiate(prefabs[0], new Vector3(pos[0] - 7f, pos[1] - 7f, 0), Quaternion.identity);
           chessStack.Push(go.transform);
            go.transform.SetParent(parent);
            grids[pos[0], pos[1]] = 1;
            //判断胜负
            if (CheckWiner(pos))
            {
                GameEnd();
            }
            // 下轮
            turn = ChessType.White;
        }
        else if (turn == ChessType.White)
        {
            //白棋
            GameObject go= Instantiate(prefabs[1], new Vector3(pos[0] - 7f, pos[1] - 7f, 0), Quaternion.identity);
            chessStack.Push(go.transform);
            go.transform.SetParent(parent);
            grids[pos[0], pos[1]] = 2;
            //判断胜负
            if (CheckWiner(pos))
            {
                GameEnd();
            }
            turn = ChessType.Black;
        }      
        return true;
    }
    public bool CheckWiner(int[] pos)
    {
        //横
        if (CheckOneLine(pos, new int[2] { 1, 0 })) return true;
        //竖
        if (CheckOneLine(pos, new int[2] { 0, 1 })) return true;
        //左斜
        if (CheckOneLine(pos, new int[2] { 1,1 })) return true;
        //右斜
        if (CheckOneLine(pos, new int[2] { 1,-1 })) return true;

        return false;
    }

    void GameEnd()
    {
        gameStart = false;
        Debug.Log(turn + "胜利");
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="pos">落子的位置</param>
    /// <param name="offest">偏移量</param>
    /// <returns></returns>

    public void RetractChess()
    {
        if (chessStack.Count>1)
        {
            Transform tran = chessStack.Pop();//出栈
            grids[(int) (tran.position.x + 7), (int)(tran.position.y + 7)] = 0;
            Destroy(tran.gameObject);

            tran = chessStack.Pop();//出栈
            grids[(int)(tran.position.x + 7), (int)(tran.position.y + 7)] = 0;
            Destroy(tran.gameObject);
        }
    }
    public bool CheckOneLine(int[] pos,int[] offest)
    {
        int linkNum = 1;
        //i为x  j 为y     左边 
        for (int i = offest[0], j = offest[1];( i + pos[0] >= 0 && i + pos[0] < 15 )&& (j+pos[1] >= 0 && j + pos[1] < 15); i += offest[0], j += offest[1])
        {
            if ((int)turn== grids[i+pos[0],j+pos[1]])
            {
                linkNum++;
            }
            else
            {
                break;
            }
        }
        //右边边 
        for (int i =-offest[0], j = -offest[1]; (i + pos[0] >= 0 && i + pos[0] < 15) && (j + pos[1] >= 0 && j + pos[1] < 15); i -= offest[0], j -= offest[1])
        {
            if ((int)turn == grids[i + pos[0], j + pos[1]])
            {
                linkNum++;
            }
            else
            {
                break;
            }
        }

        if (linkNum>4)
        {
            return true;
        }
        return false;

    }
}

public enum  ChessType
{
    Watch,   
    Black,
    White,

}

用博弈树 和极小极大MiniMax算法,和剪枝算法, 可以实现更加智能的AI 

https://baike.baidu.com/item/%E5%8D%9A%E5%BC%88%E6%A0%91%E5%90%AF%E5%8F%91%E5%BC%8F%E6%90%9C%E7%B4%A2/19480042

猜你喜欢

转载自blog.csdn.net/qq2512667/article/details/84478944