C#窗体连连看小游戏(超详细)(上)

简介: C#窗体连连看小游戏(超详细)

成品链接


https://download.csdn.net/download/weixin_45525272/13713452


PS:载完了如果报错,参考这篇博文改错:


https://blog.csdn.net/weixin_45525272/article/details/117417379


项目展示




各部分功能解释


一、项目分析


系统设计思路


(1)设计连连看功能类:LinkClass,Line


(2)设计两个事件:成功事件和失败事件


(3)成功事件:1.垂直事件2.水平事件3.一个拐点事件4.两个拐点事件


(4)失败事件:


  1. 一个按钮点击两次


  1. 点击的两个按钮所带图片不一样


  1. 点击两个具有相同图案的按钮但没有通路


(5)可以选择三种行和列:88,1010,12*12


(6)可以选择两种模式:数字,图片


(7)设计随机数并且随机数要配对


(8)赋予按钮应有的相关属性


(9)计时器设计,暂停按钮时计时器停止计时,开始按钮显示继续游戏


(10)每消除一对图片,计分加100


(11)重新排列功能,每重新排列一次总得分减400


二、项目设计


一、难度和模式


用两个comboBox分别设计行和列,图片或者数字模式


if (comboBox1.Text == ""|| comboBox2.Text=="")
{
   MessageBox.Show("请选择难度或者模式", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
   // 选择的关卡难度文本
   string Diffcult = comboBox1.Text;
   //根据对应难度初始化数组并给n赋值
   switch (Diffcult)
   {
       case "8*8":
           hang = 8;
           break;
       case "10*10":
           hang = 10;
           break;
       case "12*12":
           hang = 12;
           break; 
   }
//判断用户选的是图片还是数字
         string mul = comboBox2.Text;
         if (mul == "数字")
         {
             Button btnLinker = new Button() // 设置每个小按钮的相关属性
             {
                 Text = rnd.ToString(),//按钮上显示的是随机数
                 Tag = img,//标签
                 Name = string.Format("{0},{1}", row, col),//每个按钮命名
                 Dock = DockStyle.Fill,//调整控件层次,填充
                 Margin = new Padding(1),//调整外边距
             };
             btnLinker.Click += BtnLinker_Click; // 让每个按钮都拥有点击事件
             this.TableLp.Controls.Add(btnLinker, col, row);//将按钮加进去并赋给位置
         }
         else//即用户选择了图片
         {
             Button btnLinker = new Button() // 设置每个小按钮的相关属性
             {
                 BackgroundImage = GetImageByName(img),//给按钮填充背景图片
                 BackgroundImageLayout = ImageLayout.Stretch,//背景图片拉申
                 Tag = img,
                 Name = string.Format("{0},{1}", row, col),
                 Dock = DockStyle.Fill,
                 Margin = new Padding(1),
             };
             btnLinker.Click += BtnLinker_Click; // 让每个按钮都拥有点击事件
             this.TableLp.Controls.Add(btnLinker, col, row);
         }
     }
   }
   this.LinkObj.LinkBoard = linkBoard;//初始化棋盘
   this.TableLp.Enabled = false;


2、进度条分数模块


用计时器线程来计时,进行进度条与时间标签的实时更新


        // 计时器线程
        Thread timeDelay;       // 线程变量
        int timetemp = 0;       // 计时器中间变量
 // 定时器事件:时间剩余
        void timeDelay_Tick()
        {
            //ma.WaitOne(); // 根据是否收到信号判断是否阻塞当前线程
            while (this.curDelay > 0 )   // 当前时间大于零
            {
                if (!isClosing)
                {
                    this.curDelay--;
                    this.pbTimeDelay.Value = this.curDelay;
                    this.lblTimeDelay.Text = this.curDelay.ToString();
                }
                Thread.Sleep(1000);
            }
            if (!isClosing)
            {
                this.btnStart.Enabled = true;
                this.TableLp.Enabled = false;
            }
        }


3、开始游戏(游戏初始化)


清除残余,并且设置新的表格


this.TableLp.ColumnStyles.Clear();
                this.TableLp.RowStyles.Clear();
                this.TableLp.Controls.Clear();
this.TableLp.ColumnCount = hang + 2;
                this.TableLp.RowCount = hang + 2;
设置按钮及边框所占的比列
for (int i = 0; i < hang + 2; i++)
                {
                    //设置等宽等高  边缘2,里面内容10
                    if (i == 0 || i == hang + 1)
                    {
                        this.TableLp.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 2));//占总画面的2%
                        this.TableLp.RowStyles.Add(new RowStyle(SizeType.Percent, 2));
                    }
                    else
                    {
                        this.TableLp.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 10));//占总画面的10%
                        this.TableLp.RowStyles.Add(new RowStyle(SizeType.Percent, 10));
                    }
                }
为每个按钮赋给图片或者相应的数字
 string img = string.Format("_1_{0}", rnd.ToString("00"));//给图片命名
                        linkBoard[row, col] = rnd;//赋给每个位置随机数字
                        //判断用户选的是图片还是数字
                        string mul = comboBox2.Text;
                        if (mul == "数字")
                        {
                            Button btnLinker = new Button() // 设置每个小按钮的相关属性
                            {
                                Text = rnd.ToString(),//按钮上显示的是随机数
                                Tag = img,//标签
                                Name = string.Format("{0},{1}", row, col),//每个按钮命名
                                Dock = DockStyle.Fill,//调整控件层次,填充
                                Margin = new Padding(1),//调整外边距
                            };
                            btnLinker.Click += BtnLinker_Click; // 让每个按钮都拥有点击事件
                            this.TableLp.Controls.Add(btnLinker, col, row);//将按钮加进去并赋给位置
                        }
                        else//即用户选择了图片
                        {
                            Button btnLinker = new Button() // 设置每个小按钮的相关属性
                            {
                                BackgroundImage = GetImageByName(img),//给按钮填充背景图片
                                BackgroundImageLayout = ImageLayout.Stretch,//背景图片拉申
                                Tag = img,
                                Name = string.Format("{0},{1}", row, col),
                                Dock = DockStyle.Fill,
                                Margin = new Padding(1),
                            };
                            btnLinker.Click += BtnLinker_Click; // 让每个按钮都拥有点击事件
                            this.TableLp.Controls.Add(btnLinker, col, row);
                        }
                    }
  }


4、暂停游戏


通过将当前时间的变量赋值为0来结束当前计时器线程,并且当点击继续游戏的时候,通过开启新线程来重新运行计数功能


暂停


        private void btnPause_Click(object sender, EventArgs e)
        {
            this.TableLp.Enabled = false;
            btnStart.Enabled = true;
            btnStart.Text = "继续游戏";
            //ma.Reset();//信号关闭阻塞当前线程
            timetemp = this.curDelay;
            this.curDelay = 0;
            lblTimeDelay.Text = timetemp.ToString();
       }


继续


 else if (btnStart.Text == "继续游戏")
            {
                //ma.Set();//信号打开,不阻塞当前线程
                btnStart.Text = "开始游戏";
                btnStart.Enabled = false;
                this.TableLp.Enabled = true;
                this.curDelay= timetemp;// 将中间变量赋值给新的当前时间
                lblTimeDelay.Text = this.curDelay.ToString();
                timeDelay = new Thread(new ThreadStart(this.timeDelay_Tick));//新的计时器
                timeDelay.Start();
            }


5、重新排列


设置随机二维数组,将原来的数字所在的行和列用随机的二维数组替代


int rnd, rnd2;
                // 打乱二维数组顺序
                for (int row = 1; row < hang + 1; row++)
                {
                    for (int col = 1; col < hang + 1; col++)
                    {
                        // 产生随机数
                        rnd = r.Next(1, 1000) % hang + 1;
                        rnd2 = r.Next(1, 1000) % hang + 1;
                        int temp = linkBoard[rnd, rnd2];
                        linkBoard[rnd, rnd2] = linkBoard[row, col];
                        linkBoard[row, col] = temp;
                    }
                }


6、判断连接的核心算法


(1)垂直事件


获取按钮1和按钮2的y值,并利用循环来判断按钮1和按钮2之间是否为通路


 private bool vertical(Point a, Point b)
        {
            int col_start = a.Y < b.Y ? a.Y : b.Y;    //获取a,b中较小的y值
            int col_end = a.Y < b.Y ? b.Y : a.Y;      //获取a,b中较大的y值
            //遍历a,b之间是否通路,如果一个不是就返回false;
            for (int i = col_start + 1; i < col_end; i++)
            {
                if (this.LinkBoard[a.X, i] != 0)//0是空的状态(即没有按钮)
                {
                    return false;
                }
            }
            return true;
        }


