E 'facile da implementare una macchina a stati in F #. Di solito segue un processo in tre fasi, con la terza fase è facoltativa:
- Definire un Unione discriminata un caso per ogni stato
- Definire una funzione di transizione per ciascun caso
- opzionale: implementare tutte le resto del codice
Fase 1
In questo caso sembra a me che ci sono due stati:
- Un primo mano con due carte
- Un Hit con una carta in più
Questo suggerisce che questo Deal
unione discriminati:
type Deal = Hand of Card * Card | Hit of Card
Inoltre, definire ciò che un Game
è :
type Game = Game of Deal list
Si noti l'uso di un'unione discriminata a caso singolo; there's a reason for that.
Fase 2
Ora definire una funzione che passa da ogni stato ad un Game
.
Si scopre che non è possibile la transizione da qualsiasi stato del gioco al caso Hand
, perché un Hand
è quello che inizia una nuova partita.D'altra parte (gioco di parole) è necessario fornire i carte che vanno in mano:
let init c1 c2 = Game [Hand (c1, c2)]
L'altro caso è quando un gioco è in corso, si dovrebbe consentire solo Hit
, ma non Hand
, in modo da definire questo passaggio:
let hit (Game deals) card = Game (Hit card :: deals)
Come si può vedere, la funzione hit
richiede di passare in un Game
esistente.
Fase 3
Che cosa impedisce un client di creare un valore non valido Game
, per esempio [Hand; Hit; Hand; Hit; Hit]
?
È possibile incapsulare la macchina dello Stato di cui sopra con un signature file:
BlackJack.fsi:
type Deal
type Game
val init : Card -> Card -> Game
val hit : Game -> Card -> Game
val card : Deal -> Card list
val cards : Game -> Card list
Qui, i tipi Deal
e Game
sono dichiarati, ma i loro 'costruttori' non sono . Ciò significa che non puoi creare direttamente valori di questi tipi. Questo, ad esempio, non compila:
let g = BlackJack.Game []
L'errore dato è:
errore FS0039: Il valore, costruttore dello spazio dei nomi o il tipo di 'gioco' non è definito
L'unico modo per creare un valore Game
è quello di chiamare una funzione che crea per voi:
let g =
BlackJack.init
{ Face = Ace; Suit = Spades }
{ Face = King; Suit = Diamonds }
Questo permette anche di continuare il gioco:
let g' = BlackJack.hit g { Face = Two; Suit = Spades }
Avrete notato che il file di firma di cui sopra definisce anche due funzioni per ottenere le carte di Game
e Deal
valori. Qui ci sono le implementazioni:
let card = function
| Hand (c1, c2) -> [c1; c2]
| Hit c -> [c]
let cards (Game deals) = List.collect card deals
Un cliente li può usare in questo modo:
> let cs = g' |> BlackJack.cards;;
>
val cs : Card list = [{Suit = Spades;
Face = Two;};
{Suit = Spades;
Face = Ace;};
{Suit = Diamonds;
Face = King;}]
Si noti che questo approccio è in gran parte strutturale; ci sono poche parti mobili.
Appendice
Questi sono i file utilizzati in precedenza:
Cards.fs:
namespace Ploeh.StackOverflow.Q34042428.Cards
type Suit = Diamonds | Hearts | Clubs | Spades
type Face =
| Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten
| Jack | Queen | King | Ace
type Card = { Suit: Suit; Face: Face }
BlackJack.FSI:
module Ploeh.StackOverflow.Q34042428.Cards.BlackJack
type Deal
type Game
val init : Card -> Card -> Game
val hit : Game -> Card -> Game
val card : Deal -> Card list
val cards : Game -> Card list
BlackJack.fs:
module Ploeh.StackOverflow.Q34042428.Cards.BlackJack
open Ploeh.StackOverflow.Q34042428.Cards
type Deal = Hand of Card * Card | Hit of Card
type Game = Game of Deal list
let init c1 c2 = Game [Hand (c1, c2)]
let hit (Game deals) card = Game (Hit card :: deals)
let card = function
| Hand (c1, c2) -> [c1; c2]
| Hit c -> [c]
let cards (Game deals) = List.collect card deals
Client.fs:
module Ploeh.StackOverflow.Q34042428.Cards.Client
open Ploeh.StackOverflow.Q34042428.Cards
let g =
BlackJack.init
{ Face = Ace; Suit = Spades }
{ Face = King; Suit = Diamonds }
let g' = BlackJack.hit g { Face = Two; Suit = Spades }
let cs = g' |> BlackJack.cards
Grazie Marco. Nel file fsi, se volessimo esplicitamente consentire al mondo esterno di utilizzare "Gioco", come lo esprimeremo? Sarebbe solo "tipo Game()" all'interno del file fsi? –
@ScottNimrod Se si volesse fare ciò, il modo più semplice sarebbe quello di eliminare il file '.fsi', perché in tal caso sarebbe valida la normale dichiarazione del modulo. Altrimenti, sono sicuro che [la documentazione] (https://msdn.microsoft.com/en-us/library/dd233196.aspx) può dirti come farlo. –