C#拼图游戏编写代码(2)
发布时间 - 2026-01-11 00:40:58 点击率:次前言:在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#实现拼图小游戏
# 遍历
# 的是
# 当我们
# 就可以
# 单元格
# 小图
# 而在
# 鼠标点击
# 很容易
# 我们可以
# 上下左右
# 很简单
# 自定义
# 说白了
# 写在
# 已于
# 则为
# 按序
# 搞定了
# 类中
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
标题:Vue + Vuex 项目中正确使用 JWT 进行身份认证的实践指南
Laravel定时任务怎么设置_Laravel Crontab调度器配置
jimdo怎样用html5做选项卡_jimdo选项卡html5实现与切换效果【指南】
文字头像制作网站推荐软件,醒图能自动配文字吗?
网站设计制作书签怎么做,怎样将网页添加到书签/主页书签/桌面?
利用vue写todolist单页应用
如何在Windows服务器上快速搭建网站?
制作公司内部网站有哪些,内网如何建网站?
javascript事件捕获机制【深入分析IE和DOM中的事件模型】
Claude怎样写约束型提示词_Claude约束提示词写法【教程】
Laravel怎么定时执行任务_Laravel任务调度器Schedule配置与Cron设置【教程】
网站制作报价单模板图片,小松挖机官方网站报价?
深圳网站制作设计招聘,关于服装设计的流行趋势,哪里的资料比较全面?
JavaScript常见的五种数组去重的方式
Laravel如何使用Gate和Policy进行权限控制_Laravel权限判定与策略规则配置
如何在万网ECS上快速搭建专属网站?
JavaScript如何实现继承_有哪些常用方法
高端云建站费用究竟需要多少预算?
如何确保西部建站助手FTP传输的安全性?
Laravel怎么发送邮件_Laravel Mail类SMTP配置教程
EditPlus中的正则表达式 实战(4)
Laravel如何处理表单验证?(Requests代码示例)
Laravel Octane如何提升性能_使用Laravel Octane加速你的应用
如何快速登录WAP自助建站平台?
jQuery validate插件功能与用法详解
东莞专业网站制作公司有哪些,东莞招聘网站哪个好?
ChatGPT常用指令模板大全 新手快速上手的万能Prompt合集
js实现获取鼠标当前的位置
活动邀请函制作网站有哪些,活动邀请函文案?
Laravel广播系统如何实现实时通信_Laravel Reverb与WebSockets实战教程
安克发布新款氮化镓充电宝:体积缩小 30%,支持 200W 输出
Laravel如何实现数据导出到PDF_Laravel使用snappy生成网页快照PDF【方案】
音乐网站服务器如何优化API响应速度?
武汉网站设计制作公司,武汉有哪些比较大的同城网站或论坛,就是里面都是武汉人的?
如何在阿里云购买域名并搭建网站?
香港网站服务器数量如何影响SEO优化效果?
香港服务器WordPress建站指南:SEO优化与高效部署策略
Laravel怎么实现模型属性转换Casting_Laravel自动将JSON字段转为数组【技巧】
黑客如何利用漏洞与弱口令入侵网站服务器?
html5源代码发行怎么设置权限_访问权限控制方法与实践【指南】
轻松掌握MySQL函数中的last_insert_id()
网页设计与网站制作内容,怎样注册网站?
Win11怎么关闭透明效果_Windows11辅助功能视觉效果设置
猪八戒网站制作视频,开发一个猪八戒网站,大约需要多少?或者自己请程序员,需要什么程序员,多少程序员能完成?
LinuxShell函数封装方法_脚本复用设计思路【教程】
如何解决hover在ie6中的兼容性问题
Laravel如何实现多级无限分类_Laravel递归模型关联与树状数据输出【方法】
Laravel如何使用Livewire构建动态组件?(入门代码)
JS弹性运动实现方法分析
开心动漫网站制作软件下载,十分开心动画为何停播?

