unity 人機五子棋(附程序代碼)

unity2018.4.8項目下載網址:https://pan.baidu.com/s/1JdN62plr433NGb8KN1eCUg

        這兩天交了人工智能的期末大作業,花兩天時間查閱思考算法、編寫優化程序代碼以及製作界面,做了個智能五子棋人機對下系統。思路是結合了求棋盤各點位置的權重與博弈樹的一些改進,運行效果是電腦的水平和普通的人差不多,不過有1%左右的概率落子失誤(不知道是因爲沒想那麼多呢還是因爲想的太多了呢)。速度也挺快,一兩秒就能計算出結果,我看它不是很卡也就沒有再剪枝優化。畢竟是五子棋,不同於象棋圍棋,五子相連就贏了,其實也不用怎麼搜索太多步,差不多黑白來回7次就已經很好了,廣度的話大概4-8個最好。下面是運行界面截圖:

 開發工具是Unity2018.4.8,語言是C#,項目代碼量800行左右,下面是主要類的代碼:

Data類:

using UnityEngine;

public static class Data {
    private static int[,] map = new int[15, 15];
    public static int[,] Map { get => map; set => map = value; }

    public static void ResetMap() {
        for (int i = 0; i < 15; i++) {
            for (int j = 0; j < 15; j++) {
                map[i, j] = 0;
            }
        }
    }
}

GameTree類:

//博弈樹結點
public class GameTree {

    public GameTree[] child;
    public GameTree father;
    public int depth;
    public int posi;
    public int posj;
    public int score;
    public int chess;
    public int solve;

    public override string ToString() {
        string ans = "";
        ans += "深度:" + depth + "\n";
        ans += "位置:" + (posi - 7) + "," + (posj - 7) + "\n";
        ans += "分數:" + score + "\n";
        if (chess == 1) ans += "黑棋\n";
        else if (chess == 2) ans += "白棋\n";
        ans += "隸屬於方案:" + solve;
        return ans;
    }
}

Control類:(主要邏輯類)

using System;
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using UnityEngine.SceneManagement;

//狀態枚舉
public enum State {
    Ready,
    Black,
    White,
    BlackWin,
    WhiteWin
}

public struct ScorePos {
    public int score;
    public int posi;
    public int posj;
}

//ScorePos類比較
class ScorePosComparer : IComparer<ScorePos> {
    public int Compare(ScorePos x, ScorePos y) {
        if (x.score < y.score) return 1;    //從大到小排序
        else if (x.score > y.score) return -1;
        else return 0;
    }
}

public class Control : MonoBehaviour {

    #region 變量
    public Transform BlackChess, WhiteChess;    //棋子
    public GameObject Frame;                    //方框
    public GameObject Float;                    //遊戲結束彈窗
    public Text Message;                        //消息
    public GameObject BlackIcon, WhiteIcon;     //棋子圖標
    public GameObject StartButton, StartButton1;//開始按鈕
    public Text BlackTime, WhiteTime;           //黑、白方剩餘時間
    public Toggle Mode;                         //電腦模式
    private float computerTime;                 //電腦思考時間
    private int totalTime;                      //總時間
    private State state;                        //遊戲狀態
    private float blackCount, whiteCount;       //雙方的計時
    private float thinkCount;                   //思考時間計時
    private bool frameBig;                      //方框變化方向
    private bool ComputerMode;                  //電腦智能模式
    private int width = 5;  //每層向下拓展的寬度
    private int depth = 7;  //總層數(必須是奇數)
    ScorePosComparer compare;   //比較規則
    #endregion

    private void Start() {
        Data.ResetMap();
        computerTime = 0.19f;
        totalTime = 1800;
        ComputerMode = false;
        state = State.Ready;
        blackCount = whiteCount = 0;
        BlackTime.text = GetTime(blackCount);
        WhiteTime.text = GetTime(whiteCount);
        thinkCount = 0;
        frameBig = true;
        compare = new ScorePosComparer();
    }

