Wie kann ich sicherstellen, dass illegales Verhalten nicht ausführbar ist?

8

Wie mache ich illegales Verhalten unausführbar?

Zusammenfassung:

Seit ich meine Reise begonnen habe, um F # zu lernen, lerne ich über typgetriebenes Design und eigenschaftsbasiertes Testen. Als Ergebnis verliebte ich mich in die Idee, illegale Staaten nicht darstellbar zu machen.

Aber was ich wirklich gerne tun würde, ist illegales Verhalten nicht ausführbar zu machen.

Ich lerne F #, indem ich ein BlackJack-Spiel schreibe. Als Ergebnis möchte ich sicherstellen, dass, wenn ein Dealer Karten verteilt, der Dealer nur eine "anfängliche Hand" oder einen "Treffer" ausführen kann. Alle anderen Kartenverteilungen sind illegal.

In C # würde ich das Strategie-Muster implementieren und somit einen DealHandCommand und einen DealHitCommand erstellen. Dann würde ich einen konstanten Integer-Wert für die Anzahl der auszugebenden Karten festschreiben (pro Strategie).

DealHandCommand = 2 Karten

DealHitCommand = 1 Karte

Basierend auf diesen Strategien würde ich dann eine Zustandsmaschine implementieren, um eine Sitzung eines BlackJack-Spiels darzustellen. Nachdem ich die erste Hand behandelt habe (d. H. DealHandCommand), führe ich einen Zustandsübergang durch, bei dem zukünftige Deals nur den "DealHitCommand" ausführen können.

Ist es insbesondere sinnvoll, eine State-Machine in einer hybriden Sprache zu implementieren, um illegales Verhalten als nicht ausführbar zu erreichen?

    
Scott Nimrod 02.12.2015, 12:11
quelle

3 Antworten

9

Es ist einfach, eine Zustandsmaschine in F # zu implementieren. Es folgt normalerweise einem dreistufigen Prozess, wobei der dritte Schritt optional ist:

  1. Definieren Sie eine diskriminierte Union mit einem Fall für jeden Staat
  2. Definieren Sie für jeden Fall eine Übergangsfunktion
  3. Optional: Implementieren Sie den gesamten restlichen Code

Schritt 1

In diesem Fall klingt es für mich wie zwei Zustände:

  • Eine erste Hand mit zwei Karten
  • Ein Hit mit einer zusätzlichen Karte

Das deutet darauf hin, dass diese Deal diskriminierte Gewerkschaft:

%Vor%

Definieren Sie auch, was ein Game ist:

%Vor%

Beachten Sie die Verwendung einer diskriminierten Einheit im Einzelfall; es gibt einen Grund dafür .

Schritt 2

Definieren Sie nun eine Funktion, die von jedem Zustand in ein Game übergeht.

Es stellt sich heraus, dass Sie von keinen Spielstatus in den Fall Hand umwandeln können, weil ein % ein neues Spiel startet . Auf der anderen Seite (Wortspiel beabsichtigt) müssen Sie die Karten liefern, die in die Hand gehen:

%Vor%

Im anderen Fall, wenn ein Spiel läuft, sollten Sie nur Hand , aber nicht Hit erlauben, also definieren Sie diesen Übergang:

%Vor%

Wie Sie sehen können, müssen Sie für die Funktion Hand eine vorhandene hit übergeben.

Schritt 3

Was verhindert, dass ein Client einen ungültigen Game -Wert, z.B. Game ?

Sie können den obigen Zustandsautomaten mit einer Signaturdatei verkapseln:

BlackJack.fsi:

%Vor%

Hier sind die Typen [Hand; Hit; Hand; Hit; Hit] und Deal deklariert, aber ihre 'Konstruktoren' nicht. Dies bedeutet, dass Sie Werte dieser Typen nicht direkt erstellen können. Dies wird zum Beispiel nicht kompiliert:

%Vor%

Der angegebene Fehler ist:

  

Fehler FS0039: Der Wert, Konstruktor, Namespace oder Typ 'Spiel' ist nicht definiert

Die einzige Möglichkeit, einen Wert für Game zu erstellen, besteht darin, eine Funktion aufzurufen, die sie für Sie erstellt:

%Vor%

Dies ermöglicht Ihnen auch, das Spiel fortzusetzen:

%Vor%

Sie haben vielleicht bemerkt, dass die obige Signaturdatei auch zwei Funktionen definiert, um die Karten aus den Werten Game und Game zu entfernen. Hier sind die Implementierungen:

%Vor%

Ein Client kann sie folgendermaßen verwenden:

%Vor%

Beachten Sie, dass dieser Ansatz hauptsächlich strukturell ist; Es gibt wenige bewegliche Teile.

Anhang

Dies sind die oben verwendeten Dateien:

Cards.fs:

%Vor%

BlackJack.fsi:

%Vor%

BlackJack.fs:

%Vor%

Client.fs:

%Vor%     
Mark Seemann 02.12.2015, 13:50
quelle
2

Ein Treffer ist eine weitere Karte richtig?

Wenn ja, dann verwenden Sie einfach zwei Typen :

  • type HandDealt = Dealt of Card * Card
  • und type Playing = Playing of Cards
  • (vielleicht mehr - hängt davon ab, was Sie wollen).

Dann haben Sie anstelle von Befehlen einfache Funktionen:

  • dealHand :: Card * Card -> HandDealt
  • start :: HandDealt -> Playing
  • dealAnother :: Playing -> Card -> Playing

Auf diese Weise können Sie nur einem bestimmten Verhalten folgen und es wird statisch überprüft.

natürlich möchten Sie diese Typen auf mehrere Spieler erweitern, aber ich denke, Sie bekommen, was ich

gehe

PS: Vielleicht möchtest du sogar die Phase HandDealt / start überspringen (wenn du die mittlere Phase für Dinge wie Wetten / Splitten / etc. nicht brauchst) - aber bitte bedenke, dass ich keine Ahnung von Blackjack habe ):

  • dealHand :: Card * Card -> Playing
  • dealAnother :: Playing -> Card -> Playing

es liegt an dir

    
Carsten 02.12.2015 12:53
quelle
1

Eine der Möglichkeiten könnte eine diskriminierte Union sein:

%Vor%

(vorausgesetzt, Sie haben Card eingegeben)

    
Petr 02.12.2015 12:53
quelle