PoorMonk

五子棋游戏笔记

五子棋游戏实现

1 导入资源图片。拖入棋盘后,将棋盘的大小进行缩放,使每一格间距一米(对齐背景虚线即可),方便鼠标点击时落子;黑白子也适当缩放成合适的大小。如下图:

2 点击棋盘上的位置落子

private void CreateChess(GameObject chess, int[] pos)
{
    GameObject go = Instantiate(chess, new Vector3(pos[0] - 7, pos[1] - 7), Quaternion.identity);
    go.transform.SetParent(m_chessParent);
    m_grid[pos[0], pos[1]] = (int)m_turn;
    m_chessStack.Push(go);
}

3 判断五子相连

private bool CheckOneLine(int[] pos, int[] offset)
{       
    int iTotal = 1;
    
	//从落点的右边开始扫描,直到不是同样颜色就停止
    for (int x = pos[0] + offset[0], y = pos[1] + offset[1]; x <= 14 && 0 <= x && y <= 14 && 0 <= y; x += offset[0], y += offset[1])
    {
        if (m_grid[x, y] == (int)m_turn)
        {
            iTotal++;
        }
        else
        {
            break;
        }          
    }

	//再接着扫描左边,也直到不是同样颜色就停止
    for (int x = pos[0] - offset[0], y = pos[1] - offset[1]; x <= 14 && 0 <= x && y <= 14 && 0 <= y; x -= offset[0], y -= offset[1])
    {
        if (m_grid[x, y] == (int)m_turn)
        {
            iTotal++;
        }
        else
        {
            break;
        }
    }
    
    if (iTotal >= 5)
    {
        return true;
    }
    return false;
}

4 加入悔棋操作

//取出保存在栈顶的棋子,将对应位置清零之后销毁
private void PopChess()
{
    GameObject go = m_chessStack.Pop();
    m_grid[(int)(go.transform.position.x + 7), (int)(go.transform.position.y + 7)] = 0;
    Destroy(go.gameObject);        
}

5 用一个红点标识刚落的子

void Update ()
{
    if (ChessBoard.Instance.m_chessStack.Count > 0)
    {
        transform.position = ChessBoard.Instance.m_chessStack.Peek().transform.position; //跟随栈顶棋子的位置
    }
}

AI

思路:不同的棋子组合有不同的分数,每次落子时考虑棋盘上每一个空位,根据最佳得分位置落子。

1 AIOne 初级难度

protected Dictionary<string, float> m_toScore = new Dictionary<string, float>(); //用来保存每一种组合的分数
protected float[,] m_score = new float[15, 15]; //用来保存每一个位置的分数

//初始化每一种组合的得分比重
protected override void Start()
{
    m_toScore.Add("_aa", 50);
    m_toScore.Add("aa_", 50);
    m_toScore.Add("_aa_", 100);

    m_toScore.Add("_aaa", 500);
    m_toScore.Add("aaa_", 500);
    m_toScore.Add("_aaa_", 1000);

    m_toScore.Add("_aaaa", 5000);
    m_toScore.Add("aaaa_", 5000);
    m_toScore.Add("_aaaa_", 10000);

    m_toScore.Add("_aaaaa", float.MaxValue);
    m_toScore.Add("aaaaa_", float.MaxValue);
    m_toScore.Add("aaaaa", float.MaxValue);
    m_toScore.Add("_aaaaa_", float.MaxValue);  
}

//扫描每一行,碰到己方的子,加a,空位加_后换方向扫描,左右两边都扫描完后对比扫描出的字符处是否存在表中,有则在该位置添加分数
protected virtual void CheckOneLine(int[] pos, int[] offset, int iChess)
{
    string str = "a";
    for (int x = pos[0] + offset[0], y = pos[1] + offset[1]; x <= 14 && 0 <= x && y <= 14 && 0 <= y; x += offset[0], y += offset[1])
    {
        if (ChessBoard.Instance.m_grid[x, y] == iChess)
        {
            str += "a";
        }
        else if (ChessBoard.Instance.m_grid[x, y] == 0)
        {
            str += "_";
            break;
        }
        else
        {
            break;
        }
    }

    for (int x = pos[0] - offset[0], y = pos[1] - offset[1]; x <= 14 && 0 <= x && y <= 14 && 0 <= y; x -= offset[0], y -= offset[1])
    {
        if (ChessBoard.Instance.m_grid[x, y] == iChess)
        {
            str = "a" + str ;
        }
        else if (ChessBoard.Instance.m_grid[x, y] == 0)
        {
            str = "_" + str;
            break;
        }
        else
        {
            break;
        }
    }  
    if (m_toScore.ContainsKey(str))
    {
        m_score[pos[0], pos[1]] += m_toScore[str];
    }
}

