C#实现的六边形的蜂窝型扫雷游戏

六边形的扫雷中,常规扫雷的方格变成六边形的蜂窝,一个蜂窝直接相邻的有左、左上、右上、右、右下、左下6个蜂窝。

一般的扫雷游戏中,一个方格直接相邻的只有上、下、左、右4个方格,对应二位数组行列下标加减1即为其相邻的放个。

去年夏天一个哥们让我帮忙看看六边形的扫雷如何实现——把常规扫雷的方格变成六边形的蜂窝,一个蜂窝直接相邻的有左、左上、右上、右、右下、左下6个蜂窝。一开始很纠结用什么数据结构,如果用纵横交错的链表感觉还略微有点纠结;实际上一看图片后很简单了,底层的数据结构依然可以是二位数组,并且可以很容易观察出一个蜂窝的相邻蜂窝的数组下标满足的关系(下图是别处扒来的,对应相邻是上、右上、右下、下、左下、左上,相当于旋转了90°)。


实现类

最关键的是IEnumerable<Position> GetAround(Position p)方法。这个方法定义了从一个蜂窝直接相邻的蜂窝的二维坐标的取法。如果这里返回上、下、左、右4个单元格,就可以得到常规的扫雷游戏。

using System;
using System.Collections.Generic;

namespace MineSweeper
{
    enum State
    {
        Unknown = -1,
        Mine0 = 0,
        Mine1 = 1,
        Mine2 = 2,
        Mine3 = 3,
        Mine4 = 4,
        Mine5 = 5,
        Mine6 = 6,
    }

    struct Position
    {
        public int X { get; set; }
        public int Y { get; set; }
        public Position(int x, int y)
        {
            X = x;
            Y = y;
        }
    }

    class HexagonMineSweeper
    {
        public int Width { get; protected set; }
        public int Height { get; protected set; }
        public bool[,] Mines { get; protected set; }
        public State[,] States { get; protected set; }
        public bool Win
        {
            get
            {
                for (int w = 0; w < Width; w++)
                    for (int h = 0; h < Height; h++)
                    {
                        if (!Mines[w, h] && States[w, h] == State.Unknown) return false;
                    }
                return true;
            }
        }

        /// <summary>
        /// 初始化一个大小为width * height的游戏。不设置地雷。
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public HexagonMineSweeper(int width, int height)
        {
            Width = width;
            Height = height;
            Mines = new bool[width, height];
            States = new State[width, height];
            ResetStates();
        }

        /// <summary>
        /// 初始化一个大小为width * height的游戏,并随机放置count个地雷。
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="count"></param>
        public HexagonMineSweeper(int width, int height, int count) : this(width, height)
        {
            RandomizeMines(count);
        }

        /// <summary>
        /// 重置每个单元格的状态。
        /// </summary>
        public void ResetStates()
        {
            for (int w = 0; w < Width; w++)
                for (int h = 0; h < Height; h++)
                {
                    States[w, h] = State.Unknown;
                }
        }

        /// <summary>
        /// 在单元格p处扫雷。将会更新该单元格的State,及其周围单元格State(如果周围没有地雷)。
        /// </summary>
        /// <param name="p"></param>
        /// <returns>该处是(true)否(false)有地雷</returns>
        public bool Sweep(Position p)
        {
            if (Mines[p.X, p.Y]) return true;

            var around = GetAround(p);
            int minesAround = GetMines(around);
            States[p.X, p.Y] = (State)minesAround;
            if (minesAround == 0)
            {
                foreach (var pp in around)
                {
                    if (States[pp.X, pp.Y] == State.Unknown) Sweep(pp);
                }
            }
            return false;
        }

        /// <summary>
        /// 随机放置count个地雷。
        /// </summary>
        /// <param name="count"></param>
        public void RandomizeMines(int count)
        {
            //首先在前count个单元格上放置地雷
            for (int w = 0; w < Width; w++)
                for (int h = 0; h < Height; h++)
                {
                    int index = h * Width + w;
                    if (index < count)
                    {
                        Mines[w, h] = true;
                    }
                    else
                    {
                        Mines[w, h] = false;
                    }
                }

            //每个单元格上与一个随机单元格交换地雷以打乱位置
            Random rnd = new Random();
            for (int w = 0; w < Width; w++)
                for (int h = 0; h < Height; h++)
                {
                    int hh = rnd.Next(Height);
                    int ww = rnd.Next(Width);

                    bool tmp = Mines[w, h];
                    Mines[w, h] = Mines[ww, hh];
                    Mines[ww, hh] = tmp;
                }
        }

