MVVM: from C# to F#

Datetime:2016-08-23 01:26:22          Topic: F#  C#  MVVM Model           Share

Intro

I have been banging my head against the wall as I attempt to learn F#. This language is designed to simplify programming. F# reduces the amount of syntax required via declarative expressions instead over imperative statements.

Although I sincerely believe that F# is the future of .NET development, my mind is stuck on Object-Oriented design principles. As a result, I am forcing myself to either produce or absorb F# material everyday so that I can complete my transformation as a functional developer.

When I studied for my WPF certification, I decided to create a Checkers game. Hence, building a board game would demonstrate UX fundamentals via WPF. Thus, I implemented animations, styles, multitriggers, multibindings, routed events, and other features that the WPF framework provided.

I now find myself applying the same approach to F#. In my case though, I decided to build a Blackjack game. Thus, this experience has been very painful for me. I have never applied functional programming to business logic problems. For example, I have always used if-statements to dictate logical paths of execution. However, in F#, this is frowned upon.

BlackJack

The following application is a partial implementation of a Blackjack game. The languages used are XAML, F#, and C#. This partial implementation deals a “hand” and also a hit”.  In addition to the deal functions, the app also maintains the hand count. Please note that this is a partial implementation of a Blackjack game. The current implementation of this app does not yet satisfy the rules of Blackjack. For example, there is no implementation for “Bust”.  Furthermore, there is no implementation for an actual Blackjack game. Hence, this app only deals cards and maintains count.

The following is a screenshot of the app:

XAML

The XAML is fairly basic:

<Window x:Class="BlackJack.Client.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BlackJack.Client"
        mc:Ignorable="d"
        Title="Cards" Width="550">
    
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>
    
    <Grid Background="Green">
        <Grid.RowDefinitions>
            <RowDefinition Height="2*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <ItemsControl Grid.Row="0" Grid.ColumnSpan="2" ItemsSource="{Binding Hand}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Image Source="{Binding}" Height="140" Margin="5" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>

        <StackPanel Orientation="Horizontal" Margin="5">
            <TextBlock Text="Count:" FontSize="18" Foreground="DarkGreen" FontStyle="Italic" Margin="0,0,10,0"/>
            <TextBlock Text="{Binding Count}" FontSize="24" Foreground="LightGreen" FontWeight="Bold" />
        </StackPanel>
        
        <Button Grid.Row="1" Grid.Column="0" Content="Deal" Command="{Binding DealCommand}" Margin="5" />
        <Button Grid.Row="1" Grid.Column="1" Content="Hit" Command="{Binding HitCommand}" Margin="5" />
    </Grid>
</Window>

ViewModel

I usually partition my view-models into separate files. In this implementation, the view-model is a partial class, “ViewModel” and “ViewModel.internal”.

The following is the top-level view-model class:

using System.Collections.ObjectModel;
using Bizmonger.Patterns;
using Bizmonger.UILogic;

namespace BlackJack.Client
{
    public partial class ViewModel : ViewModelBase
    {
        public ViewModel()
        {
            HitCommand = new DelegateCommand(obj => OnHit());
            DealCommand = new DelegateCommand(obj => OnDeal());
        }

        ObservableCollection<string> _hand = new ObservableCollection<string>();
        public ObservableCollection<string> Hand
        {
            get
            {
                return _hand;
            }

            set
            {
                if (_hand != value)
                {
                    _hand = value;
                    OnPropertyChanged();
                }
            }
        }

        int _count;
        public int Count
        {
            get
            {
                return _count;
            }

            set
            {
                if (_count != value)
                {
                    _count = value;
                    OnPropertyChanged();
                }
            }
        }

        public DelegateCommand DealCommand { get; }
        public DelegateCommand HitCommand { get; }
    }
}

ViewModel.Internal

using Microsoft.FSharp.Collections;
using Microsoft.FSharp.Core;
using static BlackJack.Client.Constants;
using static Core;

namespace BlackJack.Client
{
    public partial class ViewModel
    {
        FSharpList<Card> _deck = shuffle(deck);

