F# is targeted mainly for specific problem domains such as AI. But since it has full access to the .NET CLR, F# can be used for GUI programming as well. This code sample demonstrates a minimal implementation of Minesweeper with the game logic written in F#.
The Visual Studio tools favor C#. In VS 2010 there are templates to create a WPF application in C# and VB, but not F#. To save time, the UI layer was developed in the VS designer to generate XAML and (minimal) C# code-behind. This keeps VS designer happy. The game logic is housed in a separate DLL which is written in F#. As a side effect the sample demonstrates some basic F#/C# interop.
There are 4 versions of this code: implemented in F# Silverlight, F# WPF, C# Silverlight and C# WPF. The Silverlight control running on this page is the F# version. Even thought the source code ended up being about 95% identical, porting the code was more work than the original implementation.
The WPF version looks like this. At game startup, a dialog pops up asking which implementation to run. You can then set a breakpoint in either DLL and trace into either the F# or C# DLLs.

The F# code follows the C# code pretty closely, so the two can be compared. If the F# code feels like C# that has been ported, it's because that's exactly how it was written. The result is not a stunning example of functional programming (or MVVM), but it works.

The flag graphic may look familiar, that's actually a character in the WingDings font.
The basic elements of the game are implemented and technically, you could actually play a game of Minesweeper. There are no guarantees about usability or entertainment value though. Niceties such clicking both buttons at the same time to uncover tiles would be easy to add but were left out to keep the sample code from getting too bloated. If there's enough interest I might spruce it up.
The Grid class is subclassed to create a surface that behaves somewhat like a minefield. Of course there are a number of other (probably better) options that could have been chosen for this, such as a user control.
Here's a sample function in F#. This is the code that reveals a patch of safe ground when you click on a square. This is probably the most interesting code in the whole project, and by the way, it's the only recursive function.
type public Minefield =
class
inherit Grid
...
member private this.RevealOrDie(rowColumn : RowColumn) =
if rowColumn.IsValid then
if this.Children.[rowColumn.Index].GetType() = typeof<Button> then
let button = this.Children.[rowColumn.Index] :?> Button
if String.IsNullOrEmpty(button.Content :?> string) then
let attrib = button.Tag :?> MineAttributes
this.SetCellContents(attrib.RowColumn, attrib.Label)
let content = attrib.Label.Content :?> string
if String.IsNullOrEmpty(content) then
this.RevealOrDie(new RowColumn(rowColumn.Row - 1, rowColumn.Column - 1))
this.RevealOrDie(new RowColumn(rowColumn.Row - 1, rowColumn.Column))
this.RevealOrDie(new RowColumn(rowColumn.Row - 1, rowColumn.Column + 1))
this.RevealOrDie(new RowColumn(rowColumn.Row, rowColumn.Column - 1))
this.RevealOrDie(new RowColumn(rowColumn.Row, rowColumn.Column + 1))
this.RevealOrDie(new RowColumn(rowColumn.Row + 1, rowColumn.Column - 1))
this.RevealOrDie(new RowColumn(rowColumn.Row + 1, rowColumn.Column))
this.RevealOrDie(new RowColumn(rowColumn.Row + 1, rowColumn.Column + 1))
elif content = Constants.BOMB then
this.timer.Stop()
ignore (MessageBox.Show(this.mainWindow, "You died!!!", "BOOM"))
let mainWindow = this.mainWindow :> Object :?> IMainWindow_FS
mainWindow.NewGame()
this.CheckForVictory();
...
end
If you're still learning F#, here is the C# equivalent. Namespaces are used to disambiguate the two subclasses so the solution will compile.
public class Minefield : Grid
{
...
private void RevealOrDie(RowColumn rowColumn)
{
Contract.Requires(rowColumn.IsAlmostValid);
if (!rowColumn.IsValid)
return;
if (this.Children[rowColumn.Index].GetType() != typeof(Button))
return;
Button button = (Button)this.Children[rowColumn.Index];
if (String.IsNullOrEmpty((string)button.Content))
{
MineAttributes attrib = (MineAttributes)button.Tag;
SetCellContents(attrib.RowColumn, attrib.Label);
string content = (string)attrib.Label.Content;
if (String.IsNullOrEmpty(content))
{
RevealOrDie(new RowColumn(rowColumn.Row - 1, rowColumn.Column - 1));
RevealOrDie(new RowColumn(rowColumn.Row - 1, rowColumn.Column));
RevealOrDie(new RowColumn(rowColumn.Row - 1, rowColumn.Column + 1));
RevealOrDie(new RowColumn(rowColumn.Row, rowColumn.Column - 1));
RevealOrDie(new RowColumn(rowColumn.Row, rowColumn.Column + 1));
RevealOrDie(new RowColumn(rowColumn.Row + 1, rowColumn.Column - 1));
RevealOrDie(new RowColumn(rowColumn.Row + 1, rowColumn.Column));
RevealOrDie(new RowColumn(rowColumn.Row + 1, rowColumn.Column + 1));
}
else if (content == BOMB)
{
this.timer.Stop();
MessageBox.Show(this.mainWindow, "You died!!!", "BOOM");
((IMainWindow_CS)this.mainWindow).NewGame();
}
CheckForVictory();
}
}
...
}