6

MiniGame 之 扫雷实现

 2 years ago
source link: https://blog.csdn.net/tkokof1/article/details/118878757
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

MiniGame 之 扫雷实现

同时被 2 个专栏收录
140 篇文章 0 订阅
65 篇文章 0 订阅

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

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

// 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]
    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
    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);
                }

                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
}
// 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 中随机布雷的逻辑实际并不能做到雷的均匀分布,这里有编码上的权衡(获得均匀分布的收益和实现均匀分布的代价)

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK