FCards - Defining Cards

11. Interacting with a player - listening

Listening to the player

To interact with a game we would need a simple play loop:

  1. Prompt the user
  2. Listen for a command
  3. Execute the command
  4. Go back to 1.

A simple play loop for the command line would look like this:

open System

let loopGame() = 
  (fun _ -> Console.ReadKey().KeyChar |> Char.ToLowerInvariant )
  |> Seq.initInfinite
  |> Seq.takeWhile (fun x -> x <> 'q')
  |> Seq.iter (fun x -> printfn "%A" x)

In this example it just prints out the player’s choice until the player chooses ‘q’. What this does line-by-line is:

  1. Read a key from the keyboard. Note that the key is converted to lower-case so we don’t have to deal with someone using capitals
  2. Initialise an infinite sequence of key-reads. We won’t use them all, but we are saying we don’t care how many there are as long as they don’t run out. The key-read is only actually performed when a further line asks for one (i.e. lazy evaluation)
  3. Keep taking keystrokes (and pass them on as a new sequence) while the key isn’t a ‘q’
  4. Iterate (i.e. loop) though all of the keystrokes and run the function on them. In this case the function just prints out the key. The format string %A is a catch-all format where the compiler chooses the “best” format for the value.

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)

let loopGame() = 
  (fun _ -> Console.ReadKey().KeyChar |> Char.ToLowerInvariant )
  |> Seq.initInfinite
  |> Seq.takeWhile (fun x -> x <> 'q')
  |> Seq.iter (fun x -> printfn "%A" x)


;;
// DO IT!
// type "loopGame();;"