        void OnHit()
        {
            var cards = hitPlayer(_deck);
            Deal(new FSharpOption<Card>(cards.Item1), cards.Item2);
            Count += getFaceValue(cards.Item1.Face);
        }

        void OnDeal()
        {
            var result = deal(shuffle(deck));
            var hand = result.Item1;

            Deal(new FSharpOption<Card>(hand.Value.Item1), result.Item2);
            Deal(new FSharpOption<Card>(hand.Value.Item2), result.Item2);

            Count += getHandCount(hand);
        }

        void Deal(FSharpOption<Card> candidate, FSharpList<Card> deck)
        {
            var card = $"{DIRECTORY}{getFile(candidate)}";
            Hand.Accept(card);
            _deck = deck;
        }
    }
}
module Core

    open FsUnit
    open NUnit.Framework
    open Microsoft.FSharp.Reflection

    let GetUnionCaseName(x:'a) =
        match FSharpValue.GetUnionFields(x, typeof<'a>) with
        | case, _ -> case.Name

    type Suit = | Spades| Clubs | Diamonds | Hearts
 
    type Face = | Two | Three | Four | Five 
                | Six | Seven | Eight | Nine | Ten
                | Jack | Queen | King | Ace
 
    type Card = {Face:Face; Suit:Suit}
 
    type Deal = | Hand of Card * Card
                | Hit of Card

    let private suits = [Spades; Clubs; Diamonds ; Hearts]

    let private faces = [Two; Three; Four; Five; Six; Seven; Eight; Nine; Ten;
                         Jack; Queen; King; Ace]
 
    let deck = [for suit in suits do
                    for face in faces do
                        yield {Face=face; Suit=suit}]
 
    let hitPlayer (deck:Card list) =
        (deck.Head, deck.Tail)
 
    let shuffle xs =
        let swap i j (array : _[]) =
            let tmp = array.[i]
            array.[i] <- array.[j]
            array.[j] <- tmp
        let rnd = System.Random()
        let xArray = Seq.toArray xs
        let n = Array.length xArray
        for i in [0..(n-2)] do
            let j = rnd.Next(i, n-1)
            swap i j xArray
        xArray |> Seq.toList

    let deal = function
        | card1::card2::remaining -> Some(card1, card2), remaining
        | _ -> None, [];;

    let getFile card =
        let digitize (candidate:string) =
            let formatted = candidate.ToLower()
            match formatted with
            | "two" -> "2"
            | "three" -> "3"
            | "four" -> "4"
            | "five" -> "5"
            | "six" -> "6"
            | "seven" -> "7"
            | "eight" -> "8"
            | "nine" -> "9"
            | "ten" -> "10"
            | _ -> candidate

        match card with
        | Some c ->
            let face = c.Face |> GetUnionCaseName |> digitize
            let suit = c.Suit |> GetUnionCaseName
            sprintf "%s_of_%s.png" face suit
        | None -> ""

    let getCard(hand:((Card*Card) Option), position:int) =
        match position with
        | 1 -> hand |> Option.map fst |> getFile
        | 2 -> hand |> Option.map snd |> getFile
        | _ -> ""

    let mapIdToType (text:string) =
        let delimited = text.Split '_'
        let face = delimited.[0]
        let suit = delimited.[(delimited.Length - 1)]
        face, suit

    let getFaceValue = function
        | Two -> 2
        | Three -> 3
        | Four -> 4
        | Five -> 5
        | Six -> 6
        | Seven -> 7
        | Eight -> 8
        | Nine -> 9
        | Ten -> 10
        | Jack -> 10
        | Queen -> 10
        | King -> 10
        | Ace -> 11

    let getCount (hand:Card list) =
        hand |> List.sumBy (fun c -> getFaceValue c.Face)

    let getHandCount = function
        | Some(card1, card2) -> [card1; card2] |> getCount
        | None -> 0

Conclusion

In conclusion, I have been banging my head against the wall as I attempt to learn F#. This language is designed to simplify programming. F# reduces the amount of syntax required via declarative expressions instead of imperative statements. I have provided a partial implementation of Blackjack using WPF, C#, and F#.





About List