瘋狂ios講義之實現遊戲邏輯(3)

13.6.9 兩個轉折點的連接

兩個轉折點的連接是最複雜的一種連接情況,因爲兩個轉折點又可分爲如下幾種情況。

p1p2位於同一行,但不能直接相連,就必須有兩個轉折點,分向上與向下兩種連接情況。

p1p2位於同一列,但不能直接相連,也必須有兩個轉折點,分向左與向右兩種連接情況。

p2p1的右下角,有6種轉折情況。

p2p1的右上角,同樣有6種轉折情況。

提示:

對於p2位於p1的左上角、左下角的情況,同樣只要把p1p2的位置互換即可。

對於上面4種情況,同樣需要分別進行處理。


1.同一行不能直接相連

p1p2位於同一行,但它們不能直接相連,因此必須有兩個轉折點,圖13.13顯示了這種相連的示意圖。

從圖13.13可以看到,當p1p2位於同一行但不能直接相連時,這兩個點既可在上面相連,也可在下面相連,這兩種情況都代表它們可以相連。我們先把這兩種情況都加入結果中,最後計算最近的距離。

實現時可以先構建一個NSDictionaryNSDictionarykey爲第一個轉折點,NSDictionaryvalue爲第二個轉折點(每種連接情況最多隻有兩個連接點),如NSDictionarycount大於1,說明這兩個FKPoint有多種連接途徑,那麼程序還需要計算路徑最小的連接方式。

2.同一列不能直接相連

p1p2位於同一列,但它們不能直接相連,因此必須有兩個轉折點,圖13.14顯示了這種相連的示意。

093108_6NK4_262659.jpg

13.13同一行不能直接相連

093121_l4sn_262659.jpg

13.14同一列不能直接相連

從圖13.14可以看到,當p1p2位於同一列但不能直接相連時,這兩個點既可在左邊相連,也可在右邊相連,這兩種情況都代表它們可以相連。我們先把這兩種情況都加入結果中,最後計算最近的距離。

實現的方法與同一行不能直接相連的情況相同。

3p2位於p1右下角的6種轉折情況

p2位於p1右下角時,一共可能出現6種連接情況,圖13.15~圖13.20分別繪製了這6種連接情況。

093155_l45w_262659.jpg

13.15p2位於p1右下角有兩個轉折點的情況1


093204_m160_262659.jpg

13.16p2位於p1右下角有兩個轉折點的情況2

093221_oB7B_262659.jpg

13.17p2位於p1右下角有兩個轉折點的情況3


093232_e96b_262659.jpg

13.18p2位於p1右下角有兩個轉折點的情況4

093244_4ZrN_262659.jpg

13.19p2位於p1右下角有兩個轉折點的情況5

093255_PivA_262659.jpg

13.20p2位於p1右下角有兩個轉折點的情況6


實際上,p2還可能位於p1的右上角,出現的6種連接情形與此相似,此處不再詳述。

接下來定義一個getLinkPoints方法對具有兩個連接點的情況進行處理。

程序清單:codes/13/Link/Link/sources/board/FKGameService.m

wKioL1MGsDzRrbXnAAXBvNKuI-k915.jpg

wKiom1MGsG6z2V24AASJRANw8ik750.jpg

wKioL1MGsFWAgcHsAAXBG3Dg4Fs051.jpgwKiom1MGsIeArSwJAAXqhctUt7A578.jpgwKioL1MGsGyhzmENAAb03h4ds_U801.jpgwKiom1MGsJrhB259AAVBggtmIgY398.jpgwKiom1MGsYOwyn_HAADjNHY8iUY371.jpg

程序中的粗體字代碼分別調用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:方法可以找出point1point2兩個點之間所有可能的連接情況,該方法返回一個NSDictionary對象,NSDictionary中每個key-value對代表一種連接情況,其中key代表第一個連接點,value代表第二個連接點。

point1point2之間有多種連接情況時,程序還需要找出所有連接情況中的最短路徑,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講義(上)》

093605_GT9L_262659.jpg


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章