unity 人机五子棋(附程序代码)

25 篇文章 6 订阅
6 篇文章 0 订阅

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;
    }

}

 

  • 11
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值