全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

C#拼图游戏编写代码(2)

前言:在C#拼图游戏编写代码程序设计 之 C#实现《拼图游戏》(上),上传了各模块代码,而在本文中将详细剖析原理,使读者更容易理解并学习,程序有诸多问题,欢迎指出,共同学习成长!

正文:

拼图是一个非常经典的游戏,基本每个人都知道他的玩法,他的开始,运行,结束。那么,当我们想要做拼图的时候如何入手呢?答案是:从现实出发,去描述需求(尽量描述为文档),当我们拥有了全面的需求,就能够提供可靠的策略,从而在代码中实现,最终成为作品!

(一)需求: (这个需求书写较为潦草,为广大小白定制,按照最最最普通人的思维来,按照参与游戏的流程来)

1.图片:我们玩拼图 最起码有个图

2.切割:拼图不是一个图,我们需要把一个整图它切割成N*N的小图

3.打乱:把这N*N的小图打乱顺序,但是要保证通过游戏规则行走能还原回来

4.判断:判拼图成功

5.交互:我们使用哪一种交互方式,这里我选择鼠标点击

6.展示原图片完整的缩略图

以上为基本功能,以下为扩展功能

7.记录步数:记录完成需要多少步

8.更换图片:一个图片玩久了我们是不是可以换一换啊 哈哈

9.选择难度:太简单?不要!3*3搞定了有5*5,5*5搞定了有9*9,舍友挑战最高难度 3000多步,心疼我的鼠标TAT

(二)分析:

有了需求,我们就可以分析如何去实现它(把现实需求映射在计算机中),其中包括:

1.开发平台:这里选择C#语言

1).存储:其中包括我们要存什么?我们用什么结构存?我们反观需求,会发现,有一些需要存储的资源

图片:使用 Image 对象存储

单元(原图片切割后的子图像集合):自定义结构体 struct Node ,其中包括Image对象用来存储单元小图片,和用整形存储的编号(切割以后,每个小单元都弄个编号,利于检验游戏是否完成)。

各单元(原图片切割后的子图像集合):使用二维数组(像拼图,五子棋,消消乐,连连看,俄罗斯方块等平面点阵游戏都可以用他来存储,为什么?因为长得像嘛!)来存储

难度:使用自定义的枚举类型(简单and普通and困难)存储

步数:整形变量 int Num存储 

有了存储,我们就可以去思考模块的划分(正确的逻辑划分已于扩展,也可以使通信变得更加清晰)并搭建,并实现各模块涉及到的具体算法

首先程序的模块分为四个:

逻辑型:

1.拼图类:用于描述拼图

2.配置类:存储配置变量

交互型:

3.游戏菜单窗口:进行菜单选项

4.游戏运行窗口:游戏的主要界面

1.通过游戏菜单可以操纵配置,如难度或图片。

2.运行窗口可以访问并获得游戏配置,并利用其对应构造拼图对象。

3.用户通过运行窗口进行交互,间接使拼图对象调用移动方法,获得图案方法

看代码的同学,我觉得最有问题的地方,不合理的地方就是把 难度的枚举类型写在了拼图类中,应该写在配置类中,或单独成类,读者们自行更改

 public enum Diff //游戏难度
 {
  simple,//简单
  ordinary,//普通
  difficulty//困难
 } 

我们可以认为,配置类就像数据存储,而拼图类呢作为逻辑处理,菜单和运行窗口作为表现用于交互,我承认这种设计不是很合理,但是在问题规模不够大的时候,过分的考虑设计,会不会使程序变得臃肿?我想一定是有一个度,具体是多少,我不得而知,但我感觉,针对这个程序,实现就好,沉迷设计(套路型),有时得不偿失。(个人不成熟的小观点)

(三)代码实现:

说明:本块重点描述 Puzzle(拼图)类与游戏运行类的具体实现及实体通讯:

拼图的构造方法:

1.赋值 :

public Puzzle(Image Img,int Width, Diff GameDif)

// 拼图的图片,宽度(解释:正方形的边长,单位是像素,名字有歧义,抱歉),游戏的难度 

 游戏的难度决定你分割的程度,分割的程度,决定你存储的数组的大小,如简单对应3行3列,普通对应5行5列,困难对应9行9列

 switch(this._gameDif)
  {
  case Diff.simple:    //简单则单元格数组保存为3*3的二维数组
   this.N = 3;
   node=new Node[3,3];
   break;
  case Diff.ordinary:   //一般则为5*5
   this.N = 5;
   node = new Node[5, 5];
   break;
  case Diff.difficulty:  //困难则为9*9
   this.N = 9;
   node = new Node[9, 9];
   break;
  }

