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.
- Accessibility
- PresentationCore
- PresentationFramework
- System
- System.Xaml
- System.Xml
- System.Core
- System.Numerics
- System.Data
- System.Drawing
- WindowsBase
- UIAutomationProvider
- UIAutomationTypes
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.