Rash thoughts about .NET, C#, F# and Dynamics NAV.


"Every solution will only lead to new problems."

Category Kata

Monday, 6. December 2010


Sudoku solver in F#

Filed under: F#,Kata — Steffen Forkmann at 15:07 Uhr

On my flight to the US I had a small competition with my dad. He was solving a Sudoku and I tried to write a generic solver in the same time. I admit he was a little bit faster but anyway I put my code on github.

Tags: ,

Sunday, 20. June 2010


CodingDojo in TDD – Die einfachste Lösung siegt

Filed under: C#,Coding Dojo,Kata — Steffen Forkmann at 18:27 Uhr

Auf dem .NET OpenSpace Süd in Karlsruhe hatte ich heute die Gelegenheit bei einem Coding Dojo mit Ilker Cetinkaya mitzumachen. Leider ist das Dojo nicht so richtig in Fahrt gekommen und wir haben es praktisch nicht geschafft auch nur eine einzige Regel der Kata.RomanNumerals zu implementieren. Der Grund dafür war (sicherlich nicht nur) aus meiner Sicht, dass es zwei starke Fraktionen innerhalb des Teams gab. Auf der einen Seite “Coder”, die sich tatsächlich von den Tests treiben lassen wollten und auf der anderen Seite eine Gruppe der “Knobler”, die sich nicht mit dem Test-first-Ansatz identifizieren können und lieber am Board einen Algorithmus entwickeln.

Aus meiner Sicht sind beide Ansätze für sich betrachtet auch völlig in Ordnung und können zu sehr guten Lösungen führen.

Wer z.B. nicht glaubt, dass der strikte Test-first-Ansatz für Kata.RomanNumerals funktioniert, der kann gern die Teilnehmer vom 2.ten Hamburger Coding Dojo fragen oder das github-Repository dazu durch browsen und die Entwicklung der Commits verfolgen.

Was aber offensichtlich überhaupt nicht funktioniert ist der Mittelweg. Wenn sich beide Gruppen nicht auf das Vorgehen einigen und im Zweifel immer nur der “lauteste” Änderungsvorschlag gewinnt, dann wird das Ergebnis mitunter unbefriedigend für beide Seiten. Beim Karlsruher-Dojo hatten wir am Ende nach einer gescheiterten Implementierungsphase und abrupten Abbruch sogar nur 7 rote und einen grünen Test vorzuweisen. Bei dem Grünen frage ich momentan sogar immer noch warum ausgerechnet dieser Test überhaupt gelaufen ist.

Um also eine solche Situation zu vermeiden sollte man sich also am Anfang entscheiden wie man sein Dojo gemeinsam durchziehen will. Im weiteren werde ich jetzt eher auf die von mir favorisierte Variante mit striktem TDD eingehen, auch wenn mich eine Knobler-Session mit ausgeprägter Analysephase auch mal als Vergleichswert interessieren würde.

Wie gesagt: Ich bin ausdrücklich davon überzeugt, dass beide Lösungswege funktionieren können – je nach Problem mal besser und mal schlechter.

Striktes TDD und der einfachste Lösungsvorschlag gewinnt

Ein oft genanntes Argument der “Knobler”-Fraktion am TDD-Ansatz ist, dass nicht vorrausschauend entwickelt wird. Hey Jungs – das ist doch genau der Trick. Solange ich keine Anforderung für irgendwelche Dinge habe (nicht im Kopf oder auf dem Papier, sondern als Test) wird keine Komplexität hinzugefügt. Es wird dann behauptet, dass bei Test-first am Anfang sinnfreie Implementierungen gewählt werden und dass man diesen Code am Ende ganz offensichtlich sowieso wieder wegschmeißen würde. Aber das stimmt einfach nicht. Auch wenn ich immer mehr Tests hinzunehme und meine Implementierung immer generischer wird, so wird die vorherige Lösung letztlich immer ein Spezialfall der generischen Variante sein und geht somit nie verloren.

