当前位置: 首页 > news >正文

疯狂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


相关文章:

  • 常见开源协议GPL、Apache、CDDL、BSD等区别
  • 剑指 offer set 24 扑克牌的顺子
  • SCCM 2012 R2---安装客户端代理软件
  • Windows脚本初探之VBScrip流程控制If...Then...
  • 【Cocos2d-x for WP8 学习整理】(3)CCScrollView 实现捕鱼达人一样的场景选择界面...
  • ubuntu下安装oracle
  • Asp.net MVC
  • VCS etc. git
  • Android 中的BroadCastReceiver
  • linux 生成随机密码和wordlist常用方法
  • gnome-shell如何删除菜单项
  • maven学习系列一:maven的安装
  • 软件性能评估
  • Oracle 11g系列:数据库
  • 【Node.js】初体验之安装和HelloWorld
  • php的引用
  • 《深入 React 技术栈》
  • 5分钟即可掌握的前端高效利器:JavaScript 策略模式
  • CentOS 7 修改主机名
  • Java基本数据类型之Number
  • NLPIR语义挖掘平台推动行业大数据应用服务
  • Puppeteer:浏览器控制器
  • V4L2视频输入框架概述
  • Windows Containers 大冒险: 容器网络
  • 从零开始学习部署
  • 今年的LC3大会没了?
  • 前端技术周刊 2019-02-11 Serverless
  • 入门级的git使用指北
  • 使用 QuickBI 搭建酷炫可视化分析
  • 使用权重正则化较少模型过拟合
  • 运行时添加log4j2的appender
  • #HarmonyOS:软件安装window和mac预览Hello World
  • (003)SlickEdit Unity的补全
  • (Bean工厂的后处理器入门)学习Spring的第七天
  • (Java)【深基9.例1】选举学生会
  • (安全基本功)磁盘MBR,分区表,活动分区,引导扇区。。。详解与区别
  • (仿QQ聊天消息列表加载)wp7 listbox 列表项逐一加载的一种实现方式,以及加入渐显动画...
  • (附源码)计算机毕业设计ssm高校《大学语文》课程作业在线管理系统
  • (黑客游戏)HackTheGame1.21 过关攻略
  • (三十五)大数据实战——Superset可视化平台搭建
  • .NET 8.0 中有哪些新的变化?
  • .NET Framework 4.6.2改进了WPF和安全性
  • .net生成的类,跨工程调用显示注释
  • .NET是什么
  • @NoArgsConstructor和@AllArgsConstructor,@Builder
  • [ vulhub漏洞复现篇 ] GhostScript 沙箱绕过(任意命令执行)漏洞CVE-2019-6116
  • [Angular] 笔记 7:模块
  • [BZOJ 2142]礼物(扩展Lucas定理)
  • [cb]UIGrid+UIStretch的自适应
  • [Cocoa]iOS 开发者账户,联机调试,发布应用事宜
  • [Docker]五.Docker中Dockerfile详解
  • [Enterprise Library]调用Enterprise Library时出现的错误事件之关闭办法
  • [ERROR] Plugin 'InnoDB' init function returned error
  • [GN] DP学习笔记板子
  • [GXYCTF2019]禁止套娃