您好,登錄后才能下訂單哦!
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
程序中的粗體字代碼分別調用getYLinkPoints: p2Chanel: pieceHeight:、getXLinkPoints: p2Chanel: pieceWidth:方法來收集各種可能出現的連接路徑,兩個方法的代碼如下。
程序清單:codes/13/Link/Link/sources/board/FKGameService.m
/** * 遍歷兩個集合,先判斷第一個集合中元素的x坐標與另一個集合中元素的x坐標是否相同(縱向), * 如果相同,即在同一列,再判斷是否有障礙,沒有則加到NSMutableDictionary中 * @return 存放可以縱向直線連接的連接點的鍵值對 */ - (NSDictionary*) getYLinkPoints:(NSArray*) p1Chanel p2Chanel:(NSArray*) p2Chanel pieceHeight:(NSInteger) pieceHeight { NSMutableDictionary* result = [[NSMutableDictionary alloc]init]; for (int i = 0; i < p1Chanel.count; i++) { FKPoint* temp1 = [p1Chanel objectAtIndex:i]; for (int j = 0; j < p2Chanel.count; j++) { FKPoint* temp2 = [p2Chanel objectAtIndex:j]; // 如果x坐標相同(在同一列) if (temp1.x == temp2.x) { // 沒有障礙則加到結果的NSMutableDictionary中 if (![self isYBlockFromP1:temp1 toP2:temp2 pieceHeight:pieceHeight]) { [result setObject:temp2 forKey:temp1]; } } } } return [result copy]; } /** * 遍歷兩個集合,先判斷第一個集合中元素的y坐標與另一個集合中元素的y坐標是否相同(橫向), * 如果相同,即在同一行,再判斷是否有障礙,沒有則加到NSMutableDictionary中 * @return 存放可以橫向直線連接的連接點的鍵值對 */ - (NSDictionary*) getXLinkPoints:(NSArray*) p1Chanel p2Chanel:(NSArray*) p2Chanel pieceWidth:(NSInteger) pieceWidth { NSMutableDictionary* result = [[NSMutableDictionary alloc]init]; for (int i = 0; i < p1Chanel.count; i++) { // 從第一通道中取一個點 FKPoint* temp1 = [p1Chanel objectAtIndex:i]; // 再遍歷第二個通道,看第二通道中是否有點可以與temp1橫向相連 for (int j = 0; j < p2Chanel.count; j++) { FKPoint* temp2 = [p2Chanel objectAtIndex:j]; // 如果y坐標相同(在同一行),再判斷它們之間是否有直接障礙 if (temp1.y == temp2.y) { if (![self isXBlockFromP1:temp1 toP2:temp2 pieceWidth:pieceWidth]) { // 沒有障礙則加到結果的NSMutableDictionary中 [result setObject:temp2 forKey:temp1]; } } } } return [result copy]; }
經過上面的實現之后,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返回即可。
下面的方法實現了上面的思路。
程序清單:codes/13/Link/Link/sources/board/FKGameService.m
/** * 獲取p1和p2之間最短的連接信息 * @param p1 第一個點 * @param p2 第二個點 * @param turns 放轉折點的NSDictionary * @param shortDistance 兩點之間的最短距離 * @return p1和p2之間最短的連接信息 */ - (FKLinkInfo*) getShortcutFromPoint:(FKPoint*) p1 toPoint:(FKPoint*) p2 turns:(NSDictionary*) turns distance:(NSInteger)shortDistance { NSMutableArray* infos = [[NSMutableArray alloc] init]; // 遍歷結果NSDictionary for (FKPoint* point1 in turns) { FKPoint* point2 = turns[point1]; // 將轉折點與選擇點封裝成FKLinkInfo對象,放到NSArray集合中 [infos addObject:[[FKLinkInfo alloc] initWithP1:p1 p2:point1 p3:point2 p4:p2]]; } return [self getShortcut:infos shortDistance:shortDistance]; } /** * 從infos中獲取連接線最短的那個FKLinkInfo對象 * @param infos * @return 連接線最短的那個FKLinkInfo對象 */ - (FKLinkInfo*) getShortcut:(NSArray*) infos shortDistance:(int) shortDistance { int temp1 = 0; FKLinkInfo* result = nil; for (int i = 0; i < infos.count; i++) { FKLinkInfo* info = [infos objectAtIndex:i]; // 計算出幾個點的總距離 NSInteger distance = [self countAll:info.points]; // 將循環第一個的差距用temp1保存 if (i == 0) { temp1 = distance - shortDistance; result = info; } // 如果下一次循環的值比temp1還小, 則用當前的值作為temp1 if (distance - shortDistance < temp1) { temp1 = distance - shortDistance; result = info; } } return result; } /** * 計算NSArray中所有點的距離總和 * @param points 需要計算的連接點 * @return 所有點的距離總和 */ - (NSInteger) countAll:(NSArray*) points { NSInteger result = 0; for (int i = 0; i < points.count - 1; i++) { // 獲取第i個點 FKPoint* point1 = [points objectAtIndex:i]; // 獲取第i + 1個點 FKPoint* point2 = [points objectAtIndex:i + 1]; // 計算第i個點與第i + 1個點的距離,并添加到總距離中 result += [self getDistanceFromPoint:point1 toPoint:point2]; } return result; } /** * 獲取兩個點之間的最短距離 * @param p1 第一個點 * @param p2 第二個點 * @return 兩個點的距離距離總和 */ - (CGFloat) getDistanceFromPoint:(FKPoint*) p1 toPoint:(FKPoint*) p2 { int xDistance = abs(p1.x - p2.x); int yDistance = abs(p1.y - p2.y); return xDistance + yDistance; }
至此,《瘋狂連連看》游戲中兩個方塊可能相連的所有情況都處理完成了,應用程序即可調用FKGameService所提供的(FKLinkInfo*) linkWithBeginPiece:(FKPiece*)p1 endPiece: (FKPiece*) p2方法來判斷兩個方塊是否可以相連,這個過程也是編寫該游戲最煩瑣的地方。
通過對《瘋狂連連看》游戲的分析與開發,讀者應該發現編寫一個游戲并沒有想象的那么難,開發者需要冷靜、條理化的思維,先分析游戲中所有可能出現的情況,然后在程序中對所有的情況進行判斷,并進行相應的處理。
提示:
本程序中FKGameService組件的實現思路與《瘋狂Android講義》中Android版《瘋狂連連看》游戲的實現思路基本相同,筆者無法保證這種實現方式為最優算法。這種算法實現起來有些煩瑣,但它的條理十分清晰,非常適合初、中級程序員學習。
13.7小結
本章介紹了一款常見的單機休閑類游戲——iOS版的《瘋狂連連看》,這款流行的小游戲的開發難度適中,而且能充分激發學習熱情,對iOS學習者來說是一個不錯的選擇。學習本章需要重點掌握單機游戲的界面分析與數據建模的能力:游戲玩家眼中看到的是游戲界面,開發者眼中看到的應該是數據模型。除此之外,單機游戲通常總會有一個比較美觀的界面,因此,通常都需要通過自定義UIView來實現游戲主界面。《瘋狂連連看》游戲中需要判斷兩個方塊(圖片)是否可以相連,這需要開發者對兩個方塊的位置分別進行處理,并針對不同的情況提供相應的實現,這也是開發單機游戲需要重點掌握的能力。
——————本文節選自《瘋狂ios講義(上)》
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。