2.分割图片

 //分割图片形成各单元保存在数组中
  int Count = 0;
  for (int x = 0; x < this.N; x++)
  {
  for (int y = 0; y < this.N; y++)
  {

   node[x, y].Img = CaptureImage(this._img, this.Width / this.N, this.Width / this.N, x * (this.Width / this.N), y * (this.Width / this.N));
   node[x, y].Num = Count;
   Count++;
  }
  }

其实对单元数组进行赋值的过程,使用双层for循环对二维数组进行遍历操作,然后按序赋值编号node[x,y].Num; 

然后对node[x,y].Img,也就是单元的小图片赋值,赋值的方法是,C#的图像的类库,写一个截图方法,使用这个方法,将大图中对应对位置的对应大小的小图截取下来,并保存在node[x,y].Img中;

width/N是什么?是边长除以行数,也就是间隔嘛,间隔也就是每个单元的边长嘛!然后起始坐标(X,Y)起始就是在说,隔了几个单元后,我的位置,

即 :(x,y)=(单元边长*距离起始X轴相距单元数,单元边长*距离起始点Y轴相距单元数);

关于此类问题,希望读者能够多画画图,然后自然就明白了;

public  Image CaptureImage(Image fromImage, int width, int height, int spaceX, int spaceY)

主要逻辑:利用DrawImage方法:

//创建新图位图 
 Bitmap bitmap = new Bitmap(width, height);
//创建作图区域 
Graphics graphic = Graphics.FromImage(bitmap);
//截取原图相应区域写入作图区 
 graphic.DrawImage(fromImage, 0, 0, new Rectangle(x, y, width, height), GraphicsUnit.Pixel);
//从作图区生成新图 
 Image saveImage = Image.FromHbitmap(bitmap.GetHbitmap());

分割了以后,我们要做一个特殊处理,因为我们知道,总有那么一个位置是白的吧?我们默认为最后一个位置,即node[N-1,N-1];

就是写改成了个白色的图片,然后四周的边线都给画成红色,已于被人发现,显著一些,之前的其他单元我也画了边线,但是是白色,也是为了在拼图的观赏性上得到区分。该代码不做介绍。

3.打乱图片:

其实就是将二维数组打乱,我们可以采取一些排序打乱方法,但是请注意!不是每一种打乱都能够复原的!

那么如何做到可行呢?方法理解起来很简单,就是让我们的电脑在开局之前,将完整的有序的单元按照规则中提供的行走方式进行无规则,大次数的行走!也就是说这种方法一定能走回去!

先理解,具体打乱方法,在后面讲解。 

移动方法(Move):

拼图游戏中方格的移动,其实就是两个相邻单元的交换,而这两个单元中,必定存在一个白色单元(即上面提到的node[N-1,N-1]单元,他的编号为N*N-1,建议自己动笔算一算)

所以我们的判断条件是,如果移动一个方块,他的上下左右四个方向中,一旦有一个相邻的是白色单元,即N*N-1号单元,则与其交换。这是基本逻辑,但不包括约束条件,当我们的数组达到边界的时候,我们就不能对越界数据进行访问,如当单元为node[0,0]时,你就不能对他上面和右面的数据进行访问,因为Node[-1,0] Node[0,-1]都会越界,发生异常

移动成功,返回TRUE

移动失败,返回FALSE

/// <summary>
 /// 移动坐标(x,y)拼图单元
 /// </summary>
 /// <param name="x">拼图单元x坐标</param>
 /// <param name="y">拼图单元y坐标</param>
 public bool Move(int x,int y)
 {
  //MessageBox.Show(" " + node[2, 2].Num);
  if (x + 1 != N && node[x + 1, y].Num == N * N - 1)
  {
  Swap(new Point(x + 1, y), new Point(x, y));
  return true;
  }
  if (y + 1 != N && node[x, y + 1].Num == N * N - 1)
  {
  Swap(new Point(x, y + 1), new Point(x, y));
  return true;
  }  
  if (x - 1 != -1 && node[x - 1, y].Num == N * N - 1)
  {
  Swap(new Point(x - 1, y), new Point(x, y));
  return true;
  } 
  if (y - 1 != -1 && node[x, y - 1].Num == N * N - 1)
  {
  Swap(new Point(x, y - 1), new Point(x, y));
  return true;
  }
  return false;
  
 }

