成品链接
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)失败事件:
- 一个按钮点击两次
- 点击的两个按钮所带图片不一样
- 点击两个具有相同图案的按钮但没有通路
(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(); } }