逻辑代码部分借鉴木野狐博客中的代码,参考http://www.cnblogs.com/RChen/archive/2005/07/07/188107.html,之后我会加入自己的设计调整。
一些必要的枚举:
将MineCell声明为struct或class代表了两种不同的设计策略,简单起见,先使用struct。
逻辑代码如下:
窗口代码如下:
非常简单,不过已经可以玩了,系列稍后的文章会对功能进行改进。
项目文件下载。
更正一:在MouseClick中,先Invalidate,再处理取胜判断,用户体验要好一些。
一些必要的枚举:
public enum GameStatus
{
NotStarted,
Playing,
Win,
Lose
}
public enum MineCellStatus : short
{
Covered,
MarkDoubt,
MarkMine,
Uncovered,
Exploded
}
public struct MineCell
{
public MineCellStatus Status;
public bool IsMine;
public byte NearbyMines;
}
{
NotStarted,
Playing,
Win,
Lose
}
public enum MineCellStatus : short
{
Covered,
MarkDoubt,
MarkMine,
Uncovered,
Exploded
}
public struct MineCell
{
public MineCellStatus Status;
public bool IsMine;
public byte NearbyMines;
}
将MineCell声明为struct或class代表了两种不同的设计策略,简单起见,先使用struct。
逻辑代码如下:
namespace xingd.Minesweeper
{
public class MineBoard
{
private MineCell[,] cells = new MineCell[0, 0];
private int totalMines;
private int makedMines;
private GameStatus status;
public int Columns { get { return cells.GetLength(1); } }
public int Rows { get { return cells.GetLength(0); } }
public int TotalMines { get { return totalMines; } }
public int MarkedMines { get { return makedMines; } }
public GameStatus Status { get { return status; } }
public MineCell this[int row, int col] { get { return cells[row, col]; } }
public void Init(int rows, int columns, int mines)
{
if (rows <= 0 || columns <= 0 || mines >= columns * rows)
{
throw new ArgumentException();
}
cells = new MineCell[rows, columns];
totalMines = mines;
makedMines = 0;
status = GameStatus.NotStarted;
PlaceRandomMines();
UpdateNearbyMinesCount();
}
public void Uncover(int row, int col)
{
if (!IsValidCell(row, col))
return;
if (status == GameStatus.NotStarted)
{
status = GameStatus.Playing;
}
MineCell cell = cells[row, col];
if (cell.Status != MineCellStatus.Covered)
return;
if (cell.IsMine)
{
cells[row, col].Status = MineCellStatus.Exploded;
this.status = GameStatus.Lose;
return;
}
else
{
cells[row, col].Status = MineCellStatus.Uncovered;
}
// 如果周围雷数是 0, 那么顺便把周围的 8 个位置都翻开
if (!cell.IsMine && cell.NearbyMines == 0)
{
for (int i = 0; i < moves.GetLength(0); i++)
Uncover(row + moves[i, 0], col + moves[i, 1]);
}
}
/**/
/// <summary>
/// 设定或取消标志, 相当于鼠标右键单击的行为
/// </summary>
/// <param name="row"></param>
/// <param name="col"></param>
public void ChangeMark(int row, int col)
{
if (!IsValidCell(row, col))
return;
if (status == GameStatus.NotStarted)
{
status = GameStatus.Playing;
}
MineCell cell = cells[row, col];
// 轮换几种标志
switch (cell.Status)
{
case MineCellStatus.Covered:
cell.Status = MineCellStatus.MarkMine;
makedMines++;
break;
case MineCellStatus.MarkMine:
cell.Status = MineCellStatus.MarkDoubt;
break;
case MineCellStatus.MarkDoubt:
makedMines--;
cell.Status = MineCellStatus.Covered;
break;
default:
break;
}
cells[row, col] = cell;
}
/// <summary>
/// 鼠标左右键同时按下行为
/// </summary>
/// <param name="row"></param>
/// <param name="col"></param>
public void UncoverNearBy(int row, int col)
{
MineCell cell = cells[row, col];
if (cell.Status == MineCellStatus.Uncovered && cell.NearbyMines > 0)
{
byte minesFound = 0;
for (int i = 0; i < moves.GetLength(0); i++)
{
int r = row + moves[i, 0];
int c = col + moves[i, 1];
if (IsValidCell(r, c) && cells[r, c].Status == MineCellStatus.MarkMine)
minesFound++;
}
if (minesFound == cell.NearbyMines)
{
for (int i = 0; i < moves.GetLength(0); i++)
{
int r = row + moves[i, 0];
int c = col + moves[i, 1];
Uncover(r, c);
}
}
}
}
/// <summary>
/// 检验游戏结果
/// </summary>
public void CheckGameResult()
{
if (makedMines == totalMines)
{
for (int i = 0; i < Rows; i++)
{
for (int j = 0; j < Columns; j++)
{
if (cells[i, j].Status == MineCellStatus.Covered)
{
return;
}
}
}
status = GameStatus.Win;
}
}
private void PlaceRandomMines()
{
int count = 0;
Random r = new Random((int)DateTime.Now.Ticks);
while (count < totalMines)
{
int row = r.Next(0, Rows);
int col = r.Next(0, Columns);
if (!cells[row, col].IsMine)
{
cells[row, col].IsMine = true;
count++;
}
}
}
private bool IsValidCell(int row, int col)
{
return (row >= 0 && col >= 0 && row < Rows && col < Columns);
}
/**/
/// <summary>
/// 从一个格子出发向四周的可能的位移
/// </summary>
private int[,] moves = new int[8, 2] { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 }, { 0, 1 }, { 1, -1 }, { 1, 0 }, { 1, 1 } };
private byte GetNearbyMines(int row, int col)
{
byte minesFound = 0;
for (int i = 0; i < moves.GetLength(0); i++)
{
int r = row + moves[i, 0];
int c = col + moves[i, 1];
if (IsValidCell(r, c) && cells[r, c].IsMine)
minesFound++;
}
return minesFound;
}
private void UpdateNearbyMinesCount()
{
for (int i = 0; i < Rows; i++)
{
for (int j = 0; j < Columns; j++)
{
cells[i, j].NearbyMines = GetNearbyMines(i, j);
}
}
}
}
}
{
public class MineBoard
{
private MineCell[,] cells = new MineCell[0, 0];
private int totalMines;
private int makedMines;
private GameStatus status;
public int Columns { get { return cells.GetLength(1); } }
public int Rows { get { return cells.GetLength(0); } }
public int TotalMines { get { return totalMines; } }
public int MarkedMines { get { return makedMines; } }
public GameStatus Status { get { return status; } }
public MineCell this[int row, int col] { get { return cells[row, col]; } }
public void Init(int rows, int columns, int mines)
{
if (rows <= 0 || columns <= 0 || mines >= columns * rows)
{
throw new ArgumentException();
}
cells = new MineCell[rows, columns];
totalMines = mines;
makedMines = 0;
status = GameStatus.NotStarted;
PlaceRandomMines();
UpdateNearbyMinesCount();
}
public void Uncover(int row, int col)
{
if (!IsValidCell(row, col))
return;
if (status == GameStatus.NotStarted)
{
status = GameStatus.Playing;
}
MineCell cell = cells[row, col];
if (cell.Status != MineCellStatus.Covered)
return;
if (cell.IsMine)
{
cells[row, col].Status = MineCellStatus.Exploded;
this.status = GameStatus.Lose;
return;
}
else
{
cells[row, col].Status = MineCellStatus.Uncovered;
}
// 如果周围雷数是 0, 那么顺便把周围的 8 个位置都翻开
if (!cell.IsMine && cell.NearbyMines == 0)
{
for (int i = 0; i < moves.GetLength(0); i++)
Uncover(row + moves[i, 0], col + moves[i, 1]);
}
}
/**/
/// <summary>
/// 设定或取消标志, 相当于鼠标右键单击的行为
/// </summary>
/// <param name="row"></param>
/// <param name="col"></param>
public void ChangeMark(int row, int col)
{
if (!IsValidCell(row, col))
return;
if (status == GameStatus.NotStarted)
{
status = GameStatus.Playing;
}
MineCell cell = cells[row, col];
// 轮换几种标志
switch (cell.Status)
{
case MineCellStatus.Covered:
cell.Status = MineCellStatus.MarkMine;
makedMines++;
break;
case MineCellStatus.MarkMine:
cell.Status = MineCellStatus.MarkDoubt;
break;
case MineCellStatus.MarkDoubt:
makedMines--;
cell.Status = MineCellStatus.Covered;
break;
default:
break;
}
cells[row, col] = cell;
}
/// <summary>
/// 鼠标左右键同时按下行为
/// </summary>
/// <param name="row"></param>
/// <param name="col"></param>
public void UncoverNearBy(int row, int col)
{
MineCell cell = cells[row, col];
if (cell.Status == MineCellStatus.Uncovered && cell.NearbyMines > 0)
{
byte minesFound = 0;
for (int i = 0; i < moves.GetLength(0); i++)
{
int r = row + moves[i, 0];
int c = col + moves[i, 1];
if (IsValidCell(r, c) && cells[r, c].Status == MineCellStatus.MarkMine)
minesFound++;
}
if (minesFound == cell.NearbyMines)
{
for (int i = 0; i < moves.GetLength(0); i++)
{
int r = row + moves[i, 0];
int c = col + moves[i, 1];
Uncover(r, c);
}
}
}
}
/// <summary>
/// 检验游戏结果
/// </summary>
public void CheckGameResult()
{
if (makedMines == totalMines)
{
for (int i = 0; i < Rows; i++)
{
for (int j = 0; j < Columns; j++)
{
if (cells[i, j].Status == MineCellStatus.Covered)
{
return;
}
}
}
status = GameStatus.Win;
}
}
private void PlaceRandomMines()
{
int count = 0;
Random r = new Random((int)DateTime.Now.Ticks);
while (count < totalMines)
{
int row = r.Next(0, Rows);
int col = r.Next(0, Columns);
if (!cells[row, col].IsMine)
{
cells[row, col].IsMine = true;
count++;
}
}
}
private bool IsValidCell(int row, int col)
{
return (row >= 0 && col >= 0 && row < Rows && col < Columns);
}
/**/
/// <summary>
/// 从一个格子出发向四周的可能的位移
/// </summary>
private int[,] moves = new int[8, 2] { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 }, { 0, 1 }, { 1, -1 }, { 1, 0 }, { 1, 1 } };
private byte GetNearbyMines(int row, int col)
{
byte minesFound = 0;
for (int i = 0; i < moves.GetLength(0); i++)
{
int r = row + moves[i, 0];
int c = col + moves[i, 1];
if (IsValidCell(r, c) && cells[r, c].IsMine)
minesFound++;
}
return minesFound;
}
private void UpdateNearbyMinesCount()
{
for (int i = 0; i < Rows; i++)
{
for (int j = 0; j < Columns; j++)
{
cells[i, j].NearbyMines = GetNearbyMines(i, j);
}
}
}
}
}
窗口代码如下:
namespace xingd.Minesweeper
{
public partial class MineForm : Form
{
private MineBoard board = new MineBoard();
private MouseButtons lastClickButton = MouseButtons.None;
private long lastClickTicks = 0;
private const int CellSize = 16;
private const long ShortPeriod = 2000000L;
private static readonly Rectangle CellRectangle = new Rectangle(0, 0, CellSize, CellSize);
public MineForm()
{
InitializeComponent();
SetStyle(ControlStyles.ResizeRedraw | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
Init(9, 9, 10);
}
public void Init(int rows, int columns, int mines)
{
board.Init(rows, columns, mines);
ClientSize = new Size(columns * CellSize + 1, rows * CellSize + mainMenu.Height + 1);
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
for (int i = 0; i < board.Rows; i++)
{
for (int j = 0; j < board.Columns; j++)
{
g.ResetTransform();
g.TranslateTransform(j * 16, i * 16 + mainMenu.Height);
DrawCell(g, board[i, j]);
}
}
}
private void DrawCell(Graphics g, MineCell cell)
{
Rectangle rt = CellRectangle;
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
if (cell.IsMine && board.Status == GameStatus.Lose)
{
g.FillRectangle(Brushes.DarkGray, rt);
g.DrawString("@", Font, cell.Status == MineCellStatus.Exploded ? Brushes.Red : Brushes.DarkBlue, rt, sf);
}
else
{
if (cell.Status == MineCellStatus.Covered)
{
g.FillRectangle(Brushes.DarkGray, rt);
}
else if (cell.Status == MineCellStatus.MarkMine)
{
g.FillRectangle(Brushes.DarkGray, rt);
g.DrawString("#", Font, Brushes.DarkBlue, rt, sf);
}
else if (cell.Status == MineCellStatus.MarkDoubt)
{
g.FillRectangle(Brushes.DarkGray, rt);
g.DrawString("?", Font, Brushes.DarkBlue, rt, sf);
}
else
{
if (cell.NearbyMines > 0)
{
g.DrawString(cell.NearbyMines.ToString(), Font, Brushes.DarkBlue, rt, sf);
}
}
}
g.DrawRectangle(Pens.Black, rt);
sf.Dispose();
}
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
if (board.Status != GameStatus.Playing && board.Status != GameStatus.NotStarted)
return;
int row, col;
HitTest(e.Location, out row, out col);
if (DateTime.Now.Ticks - lastClickTicks < ShortPeriod && ((lastClickButton | e.Button) == (MouseButtons.Left | MouseButtons.Right)))
{
board.UncoverNearBy(row, col);
}
if (e.Button == MouseButtons.Left)
{
board.Uncover(row, col);
}
else if (e.Button == MouseButtons.Right)
{
board.ChangeMark(row, col);
}
board.CheckGameResult();
if (board.Status == GameStatus.Win)
{
MessageBox.Show("You Win!");
}
Invalidate();
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (DateTime.Now.Ticks - lastClickTicks < ShortPeriod)
{
lastClickButton |= e.Button;
}
else
{
lastClickButton = e.Button;
lastClickTicks = DateTime.Now.Ticks;
}
}
private void HitTest(Point pt, out int row, out int col)
{
col = pt.X / CellSize;
row = (pt.Y - mainMenu.Height) / CellSize;
}
private void menuItemExit_Click(object sender, EventArgs e)
{
this.Close();
}
private void menuItemNew_Click(object sender, EventArgs e)
{
this.Init(9, 9, 10);
}
}
}
{
public partial class MineForm : Form
{
private MineBoard board = new MineBoard();
private MouseButtons lastClickButton = MouseButtons.None;
private long lastClickTicks = 0;
private const int CellSize = 16;
private const long ShortPeriod = 2000000L;
private static readonly Rectangle CellRectangle = new Rectangle(0, 0, CellSize, CellSize);
public MineForm()
{
InitializeComponent();
SetStyle(ControlStyles.ResizeRedraw | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
Init(9, 9, 10);
}
public void Init(int rows, int columns, int mines)
{
board.Init(rows, columns, mines);
ClientSize = new Size(columns * CellSize + 1, rows * CellSize + mainMenu.Height + 1);
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
for (int i = 0; i < board.Rows; i++)
{
for (int j = 0; j < board.Columns; j++)
{
g.ResetTransform();
g.TranslateTransform(j * 16, i * 16 + mainMenu.Height);
DrawCell(g, board[i, j]);
}
}
}
private void DrawCell(Graphics g, MineCell cell)
{
Rectangle rt = CellRectangle;
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
if (cell.IsMine && board.Status == GameStatus.Lose)
{
g.FillRectangle(Brushes.DarkGray, rt);
g.DrawString("@", Font, cell.Status == MineCellStatus.Exploded ? Brushes.Red : Brushes.DarkBlue, rt, sf);
}
else
{
if (cell.Status == MineCellStatus.Covered)
{
g.FillRectangle(Brushes.DarkGray, rt);
}
else if (cell.Status == MineCellStatus.MarkMine)
{
g.FillRectangle(Brushes.DarkGray, rt);
g.DrawString("#", Font, Brushes.DarkBlue, rt, sf);
}
else if (cell.Status == MineCellStatus.MarkDoubt)
{
g.FillRectangle(Brushes.DarkGray, rt);
g.DrawString("?", Font, Brushes.DarkBlue, rt, sf);
}
else
{
if (cell.NearbyMines > 0)
{
g.DrawString(cell.NearbyMines.ToString(), Font, Brushes.DarkBlue, rt, sf);
}
}
}
g.DrawRectangle(Pens.Black, rt);
sf.Dispose();
}
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
if (board.Status != GameStatus.Playing && board.Status != GameStatus.NotStarted)
return;
int row, col;
HitTest(e.Location, out row, out col);
if (DateTime.Now.Ticks - lastClickTicks < ShortPeriod && ((lastClickButton | e.Button) == (MouseButtons.Left | MouseButtons.Right)))
{
board.UncoverNearBy(row, col);
}
if (e.Button == MouseButtons.Left)
{
board.Uncover(row, col);
}
else if (e.Button == MouseButtons.Right)
{
board.ChangeMark(row, col);
}
board.CheckGameResult();
if (board.Status == GameStatus.Win)
{
MessageBox.Show("You Win!");
}
Invalidate();
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (DateTime.Now.Ticks - lastClickTicks < ShortPeriod)
{
lastClickButton |= e.Button;
}
else
{
lastClickButton = e.Button;
lastClickTicks = DateTime.Now.Ticks;
}
}
private void HitTest(Point pt, out int row, out int col)
{
col = pt.X / CellSize;
row = (pt.Y - mainMenu.Height) / CellSize;
}
private void menuItemExit_Click(object sender, EventArgs e)
{
this.Close();
}
private void menuItemNew_Click(object sender, EventArgs e)
{
this.Init(9, 9, 10);
}
}
}
非常简单,不过已经可以玩了,系列稍后的文章会对功能进行改进。
项目文件下载。
更正一:在MouseClick中,先Invalidate,再处理取胜判断,用户体验要好一些。