0 Comments

For my entry in the F# Advent Calendar we’re going to port a simple game to F#. The game is called “Asterisk”, and has very simple mechanics – you must progress through levels, avoiding hitting stars, and you can only go up or down. With each new level, three more stars appear, until it is impossible to progress any further.

It’s based on a game I used to play for hours on my dad’s BBC Micro, and I’ve implemented it a few times in various languages including C# WinForms, Silverlight and IronPython with WPF. You can find the code for some of these older versions here.

image

Port vs Rewrite

To get me started I decided that I would port my IronPython version to F#, since that used WPF for the graphics and collision detection, which made sense for my F# version to use.

Of course, F# is a very different language to Python. With Python I used lots of dynamic language features and mutable state. But the F# language encourages us towards strongly typed and immutable code.

Maybe things would have gone more smoothly had I rewritten from scratch in F#, but porting is what I opted for, so in this post I’ll share a few of the things I learned along the way.

WPF and F#

First of all, I needed a basic infrastructure for using WPF and the MVVM pattern in F#. I created a MVVM module to cover this:

module Mvvm
    open System
    open System.Windows
    open System.Windows.Input
    open System.Windows.Markup
    open System.Windows.Media
    open System.ComponentModel

    let checkCollisionPoint (point:Point) (control:UIElement) =
        let transformPoint = control.RenderTransform.Inverse.Transform(point)
        VisualTreeHelper.HitTest(control, transformPoint) <> null

    let loadXaml path =
        let uri = new Uri(path, UriKind.Relative)
        let stream = Application.GetResourceStream(uri).Stream
        XamlReader.Load stream

    let runApp rootElement =
        let app = new Application()
        app.Run rootElement

    type ViewModelBase() =
        let propertyChangedEvent = new DelegateEvent<PropertyChangedEventHandler>()
        
        interface INotifyPropertyChanged with
            [<CLIEvent>]
            member x.PropertyChanged = propertyChangedEvent.Publish

        member x.OnPropertyChanged propertyName = 
            propertyChangedEvent.Trigger([| x; new PropertyChangedEventArgs(propertyName) |])
     
    type DelegateCommand (action:(obj -> unit)) =
        let event = new DelegateEvent<EventHandler>()
        let mutable canExecute = true
        interface ICommand with
            [<CLIEvent>]
            member x.CanExecuteChanged = event.Publish
            member x.CanExecute arg = canExecute
            member x.Execute arg = action(arg)
        member x.SetCanExecute ce = 
            canExecute <- ce
            event.Trigger([|x; EventArgs.Empty|])

A few things of interest in here. First of all, this code demonstrates some of the techniques you can use to work with .NET events in F#, using the [<CLIEvent>] attribute, and the Publish and Trigger methods.

In the loadXaml function you can see that I decided to make my XAML files into application resources, so I needed to read them in with Application.GetResourceStream.

One of the big benefits of WPF for this game is that collision detection comes built in to the framework. We can test a point to see if it collides with a control on our canvas, which we do in checkCollisionPoint using VisualTreeHelper.HitTest.

The Game Area

The GameArea module manages the visual part of the game. It draws new levels, and places stars in random positions on the canvas. Here’s the code to generate and place the stars for a new level:

let redrawScreen { canvas = canvas; polyline = polyline; stars = stars } level =
    canvas.Children.Clear()
    drawBorders canvas

    stars.Clear()
    for n in [1..level*3] do
        let star = Mvvm.loadXaml "star.xaml" :?> Path
        stars.Add star
        Canvas.SetLeft(star, float(rand.Next(10, int(width) - 10)))
        Canvas.SetTop(star, float(rand.Next(2, int(height) - 10)))
        canvas.Children.Add star |> ignore

    polyline.Points.Clear()
    canvas.Children.Add(polyline) |> ignore

The need to use float and int in the calculations of the star positions highlights how much stricter the F# compiler is about types compared to C# or IronPython. You can’t just mix and match ints and floats; you have to explicitly cast them.

Every time the user needs to move to a new position, we must perform a collision detection. Have they hit any of the stars on the screen, or have they hit one of the walls? Here’s the full collision detection code:

let isCollision stars x y width height =
    if y <= 0.0 || y >= height then
        // we've hit top or bottom
        true
    elif x >= width then
        // we've hit the right edge but missed the gap
        not ((height / 2.0 + 15.0) > y && y > (height / 2.0 - 15.0))
    else
        let isStarCollision star =
            let testX = x - Canvas.GetLeft(star)
            let testY = y - Canvas.GetTop(star)
            let testPoint = Point (testX, testY)
            Mvvm.checkCollisionPoint testPoint star
        stars |> Seq.exists isStarCollision

The Main ViewModel

Porting the MainViewModel to F# was relatively straightforward except for one circular dependency issue I ran into.  To illustrate, imagine this simple MVVM Command used by a ViewModel. The code won’t compile because we can’t reference the MyCommand member. There’s no easy way to reorganize these declaration to eliminate the circular references:

type MyCommand(func) =
    let mutable enabled = true
    
    member x.Execute() = func
    member x.Enabled 
        with get () = enabled
        and set (value) = enabled <- value

