Created
December 1, 2022 23:11
-
-
Save baronfel/7a479e1dbcda1db8b4ba1ff2e0caa62d to your computer and use it in GitHub Desktop.
Blackjack kata
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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