EarthShakerDS 20120513

It’s about time for another EarthShakerDS release:

EarthShakerDS 20120513

Changes this time around include:

  • The ability to load and save levels in the editor with user-defined names (so you can have more than one custom level, and share them with other people);
  • A “Custom Level” option on the title screen listing all user-created levels
  • Slightly re-organised level editor;
  • The ability to load and save boulder/wall/door/soil types in a custom level;
  • Fixed for the latest devkitARM;
  • Updated to the latest WoopsiGfxGameTemplate (fixes SDL version);
  • Start button can be used to test a level in the editor;
  • Select button can page through level editor menu panels.

I need to finish off the file requester in the level editor and it’ll finally be done.

Looking through this code again, I’m astonished at the level of spaghettification. On the other hand, I did knock the whole thing up in a couple of weeks.

iOS Provisioning

The code signing and provisioning aspect of iOS development is, without a doubt, the worst thing that Apple has ever produced. I’ve been reminded of this because I just updated to OSX 10.7.4 and for some reason all of my provisioning profiles now complain that they can’t find a valid signing identity. An hour or so of googling and button clicking later and I’m admitting defeat for the moment.

As an example of how hateful the process is and how little regard Apple has for its community of developers, here’s a screenshot from their certificate history page:

Yes, those are dates. And yes, they are sorted alphabetically. This from a company that is universally praised for its attention to detail and the ease of use of its products.

Developers! Apple hates you.

EarthShakerDS Revived

It’s been 10 months since the last update, so I figure it’s about time to revive the EarthShakerDS code and finish off the level editor. Changes so far:

  • Updated the OSX version to the latest codebase;
  • Fixed SDL bugs in the WoopsiGfxGameTemplate;
  • Added the ability to save and load soil/boulder/wall/door types to the level editor;
  • Added a keyboard and file lister to the level editor to allow the saving and loading of custom levels with user-defined names;
  • Added a list of custom levels to the title screen menu;
  • The Start button tests the current level in the level editor;
  • The Select button flips between level editor panels.

I’ve got to make the custom level select on the title screen actually start a level and allow interaction with the file requester in the level editor. Once those are done the game will finally be finished.

Here’s a grab of the new file requester. It isn’t pretty but it’ll work…

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) {
 
					// " 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

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];
}

Blogging with Mercurial from the Rocky Mountains

It’s been a while since the last update, but I do have a good excuse. I’ve upped sticks and moved from dingy Birmingham, in the UK, to sunny Littleton, Colorado, in the USA.

So, who’s hiring?

I’ve decided to start blogging about those experiences, but rather than flood this blog with personal posts I’m making a separate blog. But what blogging platform to use? It’s not going to have much traffic, I don’t want to put any admin time into it, I don’t want comments or tags or categories. All I want is to be able to post text as quickly as possible using Markdown.

It would be great if there was a blogging platform for Mercurial like the one available to Github users, but I’ve looked around and haven’t been able to find one. So why not write one?

Here’s my list of requirements:

  • Displays blog posts in reverse chronological order (newest first).
  • Posts are written in Markdown and reformatted into HTML automatically.
  • Posts are stored in a repository on BitBucket.
  • Pushing to the posts repository automatically updates the blog.
  • Allows paging through blog posts.
  • Includes an “Archive” page that lists all blog posts.
  • Has an RSS feed.
  • Maintains a cache of blog posts to minimise queries to BitBucket’s API.
  • Looks a bit like WordPress’ “TwentyEleven” theme, as that’s my favourite theme at the moment.

After a couple of evenings of hacking, here’s the result:

And here’s a demo:

A blog powered by BitBucket and Mercurial. Neat!

I’ve included instructions for getting a BitBlogger instance up and running on AppHarbor, a .NET hosting site that offers a basic freemium option. As BitBlogger doesn’t use any local storage or background events (BitBucket notifies it of changes, rather than BitBlogger needing to poll for updates), it doesn’t need anything above the free package.

