13.6.9 兩個轉折點的連接配接
兩個轉折點的連接配接是最複雜的一種連接配接情況,因為兩個轉折點又可分為如下幾種情況。
p1、p2位于同一行,但不能直接相連,就必須有兩個轉折點,分向上與向下兩種連接配接情況。
p1、p2位于同一列,但不能直接相連,也必須有兩個轉折點,分向左與向右兩種連接配接情況。
p2在p1的右下角,有6種轉折情況。
p2在p1的右上角,同樣有6種轉折情況。
提示:
對于p2位于p1的左上角、左下角的情況,同樣隻要把p1、p2的位置互換即可。
對于上面4種情況,同樣需要分别進行處理。
p1、p2位于同一行,但它們不能直接相連,是以必須有兩個轉折點,圖13.13顯示了這種相連的示意圖。
從圖13.13可以看到,當p1與p2位于同一行但不能直接相連時,這兩個點既可在上面相連,也可在下面相連,這兩種情況都代表它們可以相連。我們先把這兩種情況都加入結果中,最後計算最近的距離。
實作時可以先建構一個NSDictionary,NSDictionary的key為第一個轉折點,NSDictionary的value為第二個轉折點(每種連接配接情況最多隻有兩個連接配接點),如NSDictionary的count大于1,說明這兩個FKPoint有多種連接配接途徑,那麼程式還需要計算路徑最小的連接配接方式。
p1、p2位于同一列,但它們不能直接相連,是以必須有兩個轉折點,圖13.14顯示了這種相連的示意。

