FCards - Simple Pickup Game

12. Interacting with a player - making a move

Listening to the player

To play a game, we can expand to a standard play loop:

  1. Print out state of the game
  2. Print out list of commands the player can perform (optional?)
  3. Wait for player to type a command
  4. Execute the command
  5. Print out result of command (maybe - depends if the state is obvious enough)
  6. Go back to 1.

So that means within our play loop we need to include a function that updates the game according to the command.

let moveUpLines n = printfn "\x1B[%dA" n  //moves the cursor up "n" lines

let printScreen game =
  printfn "===FCARDS==="
  printfn "%O" game
  printfn "<p>ickup card <q>uit"
  moveUpLines 5

let combineUpdaterAndPrinter updater game command = 
  let updated = updater game command
  printScreen updated
  updated 

let loopGame (updater: Game -> char -> Game) (initialGame: Game) = 
  printScreen initialGame
  (fun _ -> Console.ReadKey().KeyChar |> Char.ToLowerInvariant)
  |> Seq.initInfinite
  |> Seq.takeWhile (fun x -> x <> 'q')
  |> Seq.fold (combineUpdaterAndPrinter updater) initialGame

Fold

One the last line we use a new standard function called fold. This loops through the collection of things (in this case the keystrokes) and applies them to an accumulator (in our case it’s the initial Game), but in each step in the loop it uses the updated Game from the previous step. It’s a way of doing the following but with a collection of things that you may not know the value of yet (e.g. the player’s chosen keystrokes):

game
|> updater 'p'
|> updater 'p'
|> updater 'r'
|> updater 'a'
|> updater '?'
|> updater 'q'  //yay we quit!

Partial Application

Also, on that last line, we called the function combineUpdaterAndPrinter. But the function has three inputs and we only provided one - what’s going on?!?

Functions in F# are pretty cool. They actually only take one input at a time and then generate a sub-function for taking the next input, and so on, and so on.

So, the function “let combineUpdaterAndPrinter updater game command = ...” is actually

let combineUpdaterAndPrinter updater =
  let sub_function1 game =
    let sub_function2 command =
      ...

That means when we only provided the first updater input, it actually returned a function that takes a game ( and command ), which just happens to be the exact right shape for the fold operation. In this way we can pre-bake a function into a desired shape with already setup values.

TIP: To take advantage of this partial application of functions, the order of the inputs is important. You can only pre-bake from left to right. Generally, this means that we list the inputs that change the least (or are considered configuration?) first, and the things that change the most (or get passed along to the next function in the chain of pipes?) go to the right.

Extra TIP for experts: Functions that aren’t built in F# (i.e. probably built in C#) don’t use partial application, and instead have a single tuple of inputs.

You may have noticed this when we used the standard function String.Join() in chapter 10. It takes a tuple of a separator and an array of strings

  • So it’s String.Join: (string * string array) -> string called as String.Join(", ", values)
  • Not as String.Join: string -> string array -> string, called as String.Join ", " values

Exercise:

Design the updater function. It takes the parameters of a Game and a Char and returns a newly updated Game.

We want it to pickup a card only if the player presses ‘p’, otherwise return the Game unchanged.

See an answer

let updateGame game command = 
  match command with 
  | 'p' -> game |> pickupCard
  | _ -> game

Now we can play a simple game of “pick up the cards”!

let play() =
  let startingpoint = 
    {
      deck = newDeck |> shuffle
      hand = []
    }
  loopGame updateGame startingpoint

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 moveUpLines n = printfn "\x1B[%dA" n  //moves the cursor up "n" lines
let printScreen game =
  printfn "===FCARDS==="
  printfn "%O" game
  printfn "<p>ickup card <q>uit"
  moveUpLines 5
  
let combineUpdaterAndPrinter updater game command= 
  let updated = updater game command
  printScreen updated
  updated 

let loopGame (updater: Game -> char -> Game) (initialGame: Game) = 
  printScreen initialGame
  (fun _ -> Console.ReadKey().KeyChar |> Char.ToLowerInvariant)
  |> Seq.initInfinite
  |> Seq.takeWhile (fun x -> x <> 'q')
  |> Seq.fold (combineUpdaterAndPrinter updater) initialGame

let updateGame game command = 
  match command with 
  | 'p' -> game |> pickupCard
  | _ -> game

let play() =
  let startingpoint = 
    {
      deck = newDeck |> shuffle
      hand = []
    }
  loopGame updateGame startingpoint


;;
// DO IT!
play()