텍스트:
Puzzle은 매우 고전적인 게임입니다. 기본적으로 모두가 알고 있습니다. 시작, 실행, 종료. 그렇다면 퍼즐을 만들고 싶을 때 어떻게 시작해야 할까요? 대답은: 현실에서 시작하여 요구 사항을 설명하는 것입니다.(문서로 설명하려고 노력하십시오.) 포괄적인 요구 사항이 있으면 코드에서 구현되어 결국 작업이 될 수 있는 신뢰할 수 있는 전략을 제공할 수 있습니다.
(1) 요구 사항: (이 요구 사항은 다소 엉성하게 작성되었으며 초보자를 대상으로 맞춤 제작되었습니다. 가장 일반적인 사람들의 생각과 게임에 참여하는 과정을 기반으로 합니다. )
1. 그림: 퍼즐을 할 때 최소한 그림은 있어요
2. 자르기: 퍼즐은 그림이 아니라 잘라야 합니다. 전체 그림을 N*N개의 작은 조각 그림
3. 방해: N*N개의 작은 그림의 순서를 섞되 게임 규칙에 따라 걸어가면 복원할 수 있는지 확인하세요
4. 판단: 퍼즐이 성공했는지 판단
5. 상호 작용: 어떤 상호 작용 방법을 사용합니까? 여기서 마우스 클릭을 선택합니다.
6. 퍼즐의 전체 썸네일을 표시합니다. 원본 이미지
위는 기본 기능, 아래는 확장 기능입니다
7. 단계 수 기록: 완료하는 데 몇 단계가 필요한지 기록
8. 사진 바꾸기 : 한 장의 사진을 오랫동안 가지고 놀다 보면 바꿀 수 있을까요 ㅎㅎ
9. 난이도 선택 : 너무 쉬우나요? 원하지 않는다! 3*3 다음은 5*5, 5*5 다음은 9*9 입니다. 룸메이트가 0걸음 이상으로 최고 난이도300에 도전했습니다.
(2) 분석:
수요를 바탕으로 다음을 포함하여 이를 구현하는 방법(실제 수요를 컴퓨터에 매핑)을 분석할 수 있습니다.
1. 개발 플랫폼: 여기에서 C# 언어를 선택하세요
1. 스토리지: 무엇을 저장하고 싶은가요? 그것을 저장하기 위해 어떤 구조를 사용합니까? 요구 사항을 다시 살펴보면
이미지를 저장해야 하는 일부 리소스가 있음을 알 수 있습니다. 저장하려면 이미지
개체를 사용합니다. 원본 이미지를 잘라낸 후 하위 이미지): 이후 유닛의 작은 그림을 저장하는 데 사용되는 이미지 객체와 정수로 저장된 숫자를 포함하는 구조 구조체 노드를 정의합니다(절단 후 각 작은 유닛에는 게임 완료 여부를 쉽게 확인할 수 있도록 숫자가 부여됩니다.
각 유닛(원본 사진을 잘라낸 후 하위 이미지 모음): 2차원 배열 사용(예: 퍼즐, 주사위 놀이, 제거 음악, Lianliankan, Tetris 및 기타 평면 도트) 매트릭스 게임) 저장하는 데 사용할 수 있는데 왜 비슷해 보이나요? ) 저장하는 방법
난이도: 사용자 지정 열거 유형(쉬움 및 일반 및 어려움)을 사용하여 저장
단계 수 : shaping Variable int Num Storage
Storage를 사용하면 모듈의 구분을 생각해볼 수 있습니다(올바른 논리 구분이 확장되어 통신도 더 명확해집니다) 각 모듈에 관련된 특정 알고리즘을 구축하고 구현합니다
우선 프로그램 모듈은 4가지로 구분됩니다:
논리 유형:
1. 퍼즐 클래스: 퍼즐을 설명하는 데 사용
2. 구성 클래스: 저장 구성 변수
인터랙티브:
3. 게임 메뉴 창 : 메뉴 옵션 만들기
4. 게임 실행 창 : 게임의 메인 인터페이스
1. 구성은 다음을 통해 조작 가능 난이도나 그림 같은 게임 메뉴.
2. 실행 중인 창은 게임 구성에 액세스하고 이를 얻을 수 있으며 해당 구성 퍼즐 개체를 사용할 수 있습니다.
3. 사용자는 실행 중인 창을 통해 상호 작용하여 퍼즐 객체가 간접적으로 이동 메서드를 호출하고 패턴 메서드를 얻도록 합니다
코드를 보는 학생들은 가장 문제가 되는 부분은 퍼즐 클래스에서 열거형을 작성하는 곳인데, 구성 클래스에서 작성하거나, 독자가 직접 변경할 수 있습니다.
아아아아
我们可以认为,配置类就像数据存储,而拼图类呢作为逻辑处理,菜单和运行窗口作为表现用于交互,我承认这种设计不是很合理,但是在问题规模不够大的时候,过分的考虑设计,会不会使程序变得臃肿?我想一定是有一个度,具体是多少,我不得而知,但我感觉,针对这个程序,实现就好,沉迷设计(套路型),有时得不偿失。(个人不成熟的小观点)
(三)代码实现:
说明:本块重点描述 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私有权限
= = .node[a.X, a.Y] = .node[b.X, b.Y] =
打乱方法:
前面提到,其实就是让电脑帮着乱走一通,说白了就是大量的调用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
count= ( x = ; x < .N; x++ ( y = ; y < .N; y++ (.node[x, y].Num != ++
游戏运行窗口:即游戏玩耍时用于交互的窗口
这里只讲一个方法:即当接受用户鼠标点击事件时我们应该怎么处理并作出什么样反应
其实说白了就这句难懂:
puzzle.Move(e.X / (puzzle.Width / puzzle.N), e.Y / (puzzle.Width / puzzle.N)) 调用了移动方法,移动方块 横坐标为:e.X / (puzzle.Width / puzzle.N)
<em>纵坐标为:e.Y / (puzzle.Width / puzzle.N)<br></em><br><span style="font-size: 15px">我们<a href="http://www.php.cn/wiki/44.html" target="_blank">编程</a>中的<a href="http://www.php.cn/code/12117.html" target="_blank">整数</a>除法和数学里的除法是不一样的!比如10/4数学上等于2余2或者2.5,计算机里直接就是等于2了,只取整数部分<br><img src="https://img.php.cn/upload/article/000/000/004/48684ccad126d0d6436ec85a0a36293a-3.jpg" alt=""></span>
行数=行坐标 / 方块边长
列数=列坐标 / 方块边长
我们看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#에서는 "Jigsaw Game"을 구현합니다(2부).의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!