Windows 自带的扫雷是一代经典的游戏,本文介绍如何采用 C# 重写经典的扫雷程序,一起试试吧。

实验目的

  • 掌握窗体设计步骤、窗体调用
  • 学习Form窗体中控件的灵活使用,能基本理解所学代码的基本含义
  • 会使用button、panel、label、textbox、menestrip等按钮,并会使用二维数组的定义与使用二维数组定义雷区,通过enable按钮控制器可使用性
  • 能通过Loction、size等控制界面的合理大方
  • 能使用imagebox、timer等控件的加入,并能通过属性按钮来完成所需要的结果
  • 自学制作过程中不会控件的使用。

实验内容

  • 通过一学期所学的C#知识来完成基本的可视化程序设计需求
  • 仿Windows系统扫雷,完善扫雷游戏:添加菜单(包含快捷键、热键等)、实现初级、中级、高级及自定义扫雷;添加扫雷英雄榜;实现各方块中标识周边雷数量的文字采用不同颜色的数字显示

程序开发操作步骤

说明:以下共分为4各部分来介绍,分别为:扫雷主界面、自定义窗体、请输入窗体、英雄榜窗体等.

主界面

  • 打开Microsoft Visual Studio 2008,选择c#编译环境,创建一个项目,使用Windows窗体应用程序,并命名为“扫雷_xxb”
  • 点击Form1,从text属性栏中的Form1改为扫雷,icon属性中加入mine.ico图片。再将Font属性改为新宋体,9pt,加粗。并向其中添加一个timer1控件其属性中的Interval属性改为1000.添加一个imageList1控件,添加图片。
  • 界面设计。根据扫雷要求,设计所需界面,(下图不全,详情请看压缩包—附加图片)
  • 按照如上设计分别加入menestrip控件,设计好菜单为:游戏(&G):新游戏、初级、中级、高级、自定义…、英雄榜…、退出;帮助(&H):游戏说明、关于扫雷…、关于版权…等。按顺序先把前者属性栏中的ShowShortcutKeys属性设置为True,ShortcutKeys 分别设置为Ctrl+N、Ctrl+L、Ctrl+M、Ctrl+H、Ctrl+D、Ctrl+C、Ctrl+E;后者分别设置为Ctrl+I、Ctrl+F、Ctrl+B。
  • 向Form1中拖入一个menuStrip控件,Loction和size属性按上图设计,分别向其中加入1个button控件和2个label控件。设置label控件的Font属性为宋体,12pt,加粗,其中ForeColor设置为Red。并在buttion窗体的text设置为空,Image加入face.bmp图片。
  • 再向Form1窗体中拖入一个button控件,其Text属性改为标示出雷,Font属性设置为新宋体,9pt,加粗。其中各个控件的位置是一定的。
  • 选中Form1在其属性事件中单击Load,添加如下代码:
        private void Form1_Load(object sender, EventArgs e)   //Form1_Load,用于基本开始界面的设置
        {
            string path;   //定义字符串,用于存放“扫雷英雄榜.txt”的位置
            path = System.Windows.Forms.Application.StartupPath;    // bin路径
            if (!File.Exists(path + "\\扫雷英雄榜.txt"))   //if语句,主要是通过检测是否有扫雷英雄榜.txt,如果没有。。。
                File.Create(path + "\\扫雷英雄榜.txt");    //创建扫雷英雄榜.txt
            初级ToolStripMenuItem.Checked = true;   //使菜单中的初级可用
            Load_Mine();   //用于解决开始时单击任意按钮均胜利的问题
            GameInit();    //游戏初始化
            timer1.Enabled = true;   //开启时钟计时
        }
  • 设置button1按钮:在点击事件下加入如下代码:
        private void button1_Click(object sender, EventArgs e)   //这是一个开始按钮,单击即开始游戏
        {
            button1.Image = Image.FromFile("face.bmp");   //用于控制开始按钮的图标,开始时位笑脸
            DelAllMines();   //删除所有的雷区控件(很重要,用于不让其改变等级时有参与)
            RestMine = MineNum;   //用于记录雷数,开始时剩余雷数为总雷数
            CostTime = 0;   //用于记录从开始到现在游戏用时,开始为0
            label1.Text = CostTime.ToString();   //label1窗体中用于显示所用的时间
            GamesBegin();   //开始游戏
            GameInit();   //游戏初始化
            timer1.Enabled = true;   //触发计时器
        }
  • 设置button2按钮:在点击事件下加入如下代码:
        private void button2_Click(object sender, EventArgs e)   //显示所有的雷数
        {
            show();   //将地图中所有雷标识出来
        }

        private void show()//将地图中所有雷标识出来
        {
            for (int i = 0; i < XNum; i++)
                for (int j = 0; j < YNum; j++)
                    if (Convert.ToInt16(Mines[i, j].Tag) == 1)
                    {
                        //==1时,代表这个位置是地雷
                        Mines[i, j].BackgroundImage = Image.FromFile("mine.bmp");
                    }
        }
  • 分别通过菜单栏中选项的单击添加如代码:
        private void 新游戏ToolStripMenuItem_Click(object sender, EventArgs e)    //新游戏按钮的定义
        {
            button1.Image = Image.FromFile("face.bmp");   //开始时,让button1按钮的Image设为face.bmp
            DelAllMines();   //删除所有的雷区控件(很重要,用于不让其改变等级时有参与)
            RestMine = MineNum;   //计数剩余的雷数
            CostTime = 0;   //计数所用时间
            label1.Text = CostTime.ToString();   //用于在开始界面显示所用的时间
            GamesBegin();   //开始游戏
            GameInit();//游戏初始化
            timer1.Enabled = true;   //触发计时器
        }

        private void 退出ToolStripMenuItem_Click(object sender, EventArgs e)   //定义退出
        {
            Application.Exit();   //单击退出
        }

        private void 自定义ToolStripMenuItem_Click(object sender, EventArgs e)    //自定义按钮的定义
        {
            button1.Image = Image.FromFile("face.bmp");   //开始时,让button1按钮的Image设为face.bmp
            初级ToolStripMenuItem.Checked = false;   //初级按钮不可用
            中级ToolStripMenuItem.Checked = false;   //中级按钮不可用
            高级ToolStripMenuItem.Checked = false;   //高级按钮不可用
            自定义ToolStripMenuItem.Checked = true;   //自定义按钮可用
            Form zdy = new Form2();   //通过定义调用新的Form窗体
            zdy.ShowDialog();   //显示自定义窗体
            if (zdy.DialogResult == DialogResult.OK)   //如果在自定义窗体中单击 确定
                zdyGames();   //开始自定义游戏
        }

        private void zdyGames()   //自定义游戏
        {
            DelAllMines();   //删除残余雷片
            XNum = zdyXNum;   //定义雷区的列数
            YNum = zdyYNum;   //定义雷区的行数
            MineNum = zdyMineNum;   //定义总雷数
            label1.Text = CostTime.ToString();   //用于在开始界面显示所用的时间
            GamesBegin();   //开始游戏
            GameInit();//游戏初始化
            timer1.Enabled = true;   //触发计时器
        }

        private void 关于扫雷ToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("游戏简介:\r\n\r\n------------------------------------------\r\n\r\n扫雷是单人计算机游戏,游戏的用户界面十分友好!\r\n\r\n------------------------------------------\r\n\r\n当游戏方格组成的战场构成开始时,计算机将固定的地雷数随机的放到战场的方格。\r\n\r\n------------------------------------------\r\n\r\n此游戏还允许玩家指定雷区中的地雷数,让您尽情玩到底!!\r\n\r\n------------------------------------------\r\n\r\n    据有关调查显示:此游戏具有开发智力少儿的功能!!  ", " 说 明:");   //显示游戏简介
        }

        private void 关于游戏ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show("关于版权:\r\n\r\n本游戏是防Micosoft软件扫雷游戏 由\r\n\r\n    郑州轻工业学院 \r\n    计算机与通信工程学院\r\n    计算机科学与技术10-01班\r\n    谢先斌\r\n\r\n改编用于C#作业使用!特此声明!\r\n\r\n  辅导老师:李灿林\r\n\r\n声明:因版权问题敬请访问个人网站: http://www.xiexianbin.co.co \r\n\r\n                            谢谢合作!!\r\n\r\n    如有雷同,纯属巧合", "版权声明:");   //显示游戏版本及版权说明
        }

        private void 关于扫雷ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show("游戏说明:\r\n\r\n  ▲在“游戏”菜单上,单击“开局”;\r\n  ▲要启动计时器,请单击游戏区中的任何方块;\r\n\r\n注意:\r\n\r\n  ▲ 单击就可以揭开方块,如果揭开的是地雷,您便输掉游戏;\r\n  ▲ 如果方块上出现数字,则表示在它周围的八个方块中共有多少颗地雷(0-8)如有都可将用不同的图片标示;\r\n  ▲ 要标记您认为可能有地雷的方块,请右键单击它;\r\n  ▲ 游戏区包括菜单、雷区、地雷计数器、计时器和开始按钮,请按游戏规则游戏!\r\n  ▲本游戏的英雄榜是记录游戏者的成绩使用,因能力有限仅支持初中高级的游戏记录。并设置了重置功能,方便玩家。\r\n\r\n   谢谢您对本游戏的支持 小谢 祝 您游戏愉快!", "游戏说明:");   //游戏的使用说明,简介游戏使用问题
        }

        private void 扫雷英雄榜ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Form herof = new Form3();   //定义新的窗体,通过关联调用
            herof.Show();   //显示扫雷英雄棒的窗体
        }

        private bool IsInMineArea(int row, int col)   //判断是否已出雷区
        {
            return (row >= 0 && row < XNum && col >= 0 && col < YNum);   //返回true or false
        }

        private void button1_Click(object sender, EventArgs e)   //这是一个开始按钮,单击即开始游戏
        {
            button1.Image = Image.FromFile("face.bmp");   //用于控制开始按钮的图标,开始时位笑脸
            DelAllMines();   //删除所有的雷区控件(很重要,用于不让其改变等级时有参与)
            RestMine = MineNum;   //用于记录雷数,开始时剩余雷数为总雷数
            CostTime = 0;   //用于记录从开始到现在游戏用时,开始为0
            label1.Text = CostTime.ToString();   //label1窗体中用于显示所用的时间
            GamesBegin();   //开始游戏
            GameInit();   //游戏初始化
            timer1.Enabled = true;   //触发计时器
        }

        private void DelAllMines()   //删除所有的雷区
        {
            for (int i = 0; i < XNum; i++)
                for (int j = 0; j < YNum; j++)   //二维数组逐个删除
                {
                    Button n = new Button();   //定义一个新的button
                    n = (Button)Mines[i,j];   //强制类型转换
                    this.Controls.Remove(Mines[i, j]);   //删除所指雷区
                }
        }

        private void GamesBegin()   //用于开始游戏,主要是定义雷区
        {
            Turn = new int[XNum, YNum];   //定义新的二维数组
            Mines = new Button[XNum, YNum];   //定义按钮
            for (int x = 0; x < XNum; x++)
                for (int y = 0; y < YNum; y++)   //通过二维数组,逐个定义初始化button按钮
                {
                    Mines[x, y] = new Button();
                    this.Controls.Add(Mines[x, y]);   //增加新按钮
                    Mines[x, y].Left = 10 + MineWidth * x;   //定义雷区开始在Form窗体中的左边界
                    Mines[x, y].Top = 65 + MineWidth * y;   //定义雷区开始在Form窗体中的上边界
                    Mines[x, y].Width = MineWidth;   //定义雷块的宽度
                    Mines[x, y].Height = MineWidth;   //定义雷块的高度
                    Mines[x, y].Font = new Font("宋体", 10.5F, FontStyle.Bold, GraphicsUnit.Point, ((byte)(134)));   //定义字体
                    Mines[x, y].BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch;   //定义backgroundimageLayout
                    Mines[x, y].Name = "Mines" + (x + y * XNum).ToString();   //定义雷区的名字
                    Mines[x, y].MouseUp += new MouseEventHandler(bt_MouseUp);   //定义单击事件
                    Mines[x, y].Visible = true;   //控制Mines按钮的可见
                }
            detform();
        }

略大量代码。。。

自定义窗体

  • 通过单击项目,添加Windows窗体,添加一个Windows窗体,命名为Form2(可能程序中有点问题,因为我改过的),把其text属性改为:自定义. . .,并在icon中添加图片。

  • 添加一个groupbox窗体,把其text属性改为:请自定义难度. . .,在其中添加3个label控件和3个textbox控件,按如图所示布局,再添加2个button按钮,分别把其属性改为:确定、取消。

  • 在Form1的Load属性中添加如下代码:

        private void Form2_Load(object sender, EventArgs e)
        {
            textBox1.Text = "";
            textBox2.Text = "";
            textBox3.Text = "";
        }

通过button1和button2按钮的点击属性添加如下代码:

        private void button1_Click(object sender, EventArgs e)
        {
            if (textBox1.Text == "" || textBox2.Text == "" || textBox3.Text == "")
            {
                MessageBox.Show("不要激动!请输入完整!!", "提示", MessageBoxButtons.OK);
                return;
            }
            int h, w, m;
            h = Convert.ToInt16(textBox1.Text);
            w = Convert.ToInt16(textBox2.Text);
            m = Convert.ToInt16(textBox3.Text);

            if (m > h * w)
            {
                MessageBox.Show("雷量太大,请重新输入!", "提示", MessageBoxButtons.OK);
                return;
            }
            if (h < 9 || h > 24 || w < 9 || w > 40 || m < 10 || m > 668)
            {
                MessageBox.Show("系统错误,请按要求填写!谢谢!!", "提示", MessageBoxButtons.OK);
                return;
            }

            扫雷_xxb.Form1.zdyXNum = w;
            扫雷_xxb.Form1.zdyYNum = h;
            扫雷_xxb.Form1.zdyMineNum = m;

            this.Close();
            DialogResult = DialogResult.OK;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            this.Close();
            DialogResult = DialogResult.Cancel;
        }

英雄榜窗体

  • 同上添加Form3窗体,改变其中的Image和text属性,设计好其大小:

  • 在From3中添加label,textbox,button按钮各一个。在Form3的Load的事件中添加如下代码:

        private void Form3_Load(object sender, EventArgs e)
        {
            string path1, mr1 = "名字\t\t成绩\r\n", mystr1 = "";
            path1 = System.Windows.Forms.Application.StartupPath; 
            FileStream fs1 = File.OpenRead(path1 + "\\扫雷英雄榜.txt");
            StreamReader sr1 = new StreamReader(fs1);
            fs1.Seek(0, SeekOrigin.Begin);
            while (sr1.Peek() > -1)
            {
                mystr1 += sr1.ReadLine() + "\t\t";
                mystr1 += sr1.ReadLine() + "\r\n";
            }
            sr1.Close();
            fs1.Close();
            textBox1.Text = mr1 + mystr1;
        }

在button1_click事件中添加如下代码:

        private void button2_Click(object sender, EventArgs e)
        {
            this.Close();
        }

请输入窗体:

  • 添加窗体Form4,如图改变Icon属性和text属性,在分别添加一个label控件,一个textbox控件和一个button控件,把label的text改为:江山代有人才出,各领风骚数百年!!,button1的text属性改为确定。并在picturebox属性中添加图片

胜利女神像:

  • 在button1_Click事件下添加如下代码:
        private void button1_Click(object sender, EventArgs e)
        {
            string path = System.Windows.Forms.Application.StartupPath; // bin路径 
            string name;
            name = textBox1.Text;
            if (name == "") 
            {
                this.Close(); 
                return;
            }
            string  mystr = "";//mr = "名字\t\t成绩\r\n",
            path = System.Windows.Forms.Application.StartupPath; // bin路径 
            FileStream fs = File.OpenRead(path + "\\扫雷英雄榜.txt");
            StreamReader sr = new StreamReader(fs);
            fs.Seek(0, SeekOrigin.Begin);
            while (sr.Peek() > -1)
            {
                mystr += sr.ReadLine() + "\t\t";
                mystr += sr.ReadLine();
            }
            sr.Close();
            fs.Close();
            mystr += name + "\r\n" + 扫雷_xxb.Form1.CostTime.ToString() + "\r\n";
            FileStream fsa = File.OpenWrite(path + "\\扫雷英雄榜.txt");
            StreamWriter sw = new StreamWriter(fsa);
            sw.WriteLine(mystr);
            sw.Close();
            fsa.Close();
            this.Close();
        }

程序运行结果与分析

  • 单机游戏的运行结果如图:

实验心得

本次在做C#扫雷的过程中使我明白了,只要努力的不断接受新知识才能适应时代的发展,才能掌握更多的知识去迎接新的挑战。

从接到C#作业的那一刻,到现在我成扫雷的完整版的整个过程中自己遇到啦很多的困难,也不止一次的向老师,同学们求助,在此表示感谢!!谢谢他们对我的支持。

在第一次的考虑雷区的重载问题,即队已经定一定二维数组的在此初始化,经过向老师求助,明白只要通过 this.Controls.Remove(Mines[i, j]);,从界面上删掉在初级时加进来的雷方块控件。

当然,在其中还有很多的问题,如怎么调用不同的窗体,怎么在程序的运行过程中改变图片,如何通过Loction、Size、ActiveForm等来控制所需控件在Form窗体中的位置,大小,和控制Form控件的大小。

总之:不经过实践就不知道自己有多少东西不会,自己有多少东西需要继续学习,相信我会继续努力,继续前行,继续在未知中求索。

源码地址

百度网盘:http://pan.baidu.com/share/link?shareid=62587&uk=1930577918

github:https://github.com/xiexianbin/minesweeping

参考

迁移自谢先斌的博客:http://blog.sina.com.cn/s/blog_002e203101013gn9.html

完毕。