5
\$\begingroup\$

I created a pretty complex algorithm for calculating matches on a Bejeweled board that kept track of matches involving wilds. After doing some in-game testing, I found quite a few bugs that needed to be eliminated.

In order to deal with this problem, I first created an undo system for the game, and then when I saw unexpected behavior during a match, I stepped the game back and made a note of the sequence of orbs that caused the problem. Then I wrote unit tests for those sequences of orbs and refined the algorithm until all of the tests passed. I am now quite sure that there are none or very few bugs.


The match requirements:

1. Three or more consecutive orbs of the same type is a match.

2. Wilds match with any color including other wilds and can be part of more than one match at a time.


I have heard that unit tests are more about writing them than reading them, but I will let you decide:

DMBoardEvalTests.m

#import <XCTest/XCTest.h>
#import "DMBoardEval.h"
#import "DMOrb.h"

@interface DMBoardEval (Testing)
    -(NSMutableArray *) matchesForOrbs:(NSMutableArray *)orbs;
@end

@interface DMBoardEvalTests : XCTestCase
@end

@implementation DMBoardEvalTests {
    DMBoardEval *_boardEval;
}

- (void)setUp {
    [super setUp];
    _boardEval = [[DMBoardEval alloc]init];
}

#pragma mark - First Bugs Found
-(void) testBugWithTwoMatchesWhenShouldBeOne {
    //originally this returned 2 matches because of problems with the wild search algorithm
    NSMutableArray *customArray = [[NSMutableArray alloc]init];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeYellow]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeYellow]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeYellow]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBomb]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRed]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    NSMutableArray *matches = [_boardEval matchesForOrbs:customArray];
    XCTAssert(matches.count == 1, @"Pass");
}
-(void) testBugWithOneBigMatchInsteadOfTwoSmall {
    //this returns one big match of WRWBrBr when it should be multiple

    //it returns them all as one big array because when it reaches the brown, wild search is active
    //and the previous one was a wild, so it adds the current one

    //fix involved keeping track of the previous orb instead of just whether it is wild

    NSMutableArray *customArray = [[NSMutableArray alloc]init];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeYellow]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRed]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeSwapCards]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    NSMutableArray *matches = [_boardEval matchesForOrbs:customArray];
    XCTAssert(matches.count == 2, @"Pass");
}

#pragma mark - Easy Tests
-(void) testThreeOnALineNoWilds {
    NSMutableArray *customArray = [[NSMutableArray alloc]init];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    NSMutableArray *matches = [_boardEval matchesForOrbs:customArray];
    XCTAssert(matches.count == 3, @"Pass");
}
-(void) testTwoOnALineNoWilds {
    NSMutableArray *customArray = [[NSMutableArray alloc]init];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    NSMutableArray *matches = [_boardEval matchesForOrbs:customArray];
    XCTAssert(matches.count == 2, @"Pass");
}
-(void) testMatchOfThreeBlueWildBlue {
    NSMutableArray *customArray = [[NSMutableArray alloc]init];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeSwapCards]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeYellow]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRed]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    NSMutableArray *matches = [_boardEval matchesForOrbs:customArray];
    XCTAssert(matches.count == 1, @"Pass");
}

#pragma mark Dupicate Match bug
-(void) testTwoOnALineWithWild {
    //reveals a problem where three mathces will be found, 6 Blue with Wild on the end, one with Wild and 3 greens, one with only 3 greens

    //fix involved creating a duplicate match culler
    NSMutableArray *customArray = [[NSMutableArray alloc]init];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    NSMutableArray *matches = [_boardEval matchesForOrbs:customArray];
    XCTAssert(matches.count == 2, @"Pass");
}
-(void) testTwoOnALineWithIntermixedWilds {
    //related to the same problem as above, gives 5 blue, 6 blue, 3 green, 4 green with wild mixed in
    NSMutableArray *customArray = [[NSMutableArray alloc]init];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    NSMutableArray *matches = [_boardEval matchesForOrbs:customArray];
    XCTAssert(matches.count == 2, @"Pass");
}
-(void) testBugAnExtraMatchWithWildCenter {
    //the logger showed this as two instances of BrWBr
    NSMutableArray *customArray = [[NSMutableArray alloc]init];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeYellow]];
    NSMutableArray *matches = [_boardEval matchesForOrbs:customArray];
    XCTAssert(matches.count == 1, @"Pass");
}

