Phillip Trelford's Array

POKE 36879,255

Light Cycles

Back in 2007 I wrote a little game in F# using Windows Forms and Visual Studio 2005. It is based on the Light Cycle sequence in the 80s movie Tron which has been recently seen a sequel. The game is about as simple as they come weighing in at just under 200 lines of code. For fun I’ve ported it over to Silverlight:

 

The original code supported XBox 360 controllers via DirectX. For now in the Silverlight version you’ll have to can use joy2key to map your controller to keys.

Techie Bits

Where business applications tend to react to keyboard events, a game typically polls the keyboard state at a regular interval. Here I use a class to encapsulate the set of keys down:

/// Tracks which keys are down
type KeyState (control:Control) =
    let mutable keysDown = Set.empty
    do  control.KeyDown.Add (fun e -> keysDown <- keysDown.Add e.Key)
    do  control.KeyUp.Add (fun e -> keysDown <- keysDown.Remove e.Key)
    member this.IsKeyDown key = keysDown.Contains key
    member this.IsAnyKeyDown () = keysDown.Count > 0

The player’s colour, position and keys are encapsulated simply as a type so you could easily extend the game from 2 to 3 or 4 players or even add your own AI player:

/// Player state
type Player (color,startX,startY,startDirection,keys,keyHandler:KeyState) =
    let mutable x, y, direction = startX, startY, startDirection
    let up, down, left, right = keys
    member this.X = x
    member this.Y = y
    member this.Color = color
/// Player array
let players = 
    [|Player(Colors.Red,playArea/2-20,playArea/2,Down,
             (Key.Q,Key.A,Key.Z,Key.X),keys)
      Player(Colors.Cyan,playArea/2+20,playArea/2,Up,
             (Key.P,Key.L,Key.N,Key.M),keys)|]

The play area for the light cycles is represented simply as a WriteableBitmap:

let playArea = 500
let bitmap = WriteableBitmap(playArea,playArea)

Finally game updates are synchronised with Silverlight’s rendering by hooking the CompositionTarget.Rendering event:

/// Run game
let runGame () =
    let state = gameState.GetEnumerator()
    let rate = TimeSpan.FromSeconds(1.0/50.0)
    let lastUpdate = ref DateTime.Now
    let residual = ref (TimeSpan())
    CompositionTarget.Rendering.Add (fun x -> 
        let now = DateTime.Now
        residual := !residual + (now - !lastUpdate)
        while !residual > rate do
            state.MoveNext() |> ignore
            residual := !residual - rate
        lastUpdate := now
    )

 

References:

Invadurz

Keys: Z = left, X = right, Space = fire

 

Play along to the Prodigy:

Techie bit

The game was developed in F# using the freely available Visual Studio 2010 Shell and targets Silverlight 4. F# is a multi-paradigm .Net programming language, that encompasses functional programming as well as imperative and object-oriented programming styles.

Graphics

The graphics were drawn in binary as 0s and 1s:

let invader1 = 
    [|
    12,
        [|
        0b000001100000
        0b000011110000
        0b000111111000
        0b001101101100
        0b001111111100
        0b000010010000
        0b000101101000
        0b001010010100
        |]
    |]

and transformed to a WriteableBitmap

let toBitmap (width,xs:int []) =
    let bitmap = WriteableBitmap(width,xs.Length)
    let pixels = bitmap.Pixels
    xs |> Array.iteri (fun y xs ->
        for x = 0 to width-1 do
            let bit = 1 <<< (width - 1 - x) 
            pixels.[x+y*width] <-
                xs &&& bit = bit
                |> (function true -> 0xffffffff | false -> 0x00000000)
    )
    bitmap

Yield!

Sprite animation is implemented as state machines using F# Sequences:

let wait n = seq { for x = 1 to n do yield () }
let animation = seq {
    let i = ref i
    while true do
        yield! wait 6
        images.[!i].Visibility <- Visibility.Collapsed
        i := (!i+1) % images.Length
        images.[!i].Visibility <- Visibility.Visible
}

 

the sequence cycles through the provided images.

Keys

Games need to know if a key is down. F# has a lightweight equivalent of Reactive Extensions (Rx) baked in the Observable Module which provides higher order functions over Events:

    type Action = Left | Right | Fire
 
    let toAction = function
        | Key.Z -> Some Left
        | Key.X -> Some Right
        | Key.Space -> Some Fire
        | _ -> None

    let mutable actions = set []

    let startObservingKeys () =
        control.KeyDown
        |> Observable.choose (fun ke -> toAction ke.Key)
        |> Observable.subscribe(fun action -> 
            actions <- Set.add action actions)
        |> remember
        control.KeyUp
        |> Observable.choose (fun ke -> toAction ke.Key)
        |> Observable.subscribe (fun action -> 
            actions <- Set.remove action actions)
        |> remember

 

Selective memory

Well-behaved classes should remember the resources they have acquired and forget them once they are finished with them:

let mutable disposables = []
let remember disposable = disposables <- disposable :: disposables
let dispose (d:IDisposable) = d.Dispose()
let forget () = disposables |> List.iter dispose; disposables <- []

Je regrette rien:

interface System.IDisposable with
    member this.Dispose() = forget()

References

Fractal Zoom

A quick search for “F# Mandelbrot” gave an article written by Luke Hoban back in 2007, that draws a Fractal to the console.

Time for a makeover with a sprinkling of Active patterns, Reactive programming, Parallel execution and Silverlight.

Simply draw a rectangle onto the Fractal to zoom in:


“Complex” maths using F# PowerPack library and an Active Pattern:

open Microsoft.FSharp.Math

let maxIteration = 255
let modSquared (c : Complex) = 
    c.RealPart * c.RealPart + c.ImaginaryPart * c.ImaginaryPart
    
let (|Escaped|DidNotEscape|) c =
    let rec compute z iterations =
        if(modSquared z >= 4.0) 
            then Escaped iterations
        elif iterations = maxIteration
            then DidNotEscape
        else compute ((z * z) + c) (iterations + 1)
    compute c 0

Tomas Petricek’s original Reactive Rectangles sample to select the zoom area:

let rec waiting() = async {
  let! md = Async.AwaitObservable(main.MouseLeftButtonDown)
  let rc = new Canvas(Background = transparentGray)
  main.Children.Add(rc) 
  do! drawing(rc, md.GetPosition(main)) }

and drawing(rc:Canvas, pos) = async {
  let! evt = Async.AwaitObservable(main.MouseLeftButtonUp, main.MouseMove)
  match evt with
  | Choice1Of2(up) -> 
      rc.Background <- SolidColorBrush(colorSelect.CurrentColor)
      do! waiting() 
  | Choice2Of2(move) ->
      moveControl rc pos (move.GetPosition(main))
      do! drawing(rc, pos) }

do waiting() |> Async.StartImmediate

Parallel rendering over up to 4 cores using Async workflows:

do! [0..3] 
    |> List.map (fun y -> async {
        render points (y,(height/4),4) buffer
    })
    |> Async.Parallel

Resources:

Source: FractalZoom.zip (3.89 kb)