FCards - Simple Pickup Game

13. Separation of core and our game

What’s part of “pickup” and what is just generally Cards?

In the next part we want to make a more complex game. In light of this, we can ask ourselves “What parts of our code are re-useable across different card games?”

   
CardNumber Re-usable
Card Re-usable
printOut(hand) Re-usable
newDeck Re-usable
Game other games may have many hands or face up cards
pickupCard only work for our definition of Game
shuffle Re-usable
moveUpLines Re-usable
printScreen only prints our game
loopGame (loopGame will need to be tweaked to make it more general) Re-usable
updateGame this is our specific game logic
play runs our game only

Generics - Specifying a type like a variable

We would like to generalise the loopGame() function so that it can operate on any kind of “Game”, as it doesn’t actually need to know any about the specifics of how the game works.

In F#, we can say that the type of something can be anything, as long as it’s consistent using a type parameter. A Type parameter is shown in F# by starting with a single apostrophe before the name of the label - 'T. Usually, type parameters are a single capital letter, but they can be anything that starts with a letter (e.g. 'myAwesomeTypeParameter_Variable).

When we use a type parameter, we add the list of type parameters used in the function after the function name in angle brackets. This gives users and the compiler a hint that this function is generic.

let doAwesome<'A, 'B, 'C> (something: 'A) (another: 'A) (b:int) : 'C = ...

This enforces that something and another must have the same type, then followed by b that is an int, and the function returns a value of another type again.

So, we can modify our loopGame function to take any kind of Game object

// ORIGINAL
let loopGame (updater: Game -> char -> Game) (initialGame: Game) = 

//GENERIC
let loopGame<'G> (updater: 'G -> char -> 'G) (initialGame: 'G) = 

We also identified that the printScreen function is specific to our game, so we will need to supply it to the loopGame

let loopGame<'G> (printScreen: 'G -> 'G) (updater: 'G -> char -> 'G) (initialGame: 'G) = 

TIP: F# uses the type unit to mean a nothing. Other languages can use the word void or null.

Excercise:

Separate the general stuff into a Core module, followed by a Pickup module

Code so far

open System

module Core =
  //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)

  let rec shuffle deck = 
    let random = System.Random()
    match deck with 
    | [] -> []
    | [a] -> [a]
    | _ ->
        let randomPosition = random.Next(deck.Length)
        let cardAtPosition = deck[randomPosition]
        let rest = deck |> List.removeAt randomPosition
        [cardAtPosition] @ (shuffle rest)

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

  let combineUpdate printScreen updater game command = 
    let updated = updater game command
    printScreen updated
    updated 

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

////////////////////  OUR SIMPLE PICKUP GAME ////////////////////
module Pickup = 
  open Core 

  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 printPickupScreen game =
    printfn "===Pickup Cards==="
    printfn "%O" game
    printfn "<p>ickup card <q>uit"
    moveUpLines 5

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

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


;;
// DO IT!
Pickup.play()