5
\$\begingroup\$

This is a tally calculator for the Saudi card game Baloot. As in, they're a set of functions so that the user would provide their winnings at the end of a round (card captured, projects declared, etc) and the functions would give them the score for themselves and their opponent.

Domain Model

(I listed most of these types in one line for vertical brevity).

// Types
type Team = Us | Them
type Suit = Hearts | Diamonds | Clubs | Spades
type Rank = Ace | King | Queen | Jack | Ten | Nine | Eight | Seven
type Card = 
    { Rank : Rank
      Suit : Suit }
type Mode = Sun | Trump of Suit
type Project = Sira | Fifty | Hundred | FourHundred | Baloot    
type Hand = 
    { Mode : Mode
      Captured : Card list
      Projects : Project list
      Ground : bool
      Bettor : Team
      Owner : Team }
Point Tally functions
// Trick Total Point Calculations
let sunTrickPts card = 
    match card.Rank with
    | Ace -> 11
    | Ten -> 10
    | King -> 4
    | Queen -> 3
    | Jack -> 2
    | _ -> 0

let trumpTrickPts card trump = 
    match (card.Rank, card.Suit) with
    | (Jack, s) when s = trump -> 20
    | (Nine, s) when s = trump -> 14
    | _ -> sunTrickPts card

let capturedTrickPts hand = 
    match hand.Mode with
    | Sun -> 
        hand.Captured 
        |> List.fold (fun acc card -> acc + (sunTrickPts card)) 0
    | Trump(suit) -> 
        hand.Captured 
        |> List.fold (fun acc card -> acc + (trumpTrickPts card suit)) 0
    + if hand.Ground then 10
      else 0

// Captured Cards Point Calculations
let sunHandPts pts = 
    match pts % 10 with
    | 5 -> pts
    | n when n < 5 -> pts - n
    | n -> pts - n + 10
    / 5

let trumpHandPts pts = 
    match pts % 10 with
    | n when n <= 5 -> pts - n
    | n -> pts - n + 10
    / 10

let capturedHandPts hand = 
    let pts = capturedTrickPts hand
    match hand.Mode with
    | Sun -> sunHandPts pts
    | Trump(_) -> 
        match pts % 10, hand.Bettor with
        | 6, t when t = hand.Owner -> (trumpHandPts pts) - 1
        | _ -> trumpHandPts pts

// Project Points Calculations
let sunProjectPts = 
    function 
    | Sira -> 4
    | Fifty -> 10
    | Hundred -> 20
    | FourHundred -> 40
    | _ -> 0

let trumpProjectPts = 
    function 
    | Sira | Baloot -> 2
    | Fifty -> 5
    | Hundred | FourHundred -> 10

let projectHandPts hand = 
    let projectPts = 
        match hand.Mode with
        | Sun -> sunProjectPts
        | Trump(_) -> trumpProjectPts
    hand.Projects |> List.fold (fun acc pro -> acc + (projectPts pro)) 0

// Total Point for Hand
// TARGET FUNCTION
let handPts hand = (capturedHandPts hand) + (projectHandPts hand)
Functions to test the logic.
// Type Creations
let newCard (rank, suit) = 
    { Card.Rank = rank
      Card.Suit = suit }

let newHand (mode, captured, projects, ground, bettor, owner) = 
    { Hand.Mode = mode
      Hand.Captured = captured
      Hand.Projects = projects
      Hand.Ground = ground
      Hand.Bettor = bettor
      Hand.Owner = owner }

// The Other Team
let opp = 
    function 
    | Us -> Them
    | Them -> Us

open Microsoft.FSharp.Reflection

// Deck Creation and Shuffling
let fullDeck = 
    let suits = FSharpType.GetUnionCases typeof<Suit>
    let ranks = FSharpType.GetUnionCases typeof<Rank>
    [ for s in suits do
          let suit = FSharpValue.MakeUnion(s, [||]) :?> Suit
          for r in ranks do
              let rank = FSharpValue.MakeUnion(r, [||]) :?> Rank
              yield newCard (rank, suit) ]

let shuffleDeck deck = 
    let r = new System.Random()
    deck |> List.sortBy (fun _ -> r.Next())

let cutDeck deck = 
    let rand = new System.Random()
    let rInt = rand.Next(0, 7) * 4
    deck |> List.splitAt rInt

// Randomized Hand Creation for testing
let createTestHands (mode) = 
    let rInt = System.Random().Next(100)
    let rBool = rInt % 2 = 0

    let rBettor, rOwner = 
        match rInt % 4 with
        | 0 -> Us, Us
        | 1 -> Us, Them
        | 2 -> Them, Them
        | _ -> Them, Us

    let capped1, capped2 = 
        fullDeck
        |> shuffleDeck
        |> cutDeck

    let hand1 = newHand (mode, capped1, [], rBool, rBettor, rOwner)
    let hand2 = newHand (mode, capped2, [], not rBool, opp rBettor, opp rOwner)
    hand1, hand2

I used that last function to test the handPts function on random shuffled sets of hands.

PS. Is there a simpler method to generate the fullDeck? Reflection seems to be a bit of an overkill.

\$\endgroup\$
  • 1
    \$\begingroup\$ I really love the F# type system. It reads like a formal language grammar. \$\endgroup\$ – RubberDuck Jul 6 '16 at 20:40
3
\$\begingroup\$

In my opinion, the code is pretty good. The only thing:

In function trumpTrickPts all versions share the same "guarding rules".

So, you can write:

let trumpTrickPts card trump = 
    match (card.Rank, card.Suit = trump) with
    | (Jack, true) -> 20
    | (Nine, true) -> 14
    | _ -> sunTrickPts card

Instead of reflection, you can enumerate all the options in the list:

let fullDeck = 
    let suits = 
        [Hearts; Diamonds; Clubs; Spades]
    let ranks = 
        [Ace ; King ; Queen ; Jack ; Ten ; Nine ; Eight ; Seven]

    [for s in suits do
          for r in ranks -> 
            newCard (r, s) ]

In the function shuffleDeck you creating a new Random inside the function. By default, Random "starts" with the current system clock. So, if you quickly run two stirring new deck, you'll get same results:

Example

Since you are using Random in several function, then better to "take away" it.

Instead of rand.Next(0, 7) * 4 you can use rand.Next(7) * 4

let rand = System.Random()

let shuffleDeck deck = 
    deck |> List.sortBy (fun _ -> rand.Next())

let cutDeck deck = 
    let rInt = rand.Next(7) * 4
    deck |> splitAt rInt

// Randomized Hand Creation for testing
let createTestHands (mode) = 
    let rInt = rand.Next(100)
    ...
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.