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: