Today I am creating a game application named BallGame in F# using WPF, Silverlight. Steps are given below.

Step 1: Firstly Open a new project in F# using Visual Studio 2010. Select F# WPF application template and give a name to it like the below image.

New Project Dialog Box

Step 2: Now add the below define references, a new F# silverlightapp project and some beep tune files to the project by right clicking on project in solution explorer.

Step 3: When you have added all these references, new project and all beep tune files your Solution Explorer will look like the below image.

Solution Explorer

Step 4: Now click on the Module1.fs file in the Solution Explorer and write the below code in the Module1.fs window, your window will look like below.

BallgameCode part1

Ballgame Code part2


Ballgame code part3

Ballgame code part 4

Ballgame code part5

Ballgame code part6

Ballgame Code part7

namespace Ballgame

open System.Windows
open System.Windows.Shapes
open System.Windows.Controls
open System.Windows.Controls.Primitives
open System.Windows.Media

module BGameWpf =
module Cnvs =
let SetTopLeft(element, top, left) =
Canvas.SetTop(element, top)
Canvas.SetLeft(element, left)
type System.Windows.Controls.Canvas with
member this.AddAt(top, left, element) =
Cnvs.SetTopLeft(element,top,left)
this.Children.Add(element) |> ignore
let YELLOW = new SolidColorBrush(Colors.Yellow)
let WHITE = new SolidColorBrush(Colors.White)
let RED = new SolidColorBrush(Colors.Red)
open BGameWpf

module GlobConstConfig =
let a = 0.0003
// pixel size of a ball/brick
let TotSIZE = 7.0
// initial grid size of blocks
let TOTWDT = 80
let TOTHGT = 20
// paddle size
let RESTHEGT = 11.0
let RESTWDT = 9.0 * TotSIZE
let RESTCHT =
false
open GlobConstConfig

module GlobCompConst =
let HLFSIZE = TotSIZE / 2.0
// pixel location of bottom of bricks
let BTBALL = float TOTHGT * TotSIZE
// canvas size
let CNWDT=TotSIZE * float TOTWDT
let CNHGT=TotSIZE * 90.0
// pixel location of top of paddle
let TOPPAD = CNHGT-70.0
let HLFRESTWDT = RESTWDT / 2.0
open GlobCompConst

module GLBLS =
let ldXaml<'T when 'T :> FrameworkElement>(xamlPath) =
use stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(xamlPath)
// if BuildAction=EmbeddedResource
#if SILVERLIGHT
let stream = (new System.IO.StreamReader(stream)).ReadToEnd()
#endif
let nxaml = System.Windows.Markup.XamlReader.Load(stream)
let uObj = nxaml :?> 'T
uObj

let (?) (fe:FrameworkElement) firstName : 'T =
fe.FindName(firstName) :?> 'T

let nwPnl : StackPanel = ldXaml("MainWindow.xaml")
let cnvs : Canvas = nwPnl?canvas
let pup : Popup = nwPnl?popup
let pupCanvas : Canvas = nwPnl?popupCanvas
let pupTp : TextBox = nwPnl?popupTop
let pupMdl : TextBox = nwPnl?popupMiddle
let pupBtm : TextBox = nwPnl?popupBottom
let RANG = new System.Random()
// main data objects
let mutable rmnng = TOTWDT * TOTHGT
let mutable actv= 1
let mutable wntPdlBp = false
let mutable wntBlkBp = false
let mutable fstTme = true
// main UI objects
let txtblc = new TextBlock(Height=25.0, Width=CNWDT, Text="", FontSize=20.0)
let dbug = new TextBlock(Height=25.0, Width=CNWDT, Text="", FontSize=10.0)
let pdl = new Rectangle(Width=RESTWDT, Height=RESTHEGT, Fill=YELLOW)
/// as I varies from 0-max-1, this makes a pretty color spectrum
let mkClr(I,max) =
if I < 1*max/4 then
let px = (I-0*max/4)*256*4/max
new SolidColorBrush(Color.FromArgb(0xD0uy,0xD0uy,byte(px),4uy))
elif I < 2*max/4 then
let px = (I-1*max/4)*256*4/max
new SolidColorBrush(Color.FromArgb(0xD0uy,0xD0uy,byte(px),4uy))
elif I < 3*max/4 then
let px = (I-2*max/4)*256*4/max
new SolidColorBrush(Color.FromArgb(0xD0uy,0xD0uy,byte(px),4uy))
else
let px = (I-3*max/4)*256*4/max
new SolidColorBrush(Color.FromArgb(0xD0uy,0xD0uy,byte(px),4uy))

