C#帶你玩掃雷(附源碼)
掃雷游戲,大家都應(yīng)該玩過(guò)吧!其實(shí)規(guī)則也很簡(jiǎn)單,可是我們想自己實(shí)現(xiàn)一個(gè)掃雷,我們應(yīng)該怎么做呢?
Step1: 知曉游戲原理
掃雷就是要把所有非地雷的格子揭開(kāi)即勝利;踩到地雷格子就算失敗。游戲主區(qū)域由很多個(gè)方格組成。使用鼠標(biāo)左鍵隨機(jī)點(diǎn)擊一個(gè)方格,方格即被打開(kāi)并顯示出方格中的數(shù)字;方格中數(shù)字則表示其周?chē)?個(gè)方格隱藏了幾顆雷;如果點(diǎn)開(kāi)的格子為空白格,即其周?chē)?顆雷,則其周?chē)褡幼詣?dòng)打開(kāi);如果其周?chē)€有空白格,則會(huì)引發(fā)連鎖反應(yīng);在你認(rèn)為有雷的格子上,點(diǎn)擊右鍵即可標(biāo)記雷;如果一個(gè)已打開(kāi)格子周?chē)械睦滓呀?jīng)正確標(biāo)出,則可以在此格上同時(shí)點(diǎn)擊鼠標(biāo)左右鍵以打開(kāi)其周?chē)S嗟臒o(wú)雷格。
1代表1的上下左右及斜角合計(jì)有一顆雷,依次輪推,2則有2顆,3則有3顆..
在確實(shí)是炸彈的方格上點(diǎn)了旗子,就安全了,不是炸彈的被點(diǎn)了旗子,后面會(huì)被炸死的..問(wèn)號(hào)就先不確定這里有沒(méi)有炸彈,不會(huì)存在點(diǎn)錯(cuò)了被炸死的狀況..
Step2: 由step1可知,游戲由格子組成,翻譯成代碼語(yǔ)言就叫做數(shù)組,也就是游戲地圖就是一個(gè)二維數(shù)組。格子對(duì)象,格子的值即當(dāng)前雷的數(shù)量,那么此時(shí)我們暫定雷的數(shù)字標(biāo)識(shí)為-1。除此之外,格子對(duì)象還有是否被顯示,顯示當(dāng)前雷數(shù)量等屬性,那么我們大概可以定義這樣一個(gè)類(lèi):
public class CellBlockRole { /// <summary> /// 位于游戲地圖中的坐標(biāo)點(diǎn)X /// </summary> public int X { get; set; } /// <summary> /// 位于游戲地圖中的坐標(biāo)點(diǎn)Y /// </summary> public int Y { get; set; } /// <summary> /// 是否展示最后格子所代表的結(jié)果 /// </summary> public bool IsShowResult { get; set; } = false; /// <summary> /// 是否計(jì)算數(shù)字結(jié)果 /// </summary> public bool IsComputeResult { get; set; } = false; /// <summary> /// 是否已經(jīng)展示過(guò)計(jì)算結(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)識(shí) /// </summary> public bool IsFlag { get; set; } = false; /// <summary> /// 是否是雷 /// </summary> public bool IsBoom => Number == -1; }
繪制游戲UI畫(huà)面,見(jiàn)代碼:
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)一個(gè)格子被點(diǎn)擊時(shí),顯示當(dāng)前數(shù)字的事件 /// </summary> public event OnShowANumberDelegate OnShowANumberEvent; /// <summary> /// 發(fā)布當(dāng)前游戲的時(shí)間 /// </summary> public event OnPublishGameTimeDelegate OnPublishGameTimeEvent; /// <summary> /// 游戲繪制地圖的每個(gè)格子的大小 /// </summary> public static readonly int CellSize = 40; /// <summary> /// 游戲規(guī)模N*N /// </summary> public static readonly int GameCellCount = 10; /// <summary> /// 移動(dòng)方向坐標(biāo)點(diǎn)改變的數(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> /// 隨機(jī)數(shù)雷生成對(duì)象 /// </summary> private static readonly Random random = new Random(Guid.NewGuid().GetHashCode()); /// <summary> /// 游戲地圖標(biāo)識(shí)數(shù)組 /// </summary> private CellBlockRole[][] gameMap = new CellBlockRole[GameCellCount][]; /// <summary> /// 雷的數(shù)量,默認(rèn)為10 /// </summary> public int BoomCount { get; set; } = 10; /// <summary> /// 游戲開(kāi)始時(shí)間 /// </summary> private DateTime gameStartTime; /// <summary> /// 計(jì)時(shí)定時(shí)器 /// </summary> private System.Windows.Forms.Timer gameTimer = new System.Windows.Forms.Timer(); public GameView() { InitializeComponent(); SetStyle(ControlStyles.OptimizedDoubleBuffer, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); InitGame(); //默認(rèn)游戲已經(jīng)開(kāi)始 SetGameTimer(); //設(shè)置游戲定時(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è)置游戲定時(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> /// 隨機(jī)生成一些地雷 /// </summary> public void GenerateBooms() { for (int i = 0; i < BoomCount; i++) { int boomNumberIndex = random.Next(0, GameCellCount * GameCellCount - 1); //生成隨機(jī)數(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); //默認(rèn)從坐標(biāo)(0,0)開(kāi)始 } /// <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> /// 顯示某點(diǎn)周邊所有格子的數(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) //如果周?chē)鷽](méi)有雷,則翻開(kāi)所有8個(gè)方向的相關(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> /// 獲取某點(diǎn)附近的雷數(shù)量 /// </summary> /// <param name="posiX">X軸坐標(biāo)點(diǎn)</param> /// <param name="posiY">Y軸坐標(biāo)點(diǎn)</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> /// 計(jì)算每個(gè)格子的數(shù)字標(biāo)識(shí) /// </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) //如果周?chē)鷽](méi)有雷,則計(jì)算周?chē)?個(gè)方向的格子 { 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)實(shí)現(xiàn),現(xiàn)已知現(xiàn)有代碼的定時(shí)器由問(wèn)題,暫時(shí)不支持Flag(旗子標(biāo)識(shí))。當(dāng)然代碼中還有其他不足的地方,游戲持續(xù)優(yōu)化中。。。
源代碼地址:MineSweeper-CShape_jb51.rar
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
c#之用戶定義的數(shù)據(jù)類(lèi)型轉(zhuǎn)換介紹
c#允許定義自己的數(shù)據(jù)類(lèi)型,這意味著需要某些工具支持在自己的數(shù)據(jù)類(lèi)型間進(jìn)行數(shù)據(jù)轉(zhuǎn)換。方法是把數(shù)據(jù)類(lèi)型轉(zhuǎn)換定義為相關(guān)類(lèi)的一個(gè)成員運(yùn)算符,數(shù)據(jù)類(lèi)型轉(zhuǎn)換必須聲明是隱式或者顯式,以說(shuō)明怎么使用它2014-01-01C#通過(guò)PInvoke調(diào)用c++函數(shù)的備忘錄的實(shí)例詳解
這篇文章主要介紹了C#通過(guò)PInvoke調(diào)用c++函數(shù)的備忘錄的實(shí)例以及相關(guān)知識(shí)點(diǎn)內(nèi)容,有興趣的朋友們學(xué)習(xí)下。2019-08-08WinForm中BackgroundWorker控件用法簡(jiǎn)單實(shí)例
這篇文章主要介紹了WinForm中BackgroundWorker控件用法,以一個(gè)簡(jiǎn)單實(shí)例形式分析了BackgroundWorker控件的定義、設(shè)置及使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08