1. 程式人生 > >五子棋基本玩法-AI實現

五子棋基本玩法-AI實現

參考: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