        /// <summary>
        /// 打印States到Console。
        /// </summary>
        public void PrintStates()
        {
            for (int w = 0; w < Width; w++)
            {
                if (w % 2 == 1) Console.Write(" ");
                for (int h = 0; h < Height; h++)
                {
                    if (States[w, h] == State.Unknown)
                    {
                        Console.Write("? ");
                    }
                    else if (States[w, h] == State.Mine0)
                    {
                        Console.Write("- ");
                    }
                    else
                    {
                        Console.Write(((int)States[w, h]).ToString() + " ");
                    }
                }
                Console.WriteLine();
            }
        }

        /// <summary>
        /// 打印Mines到Console。
        /// </summary>
        public void PrintMines()
        {
            for (int w = 0; w < Width; w++)
            {
                if (w % 2 == 1) Console.Write(" ");
                for (int h = 0; h < Height; h++)
                {
                    if (Mines[w, h])
                    {
                        Console.Write("* ");
                    }
                    else
                    {
                        if (States[w, h] == State.Unknown)
                        {
                            Console.Write("? ");
                        }
                        else if (States[w, h] == State.Mine0)
                        {
                            Console.Write("- ");
                        }
                        else
                        {
                            Console.Write(((int)States[w, h]).ToString() + " ");
                        }
                    }
                }
                Console.WriteLine();
            }
        }

        /// <summary>
        /// 获取一个单元格p相邻的单元格。
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        protected IEnumerable<Position> GetAround(Position p)
        {
            //获取下、右下、右上、上、左上、左下方向单元格的坐标
            Position[] around;

            if (p.X % 2 == 0)
            {
                //偶数列
                around = new Position[6] {
                    new Position(p.X, p.Y + 1),
                    new Position(p.X + 1, p.Y),
                    new Position(p.X + 1, p.Y - 1),
                    new Position(p.X, p.Y - 1),
                    new Position(p.X - 1, p.Y - 1),
                    new Position(p.X - 1, p.Y),
                };
            }
            else
            {
                //奇数列
                around = new Position[6] {
                    new Position(p.X, p.Y + 1),
                    new Position(p.X + 1, p.Y + 1),
                    new Position(p.X + 1, p.Y),
                    new Position(p.X, p.Y - 1),
                    new Position(p.X - 1, p.Y),
                    new Position(p.X - 1, p.Y + 1),
                };
            }

            foreach (Position pp in around)
            {
                if (pp.X >= 0 && pp.X < Width && pp.Y >= 0 && pp.Y < Height)
                {
                    yield return pp;
                }
            }
        }

        /// <summary>
        /// 获取一个目标单元格集合ps中地雷总数。
        /// </summary>
        /// <param name="p"></param>
        /// <returns>地雷总数</returns>
        protected int GetMines(IEnumerable<Position> ps)
        {
            int sum = 0;
            foreach (Position pp in ps)
            {
                sum += Mines[pp.X, pp.Y] ? 1 : 0;
            }
            return sum;
        }
    }
}

命令行测试

using System;
using System.Text.RegularExpressions;

namespace MineSweeper
{
    class Program
    {
        static Position Read(string line)
        {
            Regex reg = new Regex(@"(\d+)\s+(\d+)");
            Match match = reg.Match(line);
            if (!match.Success) throw new ArgumentException();

            return new Position(int.Parse(match.Groups[1].Value), int.Parse(match.Groups[2].Value));
        }

        static void Main(string[] args)
        {
            while (true)
            {
                HexagonMineSweeper hms = new HexagonMineSweeper(10, 15, 10);    //10行15列10个雷
                bool boom = false;
                while (!boom && !hms.Win)
                {
                    hms.PrintStates();
                    Console.WriteLine("请输入下一个点击坐标,以空格分开,如0 1输入表示0行1列。");
                    try
                    {
                        Position p = Read(Console.ReadLine());
                        boom = hms.Sweep(p);
                    }
                    catch (ArgumentException aex)
                    {

                    }
                    Console.Clear();
                }

                if (boom)
                {
                    hms.PrintMines();
                    Console.WriteLine("沙比,炸了吧。");
                }
                else
                {
                    hms.PrintStates();
                    Console.WriteLine("牛比,你赢了。");
                }

                Console.ReadKey();
                Console.Clear();
            }
        }
    }
}

寒碜的游戏截图

毕竟是一个实例,所以画质比较捉急。

称谓(*)
邮箱
留言(*)