type MyViewModel() =
    let myFunc() = 
        printfn "Hello"
        MyCommand.Enabled <- false  // won't compile

    let myCommand = MyCommand(myFunc)

    member x.MyCommand 
        with get() = myCommand

How can we resolve this? Well it turns out there was a simple bit of F# syntax I was unaware of. If we say “as this” in our type declaration, we are able to use it to reference our class members before their declaration. Simple when you know how:

type MyViewModel() as this =
    let myFunc() = 
        printfn "Hello"
        this.MyCommand.Enabled <- false  

    let myCommand = MyCommand(myFunc)

    member x.MyCommand 
        with get() : MyCommand = myCommand

Making it Functional

Now I didn’t want to simply port from IronPython over to F#. Once it was working I wanted to refactor towards a more functional approach.

So first of all, I decided to see if I could eliminate some of the mutable state and change the game so that every time a timer event occurred, a new game state was created, rather than the existing one being modified.

So I came up with the following basic domain model. The GameState stores our current position, the direction we’re headed and what level we’re on. And we also model the possible game events that can happen – a timer tick or a keypress resulting in a change of direction. Whenever a GameEvent occurs, we will generate a new GameState, but there will also be a RenderAction to apply to keep the UI in sync with the GameState.

type Direction = Up | Down
type GameState = { xPos:float; yPos:float; direction:Direction; currentLevel:int}
type RenderAction = NewPoint | NewLevel | GameOver
type GameEvent = Tick | ChangeDirection of Direction

So our timer tick handler function returns a new game state and a render action. And it needs to be passed a helper collision tester function (colTest) to help it know if the game should end:

let handleTick colTest gs =
    if gs.currentLevel = 0 then
        NewLevel, { currentLevel = 1; xPos = 0.0; yPos = GameArea.height / 2.0; direction = Down }
    else
        let newX = gs.xPos + 1.0
        let newY = gs.yPos + match gs.direction with | Up -> -1.0 | Down -> 1.0

        let crash = colTest newX newY GameArea.width GameArea.height
        if crash then
            GameOver, gs
        else if gs.xPos >= GameArea.width then
            NewLevel, { currentLevel = gs.currentLevel + 1; xPos = 0.0; yPos = GameArea.height / 2.0; direction = Down }
        else
            NewPoint,  { gs with  xPos = newX; yPos = newY } 

The advantage of this refactoring to a functional approach is that this method is completely unit testable and and agnostic of the actual presentation technology. We could use this logic with a WinForms or HTML Canvas UI if we wanted.

Observables

I also wanted to make the code more functional by treating all the game events as a stream of observables. This meant that the game logic boils down to subscribing to an observable of game events and producing a set of render actions to be handled by the UI.

In F# it’s very easy to subscribe to events with Observable.subscribe

timer.Tick |> Observable.subscribe onTimerTick

But we can go further and filter and map observable events. So for example to filter out all presses of the space bar and turn them into ChangeDirection Up commands we can do:

let isSpace (a:KeyEventArgs) = a.Key = Key.Space

xaml.KeyDown |> Observable.filter isSpace |> Observable.map (fun _ -> ChangeDirection Up)

I wanted to merge all the events I was interested in into a single stream. There’s an Observable.merge function that will combine two observables, so to combine multiple observables we need to use List.reduce. Here’s us merging five observables into a stream of Game Events:

[ timer.Tick |> Observable.map (fun _ -> Tick) 
  xaml.KeyDown |> Observable.filter isSpace |> Observable.map (fun _ -> ChangeDirection Up)
  xaml.KeyUp |> Observable.filter isSpace |> Observable.map (fun _ -> ChangeDirection Down)
  xaml.MouseLeftButtonDown |> Observable.map (fun _ -> ChangeDirection Up)
  xaml.MouseLeftButtonUp |> Observable.map (fun _ -> ChangeDirection Down)          
]
|> List.reduce Observable.merge 
|> Observable.subscribe onGameEvent

Making it Christmassy

Finally, since this is the F# Advent Calendar, I did want to make it a bit more Christmassy. Sadly time did not permit for me to add XAML snow, but I did use my incredible ninja graphic design skills to create this XAML Christmas tree:

<Path xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  Stroke="Green" 
  StrokeStartLineCap="Round" 
  StrokeEndLineCap="Round" 
  StrokeLineJoin="Round" 
  Data="M 8,18 v-4 h -5 l 5,-4 h -4 l 4,-4 h -3 l 5,-5 l 5,5 h -3 l 4,4 h-4 l 5,4 h-5 v4 Z">
    <Path.RenderTransform>
        <ScaleTransform ScaleX="0.8" ScaleY="0.8" />
    </Path.RenderTransform>
</Path>

which looks like this:

image

Now we can randomly select stars or trees and get a more festive looking game:

image

Taking it Further

Now although I did make some efforts to make this game more functional (and more Christmassy), there is certainly still a lot of scope for improvement. So over to you, whether you’re an F# beginner or guru, why not clone my GitHub repo, have a play of the game, and see if you can improve the code or add a new feature.

Happy Christmas, and enjoy the rest of your F# Advent. If you’re a regular follower of my blog, you’ll know that I’ve also been attempting to solve the Advent of Code challenges in F#. You can read about my solutions here.

Vote on HN
comments powered by Disqus