let NMBP = 20
#if SILVERLIGHT
let mkMda(file) = new MediaElement(Source = new System.Uri(file, System.UriKind.Relative), AutoPlay = false)
#else
let mkMda(file) = new MediaElement(Source = new System.Uri(file, System.UriKind.Relative), LoadedBehavior = MediaState.Manual)
#endif

let attachMedia(file) =
let sund = mkMda(file)
sund.MediaFailed.Add (fun ea -> dbug.Text <- ea.ErrorException.ToString())
cnvs.Children.Add(sund) |> ignore
sund

let bps = Array.init NMBP (fun _ -> attachMedia("BEPPUR.wma"))
let mutable curBeep = 0
let blkBps = Array.init NMBP (fun _ -> attachMedia("BEPDUB.wma"))
let windSund = attachMedia("happykids.wma")
let lseSund = attachMedia("boo.wma")
let plyOnc(sound : MediaElement) =
async { sound.Play()
let! _ = Async.AwaitEvent sound.MediaEnded
sound.Stop() } |> Async.StartImmediate
// useful functions
let Asrt(b) =
assert(b)
//if not b then raise <| new System.Exception("assert failed")

// screen coordinates, a ball hit a block (filling space [0-SIZE,0-SIZE]) at point
// (x,y) with velocity (dx,dy) - did it hit the side of the brick (as opposed to top/bottom)?
let htSde(x,y,dx,dy) =
let blSlpe = -dy/dx
if dy>0.0 then
if dx<0.0 then
// it's going 'down-left'
let s = y/(TotSIZE-x)
blSlpe < s
else
// it's going 'down-right'
let s = -y/x
blSlpe > s
else
if dx>0.0 then
// it's going 'up-right'
let s = (TotSIZE-y)/x
blSlpe < s
else
// it's going 'up-left'
let s = -(TotSIZE-y)/(TotSIZE-x)
blSlpe > s
let _ok =
Asrt(htSde(HLFSIZE,HLFSIZE,10.0,1.0)) // -
Asrt(htSde(HLFSIZE,HLFSIZE,10.0,-1.0)) // -
Asrt(not<|htSde(HLFSIZE,HLFSIZE,1.0,-10.0)) // |
Asrt(not<|htSde(HLFSIZE,HLFSIZE,-1.0,-10.0)) // |
Asrt(htSde(HLFSIZE,HLFSIZE,-10.0,-1.0)) // -
Asrt(htSde(HLFSIZE,HLFSIZE,-10.0,1.0)) // -
Asrt(not<|htSde(HLFSIZE,HLFSIZE,-1.0,10.0)) // |
Asrt(not<|htSde(HLFSIZE,HLFSIZE,1.0,10.0)) // |

let ensureNonZero x = if x=0.0 then a else x
open GLBLS

[<RequireQualifiedAccess>]
type BlkStt =
| StartPosition // in block rows at top
| Actv // a ball, moving around
| Rmvd // fell off bottom

type Blk(shape : Ellipse) =
let mutable state = BlkStt.InitialPosition
// next 3 fields only matter when state=Active
let mutable xSpeed = 0.0
let mutable ySpeed = 0.0
let mutable tail : Line = null
do Asrt(cnvs.Children.Contains(shape))
member this.State = state
member this.Shape = shape
member this.Reflect() =
ySpeed <- -abs(ySpeed)
member this.Remove() =
Asrt(state = BlkStt.Active)
cnvs.Children.Remove(shape) |> ignore
cnvs.Children.Remove(tail) |> ignore
state <- BlkStt.Removed
member this.BreakAway() =
Asrt(state = BlkStt.InitialPosition)
xSpeed <- ensureNonZero(TotSIZE * (RANG.NextDouble() - 0.5))
ySpeed <- TotSIZE * (RANG.NextDouble() + 2.0)/3.1 // trying to ensure ySpeed < SIZE, so ball never goes completely through a row undetected in a single 'step'
Canvas.SetTop(shape, Canvas.GetTop(shape)+TotSIZE*1.5)
tail <- new Line(X1=Canvas.GetLeft(shape), X2=Canvas.GetLeft(shape),
Y1=Canvas.GetTop(shape), Y2=Canvas.GetTop(shape),
StrokeThickness=TotSIZE/3.0, Stroke=WHITE)
cnvs.Children.Add(tail) |> ignore
state <- BlkStt.Active
member this.MoveOneStep() =
Asrt(state = BlkStt.Active)
let orgCntrdX = Canvas.GetLeft(shape) + HLFSIZE
let orgCntrdY = Canvas.GetTop(shape) + HLFSIZE
// compute new X
let nwX = xSpeed + Canvas.GetLeft(shape)
let flpX(r) = xSpeed <- -xSpeed; r
let nwX = if nwX < 0.0 then flpX 0.0 else nwX
let nwX = if nwX > CNWDT-a then flpX(CNWDT-a) else nwX
// compute new Y
let nwY = ySpeed + Canvas.GetTop(shape)
let flpY(r) = ySpeed <- -ySpeed; r
let nwY = if nwY < 0.0 then flpY 0.0 else nwY
// update position
Cnvs.SetTopLeft(shape, nwY, nwX)
// update trailer line
let nwCntrdX = Canvas.GetLeft(shape) + HLFSIZE
let nwCntrdY = Canvas.GetTop(shape) + HLFSIZE
tail.X2 <- nwCntrdX
tail.Y2 <- nwCntrdY
tail.X1 <- 4.0 * (orgCntrdX - nwCntrdX) + nwCntrdX
tail.Y1 <- 4.0 * (orgCntrdY - nwCntrdY) + nwCntrdY
member this.HitPaddle(dx) =
Asrt(state = BlkStt.Active)
ySpeed <- -abs(ySpeed)
xSpeed <- ensureNonZero(xSpeed + dx)
member this.ReboundOffBrick(dLeft, dTop) =
let sde = htSde(dLeft,dTop,xSpeed,ySpeed)
if sde then
xSpeed <- -xSpeed
else
ySpeed <- -ySpeed