(2)水平事件


获取按钮1和按钮2的x值,并利用循环来判断按钮1和按钮2之间是否为通路


private bool horizon(Point a, Point b)
        {
            int row_start = a.X < b.X ? a.X : b.X;//获取a,b中较小的x值
            int row_end = a.X < b.X ? b.X : a.X;//获取a,b中较大的x值
            for (int i = row_start + 1; i < row_end; i++)
            {
                if (this.LinkBoard[i, a.Y] != 0)//0是空的状态(即没有按钮)
                {
                    return false;//return是函数结束
                }
            }
            return true;
        }


(3)一个拐点事件


首先判断拐点位置是否有按钮,若没有,将剩下部分看做由一个垂直事件和一个水平事件组成


private bool oneCorner(Point a, Point b)
        {
            // 拐角点的x,y分别等于两点各个x,y
            Point c = new Point(b.X, a.Y);
            Point d = new Point(a.X, b.Y);
            // 判断C点是否有元素,有就继续执行下面判断条件,没有就看a与c,b与c之间是否能水平或者垂直连线                
            if (this.LinkBoard[c.X, c.Y] == 0)
            {
                bool path1 = vertical(b, c) && horizon(a, c);
                return path1;   //path1本身就是bool值
            }
            //判断D点是否有元素
            if (this.LinkBoard[d.X, d.Y] == 0)
            {
                bool path2 = vertical(a, d) && horizon(b, d);
                return path2;//path2本身就是bool值
            }
            // 能执行到这就说明前面判断不成功,不存在一个拐点的情况
            return false;
        }


(4)两个拐点事件


首先判断两个拐点是都有按钮,并且判断两个拐点间是否为通路,此过程用到新建的scan函数,剩下即有两个水平事件或者两个垂直事件构成


private List<Line> scan(Point a, Point b)
        {
            List<Line> linkList = new List<Line>();// 新建一个直线对象List容器,用来存成立的拐点之间的直线
            //检测a点上方是否能够水平直连
            for (int i = a.Y; i >= 0; i--)
            {
                if (this.LinkBoard[a.X, i] == 0 && this.LinkBoard[b.X, i] == 0 && horizon(new Point(a.X, i), new Point(b.X, i)))
                {
                    linkList.Add(new Line(new Point(a.X, i), new Point(b.X, i), 0));
                }
            }
            //检测a点下方是否能够水平直连
            for (int i = a.Y; i < Col; i++)
            {
                if (this.LinkBoard[a.X, i] == 0 && this.LinkBoard[b.X, i] == 0 && horizon(new Point(a.X, i), new Point(b.X, i)))
                {
                    linkList.Add(new Line(new Point(a.X, i), new Point(b.X, i), 0));
                }
            }
            //检测a点左侧是否能够垂直直连
            for (int j = a.X; j >= 0; j--)
            {
                if (this.LinkBoard[j, a.Y] == 0 && this.LinkBoard[j, b.Y] == 0 && vertical(new Point(j, a.Y), new Point(j, b.Y)))
                {
                    linkList.Add(new Line(new Point(j, a.Y), new Point(j, b.Y), 1));
                }
            }
            //检测a点右侧是否能够垂直直连
            for (int j = a.X; j < Row; j++)
            {
                if (this.LinkBoard[j, a.Y] == 0 && this.LinkBoard[j, b.Y] == 0 && vertical(new Point(j, a.Y), new Point(j, b.Y)))
                {
                    linkList.Add(new Line(new Point(j, a.Y), new Point(j, b.Y), 1));
                }
            }
            return linkList;
        }
}
 private bool twoCorner(Point a, Point b)
        {
            // list容器:相当于一个Line类型的数组,存扫到的连通路径
            List<Line> ll = scan(a, b);//此时的直线已经判断成功并且拐点位置没有数字
            if (ll.Count == 0)  // 没扫到返回false,List容器里面的计数方法
            {
                return false;
            }
            for (int i = 0; i < ll.Count; i++)
            {
                Line tempLine = ll[i];  // 临时连线变量用于判断拐点与a,b的通路是否成立
                if (tempLine.direct == 1)   // 两个拐点之间是水平直连
                {// 判断分别两个拐点与a,b能否相连
                    if (horizon(a, tempLine.a) && horizon(b, tempLine.b))//判断a与a"之间是否有通路;判断b与b"之间是否有通路
                    {
                        return true;
                    }
                }
                else if (tempLine.direct == 0)  // 两个拐点之间是垂直直连
                {// 判断分别两个拐点与a,b能否相连
                    if (vertical(a, tempLine.a) && vertical(b, tempLine.b))//判断a与a"之间是否有通路;判断b与b"之间是否有通路
                    {
                        return true;
                    }
                }
            }
            return false;
        }