交换方法(Swap):

交换数组中两个元素的位置,该方法不应该被类外访问,顾设置为private私有权限

 //交换两个单元格
 private void Swap(Point a, Point b)
 {
  Node temp = new Node();
  temp = this.node[a.X, a.Y];
  this.node[a.X, a.Y] = this.node[b.X, b.Y];
  this.node[b.X, b.Y] = temp;
 }

打乱方法:

前面提到,其实就是让电脑帮着乱走一通,说白了就是大量的调用Move(int X,int y)方法,也就是对空白位置的上下左右四个相邻的方块中随机抽取一个,并把它的坐标传递给Move使其进行移动,同样要进行越界考虑,这样的操作大量重复!代码自己看吧 ,利用随机数。

/// <summary>
 /// 打乱拼图
 /// </summary>
 public void Upset()
 {
  int sum = 100000;
  if (this._gameDif == Diff.simple) sum = 10000;
  //if (this._gameDif == Diff.ordinary) sum = 100000;
  Random ran = new Random();
  for (int i = 0, x = N - 1, y = N - 1; i < sum; i++)
  {
  long tick = DateTime.Now.Ticks;
  ran = new Random((int)(tick & 0xffffffffL) | (int)(tick >> 32)|ran.Next());
  switch (ran.Next(0, 4))
  {
   case 0:
   if (x + 1 != N)
   {
    Move(x + 1, y);
    x = x + 1;
   }
    
   break;
   case 1:
   if (y + 1 != N)
   {
    Move(x, y + 1);
    y = y + 1;
   } 
   break;
   case 2:
   if (x - 1 != -1)
   {
    Move(x - 1, y);
    x = x - 1;
   } 
   break;
   case 3:
   if (y - 1 != -1)
   {
    Move(x, y - 1);
    y = y - 1;
   }
   break;
  }

  }
 }

返回图片的方法:

当时怎么起了个这样的鬼名字。。。DisPlay。。。

这个方法与分割方法刚好相背,这个方法其实就是遍历数组,并将其进行组合,组合的方法很简单,就是将他们一个一个的按位置画在一张与原图相等大小的空白图纸上!最后提交图纸,也就是return一个Image;

 public Image Display()
 {
  Bitmap bitmap = new Bitmap(this.Width, this.Width);
  //创建作图区域 
  Graphics newGra = Graphics.FromImage(bitmap);
  for (int x = 0; x < this.N; x++)
  for (int y = 0; y < this.N; y++)
   newGra.DrawImage(node[x, y].Img, new Point(x * this.Width / this.N, y * this.Width / this.N));
  return bitmap;
 }

同样利用的是DrawImage方法,知道如何分割,这个应该很容易理解,自己算一算,在纸上比划比划就明白了;

判断方法:

该方法很容易理解,就是按序按序!遍历所有单元,如果他们的结果中有一个单元的编号

node[x, y].Num 不等于遍历的序号,那么说明,该单元不在原有位置上,即整个图片还没有完成,我们就可以直接返回假值false
如果所有遍历结果都正确,我们可认为,图片已复原,此时返回真值true

 public bool Judge()
 {
  int count=0;
  for (int x = 0; x < this.N; x++)
  {
  for (int y = 0; y < this.N; y++)
  {
   if (this.node[x, y].Num != count)
   return false;
   count++;
  }
  }
  return true;
 }

游戏运行窗口:即游戏玩耍时用于交互的窗口

这里只讲一个方法:即当接受用户鼠标点击事件时我们应该怎么处理并作出什么样反应

其实说白了就这句难懂:

puzzle.Move(e.X / (puzzle.Width / puzzle.N), e.Y / (puzzle.Width / puzzle.N))

调用了移动方法,移动方块

横坐标为:e.X / (puzzle.Width / puzzle.N)
纵坐标为:e.Y / (puzzle.Width / puzzle.N)

我们编程中的整数除法和数学里的除法是不一样的!比如10/4数学上等于2余2或者2.5,计算机里直接就是等于2了,只取整数部分

行数=行坐标 / 方块边长

列数=列坐标 / 方块边长

我们看P1,P2这两点

P1:40/30*30=1
P2:50/30*30=1

我们会发现同在一个单元格中,无论点击哪个位置,通过这个算法都能转化为
同一个坐标。

