前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MiniGame 之 扫雷实现

MiniGame 之 扫雷实现

作者头像
用户2615200
发布2021-09-10 10:50:18
4130
发布2021-09-10 10:50:18
举报

本文是 扫雷(MiniGame) 的一个实现样例(使用 Unity/C#),主要以代码为主,辅以一点简单的注解

实现

样例中的扫雷实现主要是两个类型(BombGame 和 BombGrid),下面是完整代码:

代码语言:javascript
复制
// desc bomb game implementation
// maintainer hugoyu

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

public enum BombGridState
{
    // unfixed states
    UnFlag = 0,
    Flag,
    // fixed states
    Empty,
    Number,
    Bomb,
}

public enum BombGameResult
{
    Gaming,
    Success,
    Failed,
}

public class BombGridData
{
    public bool isBomb;
    public BombGridState state;
    public BombGrid grid;

    public BombGridData(bool isBomb_, BombGridState state_, BombGrid grid_)
    {
        isBomb = isBomb_;
        state = state_;
        grid = grid_;
    }
}

public class BombGame : MonoBehaviour
{
    [SerializeField]
    int m_rowCount = 5;
    [SerializeField]
    int m_colCount = 5;
    [SerializeField]
    int m_bombCount = 5;

    [SerializeField]
    float m_gridWidth = 40;
    [SerializeField]
    float m_gridHeight = 40;
    
    [SerializeField]
    bool m_allowGridPropagation = true;
    
    [SerializeField]
    GameObject m_bombGridPrefab;

    [SerializeField]
    GameObject m_successButton;
    [SerializeField]
    GameObject m_failedButton;

    List<BombGridData> m_bombGridDatas = new List<BombGridData>();

    void Awake()
    {
        Debug.Assert(m_bombGridPrefab);
    }

    void Start()
    {
        InitGame();
    }

    #region logic for bomb game create
    public void InitGame()
    {
        CreateGrids();
        InitBombs();
        CheckGame();
    }

    void CreateGrids()
    {
        // clear old grids
        for (int i = 0; i < m_bombGridDatas.Count; ++i)
        {
            if (m_bombGridDatas[i].grid)
            {
                Destroy(m_bombGridDatas[i].grid);
            }
        }
        m_bombGridDatas.Clear();

        // create new grids
        for (int i = 0; i < m_rowCount; ++i)
        {
            for (int j = 0; j < m_colCount; ++j)
            {
                var gridGO = Instantiate(m_bombGridPrefab, new Vector3(j * m_gridWidth, -i * m_gridHeight, 0), Quaternion.identity);
                var index = i * m_colCount + j;
                gridGO.name = "Grid" + index.ToString();
                gridGO.transform.SetParent(transform, false);
                Debug.Assert(gridGO);
                var grid = gridGO.GetComponent<BombGrid>();
                Debug.Assert(grid);
                grid.SetData(this, index);
                grid.SetState(BombGridState.UnFlag);
                m_bombGridDatas.Add(new BombGridData(false, BombGridState.UnFlag, grid));
            }
        }

        // adjust grids position by tweak parent position
        transform.localPosition = new Vector3(-0.5f * (m_colCount - 1) * m_gridWidth, 0.5f * (m_rowCount - 1) * m_gridHeight);
    }

    void InitBombs()
    {
        var bombLeft = m_bombCount;
        while (bombLeft > 0)
        {
            int randIndex = Random.Range(0, m_bombGridDatas.Count);
            if (!m_bombGridDatas[randIndex].isBomb)
            {
                m_bombGridDatas[randIndex].isBomb = true;
                --bombLeft;
            }
        }
    }
    #endregion

    #region logic for check game result
    BombGameResult GetGameResult()
    {
        // check fail first
        for (int i = 0; i < m_bombGridDatas.Count; ++i)
        {
            if (m_bombGridDatas[i].state == BombGridState.Bomb)
            {
                return BombGameResult.Failed;
            }
        }

        // check gaming then
        for (int i = 0; i < m_bombGridDatas.Count; ++i)
        {
            if (m_bombGridDatas[i].state == BombGridState.UnFlag)
            {
                return BombGameResult.Gaming;
            }

            if (!m_bombGridDatas[i].isBomb && m_bombGridDatas[i].state == BombGridState.Flag)
            {
                return BombGameResult.Gaming;
            }
        }

        // success
        return BombGameResult.Success;
    }

    void CheckGame()
    {
        var gameResult = GetGameResult();
        switch (gameResult)
        {
            case BombGameResult.Success:
                m_successButton.gameObject.SetActive(true);
                m_failedButton.gameObject.SetActive(false);
                break;
            case BombGameResult.Failed:
                m_successButton.gameObject.SetActive(false);
                m_failedButton.gameObject.SetActive(true);
                break;
            default:
                m_successButton.gameObject.SetActive(false);
                m_failedButton.gameObject.SetActive(false);
                break;
        }
    }
    #endregion

    #region logic for get grid around bomb num
    int CheckGridInternal(int bombGridRow, int bombGridCol)
    {
        if (bombGridRow >= 0 && bombGridRow < m_rowCount &&
            bombGridCol >= 0 && bombGridCol < m_colCount)
        {
            int bombGridIndex = bombGridRow * m_colCount + bombGridCol;
            return m_bombGridDatas[bombGridIndex].isBomb ? 1 : 0;
        }

        return 0;
    }

    int CheckGrid(int bombGridIndex)
    {
        int num = 0;

        int row = bombGridIndex / m_colCount;
        int col = bombGridIndex % m_colCount;

        num += CheckGridInternal(row - 1, col - 1);
        num += CheckGridInternal(row - 1, col);
        num += CheckGridInternal(row - 1, col + 1);
        num += CheckGridInternal(row, col - 1);
        num += CheckGridInternal(row, col + 1);
        num += CheckGridInternal(row + 1, col - 1);
        num += CheckGridInternal(row + 1, col);
        num += CheckGridInternal(row + 1, col + 1);

        return num;
    }
    #endregion

    #region logic for grid operations
    void PropagationGridRecur(int row, int col)
    {
        int bombGridIndex = row * m_colCount + col;
        if (bombGridIndex >= 0 && bombGridIndex < m_bombGridDatas.Count)
        {
            var data = m_bombGridDatas[bombGridIndex];
            if (data.state == BombGridState.UnFlag)
            {
                if (!data.isBomb)
                {
                    var num = CheckGrid(bombGridIndex);
                    data.state = num > 0 ? BombGridState.Number : BombGridState.Empty;
                    Debug.Assert(data.grid);
                    data.grid.SetState(data.state, num);

                    if (num <= 0)
                    {
                        // when num <= 0, keep propagation
                        PropagationGridRecur(row - 1, col - 1);
                        PropagationGridRecur(row - 1, col);
                        PropagationGridRecur(row - 1, col + 1);
                        PropagationGridRecur(row, col - 1);
                        PropagationGridRecur(row, col + 1);
                        PropagationGridRecur(row + 1, col - 1);
                        PropagationGridRecur(row + 1, col);
                        PropagationGridRecur(row + 1, col + 1);
                    }
                }
            }
        }
    }

    void PropagationGrid(int bombGridIndex)
    {
        if (m_allowGridPropagation)
        {
            int row = bombGridIndex / m_colCount;
            int col = bombGridIndex % m_colCount;

            PropagationGridRecur(row - 1, col - 1);
            PropagationGridRecur(row - 1, col);
            PropagationGridRecur(row - 1, col + 1);
            PropagationGridRecur(row, col - 1);
            PropagationGridRecur(row, col + 1);
            PropagationGridRecur(row + 1, col - 1);
            PropagationGridRecur(row + 1, col);
            PropagationGridRecur(row + 1, col + 1);
        }
    }

    public void ClickGrid(int bombGridIndex)
    {
        if (bombGridIndex >= 0 && bombGridIndex < m_bombGridDatas.Count)
        {
            var data = m_bombGridDatas[bombGridIndex];
            if (data.state == BombGridState.UnFlag || data.state == BombGridState.Flag)
            {
                if (data.isBomb)
                {
                    data.state = BombGridState.Bomb;
                    Debug.Assert(data.grid);
                    data.grid.SetState(data.state);
                }
                else
                {
                    var num = CheckGrid(bombGridIndex);
                    data.state = num > 0 ? BombGridState.Number : BombGridState.Empty;
                    Debug.Assert(data.grid);
                    data.grid.SetState(data.state, num);

                    if (num <= 0)
                    {
                        // when num <= 0, propagation
                        PropagationGrid(bombGridIndex);
                    }
                }

                CheckGame();
            }
        }
    }

    public void FlagGrid(int bombGridIndex)
    {
        if (bombGridIndex >= 0 && bombGridIndex < m_bombGridDatas.Count)
        {
            var data = m_bombGridDatas[bombGridIndex];
            if (data.state == BombGridState.UnFlag || data.state == BombGridState.Flag)
            {
                if (data.state == BombGridState.UnFlag)
                {
                    data.state = BombGridState.Flag;
                    Debug.Assert(data.grid);
                    data.grid.SetState(data.state);
                }
                else if (data.state == BombGridState.Flag)
                {
                    data.state = BombGridState.UnFlag;
                    Debug.Assert(data.grid);
                    data.grid.SetState(data.state);
                }

                CheckGame();
            }
        }
    }
    #endregion
}
代码语言:javascript
复制
// desc bomb grid implementation
// maintainer hugoyu

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

public class BombGrid : MonoBehaviour
{
    #region display related data
    [SerializeField]
    Text m_text;
    [SerializeField]
    Image m_img;
    [SerializeField]
    Button m_btn;
    #endregion

    #region game related data
    BombGame m_owner;
    int m_index = -1;
    #endregion

    void Awake()
    {
        Debug.Assert(m_text && m_img && m_btn);
        // we use editor to assign callback now
    }

    #region logic for set grid data
    public void SetData(BombGame owner, int index)
    {
        m_owner = owner;
        m_index = index;
    }
    #endregion

    #region logic for set grid state
    public void SetState(BombGridState state, int param = 0)
    {
        switch (state)
        {
            case BombGridState.UnFlag:
                {
                    m_text.text = "";
                    m_img.gameObject.SetActive(false);
                    var colors = m_btn.colors;
                    colors.normalColor = Color.white;
                    colors.highlightedColor = Color.white;
                    m_btn.colors = colors;
                }
                break;
            case BombGridState.Flag:
                {
                    m_text.text = "";
                    m_img.gameObject.SetActive(true);
                    var colors = m_btn.colors;
                    colors.normalColor = Color.white;
                    colors.highlightedColor = Color.white;
                    m_btn.colors = colors;
                }
                break;
            case BombGridState.Empty:
                {
                    m_text.text = "";
                    m_img.gameObject.SetActive(false);
                    var colors = m_btn.colors;
                    colors.normalColor = Color.green;
                    colors.highlightedColor = Color.green;
                    m_btn.colors = colors;
                }
                break;
            case BombGridState.Number:
                {
                    m_text.text = param.ToString();
                    m_img.gameObject.SetActive(false);
                    var colors = m_btn.colors;
                    colors.normalColor = Color.white;
                    colors.highlightedColor = Color.white;
                    m_btn.colors = colors;
                }
                break;
            case BombGridState.Bomb:
                {
                    m_text.text = "";
                    m_img.gameObject.SetActive(false);
                    var colors = m_btn.colors;
                    colors.normalColor = Color.black;
                    colors.highlightedColor = Color.black;
                    m_btn.colors = colors;
                }
                break;
        }
    }
    #endregion

    #region logic for grid operation
    public void OnPointerClick(BaseEventData pointerData)
    {
        var pointerClickData = pointerData as PointerEventData;
        if (pointerClickData != null)
        {
            if (pointerClickData.pointerId == -1)
            {
                // left click
                if (m_owner)
                {
                    m_owner.ClickGrid(m_index);
                }
            }
            else if (pointerClickData.pointerId == -2)
            {
                // right click
                if (m_owner)
                {
                    m_owner.FlagGrid(m_index);
                }
            }
        }
    }
    #endregion
}
注解
  • BombGame 实现游戏的主体逻辑, BombGrid 实现扫雷的格子表现和操作
  • 在一般的程序开发中(不仅仅是游戏开发),逻辑与表现的分离是一种较好的开发原则(MVC 模式是一种相关的体现),如果以上面的代码为例来说的话, BombGrid 的实现应该尽量不要涉及扫雷的实际游戏逻辑(理想情况下应该都由 BombGame 来负责实现)
  • 样例代码中出于简明的原因并未做进一步的抽象,实际开发中我们可以通过接口,基类等方式做进一步的代码解耦
  • BombGame 使用了一维数组存储游戏数据,实际而言是有些反直觉的(同时代码中也涉及了一些相关处理),更符合思维的一种方式是使用多维数组
  • BombGame 中随机布雷的逻辑实际并不能做到雷的均匀分布,这里有编码上的权衡(获得均匀分布的收益和实现均匀分布的代价)
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-07-18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实现
  • 注解
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档