Watch App Development Blog – Week 1

Okay, I’m trying something new – a weekly blog post talking about my progress developing an app. The theory is that the thought of my massive readership expectantly waiting for the next update will give me enough of an incentive to get something finished. Everyone practise their sad face to make me feel guilty if I don’t post an update.

I’m keen to get out an Apple Watch app. To be honest, I don’t think they’ll make much money, but most apps I build are for other people; it would be nice to put out something good, so I can say “I did that!”

The Concept

Over the last few months, I’ve tried to be aware of instances where I need some information off my phone, but pulling it out & launching an app seems like too much of a hassle. One scenario I noticed was when I was heading to the train station – I used to know the departure times off by heart, but now I’m not sure whether to run or dawdle. It would be great if there was an app on my watch that gave me live departure times for my nearest station – challenge accepted!

Step 1: The API

Transperth don’t have a public API, although there’s an unofficial third-party one that scrapes the website. Unfortunately some parts were broken with a recent site update, and the developer now lives in Melbourne. I also have a few ideas for some custom API behaviour, so I made a probably ill-advised decision to build my own scraping API.

I really wanted to try out a web project in F#, but I didn’t want to develop on Windows and I ended up running into significant problems with Xamarin – broken project templates, unimplemented parts of the aspnetwebstack, etc. ASP.NET vNext looks promising, but I had issues with it also.

So I thought I’d give Haskell another go – I’ve tried this in the past, but I’m much gooder at Haskell now. The state of web frameworks in Haskell has also improved significantly since 2010. I went with Scotty – I like the simplicity of the Sinatra/NancyFx model, and there’s a great walk-through by Aditya Bhargava.

Haskell Web Development on OS X

If you’re playing along at home, you’ll need to follow the following steps to run the API:

  1. Install the Haskell Platform
  2. Run cabal sandbox init in your project directory. Cabal sandbox installs dependencies in a project scope, similar to Bundler in Ruby.
  3. Create a cabal file specifying your dependencies. This process I found a little odd – effectively you’re specifying your executable as a library, but it allows you to leverage cabal dependency resolution. Use Adit’s cabal file as a base.
  4. Create a Main.hs and add your Scotty routes (check out the examples).
  5. Run cabal install && .cabal-sandbox/bin/<executable name>

After an extended compile time, you should now have a web server running on localhost:<port>.

JSON Response Types

Returning JSON can be done by defining record types that implement the ToJSON type class (from Aeson):

{-# LANGUAGE DeriveGeneric #-}
module Types where

import Data.Aeson
import GHC.Generics

data Departure = Departure { time :: String, destination :: String, pattern :: String, status :: String } deriving (Generic, Show)
instance ToJSON Station

HTML Parsing

Parsing the DNN-generated web page is done using tagsoup. This differs from most other HTML parsing libraries I’ve used in that it doesn’t define a query API or CSS-like selector syntax over a DOM, it just converts the HTML into a flat list of nodes that can be manipulated using regular list functions.

My scraping function, which is probably not brilliant Haskell, looks like the following (excluding some helpers):

getTrainTimes :: String -> IO [Departure]
getTrainTimes x = do tags <- fmap parseTags $  openURL $ "http://www.transperth.wa.gov.au/Timetables/Live-Train-Times?stationname=" ++ (urlEncode x)
                     let table = head $ tables tags -- first table in page
                     let rowArray = reverse . tail . reverse . tail $ rows table -- strip first & last rows
                     let times = map (f . cells) rowArray -- convert each row into a Departure
                     return times
                  where f cs = Departure (textFromCell $ cellAtColumn 0 cs) (destFromCell $ cellAtColumn 1 cs) (patternFromCell $ cellAtColumn 2 cs) (textFromCell $ cellAtColumn 3 cs)

‘Stations Near Me’

In addition to querying for live times, I also have a flat text file of station names & locations I lifted from Darcy’s project. This is used to respond to the ‘all stations’ API call, and also supports a geospatial query endpoint using the gps package. Initially I started getting build failures with this dependency – it was trying to compile GPX file support, which I don’t need. The latest version (1.2) of the gps code has removed this dependency, but it’s not on Hackage yet.

Happily, this is solvable:

  1. Specify the specific version of the package in your cabal file: gps >=1.2
  2. Put the source code in your project directory (e.g. git submodule add git@github.com:TomMD/gps.git)
  3. Specify the new source directory with cabal sandbox add-source gps

Using the Geo.Computations model, it’s then fairly straightforward to filter the list of stations based on distance to a given point.

Bringing it Together

Once the stations, live times, and geospatial filtering was done, it was just a case of defining the appropriate route functions in Scotty:

  scotty port $ do
    get "/train/" $ do
      list <- liftIO stations
      json list

    get "/train/near" $ do
      y <- param "lat"
      x <-param "long"
      list <- liftIO $ stationsNear y x
      json list

    get "/train/:station" $ do
      stationId <- param "station"
      station <- liftIO $ station stationId
      case station of
        Just s -> do
          times <- liftIO $ getTrainTimes $ name s
          json times
        Nothing ->
          Web.Scotty.status status404

I haven’t touched cache control or more advanced error handling, and it would be nice to fall back on timetables if live times aren’t available, but I now have enough of an API running to support the basic functions of the watch app. One of the things I liked about doing it in Haskell was that once it compiled, it generally worked. It’s a pretty nice feeling.

I’ve put the code up on bitbucket – feel free to have a look through it and send some feedback if you can’t stand my beginner Haskell.

Next Steps

Next is hosting – building and deploying my dinky API somewhere I can reach it. Tune in next week for another thrilling instalment!

Advertisements

Bird Nerd 1.0

So, I built a game.

Bird Nerd IconFollowing the yanking of Flappy Bird from the App Store (and the subsequent proliferation of indie apps), a colleague said: “We should build an app called ‘Flappy Word’, where instead of flying through pipes, you collect letters and make words”. This sounded like an absolute winner of an idea, so I went home and coded up the basic game in SpriteKit that evening. We spent the next week and a bit refining gameplay, designing artwork,  gathering feedback from users, changing the name to something that doesn’t ‘leverage a popular app’, and submitted it to the App Store.

I have to confess to not being much of a gamer (and I haven’t built a game before), so I’m not well equipped to assess whether the game is any good, or predict how many will download it. However, the process of building it was certainly enjoyable, and as I learnt a lot doing it, I’ll go through some of the key design decisions here.

SpriteKit

I haven’t done much work with game engines, but for a game newbie, SpriteKit is a very well-designed framework (if you’re okay with iOS 7+ only). Its allegedly heavily inspired by the popular Cocos2D, but beyond that, it’s a first class Apple framework with expected levels of integration and consistency with the rest of UIKit & CoreFoundation. I’d picked up a copy of Dmitry Volevodz’ ‘iOS 7 Game Development’, which uses an endless runner game as an example, and was able to use this to ramp up on the framework pretty quickly.

SpriteKit has a relatively simple and understandable model, which revolves around SKScenes, SKNodes and SKActions. On an 8-bit style game it required liberal use of node.texture.filteringMode = SKTextureFilteringNearest to prevent antialiasing when scaling up low-res artwork.

Some of the other tips/techniques I discovered were:

  • OpenGL really works the simulator – it always spins up the MacBook fans regardless of whether it’s doing much.
  • Prefer using SKActions for behaviour rather than dumping everything in the -update: method.
  • Subclassing SKSpriteNode for each node type is a good idea, in order to keep the code well separated.
  • Implementing UI elements like buttons in SceneKit is pretty clunky. It would be nice if it was easier to mix UIKit controls into the scene.
  • Texture atlas support is nice, but they aren’t generated during command-line builds – this drove me crazy for a while trying to work out why the TestFlight builds kept crashing.
  • OpenGL really, really does not like running while the app’s in the background. Pause the scene in -applicationWillResignActive: and -applicationDidEnterBackground:

Word lists & game logic

The interesting problems that needed to be solved here were:

  1. spawning new letters in a random, yet playable order
  2. detecting when a valid (or invalid) word is formed

Item 2 requires a word list. As a writer of a non-US flavour of English it was important to me that there be a choice of wordlists (thanks to 12dicts). Despite the Apple documentation and some older references to the contrary, there is definitely a British English language preference in iOS, so it was relatively transparent to load the correct list according the the user’s language settings. The word lists required some pre-processing in a simple ruby script to remove words with punctuation and words shorter than 3 characters. Potentially, I could also strip out words that had a valid word as a prefix (e.g. ‘doggerel’ is unplayable as it’s prefixed with ‘dog’), but I’ve kept them in the list for the moment for potential game enhancements.

The word lists contain up to 75,000 words, which is workable as in-memory data, but should really use an efficient lookup mechanism rather than scanning the entire array each time a letter is hit. Because I know the lists are sorted, I can use a binary search – Cocoa provides one with the -indexOfObject:inSortedRange:options:usingComparator: method of NSArray, which I implemented with a prefix check in the comparator. Each collision, the game can quickly check whether the current letter combination is a valid word, is the start of a valid word, or is invalid.

For item 1, letters are spawned randomly using a very simple frequency weighting (i.e. vowels are more likely). Further localisation of the app would need the frequencies adjusted (and accented letters included, depending on language).  However, during play we noticed it got tedious waiting for a random spawn of the one letter you need, so I included a probabilistic component that includes valid next letters more frequently. This results in the game hinting fairly explicitly in some instances (i.e. if there’s only one valid letter).

Graphic design

It’s probably obvious neither of us are designers! We wanted to go for a retro style, both to make it more Flappy Bird-esque, and because we’re both ancient enough to have played C64/Amiga/classic Mac era games. It has an Australian bush theme, just for something different (see if you can spot the Western Grey Kangaroo).

As an example to try to illustrate the workflow, the bird sprite went through the following evolution:

Bird Nerd Sprite v1 S: “I slightly modified the Flappy Bird sprite to make it look more like a magpie lark”

H: “That’s meant to be a magpie lark?”

Bird Nerd Sprite v2H: “Fek me drawing is hard!”

Bird Nerd Sprite v3S: “I gave him a bigger eye.”

Bird Nerd Sprite v4H: “Larger Bird Nerd sprite. I liked what you did with the glasses thing and have tried to enhance that further.”

Bird Nerd Sprite v5S: “I think his eye still needs to be bigger. Should he have more white on his belly?

Bird Nerd Sprite v6H: “I think a coloured beak and legs looks pretty good?”

Bird Nerd Sprite v7S: “Couldn’t help myself – I had to make his glasses bigger”

The coins were added late in development, after the sound effects were added, as they were quite reminiscent of the Mario-style coin collection and solved an issue with legibility and contrast in drawing the letters directly onto the background.

Game Physics

Bird Nerd Screenshot

The basic mechanics are close to that of Flappy Bird – tap to fly up. Flappy Bird appears to instantaneously set the upward velocity to a constant value rather than apply a set amount of force, i.e. it’s irrelevant how fast you were falling before the tap. I did wind back the gravity and upward velocity to make the game a bit more controllable – the early versions were incredibly difficult, and given the spelling component of the game is quite hard, it was a reasonable trade-off to make flying a little easier.

Initially we noticed players lurked at the top or bottom of the screen to wait for letters – we solved this by ‘landing’ and pausing the game if the bird got too low, and spawning letters all the way up to the top of screen (behind the score) to prevent staying high. Initially the letters were completely random with some hit-testing to prevent overlap, but once we went to letter coins it made more visual sense to lay them out on a grid.

The actual physics and collision code was remarkably simple thanks to SpriteKit; the only real issue I came across was a little bit of ‘drift’ in the player’s x position, presumably due to collisions (easily rectified by resetting in the SKScene -update). I shudder to think how much effort it would have been writing something like this in 6502 assembler on a C64.

Revenue Model

I went for iAd in-app advertising as a revenue model – even if I felt it was worth it, charging upfront for a game in such a crowded market is a really hard sell. In-App Purchase is where nearly all of the game revenue is, but it’s a fair bit of additional development work, and is generating wariness in some consumers thanks to an increasing number of slimy implementations. iAd isn’t regarded as a high earner, but I wanted to experiment with it (partly so I could have some stats for the next Perth iOS Meetup). Plus, I just can’t stand non-retina ads. And yes, I’m aware of the irony of complaining about pixelated ads in an intentionally pixelated game!

iAd implementation is quick & easy – the contract can be configured in about 10 minutes online, and integrating a banner view is only a couple of lines of code. There are a few gotchas – most of the Apple iAd documentation still refers to deprecated methods, and there are a handful of rejection-tempting faux pas you need to be aware of, such as displaying a blank banner view, or submitting screenshots showing test ads.

Final Thoughts

Working with someone else on an app was quite valuable, both in the sense of ‘two heads are better than one’, and to keep the motivation and development pace up. At this stage I have no idea how the app will be received, or how to go about marketing it, but I’ll add another post once the dust has settled and I’ve got some download and iAd stats. In the meantime, get your Bird Nerd on and post your score on Twitter!

Download Bird Nerd from the App Store