Gerade der Fokus auf diese minimal mögliche Generalsierung der aktuellen Implementierung sorgt dafür, dass der Algorithmus am Ende korrekt herausfällt. Das Knobeln kommt an dieser Stelle natürlich wieder in den Ablauf herein. Allerdings knobeln wir so an wesentlich kleineren und damit leichteren Problemen  – selbstverständlich nun viel öfter und in kürzeren Zyklen.

Bei zukünftigen Coding Dojos würde ich also gern als Regel sehen, dass der einfachste Vorschlag, der zu Grün führt implementiert wird.

Temposteigerung durch gruppenweises Anlegen von roten Tests

Zu bestimmten Zeitpunkten und insbesondere auch am Anfang von vielen Katas sieht man jedoch oft Implementierungsmuster die gleich eine ganze Klasse an Tests abdecken könnten. Wenn man so eine Idee hat, dann sollte man die natürlich auch nutzen und so das Tempo steigern. Anstatt aber diese Idee einfach zu programmieren würde ich erst die komplette Gruppe von Tests schreiben und sicherstellen, dass alle neuen Tests auch wirklich "rot sind. Dieser Shortcut ist aus meiner Sicht methodisch völlig unproblematisch, da zu diesem Zeitpunkt nur neue Anforderungen im System dokumentiert werden und diese dann als Begründung für neue Komplexität herangezogen werden können.

Aber auch hier sollte die selbe Regel gelten: Die leichteste Implementierung gewinnt. Wenn also ein Teilnehmer eine einfachere Lösung anbieten kann, dann wird die genommen.

Training durch Wiederholung

Wie Ilker in der Auswertung bereits gesagt hat, würde ich auch gern nochmal in der selben Truppe ein Dojo machen. Vielleicht ergibt sich das ja sogar beim nächsten OpenSpace. Denn selbstverständlich geht es beim Coding Dojo nicht nur um Code sondern auch um die beteiligten Menschen.
Obwohl wir am Ende keine Lösung vorweisen konnten, habe ich in der kurzen Zeit sehr viel über die Entwicklungsmethodik anderer Teilnehmer gelernt – ich denke das hat mir wirklich eine Menge gebracht.

Training durch Wiederholung – Reloaded

Da ein wichtiges Element bei Katas auch die Wiederholung ist (und mir im Zug langweilig ist), habe ich zusätzlich für mich selbst und andere noch eine aufgeräumtere Variante der Kata mit minimalen Commits in das Repository hochgeladen. Unter http://github.com/forki/DojoHamburg/commits/Karlsruhe kann man die Änderungen von unten nach oben genau nachvollziehen. Wenn jemand ein Stelle findet wo ich zu viel implementiert habe oder wo die Änderung nicht sinnvoll und einfach erscheint, dann würde ich mich freuen darüber diskutieren zu können.

Tags: ,

Friday, 18. June 2010


Bericht vom 2.ten .NET Coding Dojo in Hamburg

Filed under: C#,Coding Dojo,Kata — Steffen Forkmann at 18:52 Uhr

Am Mittwoch hat die .NET User Group Hamburg das zweite .NET Coding Dojo in wunderbarer Lage im Neuen Wall durchgeführt. Der Dank gilt diesmal Computer Futures, die uns freundlicherweise Raum, Beamer und Getränke zur Verfügung gestellt haben.

Ablauf

Nach einer kurzen Reflektion des letzten Treffens und Klärung der aufgetretenen Syntaxprobleme hat sich das Team dafür entschieden, die Kata.RomanNumerals zu implementieren. Wie beim ersten Treffen haben wir diese relativ einfache Kata auch wieder im Randori-Stil durchgeführt. Da das Team relativ gleich geblieben ist, konnten wir den Ablauf jedoch leicht erweitern. Um etwas mehr Schwung in die Test-Phase zu bringen, habe ich vorgeschlagen immer in der Rot-Phase zu rotieren. Zusätzlich haben wir git als Versionskontrollsystem im Einsatz gehabt und bei jeder Grün-Phase sowie nach Refactorings commited. Das Ergebnis kann auf meinem Github-Account bewundert werden.

Kata

Aus meiner Sicht bildet die Kata.RomanNumerals eine ideale Fortsetzung zur Kata.StringReplacer, die wir beim ersten Treffen hatten und ist praktisch perfekt mit Test-Driven Development lösbar. Insbesondere das nachträgliche Implementieren der scheinbar komplizierten Subtraktionsregel ist wunderbar anhand von wenigen Sonderfällen (= UnitTests) lösbar.

Was lief gut?
  • Die Rotation der Teilnehmer klappte wieder problemlos – Danke Jungs 🙂
  • TDD wurde als sehr erfolgreich für die Kata angesehen
  • Nahezu alle Teilnehmer waren überrascht wie einfach sich die Subtraktionsregel in TDD implementieren lässt
  • Die Randori-Form mit Rotation bei Rot sorgt für Aktivität
  • Es wurde immer versuchen die einfachste Lösung zu wählen
  • Die Rollen Driver, Co-Pilot und Publikum wurden klarer getrennt
  • Es wurde nach jedem implementierten Test in das git Repository commited
  • Ganz wichtig: Es hat wieder viel Spaß gemacht!
Was wollen wir besser machen?
  • Noch mehr Wert auf das Naming legen
    • Überraschenderweise hat sich kein Team getraut die Namenskonvention des ersten Teams zu verbessern
    • Insbesondere auch die Commit-Kommentare sind noch nicht sehr aussagekräftig
  • In den Refactoring-Phasen sollten die Tools (Resharper) besser genutzt werden
  • Die Refactorings könnten noch ausführlicher diskutiert werden
  • Von Anfang an externe Tastatur und Maus für den Laptop bereit stellen
  • Internetverbindung um Syntaxfrage schneller klären zu können
  • Parametrisierte Tests benutzen um weniger Test-Codierungsaufwand zu haben

Vielen Dank an alle Teilnehmer – wir sehen uns dann beim nächsten Dojo.

Tags: ,

Monday, 3. May 2010


Erstes .NET Coding Dojo in Hamburg

Filed under: Coding Dojo,Kata — Steffen Forkmann at 14:42 Uhr

Inspiriert von Coding Dojos in München und Berlin veranstaltet die .NET User Group Hamburg am 19.5. das erste .NET Coding Dojo in Hamburg.

1. .NET Coding Dojo Hamburg

19.05.2010 – 19 Uhr

Masters of Arts Anwendungsentwicklung GmbH
Johannes Brahms Platz 9
20355 Hamburg

Anmeldung über Xing

Hintergrund

Bei einem Coding Dojo treffen sich Software-Entwickler um ihre Fähigkeiten beim gemeinsamen Lösen einer Programmieraufgabe (der sogenannten Code-Kata) zu verbessern. Dabei soll vor allem der Spaß im Vordergrund stehen. Folgende Grundsätze gelten:

  • Das Dojo ist nicht konkurrenzbetont sondern gemeinschafts- und spaßorientiert.
  • Vom Einsteiger bis zum erfahrenen Softwareentwickler ist jeder willkommen.
  • Lösungen werden im Team erarbeitet, gemeinsam umgesetzt und gemeinsam präsentiert.
  • Zur Lösung müssen Tests geschrieben werden.
  • Es gibt keine Musterlösung! Die Lösungsmöglichkeiten sind bewusst offen und geben Raum für unterschiedlichste Entscheidungen. Interessant ist insbesondere das gegenseitige Anleiten und Lehren.

[nach http://codingdojo.org/ und http://www.altnetberlin.de/]

RandoriKata

Es gibt verschiedene Möglichkeiten ein Coding Dojo abzuhalten. Für unser erstes Treffen wollen wir die sogenannte RandoriKata-Version probieren. Dabei wird die Problemstellung wird von einem Entwickler-Duo gelöst (Driver und Copilot). Alle anderen Personen sind jedoch eingeladen sich aktiv an der Lösung zu beteiligen und dem Entwickler-Duo Hilfestellungen und Hinweise zu geben. Nach einer gewissen Zeit (ca. 7 Minuten) wird das Duo ausgetauscht und die nächsten Entwickler haben die Chance die Lösung voranzutreiben.

Die Anmeldung ist über Xing möglich.

Tags: , ,

Saturday, 24. April 2010


Solving KataYahtzee with F# and NaturalSpec

Filed under: F#,Informatik,Kata — Steffen Forkmann at 19:04 Uhr

Today I’m starting a new blog post series about solving code katas in F# and with the help of my NaturalSpec project. A code kata is a programming exercise which helps to improve your skills through practice and repetition. In this series we want to use the Test Driven Development TDD approach which means in the context of NaturalSpec that we have to write our specs before we implement the algorithm.

Problem Description

“The game of yahtzee is a simple dice game. Each round, each player rolls five six sided dice. The player may choose to reroll some or all of the dice up to three times (including the original roll). The player then places the roll at a category, such as ones, twos, sixes, pair, two pairs etc. If the roll is compatible with the score, the player gets a score for this roll according to the rules. If the roll is not compatible, the player gets a score of zero for this roll.

The kata consists of creating the rules to score a roll in any of a predefined category. Given a roll and a category, the final solution should output the score for this roll placed in this category.”

[codingdojo.org]

Category 1 – Ones, Twos, Threes, Fours, Fives, Sixes

“Ones, Twos, Threes, Fours, Fives, Sixes: The player scores the sum of the dice that reads one, two, three, four, five or six, respectively. For example, 1, 1, 2, 4, 4 placed on "fours" gives 8 points.”

After reading this category description we could come up with the following spec:

module Yahtzee.Specs

 

open NaturalSpec

 

let placed_on category list =

    printMethod category

    calcValue category list

 

[<Scenario>]    

let “Given 1,1,2,4,4 placed on "fours" gives 8 points.“ () =  

    Given (1, 1, 2, 4, 4)

      |> When (placed_on Fours)

      |> It should equal 8

      |> Verify

Since we really want to implement six different categories we should also add some more scenarios like this one:

[<Scenario>]    

let “Given 1,1,6,4,6 placed on "sixes" gives 12 points.“ ()=  

    Given (1, 1, 6, 4, 6)

      |> When (placed_on Sixes)

      |> It should equal 12

      |> Verify

 

// …

Now we have some specs but they will all fail since we don’t have anything implemented yet. So we have to come up with a model of dice rolls and categories. As the specs suggests we model the dice roll as a tuple of ints and the category as a discriminated union:

module Yahtzee.Model

 

type Roll = int * int * int * int * int

 

type Category =

| Ones

| Twos

| Threes

| Fours

| Fives

| Sixes  

The tuple is a natural choice for the dice roll, but for easier calculation we add a helper function which converts it into a list. This allows use to use the standard list functions and therefore summing the values becomes trivial:

let toList (roll:Roll) =

    let a,b,c,d,e = roll

    [a;b;c;d;e]

 

let sumNumber number =

    Seq.filter ((=) number)

      >> Seq.sum

 

let calcValue category roll =

    let list = toList roll

    match category with

    | Ones   -> sumNumber 1 list

    | Twos   -> sumNumber 2 list

    | Threes -> sumNumber 3 list

    | Fours  -> sumNumber 4 list

    | Fives  -> sumNumber 5 list

    | Sixes  -> sumNumber 6 list

Now we can run our scenarios with any NUnit runner. I’m using the default NUnit GUI runner here, which gives me a picture like this:

Both specs in NUnit

Category 2 – Pair

“Pair: The player scores the sum of the two highest matching dice. For example, 3, 3, 3, 4, 4 placed on "pair" gives 8.”

The kata description gives us some new scenarios. As seen above we should specify them before writing the code.

[<Scenario>]    

let “Given 3,3,3,4,4 placed on "pair" gives 8.“ () =  

    Given (3, 3, 3, 4, 4)

      |> When (placed_on Pair)

      |> It should equal 8

      |> Verify

 

[<Scenario>]    

let “Given 5,3,5,4,4 placed on "pair" gives 10.“ () =  

    Given (5, 3, 5, 4, 4)

      |> When (placed_on Pair)

      |> It should equal 10

      |> Verify   

 

[<Scenario>]    

let “Given 1,2,3,4,5 placed on "pair" gives 0.“ () =  

    Given (1, 2, 3, 4, 5)

      |> When (placed_on Pair)

      |> It should equal 0

      |> Verify     

Since we use a new category we now have to extend our model and the calcValue function:

type Category =

| Ones

  // …

| Sixes

| Pair

 

// …

let sumAsPair list number =

    let numberCount =

        list

          |> Seq.filter ((=) number)

          |> Seq.length

 

    if numberCount >= 2 then 2 * number else 0

 

let calcValue category roll =

    let list = toList roll

    match category with

    | Ones   -> sumNumber 1 list

        // …

    | Sixes  -> sumNumber 6 list

    | Pair   ->

        [1..6]

          |> Seq.map (sumAsPair list)

          |> Seq.max  

Category 3 – Two pairs

“Two pairs: If there are two pairs of dice with the same number, the player scores the sum of these dice. If not, the player scores 0. For example, 1, 1, 2, 3, 3 placed on "two pairs" gives 8.”

[<Scenario>]    

let “Given 1,1,2,3,3 placed on "two pair" gives 8.“ () =  

    Given (1, 1, 2, 3, 3)

      |> When (placed_on TwoPair)

      |> It should equal 8

      |> Verify

 

[<Scenario>]    

let “Given 1,6,6,3,3 placed on "two pair" gives 18.“ () =  

    Given (1, 6, 6, 3, 3)

      |> When (placed_on TwoPair)

      |> It should equal 18

      |> Verify

 

[<Scenario>]    

let “Given 1,1,2,4,3 placed on "two pair" gives 0.“ () =  

    Given (1, 1, 2, 4, 3)

      |> When (placed_on TwoPair)

      |> It should equal 0

      |> Verify 

Implementing this category is a little bit tricky but with the help of our Pair function and some more standard sequence combinators we can get our spec green:

type Category =

| Ones

  // …

| TwoPair

 

let allPairs =

    [for i in 1..6 do

       for j in 1..6 -> i,j]

 

let calcValue category roll =

    // …

    | TwoPair   ->

        allPairs

          |> Seq.filter (fun (a,b) -> a <> b)

          |> Seq.map (fun (a,b) ->

                let a’ = sumAsPair list a

                let b’ = sumAsPair list b

                if a’ = 0 || b’ = 0 then 0 else a’ + b’)

          |> Seq.max    

Category 4 – Three of a kind

“Three of a kind: If there are three dice with the same number, the player scores the sum of these dice. Otherwise, the player scores 0. For example, 3, 3, 3, 4, 5 places on "three of a kind" gives 9.”

[<Scenario>]    

let “Given 3,3,3,4,5 placed on "three of a kind" gives 9“()=  

    Given (3, 3, 3, 4, 5)

      |> When (placed_on ThreeOfAKind)

      |> It should equal 9

      |> Verify

 

[<Scenario>]    

let “Given 3,4,3,4,5 placed on "three of a kind" gives 0“()=  

    Given (3, 4, 3, 4, 5)

      |> When (placed_on ThreeOfAKind)

      |> It should equal 0

      |> Verify

Now it is time to refactor our code. The sumAsPair function should be extended to a sumAsTuple function:

type Category =

| Ones

  // …

| ThreeOfAKind

 

let sumAsTuple value list number =

    let numberCount =

        list

          |> Seq.filter ((=) number)

          |> Seq.length

 

let takeBestTuple value list =

    [1..6]

        |> Seq.map (sumAsTuple value list)

        |> Seq.max 

    if numberCount >= value then value * number else 0

 

let calcValue category roll =

      // …

    | Pair   -> takeBestTuple 2 list

    | TwoPair   ->

        allPairs

          |> Seq.filter (fun (a,b) -> a <> b)

          |> Seq.map (fun (a,b) ->

                let a’ = sumAsTuple 2 list a

                let b’ = sumAsTuple 2 list b

                if a’ = 0 || b’ = 0 then 0 else a’ + b’)

          |> Seq.max

    | ThreeOfAKind –> takeBestTuple 3 list

Category 5 – Four of a kind

“Four of a kind: If there are four dice with the same number, the player scores the sum of these dice. Otherwise, the player scores 0. For example, 2, 2, 2, 2, 5 places on "four of a kind" gives 8.”

[<Scenario>]    

let “Given 2,2,2,2,5 placed on "four of a kind" gives 8“ ()=  

    Given (2, 2, 2, 2, 5)

      |> When (placed_on FourOfAKind)

      |> It should equal 8

      |> Verify

 

[<Scenario>]    

let “Given 2,6,2,2,5 placed on "four of a kind" gives 0“ ()=  

    Given (2, 6, 2, 2, 5)

      |> When (placed_on FourOfAKind)

      |> It should equal 0

      |> Verify  

With the help of the takeBestTuple function this becomes trivial:

type Category =

| Ones

  // …

| FourOfAKind

 

let calcValue category roll =

      // …

    | FourOfAKind  -> takeBestTuple 4 list

 

Category 6 – Small straight

“Small straight: If the dice read 1,2,3,4,5, the player scores 15 (the sum of all the dice), otherwise 0.”

[<Scenario>]    

let “Given 1,2,3,4,5 placed on "Small Straight" gives 15“()=  

    Given (1,2,3,4,5)

      |> When (placed_on SmallStraight)

      |> It should equal 15

      |> Verify

 

[<Scenario>]    

let “Given 1,2,5,4,3 placed on "Small Straight" gives 15“()=  

    Given (1,2,5,4,3)

      |> When (placed_on SmallStraight)

      |> It should equal 15

      |> Verify

 

[<Scenario>]    

let “Given 1,2,6,4,3 placed on "Small Straight" gives 0“()=  

    Given (1,2,6,4,3)

      |> When (placed_on SmallStraight)

      |> It should equal 0

      |> Verify 

As in all the above scenarios we don’t assume any specific order in our rolls but for this category it is easier to test if the data is sorted:

type Category =

| Ones

  // …

| SmallStraight

 

let calcValue category roll =

      // …

    | SmallStraight ->

        match list |> List.sort with

        | [1;2;3;4;5] -> 15

        | _ -> 0

Category 7 – Large straight

“Large straight: If the dice read 2,3,4,5,6, the player scores 20 (the sum of all the dice), otherwise 0.”

[<Scenario>]    

let “Given 2,3,4,5,6 placed on "Large Straight" gives 20“()=  

    Given (2,3,4,5,6)

      |> When (placed_on LargeStraight)

      |> It should equal 20

      |> Verify

 

[<Scenario>]    

let “Given 6,2,5,4,3 placed on "Large Straight" gives 20“()=  

    Given (6,2,5,4,3)

      |> When (placed_on LargeStraight)

      |> It should equal 20

      |> Verify

 

[<Scenario>]    

let “Given 1,2,6,4,3 placed on "Large Straight" gives 0“()=  

    Given (1,2,6,4,3)

      |> When (placed_on LargeStraight)

      |> It should equal 0

      |> Verify 

Of course the implementation is exactly the same as for the small straight:

type Category =

| Ones

  // …

| LargeStraight

 

let calcValue category roll =

      // …

    | LargeStraight ->

        match list |> List.sort with

        | [2;3;4;5;6] -> 20

        | _ -> 0

Category 8 – Full house

“Full house: If the dice are two of a kind and three of a kind, the player scores the sum of all the dice. For example, 1,1,2,2,2 placed on "full house" gives 8. 4,4,4,4,4 is not "full house".”

[<Scenario>]    

let “Given 1,1,2,2,2 placed on "full house" gives 8.“ () =  

    Given (1,1,2,2,2)

      |> When (placed_on FullHouse)

      |> It should equal 8

      |> Verify

 

[<Scenario>]    

let “Given 4,4,4,4,4 placed on "full house" gives 0.“ () =  

    Given (4,4,4,4,4)

      |> When (placed_on FullHouse)

      |> It should equal 0

      |> Verify

 

[<Scenario>]    

let “Given 1,1,2,3,2 placed on "full house" gives 0.“ () =  

    Given (1,1,2,3,2)

      |> When (placed_on FullHouse)

      |> It should equal 0

      |> Verify

Implementing the FullHouse category is easy if we reuse our solutions to the Two pairs category:

type Category =

| Ones

  // …

| FullHouse

 

let takeBestCombo value1 value2 list =

    allPairs

        |> Seq.filter (fun (a,b) -> a <> b)

        |> Seq.map (fun (a,b) ->

            let a’ = sumAsTuple value1 list a

            let b’ = sumAsTuple value2 list b

            if a’ = 0 || b’ = 0 then 0 else a’ + b’)

        |> Seq.max

 

let calcValue category roll =

      // …

    | TwoPair   -> takeBestCombo 2 2 list

      // …

    | FullHouse   -> takeBestCombo 2 3 list

Category 9 – Yahtzee

“Yahtzee: If all dice are the have the same number, the player scores 50 points, otherwise 0.”

Here we can use NaturalSpec’s ScenarioTemplates in order to specify all Yahtzees:

[<ScenarioTemplate(1)>]

[<ScenarioTemplate(2)>]

[<ScenarioTemplate(3)>]

[<ScenarioTemplate(4)>]

[<ScenarioTemplate(5)>]

[<ScenarioTemplate(6)>]

let “Given n,n,n,n,n placed on "Yahtzee" gives 50.“ n =  

    Given (n,n,n,n,n)

      |> When (placed_on Yahtzee)

      |> It should equal 50

      |> Verify

 

[<Scenario>]    

let “Given 1,1,1,2,1 placed on "Yahtzee" gives 50.“ () =  

    Given (1,1,1,2,1)

      |> When (placed_on Yahtzee)

      |> It should equal 0

      |> Verify

The implementation is pretty easy:

type Category =

| Ones

  // …

| Yahtzee

 

let calcValue category roll =

      // …

    | Yahtzee ->

        let a,b,c,d,e = roll

        if a = b && a = c && a = d && a = e then 50 else 0

Category 10 – Chance

“Chance: The player gets the sum of all dice, no matter what they read.”

[<Scenario>]    

let “Given 1,1,1,2,1 placed on "Chance" gives 6.“ () =  

    Given (1,1,1,2,1)

      |> When (placed_on Chance)

      |> It should equal 6

      |> Verify

 

[<Scenario>]    

let “Given 1,6,1,2,1 placed on "Chance" gives 11.“ () =  

    Given (1,6,1,2,1)

      |> When (placed_on Chance)

      |> It should equal 11

      |> Verify

This seems to be the easiest category as we only have to sum the values:

type Category =

| Ones

  // …

| Chance

 

let calcValue category roll =

      // …

    | Chance -> List.sum list

Conclusion

We used a lot of F#’s sequence combinators, pattern matching and discriminated unions in this kata. I think this shows that F# is very well suited for such a problem and with NaturalSpec we can easily use a TDD/BDD approach.

The complete source code can be found in the NaturalSpec repository.

If you want to know more about a specific part of the kata or NaturalSpec feel free to contact me.

Tags: , , , , , , ,