    private void Update() {
        if (Input.GetKeyDown(KeyCode.Space)) {
            OnClickReset();
        }
        if (Mode.isOn != ComputerMode) {
            ComputerMode = Mode.isOn;
        }
        if (state == State.Black) {
            //玩家點擊下棋
            blackCount += Time.deltaTime;
            BlackTime.text = GetTime(blackCount);
            if (Input.GetMouseButtonDown(0)) {  //按下
                if (PutChess(Input.mousePosition, BlackChess)) {    //可下棋
                    state = State.White;
                    if (CheckWin()) {   //是否勝利
                        state = State.BlackWin;
                    }
                }
            }
            UpdateFrame();
        }
        else if (state == State.White) {
            //電腦下棋
            whiteCount += Time.deltaTime;
            thinkCount += Time.deltaTime;
            if (thinkCount >= computerTime) {   //等待時間
                //電腦計算
                thinkCount = 0;
                if (Calculate(WhiteChess)) {    //有解
                    if (CheckWin()) {   //是否勝利
                        state = State.WhiteWin;
                    }
                    else {
                        state = State.Black;
                    }
                }
                else {
                    state = State.BlackWin;
                }
                whiteCount += 2.5f;    //算法運行時間補償
            }
            WhiteTime.text = GetTime(whiteCount);
            UpdateFrame();
        }
        else if (state == State.BlackWin) {
            //玩家勝
            Float.SetActive(true);
            Message.text = "你贏了!";
            Message.color = new Color(0, 1, 0, 1);
            WhiteIcon.SetActive(false);
        }
        else if (state == State.WhiteWin) {
            //電腦勝
            Float.SetActive(true);
            Message.text = "你輸了!";
            Message.color = new Color(1, 0, 0, 1);
            BlackIcon.SetActive(false);
        }
    }

    //生成棋
    private void CreateChess(int i, int j, Transform c) {
        if (c.name == "BlackChess") {
            Data.Map[i, j] = 1;
            BlackIcon.SetActive(false);
            WhiteIcon.SetActive(true);
        }
        if (c.name == "WhiteChess") {
            Data.Map[i, j] = 2;
            WhiteIcon.SetActive(false);
            BlackIcon.SetActive(true);
        }
        float a = 1.32f;
        Vector3 pos = new Vector3();
        pos.x = (i - 7) * a;
        pos.y = (j - 7) * a;
        pos.z = 1;
        Instantiate(c, pos, new Quaternion());
        Frame.SetActive(true);
        Frame.transform.position = pos;
    }

    //玩家下棋
    private bool PutChess(Vector2 pos, Transform c) {
        Vector2 worldPos = Camera.main.ScreenToWorldPoint(pos);
        worldPos /= 1.32f;
        worldPos += new Vector2(7, 7);
        int i = Mathf.RoundToInt(worldPos.x);
        int j = Mathf.RoundToInt(worldPos.y);
        if (i < 0 || j < 0 || i > 14 || j > 14) return false;   //出界
        if (Data.Map[i, j] != 0) return false;      //已有棋子
        CreateChess(i, j, c);
        return true;
    }

    //電腦計算
    private bool Calculate(Transform c) {
        int[,] blackScore = new int[15, 15];
        int[,] whiteScore = new int[15, 15];
        int maxScore = -1;
        int posi = 0, posj = 0;
        if (!ComputerMode) {    //非智能模式(計算每點權重)
            for (int i = 0; i < 15; i++) {
                for (int j = 0; j < 15; j++) {
                    if (Data.Map[i, j] == 0) {
                        GetScore(i, j, ref blackScore[i, j], ref whiteScore[i, j], Data.Map);  //引用參數
                        if (maxScore < blackScore[i, j] + whiteScore[i, j]) {
                            maxScore = blackScore[i, j] + whiteScore[i, j];
                            posi = i;
                            posj = j;
                        }
                    }
                }
            }
            if (maxScore != -1) {   //有解
                CreateChess(posi, posj, c);
                return true;
            }
        }
        else {  //智能模式(博弈樹+αβ剪枝尋最優)
            GetAIScore();
            return true;
        }
        return false;
    }

    //檢查是否勝利
    public bool CheckWin() {
        int i = Mathf.RoundToInt((Frame.transform.position.x / 1.32f) + 7); //最後落子位置
        int j = Mathf.RoundToInt((Frame.transform.position.y / 1.32f) + 7);
        int chess = Data.Map[i, j];     //最後落子顏色(1:黑,2:白)
        //橫向檢查
        int a = 0, b = 0;   //兩端連續棋子數量
        for (int k = i - 1; k >= 0; k--) {
            if (Data.Map[k, j] == chess) {
                a++;
            }
            else {
                break;
            }
        }
        for (int k = i + 1; k < 15; k++) {
            if (Data.Map[k, j] == chess) {
                b++;
            }
            else {
                break;
            }
        }
        if (a + b + 1 >= 5) {
            return true;
        }

        //縱向檢查
        a = b = 0;
        for (int k = j - 1; k >= 0; k--) {
            if (Data.Map[i, k] == chess) {
                a++;
            }
            else {
                break;
            }
        }
        for (int k = j + 1; k < 15; k++) {
            if (Data.Map[i, k] == chess) {
                b++;
            }
            else {
                break;
            }
        }
        if (a + b + 1 >= 5) {
            return true;
        }

        //斜左下
        a = b = 0;
        for (int m = i - 1, n = j - 1; m >= 0 && n >= 0; m--, n--) {
            if (Data.Map[m, n] == chess) {
                a++;
            }
            else {
                break;
            }
        }
        for (int m = i + 1, n = j + 1; m < 15 && n < 15; m++, n++) {
            if (Data.Map[m, n] == chess) {
                b++;
            }
            else {
                break;
            }
        }
        if (a + b + 1 >= 5) {
            return true;
        }

        //斜右下
        a = b = 0;
        for (int m = i - 1, n = j + 1; m >= 0 && n < 15; m--, n++) {
            if (Data.Map[m, n] == chess) {
                a++;
            }
            else {
                break;
            }
        }
        for (int m = i + 1, n = j - 1; m < 15 && n >= 0; m++, n--) {
            if (Data.Map[m, n] == chess) {
                b++;
            }
            else {
                break;
            }
        }
        if (a + b + 1 >= 5) {
            return true;
        }
        return false;
    }
    public bool CheckWin(int i, int j, int[,] map) {
        int chess = map[i, j];     //最後落子顏色(1:黑,2:白)
        //橫向檢查
        int a = 0, b = 0;   //兩端連續棋子數量
        for (int k = i - 1; k >= 0; k--) {
            if (map[k, j] == chess) {
                a++;
            }
            else {
                break;
            }
        }
        for (int k = i + 1; k < 15; k++) {
            if (map[k, j] == chess) {
                b++;
            }
            else {
                break;
            }
        }
        if (a + b + 1 >= 5) {
            return true;
        }

        //縱向檢查
        a = b = 0;
        for (int k = j - 1; k >= 0; k--) {
            if (map[i, k] == chess) {
                a++;
            }
            else {
                break;
            }
        }
        for (int k = j + 1; k < 15; k++) {
            if (map[i, k] == chess) {
                b++;
            }
            else {
                break;
            }
        }
        if (a + b + 1 >= 5) {
            return true;
        }

        //斜左下
        a = b = 0;
        for (int m = i - 1, n = j - 1; m >= 0 && n >= 0; m--, n--) {
            if (map[m, n] == chess) {
                a++;
            }
            else {
                break;
            }
        }
        for (int m = i + 1, n = j + 1; m < 15 && n < 15; m++, n++) {
            if (map[m, n] == chess) {
                b++;
            }
            else {
                break;
            }
        }
        if (a + b + 1 >= 5) {
            return true;
        }

        //斜右下
        a = b = 0;
        for (int m = i - 1, n = j + 1; m >= 0 && n < 15; m--, n++) {
            if (map[m, n] == chess) {
                a++;
            }
            else {
                break;
            }
        }
        for (int m = i + 1, n = j - 1; m < 15 && n >= 0; m++, n--) {
            if (map[m, n] == chess) {
                b++;
            }
            else {
                break;
            }
        }
        if (a + b + 1 >= 5) {
            return true;
        }
        return false;
    }

    //計算分數
    private void GetScore(int i, int j, ref int black, ref int white, int[,] map) {

        //若放黑色
        int chess = 1;
        int s1 = 0, s2 = 0, s3 = 0, s4 = 0; //四個方向的棋子的數量
        //橫向檢查
        int a = 0, b = 0;   //兩端連續棋子數量
        for (int k = i - 1; k >= 0; k--) {
            if (map[k, j] == chess) {
                a++;
            }
            else {
                break;
            }
        }
        for (int k = i + 1; k < 15; k++) {
            if (map[k, j] == chess) {
                b++;
            }
            else {
                break;
            }
        }
        s1 = a + b + 1;
        //縱向檢查
        a = b = 0;
        for (int k = j - 1; k >= 0; k--) {
            if (map[i, k] == chess) {
                a++;
            }
            else {
                break;
            }
        }
        for (int k = j + 1; k < 15; k++) {
            if (map[i, k] == chess) {
                b++;
            }
            else {
                break;
            }
        }
        s2 = a + b + 1;
        //斜左下
        a = b = 0;
        for (int m = i - 1, n = j - 1; m >= 0 && n >= 0; m--, n--) {
            if (map[m, n] == chess) {
                a++;
            }
            else {
                break;
            }
        }
        for (int m = i + 1, n = j + 1; m < 15 && n < 15; m++, n++) {
            if (map[m, n] == chess) {
                b++;
            }
            else {
                break;
            }
        }
        s3 = a + b + 1;
        //斜右下
        a = b = 0;
        for (int m = i - 1, n = j + 1; m >= 0 && n < 15; m--, n++) {
            if (map[m, n] == chess) {
                a++;
            }
            else {
                break;
            }
        }
        for (int m = i + 1, n = j - 1; m < 15 && n >= 0; m++, n--) {
            if (map[m, n] == chess) {
                b++;
            }
            else {
                break;
            }
        }
        s4 = a + b + 1;
        black = GetScore(s1, -1) + GetScore(s2, -1) + GetScore(s3, -1) + GetScore(s4, -1);

        //若放白色
        chess = 2;
        s1 = s2 = s3 = s4 = 0;  //四個方向的棋子的數量
        //橫向檢查
        a = b = 0;   //兩端連續棋子數量
        for (int k = i - 1; k >= 0; k--) {
            if (map[k, j] == chess) {
                a++;
            }
            else {
                break;
            }
        }
        for (int k = i + 1; k < 15; k++) {
            if (map[k, j] == chess) {
                b++;
            }
            else {
                break;
            }
        }
        s1 = a + b + 1;
        //縱向檢查
        a = b = 0;
        for (int k = j - 1; k >= 0; k--) {
            if (map[i, k] == chess) {
                a++;
            }
            else {
                break;
            }
        }
        for (int k = j + 1; k < 15; k++) {
            if (map[i, k] == chess) {
                b++;
            }
            else {
                break;
            }
        }
        s2 = a + b + 1;
        //斜左下
        a = b = 0;
        for (int m = i - 1, n = j - 1; m >= 0 && n >= 0; m--, n--) {
            if (map[m, n] == chess) {
                a++;
            }
            else {
                break;
            }
        }
        for (int m = i + 1, n = j + 1; m < 15 && n < 15; m++, n++) {
            if (map[m, n] == chess) {
                b++;
            }
            else {
                break;
            }
        }
        s3 = a + b + 1;
        //斜右下
        a = b = 0;
        for (int m = i - 1, n = j + 1; m >= 0 && n < 15; m--, n++) {
            if (map[m, n] == chess) {
                a++;
            }
            else {
                break;
            }
        }
        for (int m = i + 1, n = j - 1; m < 15 && n >= 0; m++, n--) {
            if (map[m, n] == chess) {
                b++;
            }
            else {
                break;
            }
        }
        s4 = a + b + 1;
        white = GetScore(s1, 1) + GetScore(s2, 1) + GetScore(s3, 1) + GetScore(s4, 1);

    }

    //智能計算分數
    private void GetAIScore() {
        GameTree root = new GameTree();
        root.depth = 1;
        root.chess = 1; //黑的剛走完
        root.score = 0;
        GameTree(Data.Map, root);
        //遞歸求出分數
        UpdateTreeScore(root);
        int ans = 0;
        int maxScore = 0;
        for (int i = 0; i < 0; i++) {
            if (root.child[i].score > maxScore) {
                maxScore = root.child[i].score;
                ans = i;
            }
        }
        int posi = root.child[ans].posi;
        int posj = root.child[ans].posj;
        CreateChess(posi, posj, WhiteChess);
    }

    //博弈樹遞歸
    private void GameTree(int[,] map, GameTree father) {
        ScorePos[] sp = new ScorePos[225];
        //篩選前幾個優良方案
        for (int i = 0; i < 15; i++) {
            for (int j = 0; j < 15; j++) {
                if (map[i, j] == 0) {
                    int m = 0, n = 0;
                    GetScore(i, j, ref m, ref n, map);  //引用參數
                    sp[i * 15 + j].score = m + n;
                    sp[i * 15 + j].posi = i;
                    sp[i * 15 + j].posj = j;
                }
            }
        }
        Array.Sort(sp, compare);
        father.child = new GameTree[width];
        for (int i = 0; i < width; i++) {
            GameTree point = new GameTree();
            point.child = new GameTree[width];
            father.child[i] = point;        //父子關係
            point.father = father;
            point.depth = father.depth + 1; //博弈樹深度
            point.posi = sp[i].posi;        //落子位置
            point.posj = sp[i].posj;
            point.chess = father.chess % 2 + 1; //交替下棋

            //計算分數
            if (father.depth % 2 == 0) {    //偶數層(我方)
                point.score = sp[i].score;
            }
            else {  //奇數層(對手)
                point.score = -sp[i].score;
            }
            if (father.score < -6000 || father.score > 6000) {
                point.score = father.score;    //一旦一方勝利,便沒有必要再算下去(防止局勢扭轉)
            }
            else {
                point.score += father.score;//累加父層
            }

            //更新解決方案
            if (point.depth == 2) { //節點隸屬的解決方案
                point.solve = i;
            }
            else {
                point.solve = father.solve;
            }

            //拓展新地圖
            int[,] map1 = new int[15, 15];
            for (int j = 0; j < 15; j++) {
                for (int k = 0; k < 15; k++) {
                    map1[j, k] = map[j, k];
                }
            }
            map1[point.posi, point.posj] = point.chess;
            if (point.depth < depth) {
                GameTree(map1, point);
            }

            //層數上限,開始收尾
            else {
                int maxScore = -1;
                GameTree point1 = new GameTree();
                for (int i1 = 0; i1 < 15; i1++) {
                    for (int j1 = 0; j1 < 15; j1++) {
                        if (map1[i1, j1] == 0) {
                            int m = 0, n = 0;
                            GetScore(i1, j1, ref m, ref n, map1);  //引用參數
                            if (maxScore < m + n) {
                                maxScore = m + n;
                            }
                        }
                    }
                }

                //更新point分數
                if (point.score > 6000 || point.score < -6000) {
                    point1.score = point.score;
                }
                else {
                    point1.score = point.score + maxScore;
                }
                point.score = point1.score;
                //print(point.ToString());
            }
        }
    }

    //更新樹所有節點的分數
    private int UpdateTreeScore(GameTree p) {
        if (p.depth == depth) {    //末端
            return p.score;
        }
        if (p.depth % 2 == 0) {  //偶數層,求最小子分數
            int minScore = 100000;
            for (int i = 0; i < width; i++) {
                if (p.child[i].score < minScore) {
                    minScore = UpdateTreeScore(p.child[i]);
                }
            }
            return minScore;
        }
        else {  //奇數層,求最大子分數
            int maxScore = -100000;
            for (int i = 0; i < width; i++) {
                if (p.child[i].score > maxScore) {
                    maxScore = UpdateTreeScore(p.child[i]);
                }
            }
            return maxScore;
        }
    }

    //分數計算公式
    public int GetScore(int x, int m) {
        int ans = 0;
        if (m == 1) {
            if (x == 1) ans += 1;
            else if (x == 2) ans += 10;
            else if (x == 3) ans += 100;
            else if (x == 4) ans += 1000;
            else ans += 10000;
        }
        else if (m == -1) {
            if (x == 1) ans += 1;
            else if (x == 2) ans += 8;
            else if (x == 3) ans += 64;
            else if (x == 4) ans += 512;
            else ans += 4096;
        }
        return ans;
    }

    //方框變大變小
    private void UpdateFrame() {
        if (frameBig) {
            Frame.transform.localScale += new Vector3(0.003f, 0.003f, 0);
        }
        else {
            Frame.transform.localScale -= new Vector3(0.003f, 0.003f, 0);
        }
        if (Frame.transform.localScale.x > 0.7f) {
            frameBig = false;
        }
        if (Frame.transform.localScale.x < 0.5f) {
            frameBig = true;
        }
    }

    //重置
    public void OnClickReset() {
        SceneManager.LoadScene(0);
    }

    //先手遊戲
    public void OnClickStart() {
        state = State.Black;
        StartButton.SetActive(false);
        StartButton1.SetActive(false);
        BlackIcon.SetActive(true);
    }

    //後手遊戲
    public void OnClickStart1() {
        CreateChess(7, 7, WhiteChess);
        OnClickStart();
    }

    //獲取時間
    private string GetTime(float n) {
        float n1 = totalTime - n;
        if (n1 <= 0) {  //時間到
            if (state == State.Black) {
                state = State.WhiteWin;
            }
            if (state == State.White) {
                state = State.BlackWin;
            }
            return "00:00.00";
        }
        string min, sec, msec;
        if ((int)n1 / 60 < 10) {
            min = "0";
        }
        else {
            min = "";
        }
        min += (int)n1 / 60 + "";
        if ((int)n1 % 60 < 10) {
            sec = "0";
        }
        else {
            sec = "";
        }
        sec += (int)n1 % 60 + "";
        if ((int)(n1 * 100) % 100 < 10) {
            msec = "0";
        }
        else {
            msec = "";
        }
        msec += (int)(n1 * 100) % 100 + "";
        return min + ":" + sec + "." + msec;
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章