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();
            }
        }


相关文章
|
1月前
|
Java 数据库 C#
C#winforms实现windows窗体人脸识别
C#winforms实现windows窗体人脸识别
31 0
|
6月前
|
关系型数据库 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.属性= 要复制或传给用户控件上的控件的值
37 0
|
4月前
|
JavaScript Linux C#
【傻瓜级JS-DLL-WINCC-PLC交互】1.C#用windows窗体控件创建.net控件
【傻瓜级JS-DLL-WINCC-PLC交互】1.C#用windows窗体控件创建.net控件
65 0
|
4月前
|
C# Windows
C#安装“Windows 窗体应用(.NET Framework)”
C#安装“Windows 窗体应用(.NET Framework)”
51 0
|
4月前
|
C# 数据安全/隐私保护
C# 窗体之间参数互相传递的两种方法与使用
C# 窗体之间参数互相传递的两种方法与使用
|
10月前
|
C# 数据安全/隐私保护
ApeForms | C# WinForm窗体控件平滑减速运动
在桌面软件开发中,有时会需要控制窗体或控件移动以实现某些界面效果,比如幻灯片换页、侧面的展开栏等。 通常情况下我们会使用Timer以每隔一段时间修改一下坐标位置的方式来实现目标对象的位移效果,但通过这个方式实现的动效存在几个问题: 匀速运动效果生硬; 运动过程中不便灵活改变运动状态(如侧栏展开一半时令其收起); 动效多时需要创建多个Timer对象,不易管理且占用资源; ApeForms中为控件和窗体提供了平滑运动的扩展方法,很好的解决了这些问题。不仅是坐标的平滑运动,还有控件\窗体尺寸的平滑变化、透明度的平滑变化。允许在变化的中途随时更改目标坐标\尺寸\透明度,且使用共享的Timer
11253 1
ApeForms | C# WinForm窗体控件平滑减速运动
|
C# Windows 容器
C#面向对象程序设计课程实验二: 实验名称:Windows 窗体程序
C#面向对象程序设计课程实验二: 实验名称:Windows 窗体程序
C#面向对象程序设计课程实验二: 实验名称:Windows 窗体程序
|
C#
C#中获得窗体的句柄
C#中获得窗体的句柄
223 0
C#编程学习19:mdi窗体中子窗体不能重复打开的三种实现方式
C#编程学习19:mdi窗体中子窗体不能重复打开的三种实现方式
C#编程学习19:mdi窗体中子窗体不能重复打开的三种实现方式
|
数据库 C#
C#编程学习18:使用多文档窗体框架利用DataGridView对Access数据表进行增删改及导出excel操作
C#编程学习18:使用多文档窗体框架利用DataGridView对Access数据表进行增删改及导出excel操作
C#编程学习18:使用多文档窗体框架利用DataGridView对Access数据表进行增删改及导出excel操作