#pragma mark - Consecutive Wilds Bug
-(void) testBugWhereNotAllPreviousWildsAreCounted {
    //this passed, but the problem is that the second match should have W Ro Br Br and is missing the W

    //fixed this bug by adding all of the consecutive wilds to the next match instead of just one previous
    NSMutableArray *customArray = [[NSMutableArray alloc]init];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeYellow]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRotate]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    NSMutableArray *matches = [_boardEval matchesForOrbs:customArray];
    XCTAssert(matches.count == 2, @"Pass");
}
-(void) testPreviousWildsInARow {
    //this passes, but the problem is that the second match should have W Ro Br Br and is missing the W

    //fixed this bug by adding all of the consecutive wilds to the next match instead of just one previous

    //this example will produce 3 matches since one of them is the one with 3 wilds and wilds are not culled
    NSMutableArray *customArray = [[NSMutableArray alloc]init];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRotate]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeSwapCards]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    NSMutableArray *matches = [_boardEval matchesForOrbs:customArray];
    XCTAssert(matches.count == 3, @"Pass");
}
-(void) testBugExtraWildOrbOutOfPlace {
    //fixed by putting a break in the previous wild orb loop
    NSMutableArray *customArray = [[NSMutableArray alloc]init];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeGreen]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBrown]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeYellow]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRotate]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRed]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeSwapCards]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRed]];
    NSMutableArray *matches = [_boardEval matchesForOrbs:customArray];
    XCTAssert(matches.count == 2, @"Pass");
}
-(void) testAllWildAndOneColorMixedIn {
    //similar problem to the above
    NSMutableArray *customArray = [[NSMutableArray alloc]init];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRotate]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRotate]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBomb]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBomb]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeSwapCards]];
    NSMutableArray *matches = [_boardEval matchesForOrbs:customArray];
    XCTAssert(matches.count == 1, @"Pass");
}
-(void) testAllWildAndOneColor {
    //similar problem to the above
    NSMutableArray *customArray = [[NSMutableArray alloc]init];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBlue]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    NSMutableArray *matches = [_boardEval matchesForOrbs:customArray];
    XCTAssert(matches.count == 1, @"Pass");
}

#pragma mark - Test All Wilds
//all these failed with two matches of 9 because the match culler skips wilds

//fixed by adding a wild counter and a check when all orbs are wild
-(void) testAllWilds {
    NSMutableArray *customArray = [[NSMutableArray alloc]init];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    NSMutableArray *matches = [_boardEval matchesForOrbs:customArray];
    XCTAssert(matches.count == 1, @"Pass");
}
-(void) testAllWildOrHigherMix {
    NSMutableArray *customArray = [[NSMutableArray alloc]init];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeSwapCards]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBomb]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRotate]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeSwapCards]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRotate]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBomb]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRotate]];
    NSMutableArray *matches = [_boardEval matchesForOrbs:customArray];
    XCTAssert(matches.count == 1, @"Pass");
}
-(void) testAllWildOrHigherMixWithInternalMatches {
    NSMutableArray *customArray = [[NSMutableArray alloc]init];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRotate]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRotate]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRotate]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeRotate]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBomb]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeWild]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeBomb]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeSwapCards]];
    [customArray addObject:[[DMOrb alloc]initWithType:DMOrbTypeSwapCards]];
    NSMutableArray *matches = [_boardEval matchesForOrbs:customArray];
    XCTAssert(matches.count == 1, @"Pass");
}

@end

Here is the entire match algorithm, including the wild search. The method is huge, so I have extra comments in there to explain the logic. I have tried different ways to shrink this method, but I haven't come up with any good solutions to shrink it further.

DMBoardEval.m

-(NSMutableArray *) matchesForOrbs:(NSMutableArray *)orbs {
    NSMutableArray *matches = [[NSMutableArray alloc]init];

    NSMutableArray *matchingOrbs = [[NSMutableArray alloc]init];
    NSMutableArray *wildMatchOrbs = [[NSMutableArray alloc]init];

    BOOL wildSearchActive = NO;
    DMOrb *previousOrb = nil;

    //need the type in advance to do the wild check
    DMOrbType typeToSearchFor = ((DMOrb *)orbs[0]).type;
    //if the first orb is wild or higher, search for the next one that isnt
    int index = 0;
    while ([self wildOrHigher:typeToSearchFor] && index < kNumOrbsPerRow) {
        typeToSearchFor = ((DMOrb *)orbs[index]).type;
        index++;
    }

    //Now begin the search//
    for (int i = 0; i < kNumOrbsPerRow; i++) {
        DMOrb *orb = orbs[i];

        if ([self wildOrHigher:orb.type] && !wildSearchActive) {
            wildSearchActive = YES;
        }

        //if the orb does not match and is not wild
        if (orb.type != typeToSearchFor && ![self wildOrHigher:orb.type]) {

            if (wildSearchActive) {

                if (wildMatchOrbs.count >= 3) {
                    [matches addObject:wildMatchOrbs];
                }

                //if the previous orb is wild, add all consecutive previous wilds plus the current orb
                if ([self wildOrHigher:previousOrb.type]) {
                    NSMutableArray *previousWildsInARow = [[NSMutableArray alloc]init];
                    for (DMOrb *orbInWildMatchOrbs in [wildMatchOrbs reverseObjectEnumerator]) {
                        if ([self wildOrHigher:orbInWildMatchOrbs.type]) {
                            [previousWildsInARow addObject:orbInWildMatchOrbs];
                        } else {
                            break;
                        }
                    }
                    wildMatchOrbs = [NSMutableArray arrayWithArray:previousWildsInARow];
                    [wildMatchOrbs addObject:orb];
                //otherwise reset the search
                } else {
                    wildSearchActive = NO;
                    wildMatchOrbs = [[NSMutableArray alloc]init];
                }

                //this check is required on the final orb to make sure it gets added
                if (i == kNumOrbsPerRow - 1) {
                    if (wildMatchOrbs.count >= 3) {
                        [matches addObject:wildMatchOrbs];
                    }
                }
            }

            //regular logic
            //when a different type is encountered, add the match if it has 3 or more
            if (matchingOrbs.count >= 3) {
                [matches addObject:matchingOrbs];
            }
            //reset this array and add the orb for the next iteration
            matchingOrbs = [[NSMutableArray alloc]init];
            [matchingOrbs addObject:orb];

        //if the orb matches or is valid
        } else {
            [matchingOrbs addObject:orb];

            //logic for wild searching
            if (wildSearchActive) {
                [wildMatchOrbs addObject:orb];
            }

            //regular logic
            //if the last few are the same type, this will make sure the match gets added
            if (i == kNumOrbsPerRow - 1) {
                if (matchingOrbs.count >= 3) {
                    [matches addObject:matchingOrbs];
                }
                if (wildMatchOrbs.count >= 3) {
                    [matches addObject:wildMatchOrbs];
                }
            }
        }

        //if the type is not wild, set this orb type to the one to search for
        if (![self wildOrHigher:orb.type]) {
            typeToSearchFor = orb.type;
        }
        previousOrb = orb;
    }

    return [self removeDuplicateMatches:matches];
}
-(BOOL) wildOrHigher:(DMOrbType)type {
    return type == DMOrbTypeWild ||
           type == DMOrbTypeBomb ||
           type == DMOrbTypeRotate ||
           type == DMOrbTypeSwapCards;
}
-(NSMutableArray *) removeDuplicateMatches:(NSMutableArray *)matches {
    //first, sort the match array by count size
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:@"@count" ascending:NO];
    NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
    [matches sortUsingDescriptors:sortDescriptors];

    //starting with the largest, start comparing and building
    NSMutableArray *validMatches = [[NSMutableArray alloc]init];
    NSMutableArray *orbsAlreadyEncountered = [[NSMutableArray alloc]init];
    int wildCount = 0;
    for (NSMutableArray *match in matches) {
        BOOL redundantMatch = NO;
        for (DMOrb *orb in match) {
            if (!redundantMatch &&
                ![self wildOrHigher:orb.type] &&
                [orbsAlreadyEncountered containsObject:orb]) {
                redundantMatch = YES;
            } else {
                [orbsAlreadyEncountered addObject:orb];
            }

            //wild check to fix bug
            if ([self wildOrHigher:orb.type]) {
                wildCount++;
            }
            if (wildCount >= kNumOrbsPerRow) {
                for (NSMutableArray *validMatch in validMatches) {
                    if (validMatch.count >= kNumOrbsPerRow) {
                        redundantMatch = YES;
                    }
                }
            }

        }
        if (!redundantMatch) {
            [validMatches addObject:match];
        }
    }
    return validMatches;
}

At the top of the method, I have this large comment explaining all of the logic in one place for easy reference:

//Logical Flow of Method//
//set up the variables needed
//get the type to search for if the first orb is wild
//start search and repeat for all orbs
    //if a wild is found, start wild search
    //if the orb does not match and is not wild
        //if wild search is active
            //evaluate the match and rebuild the array based on previous orb
        //else if not evaluate the match and rebuild the array
    //else if it does match or is wild, add it to the regular array
        //if the wild search active, also add it to the wild search array
    //if not wild, set the type to search for to the type of the current orb
//cull repeated matches in a separate method

Here is the usage of this mega method:

-(NSMutableArray *) matchesInAllRows {
    NSMutableArray *matches = [[NSMutableArray alloc]init];
    for (DMRow *row in _board.rows) {
        [matches addObjectsFromArray:[self matchesForOrbs:row.orbs]];
    }
    return matches;
}
-(NSMutableArray *) matchesInAllColumns {
    NSMutableArray *matches = [[NSMutableArray alloc]init];
    for (DMColumn *column in _board.columns) {
        [matches addObjectsFromArray:[self matchesForOrbs:column.orbs]];
    }
    return matches;
}

All feedback is welcome!

\$\endgroup\$
5
\$\begingroup\$

After scrolling through a whole lot of this:

enter image description here

I've decided that you need to add the following method to your DMOrb class:

+ (instancetype)orbWithType:(DMOrbType)type {
    return [[self alloc] initWithType:type];
}

And that applies, regardless of anything else.

But as thorough as it looks like your testing is... isn't it always going to be incomplete unless you just write a method that iterates through every possible combination of orbs and tests that?

Come up with an algorithm to test every single combination of orbs. That's your unit test. Anything else seems rather incomplete.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.