I’ve copied bits and pieces of the TwentyEleven theme’s CSS file in order to replicate some of the appearance, so the licence for this application is the GPL rather than my preferred MIT licence. If anyone feels like putting together a simple and very legible theme to replace the current look and feel, I’ll be able to switch it over.

Stuff I Use

Inspired by Scott Hanselman’s enormous list of tools that he uses, used, or heard of once, here’s three comparatively tiny lists of tools I use every day. The first list is comprised of cross-platform software that I use on both OSX and Windows. The second is OSX-only software and the third is Windows-only software.

I don’t use Linux enough to have a set of tools I recommend. As I’m the only person on the planet to think that Unity is actually a small step in the right direction, even if it does need optimising, my opinion doesn’t count.

Cross-Platform

  • Sublime Text 2 - Without a doubt, the best text editor I’ve used on any platform.
  • VLC - The most versatile media playback program around.
  • DiffMerge - 3-way diff tool.
  • Mercurial - Distributed version control.
  • FileZilla - FTP client.
  • Dropbox - File syncing in the cloud.

DiffMerge and FileZilla make the list over other more advanced programs by virtue of being free and cross-platform. Mercurial triumphs over Git thanks to its first-rate Windows support and its spectacular command line UI. I recommend TortoiseHg for Windows as it is often handy to see at a glance if anything in a repository has changed without firing up PowerShell.

OSX

  • iTerm2 - Replacement for the standard Terminal.app featuring tabs.
  • Hex Fiend - Because everyone needs a hex editor.
  • PlainCalc - Calculator with a console interface.
  • Adium - Fantastic IM client.
  • KeePassX - Encrypted password storage.
  • Acorn - Simple image editor.
  • VMWare Fusion - OS virtualisation, great for Windows.
  • VirtualBox - OS virtualisation, great for Linux.
  • The Unarchiver - Decompressor for zip, 7zip, etc.
  • Bean - Simple word processor.
  • XBMC - Media player.

VMWare and VirtualBox both make the list because they have different strengths. VirtualBox is open source, free and cross-platform, but it makes no effort to integrate into the host operating system. VMWare Fusion’s full screen mode means I can three-finger-swipe to switch from OSX to Windows, which is almost magical.

Windows

Was the iPad “Undesigned”?

This is what baekdal has to say about Samsung’s inability to design a tablet that doesn’t look like an iPad:

Apple didn’t trademark their design. They trademarked the natural shape a tablet could have. It’s like the New York Times trademarking the shape of the newspaper.

Samsung is saying that there is no other way to design a tablet, and they are right. They could have used different materials, but the overall shape is not Apple’s design. As I demonstrated in this article, the shape of an iPad is the logic progression of simplicity itself.

This is Steve Ballmer, holding a pre-iPad tablet computer running Windows XP:

The similarity to the iPad is uncanny. Why, there’s no other way it could be designed. The sleek, uniform black bezel around the screen, the lack of buttons and surface distractions, the round corners; even the guy in the black turtle neck holding it!

No, wait. It looks nothing like an iPad.

Let’s be honest - all tablets prior to the iPad were junk. They ran desktop operating systems that were entirely inappropriate to the constraints of the hardware (hello Windows 8!). They had terrible battery life. Aesthetically, they were dreadful. However, the iPad isn’t the only possible way to design a tablet. It’s just the best design around at the moment, which is why everyone else is copying it.

All of the ideas Apple suggested Samsung use in their design - non-black surface, hard corners, fat profile, non-centred screen, etc - sound utterly stupid. However, these are precisely the same design ideas that every tablet manufacturer was using before Apple put some thought into the problem. Apple isn’t saying, “You should use these obviously dumb ideas we’ve just come up with so that your product is crippled in comparison to ours.” They’re saying, “You should continue to use the dumb ideas that crippled your products before we showed you how to make tablets that don’t suck.”