开发者社区> 流楚丶格念> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

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

简介: C#窗体连连看小游戏(超详细)
+关注继续查看

成品链接


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


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


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


项目展示


image


image


各部分功能解释


一、项目分析


系统设计思路


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


版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
ASM 字节码增强框架详解(上)
ASM是Java中比较流行的用来读写字节码的类库,用来基于字节码层面对代码进行分析和转换。 ASM是一个Java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。 ASM可以直接产生二进制class文件,也可在类被加载入虚拟机之前动态改变类行为, ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能根据要求生成新类。目前许多框架如cglib、Hibernate、 Spring都直接或间接使用ASM操作字节码。
163 0
php-fpm - 启动参数及重要配置详解
约定几个目录 /usr/local/php/sbin/php-fpm /usr/local/php/etc/php-fpm.conf /usr/local/php/etc/php.ini 一,php-fpm的启动参数 #测试php-fpm配置 /usr/...
1147 0
科幻星系之恐怖游戏(七)
十二章:   [光芒和一些网上聊的好的朋友正讲着话,曹操走了过来,硬是拉着所有人抽他的牌。]   浪漫樱花:(一身白色的和服打扮,披着长长的头发,看起来很美,但是却被曹操称呼为贞子)曹操就是你啊!怪不得没脸见人呢,(心疼的撇着嘴)看你把可爱的小熊威尼给糟蹋了。
1046 0
小测XSP是否支持中文
早几天的时候一直对XSP支持中文的问题耿耿于怀。想重新编译XSP来修正对中文支持的问题,可惜功力不够只能够重新编译但编译后的exe却无法响应Http的访问.....(如果谁成功编译过请指教)于是今天想了想如果从数据库读来的数据包含中文情况会怎么样。
810 0
+关注
流楚丶格念
csdn平台优质创作者,51cto TOP博主,360图书馆科技博主,燕山大学目前大三在读,日拱一卒,功不唐捐,加油!!!
1010
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载