A Platform game in F# and SpriteKit – Part 7 – DSLs baby!

So far our (1) level is fairly painful code to look at – admittedly SpriteKit and F# take something that could have been horrific and simply make it tedious. Today we change that.

DSL

According to Wikipedia a DSL is

domain-specific language (DSL) is a computer language 
specialized to a particular application domain.

The domain in question for us is modelling a level.

F#’s light syntax mode means that it is ideal for building DSLs, along with record types, discriminated unions, and list comprehensions.

When building a DSL I tend to like to think about how I would like the “language” to look. Usually I want as little noise as possible, so that it almost looks like a plain text or simple markup/markdown file. Sometimes I want to include some F# code in the body but in a boiled down minimalist way.

When I think about the level I think of it as a couple of things –

  1. A bag of sprites I will interact with
  2. A music file I will play
  3. A non-interactive background I will draw but not interact with

You could expand this to include things like level rules (i.e. lower gravity for a level set in space), but the point of a DSL is to build it to suit your own domain – the above suits me for now. The great part about an internal DSL is that as it is real code, when “for now” changes, the compiler will tell me and don’t have to go into a bunch of (text? binary??!?) files and play with the content until I fix it up.

(Diversion) DSLs and Boolean Traps

A boolean trap is a term used for a part of the code where a boolean value is passed as a parameter and the intention is not clear or is counter intuitive to the expected behaviour. Languages like C# and Java allow you to define enums, which allow more expressive behaviour, but at the expense of added code (both the definition and the consumption of the enum).

F# allows for Discriminated unions, which allow you to define a new type and consume it with no ceremony.

Example (boolean trap version):

createPhysicsBody true

Obviously it’s pretty difficult to see what the true represents. C# can do it better by using enums, but the inclusion of the enum name every time is clumsy:

enum PhysicsBodyShape = { Circle, Rectangle }
createPhysicsBody(PhysicsBodyShape.Rectangle)

F# goes above and beyond with Discriminated Unions as the definition is lightweight and the consumption is trivial – F#’s type inference has your back:

type Shape = Rectangle | Circle
createPhysicsBody Rectangle

In F# it’s really easy to avoid these boolean traps, and there’s not really a good reason not to when designing an API/DSL.

Back to the language

Taking a look at the (1st pass) DSL for levels it’s really neat to see that it fits nicely on 1 page:

 [<AutoOpen>]
 module LevelDSL

 open MonoTouch
 open MonoTouch.CoreGraphics
 open MonoTouch.SpriteKit

 // Repeat action 1 time, n times or infinitely
 type RepeatAction = Once | Times of int | Forever
 type Seconds = float
 type TextureName = string

 type Path = 
 | Ellipse of int * int * int * int //Bounding rectangle
 // TODO Other path types

 // A thing can either be at a single point or follow a path over time (n times)
 type Location = 
 | Point of int * int
 | Path of Path * Seconds * RepeatAction 

 //The Shape used for Physics
 type Shape =
 | Rectangle 
 | Circle

 //Details about the Physics
 type Physics = 
 | NoPhysics
 | Static of Shape
 | Dynamic of Shape
 | DynamicNoGravity of Shape

 //Lightweight sprite definition
 type Sprite =
 | Sprite of TextureName * Location * Physics * Sprite list

 type Level = {
     Name : string
     Level : Sprite list
     Background : Sprite list }

Now with anything, there are parts I can see already that I think could be better, for example the background (parallax) layer is made up of “Sprite”s, which means you could inadvertantly enable physics for the background – this probably isn’t desirable! As with anything there’s a cost-benefit ratio and ultimately you need to decide how important some of these things are when building a DSL. In my case I am the sole consumer of this DSL so I’ll probably let it slide for now.

Also note that we use Type Abbreviations – this is yet another way we can avoid the boolean (or in this case float and string) trap in F#!

So let’s see how using our new shiny DSL looks when recreating level 1:

let level1 = {
     Name = "Level1"
     Level = [ for i in 0..100 -> Sprite("grass", Point(i * 70, 0), Static Rectangle, []) 
               for i in 104..200 -> Sprite("grass", Point(i * 70, 0), Static Rectangle, [])
               yield 
                 Sprite("stoneMid", 
                     Path(Ellipse(50,150,200,200), 3.0, Forever), 
                     Static(Rectangle),
                     [ Sprite("stoneLeft", Point(-70,0), Static Rectangle, [])
                       Sprite("stoneRight", Point(70,0), Static Rectangle, [])])
               ]

     Background = [ for i in 0..150 -> Sprite("hill_small", Point(i * 70, 50), NoPhysics, []) ]

Now this is all done inline in the level definition – as I’m storing the levels in modules I could even go a step further by adding another mini language on top of the DSL – so my level definition ends up something something like this:

Level = [ yield! platform 0
          yield! platform 104
          yield movingEllipsePlatform 50 150 ]

Ultimately it’s up to you to figure out what works best for you. Now, given we have spent so long defining a neat DSL, it would be a shame to not use it to spruce up the level with a new fixture – some steps:

let steps xpos = 
    [ for x in 1..3 do
        for y in 1..x do
            yield Sprite("grass", Point((x+xpos)*70, y*70), Static(Rectangle), []) ]

And consuming them to add more stuff to our level:

Level = [ yield! platform 0
          yield! platform 104
          yield! steps 20
          yield! steps 97
          yield! steps 110
          yield movingEllipsePlatform 50 150
          yield movingEllipsePlatform 1000 150
          yield movingEllipsePlatform 3000 150]

Finishing off

Today we’ve taken around 50 lines of code which was highly cohesive with SpriteKit and replaced it with a small DSL which allows us to break out the intent of those 50 lines into 3 lines, whilst also decoupling the API. Creating new levels is now much easier and any future changes to the level format will be caught by the F# compiler – an advantage that external DSLs cannot match.

Next time we’ll actually add a second level and a score which we can keep track of between levels until we either die or complete the game.

All the code for today is here

Advertisements

About thedo666

Software developer trying to learn a new language - English!
This entry was posted in F#, iOS and tagged , , , . Bookmark the permalink.

One Response to A Platform game in F# and SpriteKit – Part 7 – DSLs baby!

  1. Pingback: Anniversary edition of F# Weekly #43, 2013 – One year together | Sergey Tihon's Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s