掃雷游戲次乓,大家都應(yīng)該玩過吧!其實規(guī)則也很簡單孽水,可是我們想自己實現(xiàn)一個掃雷票腰,我們應(yīng)該怎么做呢?
Step1: 知曉游戲原理
掃雷就是要把所有非地雷的格子揭開即勝利匈棘;踩到地雷格子就算失敗丧慈。游戲主區(qū)域由很多個方格組成。使用鼠標(biāo)左鍵隨機點擊一個方格主卫,方格即被打開并顯示出方格中的數(shù)字逃默;方格中數(shù)字則表示其周圍的8個方格隱藏了幾顆雷;如果點開的格子為空白格簇搅,即其周圍有0顆雷完域,則其周圍格子自動打開;如果其周圍還有空白格瘩将,則會引發(fā)連鎖反應(yīng)吟税;在你認為有雷的格子上,點擊右鍵即可標(biāo)記雷姿现;如果一個已打開格子周圍所有的雷已經(jīng)正確標(biāo)出肠仪,則可以在此格上同時點擊鼠標(biāo)左右鍵以打開其周圍剩余的無雷格。
1代表1的上下左右及斜角合計有一顆雷备典,依次輪推异旧,2則有2顆,3則有3顆..
在確實是炸彈的方格上點了旗子提佣,就安全了吮蛹,不是炸彈的被點了旗子,后面會被炸死的..問號就先不確定這里有沒有炸彈拌屏,不會存在點錯了被炸死的狀況..
Step2: 由step1可知潮针,游戲由格子組成,翻譯成代碼語言就叫做數(shù)組倚喂,也就是游戲地圖就是一個二維數(shù)組每篷。格子對象,格子的值即當(dāng)前雷的數(shù)量,那么此時我們暫定雷的數(shù)字標(biāo)識為-1雳攘。除此之外带兜,格子對象還有是否被顯示,顯示當(dāng)前雷數(shù)量等屬性吨灭,那么我們大概可以定義這樣一個類:
public class CellBlockRole
{
/// <summary>
/// 位于游戲地圖中的坐標(biāo)點X
/// </summary>
public int X { get; set; }
/// <summary>
/// 位于游戲地圖中的坐標(biāo)點Y
/// </summary>
public int Y { get; set; }
/// <summary>
/// 是否展示最后格子所代表的結(jié)果
/// </summary>
public bool IsShowResult { get; set; } = false;
/// <summary>
/// 是否計算數(shù)字結(jié)果
/// </summary>
public bool IsComputeResult { get; set; } = false;
/// <summary>
/// 是否已經(jīng)展示過計算結(jié)果了
/// </summary>
public bool IsHasShowComputed { get; set; } = false;
/// <summary>
/// 當(dāng)前的格子的角色數(shù)字刚照, -1:地雷,其他當(dāng)前雷的數(shù)量
/// </summary>
public int Number { set; get; } = 0;
/// <summary>
/// 是否被Flag標(biāo)識
/// </summary>
public bool IsFlag { get; set; } = false;
/// <summary>
/// 是否是雷
/// </summary>
public bool IsBoom => Number == -1;
}
繪制游戲UI畫面喧兄,見代碼:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using SweeperLibrary.Properties;
using Timer = System.Threading.Timer;
namespace SweeperLibrary
{
public delegate void OnGameOverDelegate();
public delegate void OnShowANumberDelegate();
public delegate void OnPublishGameTimeDelegate(string timeDescription);
public partial class GameView : UserControl
{
/// <summary>
/// 游戲結(jié)束事件
/// </summary>
public event OnGameOverDelegate OnGameOverEvent;
/// <summary>
/// 當(dāng)一個格子被點擊時无畔,顯示當(dāng)前數(shù)字的事件
/// </summary>
public event OnShowANumberDelegate OnShowANumberEvent;
/// <summary>
/// 發(fā)布當(dāng)前游戲的時間
/// </summary>
public event OnPublishGameTimeDelegate OnPublishGameTimeEvent;
/// <summary>
/// 游戲繪制地圖的每個格子的大小
/// </summary>
public static readonly int CellSize = 40;
/// <summary>
/// 游戲規(guī)模N*N
/// </summary>
public static readonly int GameCellCount = 10;
/// <summary>
/// 移動方向坐標(biāo)點改變的數(shù)組
/// </summary>
public static readonly int[][] MoveDirectionPoints = {
new[]{-1, -1},
new[] {0, -1},
new[] {1, -1},
new[] {1, 0},
new[] {1, 1},
new[] {0, 1},
new[] {-1, 1},
new[] {-1, 0}
};
/// <summary>
/// 隨機數(shù)雷生成對象
/// </summary>
private static readonly Random random = new Random(Guid.NewGuid().GetHashCode());
/// <summary>
/// 游戲地圖標(biāo)識數(shù)組
/// </summary>
private CellBlockRole[][] gameMap = new CellBlockRole[GameCellCount][];
/// <summary>
/// 雷的數(shù)量,默認為10
/// </summary>
public int BoomCount { get; set; } = 10;
/// <summary>
/// 游戲開始時間
/// </summary>
private DateTime gameStartTime;
/// <summary>
/// 計時定時器
/// </summary>
private System.Windows.Forms.Timer gameTimer = new System.Windows.Forms.Timer();
public GameView()
{
InitializeComponent();
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
InitGame(); //默認游戲已經(jīng)開始
SetGameTimer(); //設(shè)置游戲定時器
}
private void GameView_Paint(object sender, PaintEventArgs e)
{
Width = GameCellCount + 1 + GameCellCount * CellSize;
Height = GameCellCount + 1 + GameCellCount * CellSize;
//繪制游戲界面
Graphics graphics = e.Graphics;
graphics.Clear(Color.WhiteSmoke);
if (gameMap != null && gameMap.Length > 0 && gameMap[0] != null && gameMap[0].Length > 0)
{
for (int y = 0; y < GameCellCount; y++)
{
for (int x = 0; x < GameCellCount; x++)
{
int dx = x + 1 + x * CellSize,
dy = y + 1 + y * CellSize;
CellBlockRole cellBlockRole = gameMap[y][x];
graphics.FillRectangle(new SolidBrush(cellBlockRole.IsShowResult ? Color.LightSlateGray : Color.WhiteSmoke),
dx, dy, CellSize, CellSize);
graphics.DrawRectangle(new Pen(Color.LightGray), dx, dy, CellSize, CellSize);
if (cellBlockRole.IsShowResult && cellBlockRole.Number != 0)
{
switch (cellBlockRole.Number)
{
case -1: //雷
graphics.DrawImage(Image.FromHbitmap(Resources.boom.GetHbitmap()), new RectangleF(dx, dy, CellSize, CellSize));
break;
default: //數(shù)字
string drawText = cellBlockRole.Number.ToString();
Font textFont = new Font(FontFamily.GenericSansSerif, 12, FontStyle.Bold);
SizeF textSize = graphics.MeasureString(drawText, textFont);
graphics.DrawString(drawText, textFont, new SolidBrush(Color.White),
dx + (CellSize - textSize.Width) / 2, dy + (CellSize - textSize.Height) / 2);
break;
}
}
}
}
}
}
private void GameView_MouseDown(object sender, MouseEventArgs e)
{
int px = (e.X - 1) / (CellSize + 1),
py = (e.Y - 1) / (CellSize + 1);
switch (e.Button)
{
case MouseButtons.Left: //鼠標(biāo)左鍵
if (!gameMap[py][px].IsShowResult)
{
if (gameMap[py][px].IsBoom)
{
new Thread(() =>
{
ShowAllCellBlockRoleNumber();
if (this.InvokeRequired)
{
MethodInvoker del = Invalidate;
this.Invoke(del);
} else
{
Invalidate();
}
}).Start();
gameTimer.Stop();
OnGameOverEvent?.Invoke();
} else
{
new Thread(() =>
{
ShowNeiborhoodCellRolesByPosi(px, py);
if (this.InvokeRequired)
{
MethodInvoker del = Invalidate;
this.Invoke(del);
} else
{
Invalidate();
}
}).Start();
OnShowANumberEvent?.Invoke();
}
}
break;
case MouseButtons.Right: //鼠標(biāo)右鍵
break;
}
}
/// <summary>
/// 初始化游戲
/// </summary>
private void InitGame()
{
new Thread(() =>
{
InitGameMap();
GenerateBooms();
if (this.InvokeRequired)
{
MethodInvoker del = Invalidate;
this.Invoke(del);
} else
{
Invalidate();
}
}).Start();
}
/// <summary>
/// 設(shè)置游戲定時器
/// </summary>
private void SetGameTimer()
{
gameTimer.Interval = 1000;
gameTimer.Enabled = true;
gameTimer.Tick += (sender, args) =>
{
long dMillisecond = DateTime.Now.Millisecond - gameStartTime.Millisecond;
long hour = dMillisecond / 60 / 60 / 1000;
long minute = (dMillisecond - hour * (60 * 60 * 1000)) / (60 * 1000);
long second = ((dMillisecond - hour * (60 * 60 * 1000)) % (60 * 1000)) / 1000;
OnPublishGameTimeEvent?.Invoke((hour > 0 ? (hour > 9 ? hour.ToString() : "0" + hour) + ":" : "")
+ (minute > 9 ? minute.ToString() : "0" + minute) + ":" + (second > 9 ? second.ToString() : "0" + second));
};
}
/// <summary>
/// 初始化游戲地圖
/// </summary>
private void InitGameMap()
{
for (int i = 0; i < GameCellCount; i++)
{
gameMap[i] = new CellBlockRole[GameCellCount];
for (int j = 0; j < GameCellCount; j++)
{
gameMap[i][j] = new CellBlockRole
{
X = j,
Y = i
};
}
}
gameStartTime = DateTime.Now;
gameTimer.Start();
}
/// <summary>
/// 重置游戲地圖
/// </summary>
public void ResetGameMap()
{
new Thread(() =>
{
for (int i = 0; i < GameCellCount; i++)
{
for (int j = 0; j < GameCellCount; j++)
{
gameMap[i][j].X = j;
gameMap[i][j].Y = i;
gameMap[i][j].Number = 0;
gameMap[i][j].IsShowResult = false;
gameMap[i][j].IsComputeResult = false;
gameMap[i][j].IsHasShowComputed = false;
}
}
GenerateBooms(); //生成一些雷
if (this.InvokeRequired)
{
MethodInvoker del = Invalidate;
this.Invoke(del);
} else
{
Invalidate();
}
}).Start();
gameStartTime = DateTime.Now;
gameTimer.Start();
}
/// <summary>
/// 隨機生成一些地雷
/// </summary>
public void GenerateBooms()
{
for (int i = 0; i < BoomCount; i++)
{
int boomNumberIndex = random.Next(0, GameCellCount * GameCellCount - 1); //生成隨機數(shù)的范圍
int boomX = boomNumberIndex % GameCellCount,
boomY = boomNumberIndex / GameCellCount;
if (gameMap[boomY][boomX].Number == 0)
gameMap[boomY][boomX].Number = -1; //-1表示雷
else // 已經(jīng)存在雷了吠冤,所以要重新處理
i--;
}
MakeAllNumberComputeInCellRole(0, 0); //默認從坐標(biāo)(0浑彰,0)開始
}
/// <summary>
/// 顯示所有的格子的信息
/// </summary>
private void ShowAllCellBlockRoleNumber()
{
for (int i = 0; i < GameCellCount; i++)
{
for (int j = 0; j < GameCellCount; j++)
{
gameMap[i][j].IsShowResult = true;
}
}
}
/// <summary>
/// 顯示某點周邊所有格子的數(shù)字
/// </summary>
/// <param name="posiX">X軸坐標(biāo)</param>
/// <param name="posiY">Y軸坐標(biāo)</param>
private void ShowNeiborhoodCellRolesByPosi(int posiX, int posiY)
{
gameMap[posiY][posiX].IsShowResult = true;
gameMap[posiY][posiX].IsHasShowComputed = true;
int boomCount = GetBoomCountInNeiborhood(posiX, posiY);
if (boomCount == 0) //如果周圍沒有雷,則翻開所有8個方向的相關(guān)數(shù)字
{
for (int i = 0; i < MoveDirectionPoints.Length; i++)
{
int[] itemPosi = MoveDirectionPoints[i];
int rx = posiX + itemPosi[0],
ry = posiY + itemPosi[1];
bool isNotOutIndexRange = rx >= 0 && rx < GameCellCount && ry >= 0 && ry < GameCellCount;
if (isNotOutIndexRange) //防止坐標(biāo)溢出
{
gameMap[ry][rx].IsShowResult = true;
if (!gameMap[ry][rx].IsHasShowComputed && gameMap[ry][rx].Number == 0)
ShowNeiborhoodCellRolesByPosi(rx, ry);
}
}
}
}
/// <summary>
/// 獲取某點附近的雷數(shù)量
/// </summary>
/// <param name="posiX">X軸坐標(biāo)點</param>
/// <param name="posiY">Y軸坐標(biāo)點</param>
/// <returns></returns>
private int GetBoomCountInNeiborhood(int posiX, int posiY)
{
int boomCount = 0;
for (int i = 0; i < MoveDirectionPoints.Length; i++)
{
int[] itemPosi = MoveDirectionPoints[i];
int rx = posiX + itemPosi[0],
ry = posiY + itemPosi[1];
bool isNotOutIndexRange = rx >= 0 && rx < GameCellCount && ry >= 0 && ry < GameCellCount;
if (isNotOutIndexRange && gameMap[ry][rx].IsBoom) //防止坐標(biāo)溢出
{
boomCount++;
}
}
return boomCount;
}
/// <summary>
/// 計算每個格子的數(shù)字標(biāo)識
/// </summary>
/// <param name="posiX">X軸坐標(biāo)</param>
/// <param name="posiY">Y軸坐標(biāo)</param>
private void MakeAllNumberComputeInCellRole(int posiX, int posiY)
{
int boomCount = GetBoomCountInNeiborhood(posiX, posiY);
if (boomCount != 0) //如果周圍沒有雷拯辙,則計算周圍的8個方向的格子
{
gameMap[posiY][posiX].Number = boomCount;
} else
{
if (!gameMap[posiY][posiX].IsBoom)
gameMap[posiY][posiX].Number = 0;
}
gameMap[posiY][posiX].IsComputeResult = true;
for (int i = 0; i < MoveDirectionPoints.Length; i++)
{
int[] itemPosi = MoveDirectionPoints[i];
int rx = posiX + itemPosi[0],
ry = posiY + itemPosi[1];
bool isNotOutIndexRange = rx >= 0 && rx < GameCellCount && ry >= 0 && ry < GameCellCount;
if (isNotOutIndexRange && !gameMap[ry][rx].IsComputeResult && !gameMap[ry][rx].IsBoom) //防止坐標(biāo)溢出
{
MakeAllNumberComputeInCellRole(rx, ry);
}
}
}
}
}
主要代碼已經(jīng)實現(xiàn)郭变,現(xiàn)已知現(xiàn)有代碼的定時器由問題,暫時不支持Flag(旗子標(biāo)識)涯保。當(dāng)然代碼中還有其他不足的地方诉濒,游戲持續(xù)優(yōu)化中。夕春。未荒。