(e.x,e.y)为鼠标点击事件点击坐标

 private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
 {
  if (puzzle.Move(e.X / (puzzle.Width / puzzle.N), e.Y / (puzzle.Width / puzzle.N)))
  {
  Num++;
  pictureBox1.Image = puzzle.Display();
  if (puzzle.Judge())
  { 
   if (MessageBox.Show("恭喜过关", "是否重新玩一把", MessageBoxButtons.OKCancel) == DialogResult.OK)
   {
   Num = 0;
   puzzle.Upset();
   pictureBox1.Image = puzzle.Display();
   
   }
   else
   {
   Num = 0;
   closefather();
   this.Close();
   }

  }

  }
  NumLabel.Text = Num.ToString();
 }

好,那么大体的逻辑,程序中最需要思考的算法已经讲完了,还有不太懂的地方,欢迎交流~么么哒~

加了点小功能 音乐历史成绩

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# C#  # 拼图  # 游戏  # C#滑动验证码拼图验证功能实现(SlideCaptcha)  # C# 拼图游戏的实战(附demo)  # C#实现拼图游戏  # C# 拼图魔方小游戏  # C#拼图游戏编写代码  # C#利用控件拖拽技术制作拼图游戏  # C#实现拼图小游戏  # 遍历  # 的是  # 当我们  # 就可以  # 单元格  # 小图  # 而在  # 鼠标点击  # 很容易  # 我们可以  # 上下左右  # 很简单  # 自定义  # 说白了  # 写在  # 已于  # 则为  # 按序  # 搞定了  # 类中 


相关文章: 微课制作网站有哪些,微课网怎么进?  如何用已有域名快速搭建网站?  浙江网站制作公司有哪些,浙江栢塑信息技术有限公司定制网站做的怎么样?  如何在阿里云购买域名并搭建网站?  建站之星安装提示数据库无法连接如何解决?  如何用VPS主机快速搭建个人网站?  企业微网站怎么做,公司网站和公众号有什么区别?  南平网站制作公司,2025年南平市事业单位报名时间?  建站之星体验版:智能建站系统+响应式设计,多端适配快速建站  广州顶尖建站服务:企业官网建设与SEO优化一体化方案  如何在万网主机上快速搭建网站?  如何制作一个表白网站视频,关于勇敢表白的小标题?  制作电商网页,电商供应链怎么做?  潮流网站制作头像软件下载,适合母子的网名有哪些?  定制建站是什么?如何实现个性化需求?  建站之星后台管理如何实现高效配置?  python的本地网站制作,如何创建本地站点?  厦门模型网站设计制作公司,厦门航空飞机模型掉色怎么办?  建站之星安装需要哪些步骤及注意事项?  建站之星后台密码遗忘?如何快速找回?  如何获取免费开源的自助建站系统源码?  湖北网站制作公司有哪些,湖北清能集团官网?  微网站制作教程,我微信里的网站怎么才能复制到浏览器里?  建站之星后台搭建步骤解析:模板选择与产品管理实操指南  哈尔滨网站建设策划,哈尔滨电工证查询网站?  建站ABC备案流程中有哪些关键注意事项?  弹幕视频网站制作教程下载,弹幕视频网站是什么意思?  如何快速配置高效服务器建站软件?  如何快速搭建响应式可视化网站?  如何快速完成中国万网建站详细流程?  官网网站制作腾讯审核要多久,联想路由器newifi官网  建站之星与建站宝盒如何选择最佳方案?  如何在西部数码注册域名并快速搭建网站?  c# 服务器GC和工作站GC的区别和设置  C#如何使用XPathNavigator高效查询XML  如何自己制作一个网站链接,如何制作一个企业网站,建设网站的基本步骤有哪些?  如何在Windows服务器上快速搭建网站?  广德云建站网站建设方案与建站流程优化指南  整人网站在线制作软件,整蛊网站退不出去必须要打我是白痴才能出去?  如何快速搭建虚拟主机网站?新手必看指南  ,南京靠谱的征婚网站?  ,怎么在广州志愿者网站注册?  浅谈Javascript中的Label语句  网站制作企业,网站的banner和导航栏是指什么?  建站之星备案是否影响网站上线时间?  网页设计网站制作软件,microsoft office哪个可以创建网页?  保定网站制作方案定制,保定招聘的渠道有哪些?找工作的人一般都去哪里看招聘信息?  如何通过山东自助建站平台快速注册域名?  C++中引用和指针有什么区别?(代码说明)  济南专业网站制作公司,济南信息工程学校怎么样? 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。