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

Refilling a Bejeweled Board

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

Problem

When you start thinking about designing a Match 3 type game, you realize that there are a great many ways to structure the rules. In this particular variant, the direction that the player swipes the orbs determines the way that the board is refilled. The new pieces will enter the board opposite the direction that the player swipes. So if the player swipes down new pieces will enter the board from the top. After the matched pieces are removed, the existing pieces are moved down to replace them, and then new random pieces are generated to replace those existing pieces.

I have tried to think of ways to reduce the redundant code here and also ways to shorten the methods, but I am not sure if it would make the code more readable or not. Left and right swipes are handled differently from up and down swipes, so they need to be in different methods. The logic is similar for each of the four directions but it is distinctly different. I would definitely appreciate feedback on the algorithm I am using.

The first part of this determines whether a match has been made and then marks the orbs for destruction if they are part of a match. You can see that code here in my previous Bejeweled clone question.

This part of the code refills the board after a successful match.

```
#pragma mark - Replace Orbs
-(void) replaceMarkedOrbsForDirection:(MODirection)direction {
NSMutableSet *rowsWithChanges = nil;
NSMutableSet *columnsWithChanges = nil;
switch (direction) {
case MODirectionLeft:
rowsWithChanges = [self rowsWithChanges];
for (DMRow *row in rowsWithChanges) {
[self replaceOrbsForRow:row direction:direction];
}
break;
case MODirectionRight:
rowsWithChanges = [self rowsWithChanges];
for (DMRow *row in rowsWithChanges) {
[self replaceOrbsForRow:row direction:direction];
}
break;
case MODirectionUp:
col

Solution

-(void) replaceMarkedOrbsForDirection:(MODirection)direction {
    NSMutableSet *rowsWithChanges = nil;
    NSMutableSet *columnsWithChanges = nil;
    switch (direction) {
        case MODirectionLeft:
            rowsWithChanges = [self rowsWithChanges];
            for (DMRow *row in rowsWithChanges) {
                [self replaceOrbsForRow:row direction:direction];
            }
            break;
        case MODirectionRight:
            rowsWithChanges = [self rowsWithChanges];
            for (DMRow *row in rowsWithChanges) {
                [self replaceOrbsForRow:row direction:direction];
            }
            break;
        case MODirectionUp:
            columnsWithChanges = [self columnsWithChanges];
            for (DMColumn *column in columnsWithChanges) {
                [self replaceOrbsForColumn:column direction:direction];
            }
            break;
        case MODirectionDown:
            columnsWithChanges = [self columnsWithChanges];
            for (DMColumn *column in columnsWithChanges) {
                [self replaceOrbsForColumn:column direction:direction];
            }
            break;
        default:
            break;
    }
}


This can be drastically shorter.

First of all, we don't need the NSMutableSet variables, and second of all, in Objective-C, switch cases fall through by default, thats why we need the break; statement.

- (void)replaceMarkedOrbsForDirection:(MODirection)direction {
    switch (direction) {
        case MODirectionLeft: case MODirectionRight:
            for (DMRow *row in [self rowsWithChanges]) {
                [self replaceOrbsForRow:row direction:direction];
            }
            break;
        case MODirectionUp: case MODirectionDown:
            for (DMRow *row in [self rowsWithChanges]) {
                [self replaceOrbsForRow:row direction:direction];
            }
            break;
    }
}


Also, notice that we don't need the default if we're switching on an enum and handling all cases.

- (NSMutableSet *) rowsWithChanges {
    NSMutableSet *rowsWithChanges = [[NSMutableSet alloc]init];
    for (DMRow *row in _board.rows) {
        for (DMOrb *orb in row.orbs) {
            if (orb.markedForDestruction) {
                [rowsWithChanges addObject:row];
            }
        }
    }
    return rowsWithChanges;
}


We're using a set, which means that adding an object multiple times does nothing. So, let's save some iterations and checks:

- (NSMutableSet *)rowsWithChanges {
    NSMutableSet *rowsWithChanges = [NSMutableSet set];
    for (DMRow *row in _board.rows) {
        for (DMOrb *orb in row.orbs) {
            if (orb.markedForDestruction) {
                [rowsWithChanges addObject:row];
                break;
            }
        }
    }
    return rowsWithChanges;
}


Notice that this break statement only breaks the inner-most loop. Once we know we need to handle this row, we don't need check any more orbs, so let's move on to the next row.

We can do the same with our columnsWithChanges method as well. But note in that method, we can eliminate this line:

NSMutableArray *columns = _board.columns;


And just reference _board.columns directly in the forin loop.

And one final note about these two methods... why don't we declare some readonly properties?

@property (readonly) NSMutableSet *rowsWithChanges;
@property (readonly) NSMutableSet *columnsWithChanges;


No local instance variable is created--there is no _rowsWithChanges or _columnsWithChanges, because we manually implemented each getter already and didn't use the variable. But meanwhile, without changing anything else specifically regarding these properties or methods, we can stop writing:

[self rowsWithChanges]


And instead, use self.rowsWithChanges. Our code will still perform identically but it will read a little nicer.

I think rather than two gigantic methods for left/right or up/down, I'd rather prefer four methods actually--one for each direction. Or--if possible, think of some way to simplify the logic into one method... but yeah...

For some reason, here, we slip out of the forin loops:

for (int i = kNumOrbsPerRow - 1; i >= 0; i--) {
    DMOrb *orb = column.orbs[i];
    if (orb.markedForDestruction) {
        [markedOrbs addObject:orb];
    }
}


Is it important to go backward here? If so, we can still forin backwards:

for (DMOrb *orb in [column.orbs reverseObjectEnumerator]) {
    if (orb.markedForDesctruction) { 
       [markedOrbs addObject:orb];
    }
}


But if backwards isn't important, just don't call reverseObjectEnumerator.

And then I read this next section:

//get the existing orbs in the proper direction
NSMutableArray *existingOrbs = [[NSMutableArray alloc]init];
DMOrb *lastMarkedOrb = [markedOrbs lastObject];
for (int i = lastMarkedOrb.boardPosition.y - 1; i >= 0 ; i--) {
    [existingOrbs addObject:column.orbs[i]];
}


Code Snippets

-(void) replaceMarkedOrbsForDirection:(MODirection)direction {
    NSMutableSet *rowsWithChanges = nil;
    NSMutableSet *columnsWithChanges = nil;
    switch (direction) {
        case MODirectionLeft:
            rowsWithChanges = [self rowsWithChanges];
            for (DMRow *row in rowsWithChanges) {
                [self replaceOrbsForRow:row direction:direction];
            }
            break;
        case MODirectionRight:
            rowsWithChanges = [self rowsWithChanges];
            for (DMRow *row in rowsWithChanges) {
                [self replaceOrbsForRow:row direction:direction];
            }
            break;
        case MODirectionUp:
            columnsWithChanges = [self columnsWithChanges];
            for (DMColumn *column in columnsWithChanges) {
                [self replaceOrbsForColumn:column direction:direction];
            }
            break;
        case MODirectionDown:
            columnsWithChanges = [self columnsWithChanges];
            for (DMColumn *column in columnsWithChanges) {
                [self replaceOrbsForColumn:column direction:direction];
            }
            break;
        default:
            break;
    }
}
- (void)replaceMarkedOrbsForDirection:(MODirection)direction {
    switch (direction) {
        case MODirectionLeft: case MODirectionRight:
            for (DMRow *row in [self rowsWithChanges]) {
                [self replaceOrbsForRow:row direction:direction];
            }
            break;
        case MODirectionUp: case MODirectionDown:
            for (DMRow *row in [self rowsWithChanges]) {
                [self replaceOrbsForRow:row direction:direction];
            }
            break;
    }
}
- (NSMutableSet *) rowsWithChanges {
    NSMutableSet *rowsWithChanges = [[NSMutableSet alloc]init];
    for (DMRow *row in _board.rows) {
        for (DMOrb *orb in row.orbs) {
            if (orb.markedForDestruction) {
                [rowsWithChanges addObject:row];
            }
        }
    }
    return rowsWithChanges;
}
- (NSMutableSet *)rowsWithChanges {
    NSMutableSet *rowsWithChanges = [NSMutableSet set];
    for (DMRow *row in _board.rows) {
        for (DMOrb *orb in row.orbs) {
            if (orb.markedForDestruction) {
                [rowsWithChanges addObject:row];
                break;
            }
        }
    }
    return rowsWithChanges;
}
NSMutableArray *columns = _board.columns;

Context

StackExchange Code Review Q#74690, answer score: 8

Revisions (0)

No revisions yet.