type NewApp() as this =
#if SILVERLIGHT
inherit Application()
#else
inherit Window()
#endif
let concon = new ContentControl()
let blks = Array2D.init TOTHGT TOTWDT (fun y x ->
let e = new Ellipse(Width=TotSIZE, Height=TotSIZE, Fill=mkClr(x,TOTWDT))
cnvs.AddAt(TotSIZE * float y, TotSIZE * float x, e)
new Blk(e))

do
cnvs.Width <- CNWDT; cnvs.Height <- CNHGT
cnvs.AddAt(TOPPAD, CNWDT / 2.0, pdl)
cnvs.AddAt(TOPPAD+RESTHEGT+5.0, 10.0, txtblc)
cnvs.AddAt(TOPPAD+RESTHEGT+30.0, 10.0, dbug)

pupCanvas.Background <- new SolidColorBrush(Color.FromArgb(0xFFuy,0uy,0uy,0xFFuy), Opacity=0.6)
pup.HorizontalAlignment <- HorizontalAlignment.Left
pup.VerticalAlignment <- VerticalAlignment.Top
#if SILVERLIGHT
// Silverlight popups are relative to the whole control
#else
// WPF popups have more control
pup.Placement <- PlacementMode.Relative
pup.PlacementTarget <- nwPnl
pup.HorizontalOffset <- 0.0
pup.VerticalOffset <- 0.0
#endif

blks.[TOTHGT-1,TOTWDT/2].BreakAway()
rmnng <- rmnng - 1
// txtblc.Text <- sprintf "%d Wall remain, %d Wall active" rmnng actv

