C# - 帶你玩掃雷

掃雷游戲次乓,大家都應(yīng)該玩過吧!其實規(guī)則也很簡單孽水,可是我們想自己實現(xiàn)一個掃雷票腰,我們應(yīng)該怎么做呢?

TIM截圖20171010162223.png

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)化中。夕春。未荒。

源代碼Git地址:https://gitee.com/yugecse/MineSweeper-CShape

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市及志,隨后出現(xiàn)的幾起案子片排,更是在濱河造成了極大的恐慌,老刑警劉巖速侈,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件率寡,死亡現(xiàn)場離奇詭異,居然都是意外死亡倚搬,警方通過查閱死者的電腦和手機冶共,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來潭枣,“玉大人,你說我怎么就攤上這事幻捏∨枥纾” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵篡九,是天一觀的道長谐岁。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么伊佃? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任窜司,我火速辦了婚禮,結(jié)果婚禮上航揉,老公的妹妹穿的比我還像新娘塞祈。我一直安慰自己,他們只是感情好帅涂,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布议薪。 她就那樣靜靜地躺著,像睡著了一般媳友。 火紅的嫁衣襯著肌膚如雪斯议。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天醇锚,我揣著相機與錄音哼御,去河邊找鬼。 笑死焊唬,一個胖子當(dāng)著我的面吹牛恋昼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播求晶,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼焰雕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了芳杏?” 一聲冷哼從身側(cè)響起矩屁,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎爵赵,沒想到半個月后吝秕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡空幻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年烁峭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秕铛。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡约郁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出但两,到底是詐尸還是另有隱情鬓梅,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布谨湘,位于F島的核電站绽快,受9級特大地震影響芥丧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坊罢,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一续担、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧活孩,春花似錦物遇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至航夺,卻和暖如春蕉朵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阳掐。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工始衅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人缭保。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓汛闸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親艺骂。 傳聞我的和親對象是個殘疾皇子诸老,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 掃雷是我第一個取得顯著成就的游戲,但一直沒有機會寫一篇關(guān)于它的文章钳恕。前不久别伏,一家游戲媒體約我就掃雷做個采訪,列舉了...
    深加思考閱讀 2,758評論 4 5
  • 初級“掃雷”攻略及人生感悟 掃雷是很普遍的小游戲忧额,不受網(wǎng)絡(luò)限制厘肮。今兒我把掃雷的心得體會,作為初級攻略睦番,分享大家类茂。希...
    Recycler閱讀 3,634評論 16 10
  • 本人的小作品示启,手機版經(jīng)典掃雷游戲發(fā)布了奏夫! 1挑秉,目前僅支持Android系統(tǒng)光酣。蘋果機死貴高镐,玩不起,但也能開發(fā)啤月,請土豪...
    熊躍輝閱讀 1,264評論 0 49
  • 自從春節(jié)上來小女兒上了小熒星藝校以后煮仇,每個星期天的下午三點多出發(fā)前,她總是要鬧上一會兒小情緒谎仲。尤其是她會通過一些欲...
    正言鋒語閱讀 327評論 0 0
  • 01浙垫、人活這一輩子,到底是什么東西在驅(qū)動郑诺? 弗洛伊德認為是性夹姥。 阿德勒認為是自卑。 弗蘭克則認為是發(fā)現(xiàn)生命的意義辙诞。...
    桃夭A閱讀 284評論 1 1