欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C# 扫雷游戏 纯控制台 附源码

程序员文章站 2024-03-19 16:42:34
...

盆友在学C#+U3D,作为一枚java开发,我也研究了一下,估计以后也会更这方面的内容。百度能搜到的内容,我从来不写。
先贴两张效果图:
C# 扫雷游戏 纯控制台 附源码
C# 扫雷游戏 纯控制台 附源码
C# 扫雷游戏 纯控制台 附源码
C# 扫雷游戏 纯控制台 附源码
废话不多说,先贴代码,再盘出逻辑。
扫雷游戏的类

using System;
using System.Threading;

namespace ConsoleGame
{
    class FindBomb
    {
        Square[,] table ;

        //起始坐标
        int x;
        int y;
        //选中的坐标
        int chooseX = 1;
        int chooseY = 1;
        //背景颜色
        ConsoleColor foreColor;
        ConsoleColor backColor;
        //布雷密度
        int bombPersent;
        //DEBUG模式
        bool debug = false;
        //开始时间
        DateTime dt;
        //重绘标志位
        bool needPaint = true;
        //时间重绘标志位
        string timeLastPaint;


        public FindBomb(int x, int y, ConsoleColor foreColor, ConsoleColor backColor,int size, int bombPersent)
        {
            this.x = x;
            this.y = y;
            this.foreColor = foreColor;
            this.backColor = backColor;
            this.table = new Square[size + 2, size + 2];
            this.bombPersent = bombPersent;
        }

        public void Start()
        {
            BoxUtil.TypeWords(ConsoleColor.Blue, ConsoleColor.Black, 38, 5, "\t\t扫雷游戏", 50);
            BoxUtil.TypeWords(ConsoleColor.Blue, ConsoleColor.Black, 38, 7, "地图边长"+(table.GetLength(0)-2)+ "格 布雷密度"+bombPersent+"%", 50);
            BoxUtil.TypeWords(ConsoleColor.Blue, ConsoleColor.Gray, 38, 8, "移动光标:↑↓←→  标记:SPACE  翻开:ENTER", 50);
            BoxUtil.TypeWords(ConsoleColor.Blue, ConsoleColor.Black, 38, 10, "玩法说明:", 50);
            BoxUtil.TypeWords(ConsoleColor.Blue, ConsoleColor.Black, 38, 11, "翻开所有非地雷的方格即可完成游戏", 50);
            BoxUtil.TypeWords(ConsoleColor.Blue, ConsoleColor.Black, 38, 12, "不小心翻到地雷则游戏失败", 50);
            BoxUtil.TypeWords(ConsoleColor.Blue, ConsoleColor.Black, 38, 13, "临近地雷的方格,会显示附近的地雷数量", 50);
            BoxUtil.TypeWords(ConsoleColor.Blue, ConsoleColor.Black, 38, 28, "F1:开启/关闭DEBUG作弊模式 Esc:退出游戏", 50);
            PutBombs(bombPersent);
            PrintTable();
            dt = DateTime.Now;
            //开启计时线程
            ThreadStart clock = new ThreadStart(Timer);
            Thread thread = new Thread(clock);
            thread.Start();

            if (ReadKeyBoard())
            {
                thread.Abort();
                //success
                AfterAll(true);
            }
            else
            {
                thread.Abort();
                //lose
                AfterAll(false);
            }
        }

        public void Timer()
        {
            while (true)
            {
                PrintTable();
            }
        }

        public bool ReadKeyBoard()
        {
            while (true)
            {
                ConsoleKey ck = Console.ReadKey(true).Key;
                switch (ck)
                {
                    case ConsoleKey.Backspace:
                        break;
                    case ConsoleKey.Spacebar://标记
                        if (!table[chooseX, chooseY].isOpen)
                        {
                            table[chooseX, chooseY].isMarked = !table[chooseX, chooseY].isMarked;//标志位取反
                        }
                        break;
                    case ConsoleKey.Enter:
                        if (TryOpen(chooseX, chooseY))//打开
                        {
                            table[chooseX, chooseY].isMarked = false;
                            return false;//如果打开了Bomb则返回
                        }//如果没打开Bomb,检测是否胜利
                        if (IsSuccess())
                        {
                            return true;
                        }
                        break;
                    case ConsoleKey.LeftArrow:
                        if (chooseX > 1)
                            chooseX--;
                        break;
                    case ConsoleKey.UpArrow:
                        if (chooseY > 1)
                            chooseY--;
                        break;
                    case ConsoleKey.RightArrow:
                        if (chooseX < table.GetLength(0) - 2)
                            chooseX++;
                        break;
                    case ConsoleKey.DownArrow:
                        if (chooseY < table.GetLength(1) - 2)
                            chooseY++;
                        break;
                    case ConsoleKey.F1:
                        debug = !debug;
                        break;
                    case ConsoleKey.Escape:
                        return false;
                    default:
                        break;
                }
                needPaint = true;
            }
        }

        /// <summary>
        /// 判断是否已经完成
        /// </summary>
        public bool IsSuccess()
        {
            for (int i = 1; i < table.GetLength(0) - 1; i++)
            {
                for (int j = 1; j < table.GetLength(1) - 1; j++)
                {
                    if (table[i,j].isBomb==false && table[i, j].isOpen==false)
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        /// <summary>
        /// 填入或重置所有方块
        /// </summary>
        /// <param name="isBombPersent">标记为true的几率</param>
        public void PutBombs(int isBombPersent)
        {
            Random random = new Random();
            for (int i = 1; i < table.GetLength(0) - 1; i++)//忽略外围
            {
                for (int j = 1; j < table.GetLength(1) - 1; j++)
                {
                    if (random.Next() % 100 < isBombPersent)
                    {
                        table[i, j] = new Square(true);
                    }
                    else
                    {
                        table[i, j] = new Square(false);
                    }
                }
            }//设置好了Bomb,设置count
            for (int i = 1; i < table.GetLength(0) - 1; i++)
            {
                for (int j = 1; j < table.GetLength(1) - 1; j++)
                {
                    if (!table[i, j].isBomb)//如果不是bomb,计算count
                    {
                        table[i, j].count = CountNumb(i, j);
                    }
                }
            }
        }
        /// <summary>
        /// 计算某坐标周边Bomb数
        /// </summary>
        /// <returns>周边Bomb的数量</returns>
        private int CountNumb(int x, int y)
        {
            int result = 0;
            if (table[x - 1, y - 1].isBomb)
                result++;
            if (table[x, y - 1].isBomb)
                result++;
            if (table[x + 1, y - 1].isBomb)
                result++;
            if (table[x - 1, y].isBomb)
                result++;
            if (table[x + 1, y].isBomb)
                result++;
            if (table[x - 1, y + 1].isBomb)
                result++;
            if (table[x, y + 1].isBomb)
                result++;
            if (table[x + 1, y + 1].isBomb)
                result++;
            return result;
        }

        /// <summary>
        /// 绘制排查中的Table
        /// </summary>
        public void PrintTable()
        {
            string timePass = "用时: " + (DateTime.Now - dt).Minutes + "分 " + (DateTime.Now - dt).Seconds + "秒   ";
            if (!timePass.Equals(timeLastPaint))//如果绘制的时间想同,则不会绘制,避免闪烁
            {
                timeLastPaint = timePass;
                BoxUtil.TypeWords(ConsoleColor.Blue, ConsoleColor.Black, 40, 29, timePass, 0);
            }
            
            if (needPaint)
            {
                needPaint = false;

                Console.ResetColor();
                Console.SetCursorPosition(2, 1);
                if (debug)
                {
                    
                    Console.WriteLine("当前选择{2},{3}  计数:{4}  {5}     ", x, y, chooseX, chooseY, table[chooseX, chooseY].count, table[chooseX, chooseY].isBomb ? "炸弹" : "空地");
                }
                else
                {
                    Console.WriteLine("                                   ");
                }
                for (int x = 1; x < table.GetLength(0) - 1; x++)//外围不绘制
                {
                    for (int y = 1; y < table.GetLength(1) - 1; y++)
                    {
                        Console.SetCursorPosition(this.x + x * 2, this.y + y);
                        if (x == chooseX && y == chooseY)//如果正被选中
                        {
                            Console.ForegroundColor = foreColor;//不交换
                            Console.BackgroundColor = backColor;
                        }
                        else
                        {
                            Console.ForegroundColor = backColor;//交换前景色和背景色
                            Console.BackgroundColor = foreColor;
                        }
                        if (table[x, y].isOpen == false)//如果没有被开启
                        {
                            if (table[x, y].isMarked == false)
                            {
                                Console.Write('□');
                            }
                            else
                            {
                                Console.Write('※');
                            }
                        }
                        else//如果已经开启
                        {
                            if (table[x, y].count == 0)
                            {
                                Console.Write("  ");
                            }
                            else
                            {
                                Console.Write(table[x, y].count + " ");
                            }
                        }
                    }
                }
            }
        }

        public void AfterAll(bool isSuccess)
        {
            string timePass = (DateTime.Now - dt).Minutes + "分 " + (DateTime.Now - dt).Seconds + "秒";

            for (int x = 1; x < table.GetLength(0) - 1; x++)//外围不绘制
            {
                for (int y = 1; y < table.GetLength(1) - 1; y++)
                {
                    Console.SetCursorPosition(this.x + x * 2, this.y + y);
                    if (x == chooseX && y == chooseY)//如果正被选中
                    {
                        Console.ForegroundColor = foreColor;//不交换
                        Console.BackgroundColor = backColor;
                    }
                    else
                    {
                        Console.ForegroundColor = backColor;//交换前景色和背景色
                        Console.BackgroundColor = foreColor;
                    }
                    if (table[x, y].isBomb && table[x, y].isMarked)//如果是正确标记的地雷
                    {
                        Console.ForegroundColor = ConsoleColor.Green;
                        Console.Write('√');
                    }else if (table[x, y].isBomb == false && table[x, y].isMarked)//标错的
                    {
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.Write('×');
                    }else if (table[x, y].isBomb)//未标记的地雷
                    {
                        Console.ForegroundColor = ConsoleColor.DarkYellow;
                        Console.Write('○');
                    }
                    else if (table[x, y].isOpen)
                    {
                        if (table[x, y].count == 0)
                        {
                            Console.Write("  ");
                        }
                        else
                        {
                            Console.Write(table[x, y].count + " ");
                        }
                    }
                    else
                    {
                        Console.Write("□");
                    }
                    Console.ResetColor();
                    
                }
            }
            if (isSuccess)
            {
                BoxUtil.TypeWords(ConsoleColor.Blue, ConsoleColor.Black, 15, 3, "你成功了,用时" + timePass + " 请按任意键继续", 50);
            }
            else
            {
                BoxUtil.TypeWords(ConsoleColor.Blue, ConsoleColor.Black, 15, 3, "游戏结束,耗时" + timePass + " 请按任意键继续", 50);
            }
            Console.ReadKey();
        }

        /// <summary>
        /// 设置开启状态,并返回isBomb属性
        /// </summary>
        /// <returns></returns>
        public bool TryOpen(int x, int y)
        {
            //如果是空的 则向四周传递
            if (x>0 && y>0 && x < table.GetLength(0) - 1 && y < table.GetLength(1) - 1)
            {
                if (table[x, y].isOpen == false && table[x, y].count == 0 && table[x, y].isBomb == false)
                {
                    table[x, y].isOpen = true;
                    TryOpen(x - 1, y - 1);//↖
                    TryOpen(x, y - 1);    //↑
                    TryOpen(x + 1, y - 1);//↗
                    TryOpen(x - 1, y);    //←
                    TryOpen(x + 1, y);    //→
                    TryOpen(x - 1, y + 1);//↙
                    TryOpen(x, y + 1);    //↓
                    TryOpen(x + 1, y + 1);//↘
                }
                table[x, y].isOpen = true;
            }
            return table[x, y].isBomb;
        }    }
    /// <summary>
    /// 单个方块结构体
    /// </summary>
    public struct Square
    {
        public Square(bool isBomb)
        {
            this.isBomb = isBomb;
            isMarked = false;
            isOpen = false;
            count = 0;
        }
        public bool isBomb;
        public bool isMarked;
        public bool isOpen;
        public int count;
    }
}

调用方式:

 /// <summary>
        /// 扫雷调用
        /// </summary>
        public static bool PlayFindBombs()
        {
            BoxUtil.CoverBackendColor(ConsoleColor.Black, 60, 31);//擦除内容
            BoxUtil.TypeWords(ConsoleColor.DarkRed, ConsoleColor.Black, 14, 2, StrConst.MINE_CLEARANCE, 0);//绘制暗色LOGO
            //选择大小
            Thread.Sleep(500);
            BoxUtil.PrintBox(baseColor, baseColor, 20, 10, 40, 20, ' ', 20);//绘制内边框
            BoxUtil.TypeWords(ConsoleColor.Red, ConsoleColor.Black, 28, 12, "请选择大小", 0);
            BoxUtil.TypeWords(ConsoleColor.Red, ConsoleColor.Black, 14, 2, StrConst.MINE_CLEARANCE, 0);//绘制亮色LOGO
            ChooseList FindBombSizeMod = new ChooseList(new ListView[] {
                new ListView(6 ,"6  X 6  -适合学龄前儿童"),
                new ListView(10,"10 X 10 -大小和厕所接近"),
                new ListView(14,"14 X 14 -标准面积的擂台"),
                new ListView(22,"22 X 22 -谁选谁是大*")}, 23, 14);

            int sizeFlag = FindBombSizeMod.RefreshAndChoose();
            BoxUtil.CoverBackendColor(ConsoleColor.Black, 21, 11, 40, 21);//擦除内容

            //选择密度
            BoxUtil.TypeWords(ConsoleColor.Red, ConsoleColor.Black, 28, 12, "请选择密度", 0);
            ChooseList FindBombPersentMod = new ChooseList(new ListView[] {
                new ListView(10,"10%  -工兵有我方间谍"),
                new ListView(20,"20%  -军费被领导贪污"),
                new ListView(30,"30%  -训练有素的敌军"),
                new ListView(40,"45%  -敌军地雷不要钱")}, 24, 14);

            int persentFlag = FindBombPersentMod.RefreshAndChoose();//显示选择列表
            BoxUtil.CoverBackendColor(ConsoleColor.Black, 60, 31);//擦除内容
            BoxUtil.TypeWords(ConsoleColor.Black, ConsoleColor.Black, 14, 2, StrConst.MINE_CLEARANCE, 0);//绘制黑色LOGO
            FindBomb fb = new FindBomb(10, 5, ConsoleColor.White, ConsoleColor.Black, sizeFlag, persentFlag);//按大小创建地图
            fb.Start();//按密度排布地雷,并开始
            return ShowRetry();
        }

这里有点长,BoxUtil和ChooseList是我自己封装的绘图和选框工具类
反正java是这么玩的
缩略一下,就是这样即可

FindBomb fb = new FindBomb(10, 5, ConsoleColor.White, ConsoleColor.Black, 18/*边长18*18*/, 10/*密度10%*/);
            fb.Start();

工具类我在最下面贴出吧,其实可以用Console.Write和Console.SetCursorPosition函数替换掉

本游戏的特点:
1.完全使用原生和控制台实现。
2.布雷密度和地图大小可调节。
3.操作使用上下左右,并且基本复刻了原版扫雷。
4.F1开启debug模式,可以作弊。
5.多线程显示游戏进行时间。
6.缺点,没有保存、暂停、排行榜功能(因为懒)
简单说明一下逻辑和用到的方法:
1.显示扫雷的框框:由于右下角实时显示游戏时间,所以使用了一个标志位来判断是否进行绘图,绘图的条件是:1.如果秒数变了,绘制。2.如果按下了上下左右等按键,绘制。3.如果秒数没变,上下左右也没按,不绘制。
2.扫雷框框的结构:一个二维数组,java的小伙伴们注意了,是C#的二维数组喔,C#还有个交错数组。每一个数组元素是一个Square结构体。
那说说Square结构体的设计:

public struct Square
    {
        public Square(bool isBomb)
        {
            this.isBomb = isBomb;
            isMarked = false;
            isOpen = false;
            count = 0;
        }
        public bool isBomb;
        public bool isMarked;
        public bool isOpen;
        public int count;
    }

通过一系列布尔值,标注了是否为炸弹isBomb,是否被玩家标记isMarked,是否被翻开isOpen,以及如果不是炸弹,那么数字应该是几count。他的构造方法是为二维矩阵布置地雷时调用。

那么地雷如何布置的呢,使用了Random,根据创建类对象时提供的密度作为比例布置地雷

诶呦,忘记说了,这里用了一个巧妙的设计,来避免数组下标越界异常(称呼来自java,c#是不是叫这个不清楚)

简单的来理解一下,首先熟悉一下扫雷的规则
C# 扫雷游戏 纯控制台 附源码
如图,如果一个黑色的小方块不是炸弹,那么我们需要在其中显示他周围炸弹的数量,如2表示他周边8个格子中有2个炸弹
C# 扫雷游戏 纯控制台 附源码
那么就会出现数组下标越界的麻烦事
C# 扫雷游戏 纯控制台 附源码
对,当你在处理边缘上的点的时候,比如当前选择x,y这个点,判断x-1,y-1这个点时,C#会告诉你x数组没那么长,你这个x-1不在数组下标范围里。

那如何化解这个尴尬,显然,可以通过if来判断,如果这个点在边缘上,我们就使用另一套判别计算方法
C# 扫雷游戏 纯控制台 附源码
那么你需要写几种计算方法呢,9种,分别是黄色的-正常,绿色的-不去找他的y-1,等等等,这显然非常麻烦。

这个办法虽然繁琐,但是是可行的,他可以简单一些,我们加上if判断,如果这个点的x-1已经小于0了,不处理,如果大于length了也不处理。
那么每个点的8个周边点,请你都套上最少带一个&&的if语句

这样是不是简单了一些,至少我不用写9种判别方法了,但是他可以更容易,比如我如果只处理黄色部分呢
C# 扫雷游戏 纯控制台 附源码
于是我想到了这种设计,如图,黄色既是布雷区域,也是显示区域,相当于对于一个长度为[x,y]的二维数组,只显示 1→x-1,1→y-1的部分,外围部分设置为空地。这样在计数的时候,也只处理黄色部分的方块,看看他周围有几个雷,而不用担心数组下标越界的问题。
所以我的显示区,布雷区,计数区,都是黄色区域,通过加宽加高这个二维数组,绕开了下标越界。

于是乎一切都变得简单了,剩下的内容主要也只剩显示了
显示我只写了一个方法,在AfterAll(bool isScuess)中,这个方法通过一个布尔来区分是游戏状态还是游戏结束状态,绘出游戏结束状态:
C# 扫雷游戏 纯控制台 附源码
如图如果我翻到了炸弹,或者翻完了框框,会显示当前选择,炸弹位置,标错的标记,标对的标记。

这块其实很纠结,写一个方法有点乱,写两个方法代码又大量重复,最终我写成了一个方法
本人的注释写的很清晰 个人认为代码可读性也基本良好
再贴一遍:

public void AfterAll(bool isSuccess)
        {
            string timePass = (DateTime.Now - dt).Minutes + "分 " + (DateTime.Now - dt).Seconds + "秒";

            for (int x = 1; x < table.GetLength(0) - 1; x++)//外围不绘制
            {
                for (int y = 1; y < table.GetLength(1) - 1; y++)
                {
                    Console.SetCursorPosition(this.x + x * 2, this.y + y);
                    if (x == chooseX && y == chooseY)//如果正被选中
                    {
                        Console.ForegroundColor = foreColor;//不交换
                        Console.BackgroundColor = backColor;
                    }
                    else
                    {
                        Console.ForegroundColor = backColor;//交换前景色和背景色
                        Console.BackgroundColor = foreColor;
                    }
                    if (table[x, y].isBomb && table[x, y].isMarked)//如果是正确标记的地雷
                    {
                        Console.ForegroundColor = ConsoleColor.Green;
                        Console.Write('√');
                    }else if (table[x, y].isBomb == false && table[x, y].isMarked)//标错的
                    {
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.Write('×');
                    }else if (table[x, y].isBomb)//未标记的地雷
                    {
                        Console.ForegroundColor = ConsoleColor.DarkYellow;
                        Console.Write('○');
                    }
                    else if (table[x, y].isOpen)
                    {
                        if (table[x, y].count == 0)
                        {
                            Console.Write("  ");
                        }
                        else
                        {
                            Console.Write(table[x, y].count + " ");
                        }
                    }
                    else
                    {
                        Console.Write("□");
                    }
                    Console.ResetColor();
                    
                }
            }
            if (isSuccess)
            {
                BoxUtil.TypeWords(ConsoleColor.Blue, ConsoleColor.Black, 15, 3, "你成功了,用时" + timePass + " 请按任意键继续", 50);
            }
            else
            {
                BoxUtil.TypeWords(ConsoleColor.Blue, ConsoleColor.Black, 15, 3, "游戏结束,耗时" + timePass + " 请按任意键继续", 50);
            }
            Console.ReadKey();
        }

大意是,类变量chooseX,chooseY记录了当前选择的点,如果是当前选择的点,则交换前景色和背景色,如果不是,则判断是否没开,显示方块,是否开了,显示空白或数字。如果是游戏结束状态呢,则显示开没开,标没标错,已经翻开的显示数字,没翻开的依旧显示方块。晕

好,显示问题也解决一部分了,在一个就是多线程实时显示游戏时间的问题,由于控制台的特性,我们不能单纯使用两个线程来分别绘制时间和扫雷块,必须全图一次绘制,类似于“垂直同步”,这是因为控制台似乎是单线程的,如果分别绘制,会有这样的问题
C# 扫雷游戏 纯控制台 附源码
这张图是我模拟出来的,不过大概就是这个意思,画的很像了,本该在右下角显示的时间,却显示到这里来了。这是因为两个线程同步运行产生的安全问题,本质是Console只有一个类,他是相当于控制台是单线程调用的
比如:
A线程 – 逐一定位在table的某个方块上,并绘制方块
B线程 – 定位到右下角,绘制游戏时间

那么会有这种情况:
B线程先定位到右下角 – B线程刚刚输出“用时:”
A线程抢占了控制台资源 – 定位到左边扫雷框 – A线程输出了一个框框
B线程抢回了控制台资源 – B线程继续输出几分几秒

由于控制台的定位已经被A线程定位到左侧扫雷框了,所以B线程的打印接在了A线程后面。

于是就发生了线程安全问题,我们可以通过锁机制,让A线程抢占锁资源,绘制完成再释放锁资源,或者通过将绘制过程整合为一个线程,来避免线程的安全问题。我使用了后者

是时候说明游戏整体逻辑了:
C# 扫雷游戏 纯控制台 附源码
进入游戏首先new出数组,布雷,并把他画出来,开启计时线程,每秒更新标志位
然后ReadKeyBoard()方法是一个死循环,只有在游戏成功或失败时才会 返回true或者false,获取他的返回值的过程就是整个游戏过程,他会在每次你敲下按键时判断你赢没赢,或者死没死。
PrintTable方法在标志位改变时绘制屏幕,也就是时间过去了一秒或者你按下了按键。死循环执行,游戏结束时停止执行。

ReadKeyBoard,IsSuccess,PutBombs等方法,不再赘述,逻辑简单,注释清晰。

最后说个TryOpen的传递。
C# 扫雷游戏 纯控制台 附源码
如图所示,我只翻开了一块,却打开了一大片,通过分析得知,如果你翻开了一块计数为0的方块,他会向四周传递,一直传递到有计数的方块为止。所以我设计了TryOpen方法

        /// <summary>
        /// 设置开启状态,并返回isBomb属性
        /// </summary>
        /// <returns></returns>
        public bool TryOpen(int x, int y)
        {
            //如果是空的 则向四周传递
            if (x>0 && y>0 && x < table.GetLength(0) - 1 && y < table.GetLength(1) - 1)
            {
                if (table[x, y].isOpen == false && table[x, y].count == 0 && table[x, y].isBomb == false)
                {
                    table[x, y].isOpen = true;
                    TryOpen(x - 1, y - 1);//↖
                    TryOpen(x, y - 1);    //↑
                    TryOpen(x + 1, y - 1);//↗
                    TryOpen(x - 1, y);    //←
                    TryOpen(x + 1, y);    //→
                    TryOpen(x - 1, y + 1);//↙
                    TryOpen(x, y + 1);    //↓
                    TryOpen(x + 1, y + 1);//↘
                }
                table[x, y].isOpen = true;
            }
            return table[x, y].isBomb;
        }
    }

不难看出这是一个递归调用方法,其实这个方法耗费了本架构师好长时间,他看起来简单,写起来还真不容易,原则上是传递打开方块这个方法,如果计数是0,要继续传递,直到把空的一片全翻开。
这里有个递归的坑,即如果A是空的,传给了他边上的B,B也是空的,又会传给A,这样死循环,来回来去的传,会栈溢出,解决很简单,在合适的时机将isOpen设置为true,并且不传递给isOpen的方块即可。
记得限制传递范围,不要传递到外圈,不然又要下标越界。

基本写到这了,为了方便拿来主义能拿来就用,我贴一下BoxUtil工具类中用到的方法

/// <summary>
        /// 模拟打字机效果,一个字一个字的敲出文本
        ///另,只贴出了重载方法中最大的一个,最后一个delay可以设置为0则无延迟
        /// </summary>
        public static void TypeWords(ConsoleColor foreColor, ConsoleColor backColor, int startX, int startY, string words,int delay)
        {
            Console.ForegroundColor = foreColor;
            Console.BackgroundColor = backColor;
            int X = startX * 2;
            int Y = startY;
            Console.SetCursorPosition(X, Y);

            char[] ch = words.ToCharArray();

            foreach (char c in ch)
            {
                if (c=='\r')
                {
                    Y++;
                    Console.SetCursorPosition(X, Y);
                }
                else if(c == '\n'){
                    continue;
                }
                else
                {
                    Console.Write(c);
                }
                Thread.Sleep(delay);
            }

            Console.ResetColor();
        }

如此可以简单重现出本游戏了,祝君c#学习愉快