圖13.13同一行不能直接相連
圖13.14同一列不能直接相連
從圖13.14可以看到,當p1與p2位于同一列但不能直接相連時,這兩個點既可在左邊相連,也可在右邊相連,這兩種情況都代表它們可以相連。我們先把這兩種情況都加入結果中,最後計算最近的距離。
實作的方法與同一行不能直接相連的情況相同。
p2位于p1右下角時,一共可能出現6種連接配接情況,圖13.15~圖13.20分别繪制了這6種連接配接情況。
圖13.15p2位于p1右下角有兩個轉折點的情況1
圖13.16p2位于p1右下角有兩個轉折點的情況2
圖13.17p2位于p1右下角有兩個轉折點的情況3
圖13.18p2位于p1右下角有兩個轉折點的情況4
圖13.19p2位于p1右下角有兩個轉折點的情況5
圖13.20p2位于p1右下角有兩個轉折點的情況6
實際上,p2還可能位于p1的右上角,出現的6種連接配接情形與此相似,此處不再詳述。
接下來定義一個getLinkPoints方法對具有兩個連接配接點的情況進行處理。
程式清單:codes/13/Link/Link/sources/board/FKGameService.m
<a href="http://s3.51cto.com/wyfs02/M02/12/6B/wKioL1MGsDzRrbXnAAXBvNKuI-k915.jpg" target="_blank"></a>
<a href="http://s3.51cto.com/wyfs02/M01/12/6A/wKiom1MGsG6z2V24AASJRANw8ik750.jpg" target="_blank"></a>
程式中的粗體字代碼分别調用getYLinkPoints: p2Chanel: pieceHeight:、getXLinkPoints: p2Chanel: pieceWidth:方法來收集各種可能出現的連接配接路徑,兩個方法的代碼如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<code>/**</code>
<code> </code><code>* 周遊兩個集合,先判斷第一個集合中元素的x坐标與另一個集合中元素的x坐标是否相同(縱向),</code>
<code> </code><code>* 如果相同,即在同一列,再判斷是否有障礙,沒有則加到NSMutableDictionary中</code>
<code> </code><code>* @return 存放可以縱向直線連接配接的連接配接點的鍵值對</code>
<code> </code><code>*/</code>
<code>- (NSDictionary*) getYLinkPoints:(NSArray*) p1Chanel</code>
<code> </code><code>p2Chanel:(NSArray*) p2Chanel pieceHeight:(NSInteger) pieceHeight</code>
<code>{</code>
<code> </code><code>NSMutableDictionary* result = [[NSMutableDictionary alloc]init];</code>
<code> </code><code>for</code> <code>(</code><code>int</code> <code>i = 0; i < p1Chanel.count; i++)</code>
<code> </code><code>{</code>
<code> </code><code>FKPoint* temp1 = [p1Chanel objectAtIndex:i];</code>
<code> </code><code>for</code> <code>(</code><code>int</code> <code>j = 0; j < p2Chanel.count; j++)</code>
<code> </code><code>{</code>
<code> </code><code>FKPoint* temp2 = [p2Chanel objectAtIndex:j];</code>
<code> </code><code>// 如果x坐标相同(在同一列)</code>
<code> </code><code>if</code> <code>(temp1.x == temp2.x)</code>
<code> </code><code>{</code>
<code> </code><code>// 沒有障礙則加到結果的NSMutableDictionary中</code>
<code> </code><code>if</code> <code>(![self isYBlockFromP1:temp1 toP2:temp2 pieceHeight:pieceHeight])</code>
<code> </code><code>{</code>
<code> </code><code>[result setObject:temp2 forKey:temp1];</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>return</code> <code>[result copy];</code>
<code>}</code>
<code> </code><code>* 周遊兩個集合,先判斷第一個集合中元素的y坐标與另一個集合中元素的y坐标是否相同(橫向),</code>
<code> </code><code>* 如果相同,即在同一行,再判斷是否有障礙,沒有則加到NSMutableDictionary中</code>
<code> </code><code>* @return 存放可以橫向直線連接配接的連接配接點的鍵值對</code>
<code>- (NSDictionary*) getXLinkPoints:(NSArray*) p1Chanel</code>
<code> </code><code>p2Chanel:(NSArray*) p2Chanel pieceWidth:(NSInteger) pieceWidth</code>
<code> </code><code>// 從第一通道中取一個點</code>
<code> </code><code>// 再周遊第二個通道,看第二通道中是否有點可以與temp1橫向相連</code>
<code> </code><code>// 如果y坐标相同(在同一行),再判斷它們之間是否有直接障礙</code>
<code> </code><code>if</code> <code>(temp1.y == temp2.y)</code>
<code> </code><code>if</code> <code>(![self isXBlockFromP1:temp1 toP2:temp2 pieceWidth:pieceWidth])</code>
<code> </code><code>// 沒有障礙則加到結果的NSMutableDictionary中</code>
經過上面的實作之後,getLinkPointsFromPoint: toPoint: width: height:方法可以找出point1、point2兩個點之間所有可能的連接配接情況,該方法傳回一個NSDictionary對象,NSDictionary中每個key-value對代表一種連接配接情況,其中key代表第一個連接配接點,value代表第二個連接配接點。
當point1、point2之間有多種連接配接情況時,程式還需要找出所有連接配接情況中的最短路徑,link(Piece p1, Piece p2)方法中的④号粗體字代碼調用了getShortcutFromPoint: toPoint: turns: distance:方法進行處理,下面進行詳細分析。
13.6.10 找出最短距離
為了找出所有連接配接情況中的最短路徑,程式實作可分為兩步。
①周遊轉折點NSDictionary中的所有key-value對,與原來選擇的兩個點構成一個FKLinkInfo。每個FKLinkInfo代表一條完整的連接配接路徑,并将這些FKLinkInfo收內建一個NSArray集合。
②周遊第1步得到的NSArray集合,計算每個FKLinkInfo中所有連接配接點的總距離,選取與最短距離相差最小的FKLinkInfo傳回即可。
下面的方法實作了上面的思路。
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<code> </code><code>* 擷取p1和p2之間最短的連接配接資訊</code>
<code> </code><code>* @param p1 第一個點</code>
<code> </code><code>* @param p2 第二個點</code>
<code> </code><code>* @param turns 放轉折點的NSDictionary</code>
<code> </code><code>* @param shortDistance 兩點之間的最短距離</code>
<code> </code><code>* @return p1和p2之間最短的連接配接資訊</code>
<code>- (FKLinkInfo*) getShortcutFromPoint:(FKPoint*) p1 toPoint:(FKPoint*) p2</code>
<code> </code><code>turns:(NSDictionary*) turns distance:(NSInteger)shortDistance</code>
<code> </code><code>NSMutableArray* infos = [[NSMutableArray alloc] init];</code>
<code> </code><code>// 周遊結果NSDictionary</code>
<code> </code><code>for</code> <code>(FKPoint* point1 in turns)</code>
<code> </code><code>FKPoint* point2 = turns[point1];</code>
<code> </code><code>// 将轉折點與選擇點封裝成FKLinkInfo對象,放到NSArray集合中</code>
<code> </code><code>[infos addObject:[[FKLinkInfo alloc]</code>
<code> </code><code>initWithP1:p1 p2:point1 p3:point2 p4:p2]];</code>
<code> </code><code>return</code> <code>[self getShortcut:infos shortDistance:shortDistance];</code>
<code> </code><code>* 從infos中擷取連接配接線最短的那個FKLinkInfo對象</code>
<code> </code><code>* @param infos</code>
<code> </code><code>* @return 連接配接線最短的那個FKLinkInfo對象</code>
<code>- (FKLinkInfo*) getShortcut:(NSArray*) infos shortDistance:(</code><code>int</code><code>) shortDistance</code>
<code> </code><code>int</code> <code>temp1 = 0;</code>
<code> </code><code>FKLinkInfo* result = nil;</code>
<code> </code><code>for</code> <code>(</code><code>int</code> <code>i = 0; i < infos.count; i++)</code>
<code> </code><code>FKLinkInfo* info = [infos objectAtIndex:i];</code>
<code> </code><code>// 計算出幾個點的總距離</code>
<code> </code><code>NSInteger distance = [self countAll:info.points];</code>
<code> </code><code>// 将循環第一個的差距用temp1儲存</code>
<code> </code><code>if</code> <code>(i == 0)</code>
<code> </code><code>temp1 = distance - shortDistance;</code>
<code> </code><code>result = info;</code>
<code> </code><code>// 如果下一次循環的值比temp1還小, 則用目前的值作為temp1</code>
<code> </code><code>if</code> <code>(distance - shortDistance < temp1)</code>
<code> </code><code>return</code> <code>result;</code>
<code> </code><code>* 計算NSArray中所有點的距離總和</code>
<code> </code><code>* @param points 需要計算的連接配接點</code>
<code> </code><code>* @return 所有點的距離總和</code>
<code>- (NSInteger) countAll:(NSArray*) points</code>
<code> </code><code>NSInteger result = 0;</code>
<code> </code><code>for</code> <code>(</code><code>int</code> <code>i = 0; i < points.count - 1; i++)</code>
<code> </code><code>// 擷取第i個點</code>
<code> </code><code>FKPoint* point1 = [points objectAtIndex:i];</code>
<code> </code><code>// 擷取第i + 1個點</code>
<code> </code><code>FKPoint* point2 = [points objectAtIndex:i + 1];</code>
<code> </code><code>// 計算第i個點與第i + 1個點的距離,并添加到總距離中</code>
<code> </code><code>result += [self getDistanceFromPoint:point1 toPoint:point2];</code>
<code> </code><code>* 擷取兩個點之間的最短距離</code>
<code> </code><code>* @return 兩個點的距離距離總和</code>
<code>- (CGFloat) getDistanceFromPoint:(FKPoint*) p1 toPoint:(FKPoint*) p2</code>
<code> </code><code>int</code> <code>xDistance = </code><code>abs</code><code>(p1.x - p2.x);</code>
<code> </code><code>int</code> <code>yDistance = </code><code>abs</code><code>(p1.y - p2.y);</code>
<code> </code><code>return</code> <code>xDistance + yDistance;</code>
至此,《瘋狂連連看》遊戲中兩個方塊可能相連的所有情況都處理完成了,應用程式即可調用FKGameService所提供的(FKLinkInfo*) linkWithBeginPiece:(FKPiece*)p1 endPiece: (FKPiece*) p2方法來判斷兩個方塊是否可以相連,這個過程也是編寫該遊戲最煩瑣的地方。
通過對《瘋狂連連看》遊戲的分析與開發,讀者應該發現編寫一個遊戲并沒有想象的那麼難,開發者需要冷靜、條理化的思維,先分析遊戲中所有可能出現的情況,然後在程式中對所有的情況進行判斷,并進行相應的處理。
本程式中FKGameService元件的實作思路與《瘋狂Android講義》中Android版《瘋狂連連看》遊戲的實作思路基本相同,筆者無法保證這種實作方式為最優算法。這種算法實作起來有些煩瑣,但它的條理十厘清晰,非常适合初、中級程式員學習。
13.7小結
本章介紹了一款常見的單機休閑類遊戲——iOS版的《瘋狂連連看》,這款流行的小遊戲的開發難度适中,而且能充分激發學習熱情,對iOS學習者來說是一個不錯的選擇。學習本章需要重點掌握單機遊戲的界面分析與資料模組化的能力:遊戲玩家眼中看到的是遊戲界面,開發者眼中看到的應該是資料模型。除此之外,單機遊戲通常總會有一個比較美觀的界面,是以,通常都需要通過自定義UIView來實作遊戲主界面。《瘋狂連連看》遊戲中需要判斷兩個方塊(圖檔)是否可以相連,這需要開發者對兩個方塊的位置分别進行處理,并針對不同的情況提供相應的實作,這也是開發單機遊戲需要重點掌握的能力。
——————本文節選自《瘋狂ios講義(上)》
本文轉自
fkJava李剛 51CTO部落格,原文連結:http://blog.51cto.com/javaligang/1361533 ,如需轉載請自行聯系原作者