#if SILVERLIGHT
this.UnhandledException.Add(fun ea -> dbug.Text <- ea.ExceptionObject.ToString())
this.Startup.Add(fun _ ->
#else
this.Loaded.Add(fun _ ->
#endif
async {
do! Async.Sleep(50) // a hack, need to wait until ActualHeight is populated
pupCanvas.Height <- nwPnl.ActualHeight
pupCanvas.Width <- nwPnl.ActualWidth
pup.IsOpen <- true
pupTp.Text <- "Quick Play Funny BallGame!"
pupTp.HorizontalAlignment <- HorizontalAlignment.Center // TODO cannot seem to auto-align these; design-time issue? recompute layout?
pupMdl.Text <- "Instructions: If you want to control the box you can move it through it Mouse\nSave Balls from falling down\nBreak walls on Upper side to get
more\nEnjoy the Game!"
pupBtm.Text <- "Press 's' to start"
} |> Async.StartImmediate
async {
do! Async.Sleep(100)
while rmnng > 0 && actv > 0 do
do! Async.Sleep(20)
do // this 'do' line is important to memory performance - code below is all sync, so need to execute outside 'async' to avoid Async allocating
if pup.IsOpen then () else
wntPdlBp <- false
wntPdlBp <- false
curBeep <- (curBeep + 1) % NMBP
let leftPad = Canvas.GetLeft(pdl)
for y in 0..TOTHGT-1 do
for x in 0..TOTWDT-1 do
let ball = blks.[y,x]
if ball.State = BlkStt.Active then
ball.MoveOneStep()
let top = Canvas.GetTop(ball.Shape)
let left = Canvas.GetLeft(ball.Shape)
if top >= TOPPAD && top < TOPPAD+RESTHEGT && left >= leftPad && left < leftPad+RESTWDT then
// hit paddle
ball.HitPaddle(dx=(left - leftPad - HLFRESTWDT)/HLFRESTWDT)
wntPdlBp <- true
elif top < BTBALL then
// see if hit a stationary brick
let brick = blks.[int(top / TotSIZE),int(left / TotSIZE)]
if brick.State = BlkStt.InitialPosition then
let t = Canvas.GetTop(brick.Shape)
let l = Canvas.GetLeft(brick.Shape)
let intersect = left >= l && left < l+TotSIZE && top >= t && top < t+TotSIZE
if intersect then
rmnng <- rmnng - 1
actv <- actv + 1
//txtblc.Text <- sprintf "%d Wall remain, %d Wall active" rmnng actv
ball.ReboundOffBrick(dLeft=l-left, dTop=t-top)
brick.BreakAway()
wntBlkBp <- true
elif top > CNHGT then
// fell off bottom
if RESTCHT then
ball.Reflect()
else
ball.Remove()
actv <- actv - 1
// txtblc.Text <- sprintf "%d Wall remain, %d Wall active" rmnng actv
if wntPdlBp then
plyOnc(bps.[curBeep])
if wntBlkBp then
plyOnc(blkBps.[curBeep])
if rmnng > 0 then
//txtblc.Text <- sprintf "left %d Wall" rmnng
plyOnc(lseSund)
else
txtblc.Text <- "Hurrah You Won!!!"
plyOnc(windSund)
} |> Async.StartImmediate
)

// to be able to get focus
concon.IsTabStop <- true
concon.IsEnabled <- true
concon.KeyDown.Add(fun keyEA ->
if keyEA.Key = Input.Key.S then
pupCanvas.Height <- nwPnl.ActualHeight\
pupCanvas.Width <- nwPnl.ActualWidth
pup.IsOpen <- not pup.IsOpen
pupTp.Text <- "STOP"
pupMdl.Text <- "F# - 'fun' is our keyword!"
pupBtm.Text <- "Press 's' to unstop and continue"
)
#if SILVERLIGHT

#else
concon.Focus() |> ignore
#endif
nwPnl.MouseMove
|> Observable.add (fun ea ->
let x = ea.GetPosition(cnvs).X
if x < HLFRESTWDT then
Canvas.SetLeft(pdl, 0.0)
elif x <= CNWDT - HLFRESTWDT then
Canvas.SetLeft(pdl, x - HLFRESTWDT)
else
Canvas.SetLeft(pdl, CNWDT - RESTWDT)
)
concon.Content <- nwPnl
#if SILVERLIGHT

#else
this.Content <- concon
this.SizeToContent <- SizeToContent.WidthAndHeight
#endif

#if SILVERLIGHT
#else
module
Main =
[<System.STAThread()>]
do
let app = new Application()
app.Run(new NewApp()) |> ignore
#endif

Step 5: Then you will add a XAML file and write the below code in the MainWindow.xaml file.

<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name
="nwPnl">
<Popup Name="popup">
<Canvas Name="pupCnvs">
<TextBox Name="pupTp" FontSize="18" Canvas.Left="20" Canvas.Top="20" />
<TextBox Name="pupMdle" FontSize="14" Canvas.Left="20" Canvas.Top="60" />
<TextBox Name="pupBtom" FontSize="18" Canvas.Left="20" Canvas.Top="200" />
</Canvas>
</Popup>
<Border BorderThickness="15.0" BorderBrush="Pink">
<StackPanel Name="stackPanel1">
<TextBlock Text="BallGame!" FontSize="24" HorizontalAlignment="Center" />
<TextBlock Text="A super Fun Game for Kids - press 's' to stop" FontSize="12" HorizontalAlignment="Center" />
<Border BorderThickness="2.0" BorderBrush="Black">
<Canvas Name="canvas" Background="Black" />
</Border>
</StackPanel>
</Border>
</
StackPanel>

Step 6: Now press F5 to execute the code. Your game is ready to play.

Output

Ballgame Output1

Ballgame Output2

Ballgame Output3

Ballgame Output4

Ballgame Output5

Summary

In this article I have discussed how you can develop a Ballgame in F# using both WPF and Silverlight.