//不仅要扫描己方的最佳位置,还要扫描对方的最佳位置,才能可攻可守
public void SetScore(int[] pos)
{
    m_score[pos[0], pos[1]] = 0;  //扫描前将该位置分数清零
    CheckOneLine(pos, new int[2] { 1, 0 }, 1);
    CheckOneLine(pos, new int[2] { 0, 1 }, 1);
    CheckOneLine(pos, new int[2] { 1, 1 }, 1);
    CheckOneLine(pos, new int[2] { 1, -1}, 1);

    CheckOneLine(pos, new int[2] { 1, 0 }, 2);
    CheckOneLine(pos, new int[2] { 0, 1 }, 2);
    CheckOneLine(pos, new int[2] { 1, 1 }, 2);
    CheckOneLine(pos, new int[2] { 1, -1 }, 2);
}

//落子时,扫描棋盘每个空位,取得分最高的空位落子
public override void PlayChess()
{
    if (ChessBoard.Instance.m_chessStack.Count == 0)
    {
        if (ChessBoard.Instance.PlayChess(new int[2] { 7, 7}))
        {
            ChessBoard.Instance.m_timer = 0.0f;
        }
        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.m_grid[i, j] == 0)
            {
                SetScore(new int[2] { i, j });
                if (m_score[i, j] >= maxScore)
                {
                    maxPos[0] = i;
                    maxPos[1] = j;
                    maxScore = m_score[i, j];
                }
            }
        }
    }
    if (ChessBoard.Instance.PlayChess(maxPos))
    {
        ChessBoard.Instance.m_timer = 0.0f;
    }
}

2 AITwo 中级难度(继承自AIOne)

