Skip to content

Instantly share code, notes, and snippets.

@baronfel
Created December 1, 2022 23:11
Show Gist options
  • Save baronfel/7a479e1dbcda1db8b4ba1ff2e0caa62d to your computer and use it in GitHub Desktop.
Save baronfel/7a479e1dbcda1db8b4ba1ff2e0caa62d to your computer and use it in GitHub Desktop.
Blackjack kata
type Suit =
| Spades
| Clubs
| Diamonds
| Hearts
type Face =
| Two
| Three
| Four
| Five
| Six
| Seven
| Eight
| Nine
| Ten
| Jack
| Queen
| King
| Ace
type Card =
{ Face: Face
Suit: Suit }
override x.ToString() = $"%A{x.Face} of %A{x.Suit}"
type Deck = Card list
let private suits = [ Spades; Clubs; Diamonds; Hearts ]
let private faces =
[ Two
Three
Four
Five
Six
Seven
Eight
Nine
Ten
Jack
Queen
King
Ace ]
let deck () =
[| for suit in suits do
for face in faces do
yield { Face = face; Suit = suit } |]
module Blackjack =
type Player =
{ Name: string
Hand: Card list
Strategy: Strategy }
and Action =
| Hit
| Stand
and Strategy = Player -> Action
let cardScore ({ Face = face }: Card) =
match face with
| Two -> 2
| Three -> 3
| Four -> 4
| Five -> 5
| Six -> 6
| Seven -> 7
| Eight -> 8
| Nine -> 9
| Ten
| Jack
| Queen
| King -> 10
| Ace -> 11
let score ({ Hand = cards }) = cards |> List.sumBy cardScore
let hit (deck: Deck, player: Player) =
match deck with
| [] -> failwith "No more cards in deck"
| _ :: [] -> failwith "Not enough cards in deck"
| card1 :: card2 :: rest ->
let player = { player with Hand = card1 :: card2 :: player.Hand }
printfn $"%s{player.Name} hits: the {card1} and the {card2} for a total score of {score player}"
rest, player
type TurnResult =
| PlayerBust of score: int
| PlayerWin of score: int
| DealerWin of score: int
| Continue of Deck * player: Player * dealer: Player
let shuffle (cards: Card []) =
let swap x y (a: 'a []) =
let tmp = a.[x]
a.[x] <- a.[y]
a.[y] <- tmp
Array.iteri
(fun i _ ->
cards
|> swap i (System.Random.Shared.Next(i, Array.length cards)))
cards
let dealerStrategy: Strategy =
fun dealer ->
if score dealer >= 17 then
Stand
else
Hit
let aggressivePlayer: Strategy = fun player -> Hit
let defensivePlayer: Strategy = fun player -> Stand
let standAt (target: int) (player: Player) =
if score player >= target then
Stand
else
Hit
let turn (deck, player, dealer) =
match player.Strategy player, dealer.Strategy dealer with
| Stand, Stand ->
if score player > score dealer then
PlayerWin (score player)
else
DealerWin (score dealer)
| Hit, Stand ->
let (deck, player) = hit (deck, player)
if score player > 21 then PlayerBust (score player)
else if score player = 21 then PlayerWin 21
else Continue(deck, player, dealer)
| Stand, Hit ->
let (deck, dealer) = hit (deck, dealer)
if score dealer > 21 then
PlayerWin (score player)
else
Continue(deck, player, dealer)
| Hit, Hit ->
let (deck, player) = hit (deck, player)
if score player > 21 then
PlayerBust (score player)
else if score player = 21 then
PlayerWin 21
else
let (deck, dealer) = hit (deck, dealer)
if score dealer > 21 then
PlayerWin (score player)
else
Continue(deck, player, dealer)
let playGame playerStrategy =
let deck = deck ()
shuffle deck
let deck = deck |> Array.toList
let card1 :: card2 :: card3 :: card4 :: deck = deck
let player =
{ Name = "Player"
Hand = [ card1; card2 ]
Strategy = playerStrategy }
printfn $"{player.Name} is dealt the {card1} and the {card2} for a total score of {score player}"
let dealer =
{ Name = "Dealer"
Hand = [ card3; card4 ]
Strategy = dealerStrategy }
printfn $"{dealer.Name} is dealt the {card3} and the {card4} for a total score of {score dealer}"
let rec loop (deck, player, dealer) =
// we have a very aggressive player
match turn (deck, player, dealer) with
| PlayerWin score -> printfn $"Player wins with a score of {score}"
| PlayerBust score -> printfn $"Player busts with a score of {score}"
| DealerWin score -> printfn $"Dealer wins with a score of {score}"
| Continue (deck, player, dealer) -> loop (deck, player, dealer)
loop (deck, player, dealer)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment