這篇文章主要為大家詳細介紹了C#拼圖遊戲的編寫程式碼下篇,具有一定的參考價值,有興趣的小夥伴們可以參考一下
前言:在C#拼圖遊戲編寫程式碼程式設計之C#實現《拼圖遊戲》(上),上傳了各模組程式碼,而在本文中將詳細剖析原理,使讀者更容易理解並學習,程式有諸多問題,歡迎指出,共同學習成長!
正文:
拼圖是一個非常經典的遊戲,基本上每個人都知道他的玩法,他的開始,運行,結束。那麼,當我們想要做拼圖的時候該如何入手呢?答案是:從現實出發,去描述需求(盡量描述為文件),當我們擁有了全面的需求,就能夠提供可靠的策略,從而在程式碼中實現,最終成為作品!
(一)需求: (這個需求書寫較為潦草,為廣大小白定制,按照最最普通人的思維來,按照參與遊戲的流程來)
1.圖片:我們玩拼圖最起碼有個圖
2.切割:拼圖不是一個圖,我們需要把一個整圖它切割成N*N的小圖
3.打亂:把這N*N的小圖打亂順序,但是要保證透過遊戲規則行走能還原回來
4.判斷:判拼圖成功
6.展示原始圖片完整的
縮圖7.記錄步數:記錄完成需要多少步驟
8.更換圖片:一個圖片玩久了我們是不是可以換一換啊哈哈
9.選擇難度:太簡單?不要! 3*3搞定了有5*5,5*5搞定了有9*9,舍友挑戰最高難度
300 (二)分析:有了需求,我們就可以分析如何去實現它(把現實需求映射在電腦中),其中包括:
1.開發平台:這裡選擇C#語言
1).儲存:其中包括我們要存什麼?我們用什麼結構存?我們反觀需求,會發現,有一些需要儲存的資源
圖片:使用Image
物件結構體struct Node ,其中包括Image物件用來儲存單元小圖片,和用整形儲存的編號(切割以後,每個小單元都弄個編號,利於檢驗遊戲是否完成)。 各單元(原圖片切割後的子圖像集合):使用
二維數組(像拼圖,五子棋,消消樂,連連看,俄羅斯方塊等平面點陣遊戲都可以用他來存儲,為什麼? 變數 int Num存儲
有了存儲,我們就可以去思考模組的劃分(
正確的邏輯劃分已於擴展,也可以使通信變得更加清晰)並搭建,並實現各模組所涉及的特定演算法## 首先程式的模組分為四個:
邏輯型:
1.拼圖類別:用於描述拼圖2.配置類別:儲存配置變數#
互動型: 3.遊戲選單視窗:進行選單選項4.遊戲運作視窗:遊戲的主要介面
1.通過遊戲的主要介面
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號單元,則與其交換。這是基本邏輯,但不包括
/// <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#編寫拼圖遊戲的圖文程式碼分享(下)的詳細內容。更多資訊請關注PHP中文網其他相關文章!