如何在Objective-C中实现决策表

时间:2014-07-22 22:47:51

标签: objective-c logic decision-tree control-structure

我是一名新手程序员,我刚刚开始阅读有关决策表的内容。我已经阅读了Code Complete中的第18章,这非常具有启发性。我环顾网络试图在Objective-C中找到任何决策表的例子,我无法找到任何样板或现实世界中如何实现这一点的例子。

我在业余时间使用Objective-C编写游戏,而且我一直在处理游戏规则日益复杂的问题。有一些有点深度嵌套的if-else语句,以及一些已经有10个或更多案例需要处理的switch语句。我认为使用决策表会更容易,但我不知道如何在Objective-C中实现这一点,以实现游戏逻辑等非平凡的东西。

例如,我需要不同的方法来执行不同的状态组合。我如何在Objective-C中实现一个决策表,它可以将状态的不同组合作为键,并根据它们的组合运行特定的逻辑?

1 个答案:

答案 0 :(得分:0)

我考虑了Objective-C中的决策表,并提出了实现基本决策表的解决方案。我不会在这里发布整个代码,只是使决策表工作的片段及其基本目的。我在Code Review SE发布了这个帖子,如果您想查看完整代码以及如何改进它的一些很好的建议。我现在发布这个是因为有人发表评论要求我这样做,但我肯定会最终改进这一点并整合评论中的建议。无论如何,这是代码。

首先在初始化方法之前,我建立了一些NSString常量,这些常量将用作NSDictionary中的键。

//Two options for the decision table, either access the dictionary directly with 0-x, the enum values, or make strings for their names
//the advantage of strings is that it is more extensible, and the position in the enum doesnt matter
NSString* const kEnemyMovementStateJustSpawned = @"enemyMovementStateJustSpawned";
NSString* const kEnemyMovementStateIdle = @"enemyMovementStateIdle";
NSString* const kEnemyMovementStateNeedsMoving = @"enemyMovementStateNeedsMoving";
NSString* const kEnemyMovementStateToFloor = @"enemyMovementStateToFloor";
NSString *const kEnemyMovementStateAtDestinationFloor = @"enemyMovementStateAtDestinationFloor";
NSString* const kEnemyMovementStateToFloorExit = @"enemyMovementStateToFloorExit";
NSString* const kEnemyMovementStateToAttackWalls = @"enemyMovementStateToAttackWalls";
NSString* const kEnemyMovementStateToAttackFloor = @"enemyMovementStateToAttackFloor";
NSString* const kEnemyMovementStateToAttackRoom = @"enemyMovementStateToAttackRoom";

然后我使用这些常量以及类中方法的名称来构建NSDictionary:

-(void) setupDecisionTable {
    //the string objects are the names of methods in the class
    _decisionTable = @{kEnemyMovementStateJustSpawned: @"doEnemyJustSpawned",
                       kEnemyMovementStateIdle: @"doEnemyIdle",
                       kEnemyMovementStateNeedsMoving: @"doEnemyNeedsMoving",
                       kEnemyMovementStateToFloorExit: @"doFloorMovement",
                       kEnemyMovementStateToFloor: @"doVerticalMovement",
                       kEnemyMovementStateAtDestinationFloor: @"doEnemyAtDestinationFloor",
                       kEnemyMovementStateToAttackWalls: @"doFloorMovement",
                       kEnemyMovementStateToAttackFloor: @"doFloorMovement",
                       kEnemyMovementStateToAttackRoom: @"doFloorMovement"
                       };
}

然后每个tick都调用此方法,该方法使用从字典中提取的对象的名称执行方法:

-(void) doMovement {
    //the selector is formed from a string inside the decision table dictionary
    SEL methodToCallName = NSSelectorFromString([_decisionTable objectForKey:[self stringForState:self.state]]);
    if (methodToCallName) {
        IMP functionPointer = [self methodForSelector:methodToCallName];
        void (*methodToCall)(id, SEL) = (void *)functionPointer;
        methodToCall(self, methodToCallName);
    }
}
-(NSString *) stringForState:(EnemyMovementState)state {
    switch (state) {
        case EnemyMovementStateJustSpawned:
            return kEnemyMovementStateJustSpawned;
        case EnemyMovementStateIdle:
            return kEnemyMovementStateIdle;
        case EnemyMovementStateNeedsMoving:
            return kEnemyMovementStateNeedsMoving;
        case EnemyMovementStateToFloor:
            return kEnemyMovementStateToFloor;
        case EnemyMovementStateAtDestinationFloor:
            return kEnemyMovementStateAtDestinationFloor;
        case EnemyMovementStateToFloorExit:
            return kEnemyMovementStateToFloorExit;
        case EnemyMovementStateToAttackWalls:
            return kEnemyMovementStateToAttackWalls;
        case EnemyMovementStateToAttackFloor:
            return kEnemyMovementStateToAttackFloor;
        case EnemyMovementStateToAttackRoom:
            return kEnemyMovementStateToAttackRoom;
        default:
            return nil;
    }
}

最后,这里有几个执行的方法,只是为了一个完整的例子:

-(void) doEnemyIdle {
    if ([self checkFloorsForJobs]) {
        self.state = EnemyMovementStateNeedsMoving;
    } else {
        [self doIdleMovement];
    }
}
-(void) doEnemyNeedsMoving {
    [self calculateFloorExitPositionByFloor];
    self.state = EnemyMovementStateToFloorExit;
}

这是一个非常简单的实现。目前它只能处理一个输入,更好的决策表将能够评估多个输入并提供适当的输出。我认为它可以通过一个中间方法扩展,该方法将状态与其他变量结合起来从字典中选择适当的对象。

完成所有这些后,我不确定决策表是否值得在Objective-C中付出努力。我不知道代码是否比switch语句更容易理解。为了向代码添加新逻辑,必须在比switch语句似乎需要的更多位置修改它。我提供此代码作为示例,如果有人在Objective-C中查看其他版本的决策表,那将会很酷。