2012-05-01

Email Address Validation For iOS

If you’ve ever needed to validate an email address, the chances are you used a regex engine and validated the address’ structure against something like this:

^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$

It works well enough for the average case. But what about this email address?

joe+bloggs@example.com

Not so good. You could modify the regex to support the “+” character, but if you read RFC 822 and RFC 5321 you’ll quickly discover that the structure of an email address isn’t just “alphanumeric@alphanumeric.alphanumeric”. Email addresses include a built-in comment system using parentheses, escape characters, quoted sections, “dot-atoms” and more. A regex to correctly parse the entirety of RFC 822 would be huge, and that’s the older, simpler version of an email address.

Once you start really digging into the syntax of an email address, you find that:

  • Domain names (the part after the “@”) can now be in character sets other than Latin, so your primitive [a-z][0-9] regex won’t work;
  • The local part of the address (the part before the “@”) can also now be in other character sets, but as this idea is about 2 months old nothing supports it yet;
  • Trying to figure out if Exchange supports internationalised domain names (IDN) or not is basically guesswork;
  • There are multiple standards for email addresses, most of which disagree with each other and most of which appear to be deprecated. Figuring out which is the current standard is like trying to find a particular Skittle in a bag of Skittles.

Pretty much the only fact you’ll be entirely sure of after your research is that regex is a really bad solution for validating email addresses.

My favourite part of RFC 3696 is where it says these addresses are valid:

Fred\ Bloggs@example.com
"Fred Bloggs"@example.com

This makes sense. The backslash is used to escape a single character, whereas the quotes are used to signify that everything between them should be automatically escaped. One’s really a shortcut for the other. Simple.

However, the errata suggests that this is a mistake, and that this is the correct form:

 "Fred\ Bloggs"@example.com

Now we have a backslash system embedded within a quoting system. The former makes the latter entirely superfluous. I think this amendment is probably why so many developers just stick with regex: The standard is crap.

So, you have two choices:

  1. Ignore the complexity, do what everyone else does, and use regex regardless;
  2. Write an email address parser.

As I’m crazy, here’s an email address parser written in Objective-C. It has some limitations:

  • It does not allow the local part of the address to be internationalised as nothing supports this yet.
  • It does not allow domain name literals (ie. joe@[10.0.1.2]) as Exchange does not support them and I’ve never seen one in the wild.
  • It may not apply all of the backslash escape rules correctly, as this is one area where every document entirely disagrees with every other document.
  • It may not enforce the omission of certain ASCII ranges correctly, as this is another area where the docs suck.