扩展棋子组合
protected override void Start()
{
    m_toScore.Add("aa___", 100);                      //眠二
    m_toScore.Add("a_a__", 100);
    m_toScore.Add("___aa", 100);
    m_toScore.Add("__a_a", 100);
    m_toScore.Add("a__a_", 100);
    m_toScore.Add("_a__a", 100);
    m_toScore.Add("a___a", 100);

    m_toScore.Add("__aa__", 500);                     //活二 "_aa___"
    m_toScore.Add("_a_a_", 500);
    m_toScore.Add("_a__a_", 500);

    m_toScore.Add("_aa__", 500);
    m_toScore.Add("__aa_", 500);

    m_toScore.Add("a_a_a", 1000);                     
    m_toScore.Add("aa__a", 1000);
    m_toScore.Add("_aa_a", 1000);
    m_toScore.Add("a_aa_", 1000);
    m_toScore.Add("_a_aa", 1000);
    m_toScore.Add("aa_a_", 1000);
    m_toScore.Add("aaa__", 1000);                     //眠三

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

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

    m_toScore.Add("a_aaa", 15000);                    //冲四
    m_toScore.Add("aaa_a", 15000);                    //冲四
    m_toScore.Add("_aaaa", 15000);                    //冲四
    m_toScore.Add("aaaa_", 15000);                    //冲四
    m_toScore.Add("aa_aa", 15000);                    //冲四        

    m_toScore.Add("_aaaa_", 1000000);                 //活四

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

//扫描时先从左边扫起,碰到己方则在左边加a,碰到空位则在左边加_,如果右边能扫就换到右边扫描,碰到敌方则左边扫描结束;
//右方扫描碰到己方在右边加a,碰到空位右边加_,如果左边还能扫则换到左边,不能则继续,碰到敌方则右边扫描结束;
//如果左右都不能扫或者已扫描棋子数达到七个则停止扫描
protected override void CheckOneLine(int[] pos, int[] offset, int iChess)
{
    int iAllNum = 1;
    bool lStop = false, rStop = false, lFirst = true;
    string str = "a";
    int rx = pos[0] + offset[0], ry = pos[1] + offset[1];
    int lx = pos[0] - offset[0], ly = pos[1] - offset[1];
    while (iAllNum < 7 && (!lStop || !rStop))
    {
        if (lFirst)
        {
            if (lx <= 14 && 0 <= lx && ly <= 14 && 0 <= ly && !lStop)
            {
                if (ChessBoard.Instance.m_grid[lx, ly] == iChess)
                {
                    iAllNum++;
                    str = "a" + str;
                }
                else if (ChessBoard.Instance.m_grid[lx, ly] == 0)
                {
                    iAllNum++;
                    str = "_" + str;
                    if (!rStop)
                    {
                        lFirst = false;
                    }
                }
                else
                {
                    if (!rStop)
                    {
                        lFirst = false;
                    }
                    lStop = true;
                }
                lx -= offset[0];
                ly -= offset[1];
            }
            else
            {
                if (!rStop)
                {
                    lFirst = false;
                }
                lStop = true;
            }
        }
        else
        {
            if (rx <= 14 && 0 <= rx && ry <= 14 && 0 <= ry && !lFirst && !rStop)
            {
                if (ChessBoard.Instance.m_grid[rx, ry] == iChess)
                {
                    iAllNum++;
                    str += "a";
                }
                else if (ChessBoard.Instance.m_grid[rx, ry] == 0)
                {
                    iAllNum++;
                    str += "_";
                    if (!lStop)
                    {
                        lFirst = true;
                    }
                }
                else
                {
                    if (!lStop)
                    {
                        lFirst = true;
                    }
                    rStop = true;
                }
                rx += offset[0];
                ry += offset[1];
            }
            else
            {
                if (!lStop)
                {
                    lFirst = true;
                }
                rStop = true;
            }
        }
    }

    string cmpStr = "";
    foreach (var keyInfo in m_toScore.Keys)
    {
        if (str.Contains(keyInfo))
        {
            if (cmpStr == "")
            {
                cmpStr = keyInfo;
            }
            else
            {
				//扫描后的字符处可能包含于多个key中,取分数大的那一个
                if (m_toScore[keyInfo] > m_toScore[cmpStr])
                {
                    cmpStr = keyInfo;
                }
            }
        }
    }
    if (cmpStr != "")
    {
        m_score[pos[0], pos[1]] += m_toScore[cmpStr];
    }
}

3 AIThree 高级难度(与AITwo一样初始化棋子组合表)

int m_deep = 3;
public class MiniMaxNode //极大极小节点
{
    public int m_chess;
    public int[] m_pos;
    public List<MiniMaxNode> m_child;
    public float m_value;
}

public override void PlayChess()
{
    if (ChessBoard.Instance.m_isGameOver)
    {
        return;
    }
    if (ChessBoard.Instance.m_chessStack.Count == 0)
    {
        if (ChessBoard.Instance.PlayChess(new int[2] { 7, 7 }))
        {
            ChessBoard.Instance.m_timer = 0;
        }
        return;
    }

    MiniMaxNode node = null;
    foreach (var item in GetList(ChessBoard.Instance.m_grid, (int)m_playerType, true))
    {
        CreateTree(item, (int[,])ChessBoard.Instance.m_grid.Clone(), m_deep, false);
        float a = float.MinValue;
        float b = float.MaxValue;
        item.m_value += AlphaBeta(item, m_deep, false, a, b);
        if (node == null)
        {
            node = item;
        }
        else
        {
            if (node.m_value < item.m_value)
            {
                node = item;
            }
        }
    }
    ChessBoard.Instance.PlayChess(node.m_pos);
}

// 获取四个极大极小节点值
List<MiniMaxNode> GetList(int[,] grid, int iChess, bool IsMySelf)
{
    List<MiniMaxNode> nodeList = new List<MiniMaxNode>();
    MiniMaxNode node;
    for (int i = 0; i < 15; i++)
    {
        for (int j = 0; j < 15; j++)
        {
            int[] pos = new int[2] { i, j };
            if (grid[pos[0], pos[1]] != 0)
            {
                continue;
            }

            node = new MiniMaxNode();
            node.m_chess = iChess;
            node.m_pos = pos;
            if (IsMySelf)
            {
                node.m_value = GetScore(grid, pos);
            }
            else
            {
                node.m_value = -GetScore(grid, pos);
            }
            

            if (nodeList.Count < 4)
            {
                nodeList.Add(node);
            }
            else
            {
                foreach (var item in nodeList)
                {
                    if (IsMySelf)
                    {
                        if (node.m_value > item.m_value)
                        {
                            nodeList.Remove(item);
                            nodeList.Add(node);
                            break;
                        }
                    }
                    else
                    {
                        if (node.m_value < item.m_value)
                        {
                            nodeList.Remove(item);
                            nodeList.Add(node);
                            break;
                        }
                    }
                }
            }
        }
    }

    return nodeList;
}

// 为每个节点构建一棵树
public void CreateTree(MiniMaxNode node, int[,] grid, int deep, bool IsMySelf)
{
    if (deep == 0 || node.m_value == float.MaxValue)
    {
        return;
    }

    grid[node.m_pos[0], node.m_pos[1]] = node.m_chess;
    node.m_child = GetList(grid, node.m_chess, !IsMySelf);
    foreach (var item in node.m_child)
    {
        CreateTree(item, (int[,])grid.Clone(), deep - 1, !IsMySelf);
    }
}

// 为每棵树进行AlphaBeta剪枝
public float AlphaBeta(MiniMaxNode node, int deep, bool IsMySelf, float alpha, float beta)
{
    if (deep == 0 || node.m_value == float.MaxValue || node.m_value == float.MinValue)
    {
        return node.m_value;
    }

    if (IsMySelf)
    {
        foreach (var child in node.m_child)
        {
            alpha = Mathf.Max(alpha, AlphaBeta(child, deep - 1, !IsMySelf, alpha, beta));
            if (alpha >= beta)
            {
                return alpha;
            }
        }
        return alpha;
    }
    else
    {
        foreach (var child in node.m_child)
        {
            beta = Mathf.Min(beta, AlphaBeta(child, deep - 1, !IsMySelf, alpha, beta));
            if (alpha >= beta)
            {
                return beta;
            }
        }
        return beta;
    }
}

public float GetScore(int[,] grid, int[] pos)
{
    float score = 0;
    score += CheckOneLine(grid, pos, new int[2] { 1, 0 }, 1);
    score += CheckOneLine(grid, pos, new int[2] { 0, 1 }, 1);
    score += CheckOneLine(grid, pos, new int[2] { 1, 1 }, 1);
    score += CheckOneLine(grid, pos, new int[2] { 1, -1 }, 1);

    score += CheckOneLine(grid, pos, new int[2] { 1, 0 }, 2);
    score += CheckOneLine(grid, pos, new int[2] { 0, 1 }, 2);
    score += CheckOneLine(grid, pos, new int[2] { 1, 1 }, 2);
    score += CheckOneLine(grid, pos, new int[2] { 1, -1 }, 2);

    return score;
}

// 与AITwo类似的扫描方法
float CheckOneLine(int[,] grid, int[] pos, int[] offset, int iChess)
{
    float score = 0.0f;
    int iAllNum = 1;
    bool lStop = false, rStop = false, lFirst = true;
    string str = "a";
    int rx = pos[0] + offset[0], ry = pos[1] + offset[1];
    int lx = pos[0] - offset[0], ly = pos[1] - offset[1];
    while (iAllNum < 7 && (!lStop || !rStop))
    {
        if (lFirst)
        {
            if (lx <= 14 && 0 <= lx && ly <= 14 && 0 <= ly && !lStop)
            {
                if (grid[lx, ly] == iChess)
                {
                    iAllNum++;
                    str = "a" + str;
                }
                else if (grid[lx, ly] == 0)
                {
                    iAllNum++;
                    str = "_" + str;
                    if (!rStop)
                    {
                        lFirst = false;
                    }
                }
                else
                {
                    if (!rStop)
                    {
                        lFirst = false;
                    }
                    lStop = true;
                }
                lx -= offset[0];
                ly -= offset[1];
            }
            else
            {
                if (!rStop)
                {
                    lFirst = false;
                }
                lStop = true;
            }
        }
        else
        {
            if (rx <= 14 && 0 <= rx && ry <= 14 && 0 <= ry && !lFirst && !rStop)
            {
                if (grid[rx, ry] == iChess)
                {
                    iAllNum++;
                    str += "a";
                }
                else if (grid[rx, ry] == 0)
                {
                    iAllNum++;
                    str += "_";
                    if (!lStop)
                    {
                        lFirst = true;
                    }
                }
                else
                {
                    if (!lStop)
                    {
                        lFirst = true;
                    }
                    rStop = true;
                }
                rx += offset[0];
                ry += offset[1];
            }
            else
            {
                if (!lStop)
                {
                    lFirst = true;
                }
                rStop = true;
            }
        }
    }

    string cmpStr = "";
    foreach (var keyInfo in m_toScore.Keys)
    {
        if (str.Contains(keyInfo))
        {
            if (cmpStr == "")
            {
                cmpStr = keyInfo;
            }
            else
            {
                if (m_toScore[keyInfo] > m_toScore[cmpStr])
                {
                    cmpStr = keyInfo;
                }
            }
        }
    }
    if (cmpStr != "")
    {
        score += m_toScore[cmpStr];
    }
    return score;
}

网络通信

引用UnityEngine.Networking;

注意点

1 由服务器生成的物体必须具有Network Identity组件

private void CreateChess(GameObject chess, int[] pos)
{
    GameObject go = Instantiate(chess, new Vector3(pos[0] - 7, pos[1] - 7), Quaternion.identity);
    go.transform.SetParent(m_chessParent);
    m_grid[pos[0], pos[1]] = (int)m_turn;
    m_chessStack.Push(go);
    NetworkServer.Spawn(go); //由服务器生成棋子
}

2 客户端需要同步的值必须加[SyncVar]属性,比如:

[SyncVar] public PlayerType m_turn;
[SyncVar] public int m_playerNum = 0;
[SyncVar] public bool m_isGameOver = true;

3 由客户端发送命令,服务器端执行的函数前必须加[Command]属性,且函数必须以Cmd开头,比如:

[Command]
public void CmdChess(Vector2 pos)
{
    if (NetChessBoard.Instance.PlayChess(new int[2] { (int)(pos.x + 7.5f), (int)(pos.y + 7.5f) }))
    {
        NetChessBoard.Instance.m_timer = 0.0f;
    }
}

知识点

1 对于鼠标点击的超出棋盘范围的点,可以通过调用Mathf.Clamp函数来取得给定范围内的值

2 删除预制体后对象名字会变成红色,选中物体后在GameObject菜单下点击Break Prefab Instance即可恢复正常的颜色

3 调用EventSystem.IsPointerOverGameObject,鼠标点在UI对象上(例如Button)不响应其它操作(即不会生成棋子)

public class MouseExample : MonoBehaviour
{
    void Update()
    {
        // Check if the left mouse button was clicked
        if (Input.GetMouseButtonDown(0))
        {
            // Check if the mouse was clicked over a UI element
            if (EventSystem.current.IsPointerOverGameObject())
            {
                Debug.Log("Clicked on the UI");
            }
        }
    }
}

4 设置玩家列表,不同的游戏模式设置不同的玩家

在UI上设置好对应的index后再进入游戏
public void SetPlayer1(int index)
{
    PlayerPrefs.SetInt("Player1", index);
}

public void SetPlayer2(int index)
{
    PlayerPrefs.SetInt("Player2", index);
}

private void Awake()
{
    int iPlayer1 = PlayerPrefs.GetInt("Player1");
    int iPlayer2 = PlayerPrefs.GetInt("Player2");
    
    for (int i = 0; i < m_playerList.Count; i++)
    {
        if (i == iPlayer1)
        {
            m_playerList[i].m_playerType = PlayerType.BLACK;
        }
        else if (i == iPlayer2)
        {
            m_playerList[i].m_playerType = PlayerType.WHITE;
        }
        else
        {
            m_playerList[i].m_playerType = PlayerType.WATCH;
        }
    }
}

完整项目源码:https://github.com/PoorMonk/Gomoku.git

posted on 2018-07-20 14:41  PoorMonk  阅读(54)  评论(0)    收藏  举报

导航