一般的扫雷游戏中,一个方格直接相邻的只有上、下、左、右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();
}
}
}
}
寒碜的游戏截图
毕竟是一个实例,所以画质比较捉急。