7.重新排列


将原本储存的二维数组重新打乱顺序,在按照打乱后的数组动态生成按钮


  // 打乱二维数组顺序
      for (int row = 1; row < hang + 1; row++)
      {
          for (int col = 1; col < hang + 1; col++)
          {
              // 产生随机数
              rnd = r.Next(1, 1000) % hang + 1;
              rnd2 = r.Next(1, 1000) % hang + 1;
              int temp = linkBoard[rnd, rnd2];
              linkBoard[rnd, rnd2] = linkBoard[row, col];
              linkBoard[row, col] = temp;
          }
      }


8.连接成功判断


用两个循环挨个遍历数组元素并计数,最后判断计数是否够元素的个数


        // 判断成功
        private void Judge()
        {
            int count=0;
            // 将内置元素进行随机数分配等操作
            for (int row = 1; row < hang + 1; row++)
            {
                for (int col = 1; col < hang + 1; col++)
                {
                    if (this.LinkObj.LinkBoard[row,col]==0)
                    {
                        count++;
                    }
                }
            }
            if (count==hang*hang)
            {
                MessageBox.Show("拼完了,你可真厉害!","提示",MessageBoxButtons.OK,MessageBoxIcon.Asterisk);
                this.TableLp.Enabled = false;
                btnStart.Enabled = true;
                timetemp = this.curDelay;
                this.curDelay = 0;
                lblTimeDelay.Text = timetemp.ToString();
            }
        }


相关文章
|
7月前
|
Java 数据库 C#
C#winforms实现windows窗体人脸识别
C#winforms实现windows窗体人脸识别
|
关系型数据库 MySQL C#
C# winform 一个窗体需要调用自定义用户控件的控件名称
给用户控件ucQRCode增加属性: //二维码图片 private PictureBox _pictureBoxFSHLQrCode; public PictureBox PictureBoxFSHLQrCode {   get { return _pictureBoxFSHLQrCode; }   set { this.pictureBoxFSHLQrCode = value; } } 在Form1窗体直接调用即可: ucQRCode uQRCode=new ucQRCode(); ucQRCode.PictureBoxFSHLQrCode.属性= 要复制或传给用户控件上的控件的值
71 0
|
2月前
|
API C# Windows
【C#】在winform中如何实现嵌入第三方软件窗体
【C#】在winform中如何实现嵌入第三方软件窗体
103 0
|
5月前
|
开发框架 数据可视化 C#
|
6月前
|
人工智能 缓存 Java
技术经验解读:【转】详细解析用C#写的小游戏《彩色连珠》(附源代码)
技术经验解读:【转】详细解析用C#写的小游戏《彩色连珠》(附源代码)
26 0
|
7月前
|
C#
C#如何实现窗体最小化到托盘
C#如何实现窗体最小化到托盘
82 0
|
7月前
|
JavaScript Linux C#
【傻瓜级JS-DLL-WINCC-PLC交互】1.C#用windows窗体控件创建.net控件
【傻瓜级JS-DLL-WINCC-PLC交互】1.C#用windows窗体控件创建.net控件
145 0
|
7月前
|
C# Windows
C#安装“Windows 窗体应用(.NET Framework)”
C#安装“Windows 窗体应用(.NET Framework)”
194 0
|
C# 数据安全/隐私保护
C# 窗体之间参数互相传递的两种方法与使用
C# 窗体之间参数互相传递的两种方法与使用
|
C# Kotlin
C#is、as关键字及获取当前活动窗体的实例
这篇日志记录一下C#中is关键字及as关键字的用法。 Is :判断检查对象是否与给定类型兼容 As :将对象转换为指定类型(强转),就跟(int )这样的用法是一样的。 获取当前窗体的活动子窗体。
59 0