FCards - Defining Cards

10. Interacting with a player - game state

Giving the player a way to do things

So far we’ve been talking about modelling how the data can work and fit together, but that’s no fun until a person can interact with the game. To do this we need two things:

  1. a way of displaying the state of the game (i.e. who has what cards, and where)
  2. a way of commanding the game to make a move

Making our types more human-friendly

All types in F# have a built-in method ToString(), which converts the value into a string that we can use to print out the value. Some functions that deal with strings will automatically call ToString() on values to get the string representation.

If we print out a hand of cards at the moment it would look like:

open System

let printOut (hand: 'a seq) =  "[" + String.Join("] [", hand) + "]"

[ Hearts Eight; Diamonds Ten; Clubs Queen; Spades Two; Joker ]
|> printOut
|> printfn "%s"

// OUTPUT //
"[Hearts Eight] [Diamonds Ten] [Clubs Queen] [Spades Two] [Joker]"

TIP: String.Join() is a built-in function that joins a sequence of things into a single string, separated by the first parameter. It is in the System namespace so you will need to open System to make the function available to you. BTW, a list can automatically be converted into a sequence by the compiler - that’s why it works here.

Extending our types

We can add new properties, methods, and functions to any types in F# using the keyword with. We can also override any built-in functions that are already defined on the type.

So we can override how the value is converted into a string by overriding the ToString() method on our DU types, and our Game.

//COLOR CODES
let COLOR_DEFAULT = "\x1B[0m"
let COLOR_RED = "\x1B[91m"
let COLOR_BLACK = "\x1B[90m"

type CardNumber =
  ...
  with 
    override this.ToString() = 
      match this with 
      | Two -> "2 "
      | Three -> "3 "
      | Four -> "4 "
      | Five -> "5 "
      | Six -> "6 "
      | Seven -> "7 "
      | Eight -> "8 "
      | Nine -> "9 "
      | Ten -> "10" //(pesky 2-digit number!!)
      | Jack -> "J "
      | Queen -> "Q "
      | King -> "K "
      | Ace -> "A "

type Card = 
  ...
  with  
    override this.ToString() = 
      match this with 
      | Hearts x -> $"{COLOR_RED}\u2665{x}{COLOR_DEFAULT}"
      | Diamonds x -> $"{COLOR_RED}\u2666{x}{COLOR_DEFAULT}"
      | Clubs x ->  $"{COLOR_BLACK}\u2663{x}{COLOR_DEFAULT}"
      | Spades x ->  $"{COLOR_BLACK}\u2660{x}{COLOR_DEFAULT}"
      | Joker -> "Jok"

type Game = {
  ...
} with
    override this.ToString() =
      $"[###] - {this.deck.Length}\n" + (printOut this.hand)

TIP: The funny looking /u1234 values are unicode codes (in hexadecimal) for the suit symbols from here

With this change our printOut function will now produce:

"[♥8] [♦10] [♣Q] [♠2] [Jok]"

Code so far

open System
//COLOR CODES
let COLOR_DEFAULT = "\x1B[0m"
let COLOR_RED = "\x1B[91m"
let COLOR_BLACK = "\x1B[90m"

type CardNumber =
  | Two 
  | Three
  | Four
  | Five
  | Six
  | Seven
  | Eight
  | Nine
  | Ten
  | Jack
  | Queen
  | King
  | Ace
  with 
    override this.ToString() = 
      match this with 
      | Two -> "2 "
      | Three -> "3 "
      | Four -> "4 "
      | Five -> "5 "
      | Six -> "6 "
      | Seven -> "7 "
      | Eight -> "8 "
      | Nine -> "9 "
      | Ten -> "10"
      | Jack -> "J "
      | Queen -> "Q "
      | King -> "K "
      | Ace -> "A "

type Card = 
  | Hearts of CardNumber
  | Diamonds of CardNumber
  | Clubs of CardNumber
  | Spades of CardNumber
  | Joker
  with  
    override this.ToString() = 
      match this with 
      | Hearts x -> $"{COLOR_RED}\u2665{x}{COLOR_DEFAULT}"
      | Diamonds x -> $"{COLOR_RED}\u2666{x}{COLOR_DEFAULT}"
      | Clubs x ->  $"{COLOR_BLACK}\u2663{x}{COLOR_DEFAULT}"
      | Spades x ->  $"{COLOR_BLACK}\u2660{x}{COLOR_DEFAULT}"
      | Joker -> "Jok"

let printOut (hand: 'a seq) =  
  "[" + String.Join("] [", hand) + "]"

let newDeck = 
  let suits = [Hearts; Diamonds; Clubs; Spades]
  let numbers = [
    Two; Three; Four; Five; Six; Seven; Eight; Nine; Ten;
    Jack; Queen; King; Ace
  ]
  List.allPairs suits numbers
  |> List.map (fun (suit, number) -> suit number)

type Game = {
  deck: Card list
  hand: Card list
} with
    override this.ToString() =
      $"[###] - {this.deck.Length}\n" + (printOut this.hand)

let pickupCard (game: Game) =
  match game.deck with 
  | [] -> failwith "No cards left!!!"
  | [a] -> { hand = game.hand @ [a]; deck = [] }
  | a::rest -> { hand = game.hand @ [a]; deck = rest }

let rec shuffle deck = 
  let random = System.Random()
  match deck with 
  | [] -> []
  | [a] -> [a]
  | _ -> // NOTE: `_` means "some value, but I don't care what it is"
      let randomPosition = random.Next(deck.Length)
      let cardAtPosition = deck[randomPosition]
      let rest = deck |> List.removeAt randomPosition
      [cardAtPosition] @ (shuffle rest)


;;
// DO IT!
let game = 
  { 
    deck = newDeck |> shuffle;
    hand = [] 
  }
  |> pickupCard
  |> pickupCard
  |> pickupCard
  |> pickupCard
  |> pickupCard

printfn "%O" game