On the plus side:

  • It validates comments and nested comments (comments are apparently deprecated, but I can’t find anywhere that specifically says that this is the case).
  • It correctly enforces at least some of the backslash escape rules.
  • It allows the use of quoted sections (that can include characters like “@”, “(”, etc) and enforces their rules (quotes must be matched; quoted sections must be the only element of the local part or must be delimited by periods).
  • It enforces period rules (periods cannot be the first or last part of the local or domain parts of the address; periods cannot be adjacent except within a quoted section).
  • It enforces the maximum and minimum lengths of each section (up to 64 chars for the local part; up to 254 chars for the entire address; there must be a local and domain part of the address).
  • It allows IDN.
  • Domain names can only contain letters, numbers, periods and hyphens.
  • It passes all of the test cases written by Dominic Sayers except those concerning carriage/line returns and domain name literals, which aren’t relevant to what I’m trying to do.

// .h
@interface SZEmailValidator : NSObject

+ (BOOL)isValid:(NSString *)candidate;

@end

// .m
#import "SZEmailValidator.h"

struct SZEmailParserState {
    BOOL quoted : 1;
    BOOL escaped : 1;
    BOOL domain : 1;
    BOOL dot : 1;
    BOOL followingQuoteBlock : 1;
};

@implementation SZEmailValidator

+ (BOOL)isValid:(NSString *)candidate {

    unsigned int domainPartStart = 0;
    unsigned int commentDepth = 0;
    
    struct SZEmailParserState state;
    
    state.dot = NO;
    state.quoted = NO;
    state.escaped = NO;
    state.followingQuoteBlock = NO;
    state.domain = NO;
    
    for (unsigned int i = 0; i < candidate.length; ++i) {
        unichar character = [candidate characterAtIndex:i];
                
        if (!state.domain) {
            
            // Do not allow characters beyond the ASCII set in the username
            if (character > 126) return NO;
             
            // Do not allow NULL
            if (character == 0) return NO;
            
            // Do not allow LF
            if (character == 10) return NO;
        }
        
        if (i > 253) {
            
            // Do not allow more than 254 characters in the entire address
            return NO;
        }
        
        // The only characters that can follow a quote block are @ and period.
        if (state.followingQuoteBlock) {
            if (character != '@' && character != '.') {
                return NO;
            }
            
            state.followingQuoteBlock = NO;
        }
        
        switch (character) {
            case '@':
                
                if (state.domain) {
                    
                    // @ not allowed in the domain portion of the address
                    return NO;
                    
                } else if (state.quoted) {
                    
                    // Ignore @ signs when quoted
                    
                } else if (state.dot) {
                    
                    // Dots are not allowed as the final character in the local
                    // part
                    return NO;
                    
                } else {
                    
                    // Swapping to the domain portion of the address
                    state.domain = YES;
                    domainPartStart = i + 1;
                    
                    if (i > 64) {
                        
                        // Do not allow more than 63 characters in the local part
                        return NO;
                        
                    }
                }
                
                // No longer in dot/escape mode
                state.dot = NO;
                state.escaped = NO;
                
                break;
                
            case '(':
                
                // Comments only activate when not quoted or escaped
                if (!state.quoted && !state.escaped) {
                    ++commentDepth;
                }
                
                break;
                
            case ')':

                // Comments only activate when not quoted or escaped
                if (!state.quoted && !state.escaped) {
                    
                    if (commentDepth == 0) return NO;
                    
                    --commentDepth;
                }
                
                break;
                
            case '\\':
                
                if (!state.quoted && commentDepth == 0) {
                    
                    // Backslash isn't allowed outside of quote/comment mode
                    return NO;
                }
                    
                // Flip the escape bit to enter/exit escape mode
                state.escaped = !state.escaped;
                
                // No longer in dot mode
                state.dot = NO;
                
                break;
            
            case '"':
                
                if (state.domain && commentDepth == 0) {
                    
                    // quote not allowed in the domain portion of the address
                    // outside of a comment
                    return NO;
                }
                
                if (!state.escaped) {
                    
                    // Quotes are only allowed at the start of the local part,
                    // after a dot or to close an existing quote part
                    if (i == 0 || state.dot || state.quoted) {
                        
                        // Remember that we just left a quote block
                        if (state.quoted) {
                            state.followingQuoteBlock = YES;
                        }
                    
                        // Flip the quote bit to enter/exit quote mode
                        state.quoted = !state.quoted;
                    } else {
                        return NO;
                    }
                }
                
                // No longer in dot/escape mode
                state.dot = NO;
                state.escaped = NO;
                
                break;
            
            case '.':
    
                if (i == 0) {
                    
                    // Dots are not allowed as the first character of the local
                    // part
                    return NO;
                    
                } else if (i == domainPartStart) {
                    
                    // Dots are not allowed as the first character of the domain
                    // part
                    return NO;
                    
                } else if (i == candidate.length - 1) {
                    
                    // Dots are not allowed as the last character of the domain
                    // part
                    return NO;
                }
                
                if (!state.quoted) {
                    
                    if (state.dot) {
            
                        // Cannot allow adjacent dots
                        return NO;
                    } else {
                        
                        // Entering dot mode
                        state.dot = YES;
                    }
                    
                }
                    
                // No longer in escape mode
                state.escaped = NO;

                break;

            case ' ':
            case ',':
            case '[':
            case ']':
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
            case 6:
            case 7:
            case 8:
            case 9:
            case 11:
            case 13:
            case 15:

                // These characters can only appear when quoted
                if (!state.quoted) {
                    return NO;
                }
                
            default:
                
                // No longer in dot/escape mode
                state.dot = NO;
                state.escaped = NO;

                // Do not allow characters outside of unicode, numerals, hyphens
                // and periods in the domain part.  We use letterCharacterSet
                // because we're supporting internationalised domain names.
                // We don't have to do anything special with the name; that's up
                // to the email client/server to handle.
                if (state.domain) {
                    if (![[NSCharacterSet letterCharacterSet] characterIsMember:character] &&
                        ![[NSCharacterSet decimalDigitCharacterSet] characterIsMember:character] &&
                        character != '-') {
                        
                        return NO;
                    }
                }
                
                break;
        }
    }
    
    // Do not allow unclosed comments
    if (commentDepth > 0) return NO;
    
    // If we didn't identify a local and a domain part the address isn't valid
    if (!state.domain) return NO;
    if (candidate.length == domainPartStart) return NO;
    if (domainPartStart == 1) return NO;
    
    // Validate domain name components
    NSArray *components = [[candidate substringFromIndex:domainPartStart] componentsSeparatedByString:@"."];
    
    for (NSString *item in components) {
        
        // We can't allow a hyphen as the first or last char in a domain name
        // component
        if ([item characterAtIndex:0] == '-' || [item characterAtIndex:item.length - 1] == '-') {
            return NO;
        }
        
        // Items must not be longer than 63 chars
        if (item.length > 63) return NO;
    }

    return YES;
}

@end

2012-01-24

JSON, .NET and NSDate

If you’re trying to access a JSON web service created using .NET from an iOS device, you’ve probably discovered that dates are produced in a less than useful format:

/Date(1233423345345)/

The rationale for the date format can be found here, but it’s basically a call to the JavaScript Date object constructor. The numeric value represents the number of milliseconds since 1970-01-01.

Below I’ve added a handy Objective-C function that will extract the value and return an NSDate object. Note that we’re losing accuracy by magnitudes as we translate from .NET DateTime objects (which measure time in ticks) to JavaScript Date objects (which measure time in milliseconds) and finally to iOS NSDate objects (which measure time in seconds).


/**
 * Converts a .NET JSON date in the format "/Date(x)/" to an NSDate object.
 * @param string The NSString to convert.
 * @return the NSDate object.
 */
(NSDate*)jsonStringToNSDate(NSString* string) {

    // Extract the numeric part of the date.  Dates should be in the format
    // "/Date(x)/", where x is a number.  This format is supplied automatically
    // by JSON serialisers in .NET.
    NSRange range = NSMakeRange(6, [string length] - 8);
    NSString* substring = [string substringWithRange:range];

    // Have to use a number formatter to extract the value from the string into
    // a long long as the longLongValue method of the string doesn't seem to
    // return anything useful - it is always grossly incorrect.
    NSNumberFormatter* formatter = [[NSNumberFormatter alloc] init];
    NSNumber* milliseconds = [formatter numberFromString:substring];

    [formatter release];

    // NSTimeInterval is specified in seconds.  The value we get back from the
    // web service is specified in milliseconds.  Both values are since 1st Jan
    // 1970 (epoch).
    NSTimeInterval seconds = [milliseconds longLongValue] / 1000;

    return [NSDate dateWithTimeIntervalSince1970:seconds];
}

2011-10-13

Storyboards in the iOS5 SDK

Compared with the old nib style of UI creation, the new storyboard system is awesome. Suddenly the whole UI system makes sense and I’ve been able to replace some nasty bodges in a little program I’ve been working on.

One more time: storyboards are awesome.

2011-10-12

Another Objective-C Gotcha - Synthesized Properties and Ivars

When synthesizing ivars and properties, Objective-C will give both the same name. Unless explicitly told to use the property by prefixing it with self., Objective-C will use the related ivar instead when referenced within the class.

I spent about two hours trying to track down the problem in the code below. It’s obvious now that I know what the problem is, but trying to isolate it from the rest of the application was tricky. The fact that the code in question was interacting with a block, a cause of much memory-related pain in the past, threw me off the scent for a while.

This is the interface for the class in question:


@interface Person : NSObject

@property (copy) NSString* name;

- (id)init;
- (id)initWithName:(NSString*)aName;
- (void)dealloc;

@end

This is the implementation:


@implementation Person

@synthesize name;

- (id)init {
    [self initWithName:nil];
}

- (id)initWithName:(NSString*)aName {
    name = aName;
}

- (void)dealloc {
    [name release];
}

@end

Here’s how we’d use it:


Person* bob = [[Person alloc] initWithName:@"Bob"];

NSLog(@"%@", bob.name);

This looks fine, but it contains a subtle bug. Trying to retrieve bob.name will result in a crash once the current autorelease pool has been drained. The synthesized property name and its backing synthesized ivar are both called name. When we set name equal to aName in the initWithName: method, we are setting the ivar, not the property. Thus, we bypass the copy logic that we’ve set up in the property and just change a pointer. The string literal @"Bob" is autoreleased, so we’ve got a pointer to a deallocated object.

There are two ways to avoid this mistake:

  • Always declare ivars explicitly, using a name that is different from the property name (ie. _name);
  • Always prefix property names with self. when referring to them.

2011-08-11

Really Bad Eggs - Objective-C Edition

The Objective-C version of Really Bad Eggs is coming along nicely. I’ll try to get some screenshots and maybe a video up, but in the meantime here’s some stream-of-consciousness pontificating on the port so far.

When I wrote DS code I used to complain about the lack of a debugger. I’ve written most of the Objective-C version of Really Bad Eggs whilst out and about on a Windows laptop, so I haven’t had a debugger or even a compiler. Despite the lack of tools and the game being my first Objective-C program it’s working well so far. Rewriting the code in a new language has allowed me to re-examine a lot of the original ideas. In some cases I’ve refactored both the C++ and Objective-C code to be simpler or more versatile. In others I’ve used features of Objective-C to improve on the original code.

Bug Fixes and General Improvements

The DS’s representation of incoming garbage eggs was inaccurate due to a typo. I’ve fixed this in both versions of the game. I’ve also improved the placement of garbage eggs that are dropped into the grid.

Garbage eggs are always dropped into the columns with the least eggs first. The (simplified) algorithm looks like this:

  • Get a list of columns and their heights, sorted in ascending order by height (insertion sort).
  • Add the leftmost column to the list of “active” columns.
  • While there are eggs to place:
    • Place a garbage egg in each active column.
    • If the column to the right of the last active column has the same height as the last active column, add it to the active column list.

It essentially fills the grid from bottom to top as though it were pouring in water.

There is a subtle problem with the algorithm. The sorting system produces a list of columns that are sorted by height and column index. In SQL, it would look like this:

SELECT
    columnHeight
   ,columnIndex
FROM
    columns
ORDER BY
    columnHeight ASC
   ,columnIndex ASC

If columns 0, 1, 2, and 5 are of height 2, whilst columns 3 and 4 are of height 1, and we add a garbage egg, we know that the egg will be added to column 3. Here’s the column state:

        000  0
        000000
        ------
Column: 012345

Here’s the sorted data:

Column: { 3, 4, 0, 1, 2, 5 }
Height: { 1, 1, 2, 2, 2, 2 }

The first column to receive an egg will be column 3, as it is the left-most column in the sorted list.

This predictability means that advanced players can start using it to their advantage. They can drop a block here because they know they’ve got an incoming garbage egg and want it to go there. They can change their plans because they know what’s going to happen next. To prevent this, the sorting algorithm now randomises columns of the same height within the sorted array.

Memory Management in Objective-C

Memory management is a pain. I can see exactly why Apple have implemented reference counting the way they have. I’ve worked extensively with low-level memory management in C++ due to the combo attack of the DS’ tiny stack/slow CPU. The combination makes both smart pointers and stack-based objects mostly unusable, leading to the same questions time and time again:

  • If I call a function, who owns the memory it allocates? Do I delete it or not?
  • Does this function expect to receive an existing block of memory that I allocate or will it allocate its own?
  • When writing functions, should I allocate memory or should I expect it to be allocated in advance?
  • Should I return allocated memory via the return statement or via a pointer-to-a-pointer passed as a parameter?
  • Should I return an object on the stack and hope RVO kicks in, or should I return a pointer to an object on the heap?
  • Should functions receive and return pointers or references to objects?
  • If an object is told to store a pointer to another object, should it store the pointer or a copy? What if the pointed-to object changes during the lifetime of my object?

I’ve become used to considering these issues every time I work with memory in C++, but Objective-C tries to solve the issues itself. Mostly it uses conventions to suggest the correct approach. Functions with “new” or “init” at the start of the names, for example, will retain the returned object and increase its reference count. The caller therefore becomes the owner of the object. Convenience methods, such as NSArray’s arrayWithObjects method, return autoreleased objects. These are released automatically by the Cocoa framework when the current iteration of the application’s state terminates. If the creating code needs them around longer then it needs to retain them. Collections retain objects added to them, so they can be deemed to be the owners of those objects. It is the responsibility of the object’s previous owner to ensure that he releases the object.

Getting the hang of the conventions is tricky, particularly when dealing with frameworks like cocos2d which aren’t the best architected examples I’ve seen. There are a number of edge cases, too. Suppose you retain a reference to object A, which creates and retains a reference to object B, which itself creates and retains a reference to object C. During its creation, object C retains object B. What happens to B and C when A is released? Why, nothing, of course. B and C have a cyclic relationship. Releasing object A reduces the reference count of B by one, but B is still referenced by C (and vice-versa) so neither will be deallocated. This isn’t an issue in C++ as the developer manually deletes objects. Deleting A will delete B which will delete C (assuming the destructors tidy things up correctly). Nor is it an issue for C#’s garbage collector because it can tell that the only references to B and C are each other.

For the most part, I’ve used what Objective-C calls “weak references” - I’ve got pointers to objects without retaining them, the same as I would in C++. My code probably looks hideous to experienced Objective-C coders as I’ve trampled conventions all over the place.

One of the main reasons I upgraded to OSX Lion so quickly was the promise of ARC, or “Automatic Reference Counting”. Skimming through the docs suggests that there are a lot of edge cases to it, though (that seems to be a common theme with Objective-C - watch out for the edge cases), so I’m sticking with the traditional method first in order to understand fully what ARC is replacing.

A bug that caught me out for a while was a classic Objective-C memory leak. The “Leaks” utility insisted I’d leaked memory from one class like crazy, but it also told me that the reference count for all leaked objects was 0. That makes no sense until you remember that Objective-C allows you to make boneheaded mistakes like this:

- (void)dealloc { }

Oops. All dealloc methods should call the superclass’ dealloc method:

- (void)dealloc {
    [super dealloc];
}

I’d have suggested that Apple use the template pattern for this instead. NSObject could have methods like this:

- (void)onDealloc { }

- (void)dealloc {
    [self onDealloc];
    [super dealloc];
}

User code would look like this:

- (void)onDealloc {
    // Cleanup here
}

It’s now impossible to leak memory by forgetting to dealloc the superclass. I imagine there’s a valid case for giving developers the power to shoot themselves in the foot, but there’s a lot of boilerplate code needed in classes that could be excised very easily by using the template pattern. The same is true for object initialisation. Here’s what you do now:

- (id)init {
    if ((self = [super init])) {
    // Initialise the object
    }

    return self;
}

This is what a templated NSObject could look like:

- (void)onInit { }

- (id)init {
    if ((self = [super init])) {
        [self onInit];
    }

    return self;
}

This is what a user class would look like:

- (void)onInit {
    // Initialise the object
}

Tidier, but potentially less powerful.

Performance

Running the OSX version of Really Bad Eggs through Xcode’s CPU profiler demonstrates how efficiently the game works. Most of the game code barely registers; cocos2d’s drawing routines use several magnitudes more CPU time than anything else in the game. It also demonstrated how abysmally slow the NSArray objects are. One function I used them in took up more CPU power than anything else in the entire game (except the cocos2d drawing routines), so I refactored the function without NSArrays and the problem disappeared.

Dynanism

The dynamic nature of Objective-C means I’m happy to use calls to an object’s isKindOfClass method, whereas in C++ I’d have been more inclined to refactor to remove the need for type checking or calls to dynamic_cast<>(). For example, the C++ function to test if two eggs can be connected looks like this:

void NormalBlock::connect(const BlockBase* top,
                          const BlockBase* right,
                          const BlockBase* bottom,
                          const BlockBase* left) {
    setConnections(top != NULL && top->getColour() == _colour && top->getState() == BlockBase::BLOCK_STATE_NORMAL,
                   right != NULL && right->getColour() == _colour && right->getState() == BlockBase::BLOCK_STATE_NORMAL,
                   bottom != NULL && bottom->getColour() == _colour && bottom->getState() == BlockBase::BLOCK_STATE_NORMAL,
                   left != NULL && left->getColour() == _colour && left->getState() == BlockBase::BLOCK_STATE_NORMAL);
}

All egg classes inherit from the BlockBase class (descriptive name, I know) which includes a “_colour” member. Each subclass sets this to a colour representative of the bitmaps used to display the egg. The red egg has an RGB value of (31, 0, 0) whilst the blue egg has an RGB value of (0, 0, 31). Checking if two eggs are of the same type simply entails a comparison between two 16-bit integers.

The Objective-C function just compares the eggs’ classes instead:

- (void)connect:(BlockBase*)top right:(BlockBase*)right bottom:(BlockBase*)bottom left:(BlockBase*)left {

    BOOL topSet = top != NULL && [top class] == [self class] && top.state == BlockNormalState;
    BOOL rightSet = right != NULL && [right class] == [self class] && right.state == BlockNormalState;
    BOOL bottomSet = bottom != NULL && [bottom class] == [self class] && bottom.state == BlockNormalState;
    BOOL leftSet = left != NULL && [left class] == [self class] && left.state == BlockNormalState;

    [self setConnectionTop:topSet right:rightSet bottom:bottomSet left:leftSet];
}

It’s not a major change, but it does remove some cruft from the code.

Blocks as Callback Functions

The Grid class represents the well of eggs and manages all of the eggs within it. It includes functions for rotating the live eggs, identifying and exploding chains of eggs, and so on. In the C++/DS version, the Grid class is also responsible for calling methods of the SoundPlayer class to trigger audio playback every time anything sound-worthy occurs. This works, but it’s not terribly clean - the Grid shouldn’t need to deal with sounds. The design ties the logic of the game to the platform it is running on.

A better solution would be to trigger an event, but if you’ve ever implemented an event system you’ll know that it’s a very verbose pattern. At the very least, you’ll need interfaces and classes that represent event arguments and event listeners. I’d like to tidy up the design but I’m not that bothered.

A considerably terser option would be to use a function pointer as a callback method, but that opens up a whole can of non-OO, global namespace-polluting nastiness. C++’s pointers-to-members are so limited - the exact type of the pointed-to object must be known - that they aren’t even worth considering.

Another possibility would be to create a message queue and have the grid push messages into it. A message pump would dispatch messages to the sound player object as necessary. Again, this is ugly and involves far too much new code for virtually no benefit.

It was only after working through this familiar thought process that I remembered that Apple has added closures (“blocks”) as a non-standard extension to C. With closures, the problems disappear. No bloated event system, no global namespace pollution with random C functions, and a simple way to extract the sound triggers from the Grid class.

The “Grid.h” file includes the following typedefs:

typedef void(^GridEvent)(Grid*);
typedef void(^GridBlockEvent)(Grid* grid, BlockBase* block);

I think of the typedefs as being analogous to C#’s delegate signatures. The class itself includes the following members:

GridEvent _onGarbageRowAdded;
GridEvent _onLand;
GridEvent _onGarbageLand;
GridBlockEvent _onBlockAdd;
GridBlockEvent _onBlockRemove;
GridBlockEvent _onGarbageBlockLand;

The class triggers the closures like so:

if (_onBlockLand != nil) _onBlockLand(self);

Wiring up code to an instance of the Grid class is simple:

Grid* grid = [[Grid alloc] initWithHeight:2 playerNumber:0];

grid.onBlockLand = ^(Grid* sender) {
    printf("A block has landed");
};

It looks like C, and it tastes like C, but I can perform JavaScript-like tricks with it. ‘Tis a beautiful abomination.

I’ve used this pattern all over the code to trigger sounds and visual effects as well as add/move/remove sprites. The game engine code is completely separate from the presentation layer.

Classes as Objects

The BlockFactory class has also benefited a little from the switch to Objective-C. When a grid needs to place a new egg, it fetches a new egg instance from the BlockFactory rather than create the egg itself.

The BlockFactory is necessary because all players receive the same sequence of eggs. Suppose player 1 requests his third pair of eggs and is given a red egg and a blue egg. When player 2 requests his third pair of eggs, he must also be given a red egg and a blue egg. No player can “cheat” due to a particularly advantageous sequence of eggs. Winning is a matter of skill and speed rather than luck.

The BlockFactory must, therefore, maintain a list of all egg types that have been generated. It also needs to remember which pair of eggs all players will receive next. Once a pair of eggs has been used by all players it can be removed from the list. Attempts to retrieve a pair of eggs not in the list will cause a new pair of random egg types to be created and appended to the list. The BlockFactory instance is shared across all players.

In the C++ version, the list stores enum values that represent an egg type. When a new egg is requested, the correct constructor is called for that value using a switch() statement. Here’s the code that adds a new item to the list:

BlockFactory::BlockType BlockFactory::getRandomBlockType() const {
    return (BlockFactory::BlockType)(rand() % _blockColourCount);
}

void BlockFactory::addRandomBlock() {
    _blockList.push_back(getRandomBlockType());
}

Here’s the code that creates a new egg from a given type:

BlockBase* BlockFactory::newBlockFromType(BlockFactory::BlockType type) const {
    switch (type) {
        case BLOCK_RED:
            return new RedBlock();
        case BLOCK_PURPLE:
            return new PurpleBlock();
        case BLOCK_YELLOW:
            return new YellowBlock();
        case BLOCK_BLUE:
            return new BlueBlock();
        case BLOCK_GREEN:
            return new GreenBlock();
        case BLOCK_ORANGE:
            return new OrangeBlock();
    }

    // Included to silence compiler warning
    return new RedBlock();
}

The BlockFactory class includes a BlockType enum necessary for this scheme to work.

The Objective-C version dumps the enum and uses a tidier solution. As classes in Objective-C are themselves objects, storing enum values in the list is unnecessary. Instead, the list stores pointers to the class objects. Here’s the code that adds a new item to the list:

- (void)addRandomBlockClass {
    [_blockList addObject:[self randomBlockClass]];
}

- (Class)randomBlockClass {
    int type = rand() % _blockColourCount;

    switch (type) {
        case 0:
            return [RedBlock class];
        case 1:
            return [BlueBlock class];
        case 2:
            return [YellowBlock class];
        case 3:
            return [PurpleBlock class];
        case 4:
            return [GreenBlock class];
        case 5:
            return [OrangeBlock class];
    }

    // Included to silence compiler warning
    return [RedBlock class];
}

Creating a new BlockBase object from the list becomes trivial. A single line of code will create a new BlockBase object from an item in the list:

BlockBase* block = [[[_blockList objectAtIndex:0] alloc] init];

The item in the list is a class, so calling its alloc and init methods gives us a new object automatically.

The difference between the two sets of code is minor, but conceptually it’s a big leap. Objective-C lets me store classes in arrays. I’m pretty certain that the only other language I’ve used that could do anything even remotely like this is JavaScript.

2011-07-14

Farewell, DS Homebrew

It’s time for me to say goodbye to the DS. I’ve been working on DS homebrew projects in my spare time since March 2007, and I’ve learned a huge amount doing so. However, I’ve noticed recently that I’m not really learning much from any of the new projects I work on. It’s still hugely enjoyable, particularly now that I can get games whipped up in a few days and polished for release in a few weeks, but I’m just repeating the same patterns in the same language. Worse, there isn’t an audience for DS homebrew any more. The audience, and most of the developers, have moved over to iOS and Android devices. The DS itself is two generations out of date.

To be honest, I wanted to ditch the DS back when Quirky switched to Android development. In fact, I wanted to ditch the DS as soon as Apple announced officially-supported apps for the iPhone. I’ve stuck with it for three reasons:

  • I wanted to see Woopsi through to completion;
  • The iPhone’s input devices aren’t a patch on the DS’ physical buttons for the kind of games I enjoy playing;
  • Having grown up playing Atari, Sega and Nintendo consoles, actually writing software for one fills me with childlike delight.

I’ve achieved everything I set out to with the DS. I’ve written developer tools, libraries and games for a handheld console, and even entered a homebrewing competition. EarthShakerDS placed 7th out of 24 in GBATemp’s homebrew bounty competition, which isn’t bad for a game knocked up in a couple of weeks.

It’s time to move on.

I do have a few updates to release before I say goodbye to devKitARM - bugfixes to Woopsi, WoopsiGfx and EarthShakerDS, and Really Bad Eggs will get a final release as soon as the title screen is done. I won’t be starting any new DS projects, though.

Unlike Quirky, I’m switching to Apple development. My current project is to port Really Bad Eggs from C++/DS/SDL to Objective-C/cocos2d. I know that I could just use the C++ version on the Mac, but I want to learn Objective-C. Porting something I’ve recently written seems like a good first project. The game engine and AI are already running; I/O is next in the list.