HiveBrain v1.2.0
Get Started
← Back to all entries
principleMinor

Complex Conditionals in a Strategy Game

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
gameconditionalscomplexstrategy

Problem

I have been looking at this method for a long time and I have finally refactored it into something that I believe is much more readable. I am not totally happy with it though so I would like to get other opinions about it. There are a great many questions about complex if-else structures here on Code Review, so I am sorry to be adding to it. Hopefully this one will be interesting, at least.

This method is very important to my Game as a whole. It checks to make sure that a Job is valid for a particular Floor in the tower. It is called when the User or AI selects a job to add to the JobQueue, and also again when the JobQueue tries to start the job. The problem is that the construction rules are different for floors that are above ground compared to those that are below ground.

In the past, I had a switch statement for the jobType, and then an if-else statement inside of that to check if the floor was above or below ground. Then inside of that were the necessary cases for all of the jobTypes. The downsides to this were that some of the code was repeated, everything was happening inside one method, and it was not totally clear what was happening.

I have refactored this down to a method that calls two other methods. It always calls the first method, and then it calls one of two additional methods depending on whether the job rules are different for above and below ground for that particular job. I am primarily concerned with the clarity and readability of this code, especially whether or not there are enough comments. If there are better ways to do this I would love to hear that also.

```
-(BOOL) checkIfFloor:(int)floorNumber isValidForJob:(JobType)jobType {
DTTowerFloor *floor = [self floorFromFloorList:floorNumber];

if (![self checkEarlyReturnConditionsForFloor:floor jobType:jobType]) {
return NO;
}

if ([self isJobSimpleJob:jobType]) {
//these conditions are the same for above and below ground
if (![self checkIfFlo

Solution

I should note that when it comes to bitwise conditionals such as
(floor.floorBuildState & FloorHasLadder) and (floor.floorBuildState &
FloorHasWalls) == 0), my understanding is that you should not pass
those back directly such as return (floor.floorBuildState &
FloorHasLadder) but should instead return YES or NO, because they are
not pure bools and can cause problems when passed around directly. If
this is incorrect, please let me know.

In general, this is mostly correct.

Technically, 0 is false, and everything else should be regarded as true.

Suppose we have this example:

int x = 2;

if (x) {
    foo();
}

if (x == YES) {
    bar();
}


In this example, foo() will execute, but bar() will not. Now, for starters, this is why we don't write == YES.

But people are foolish, and will.

floor.floorBuildState & FloorHasLadder will only ever result in being equal to 1 or 0 if FloorHasLadder is equal to 1. And chances are, its actual value could change, plus for any other values in this enum, it simply won't work.

So we need to make sure we're returning 1 or 0 from a method that claims to return a BOOL. Bitwise operations make no such guarantee.

LOGICAL operators, however, do make that guarantee.

So in the example of (floor.floorBuildState & FloorHasWalls) == 0, we'd be returning the result of the logical == operator, this is guaranteed to be either 1 or 0, YES or NO. Returning this value is perfectly fine.

We could force a logical operator onto everything by appending an && YES to everything, which will force everything to evaluate to either YES or NO, an expected return value... but this seems more likely to confuse maintainers.

Moreover, sometimes doing the explicit return and sometimes relying on the == 0 return will show inconsistency, which might encourage a foolish maintainer to make a poor decision to "fix" something that's not broken for the sake of consistency.

So with all that said, I think it's probably best to stick with the explicit returns.

One other thing I do want to point out is the fact that when you return out of a switch statement, the break is unnecessary.

In the case of both of your switch statements, you're always entering an if condition based on a logical operator, or break-ing the switch down to a final return NO;. Here, we can simply just return the result of the logic operator, get rid of all the breaks, get rid of the return NO at the bottom, and set the default case to return NO;

Code Snippets

int x = 2;

if (x) {
    foo();
}

if (x == YES) {
    bar();
}

Context

StackExchange Code Review Q#62442, answer score: 3

Revisions (0)

No revisions yet.