21 game

You are encouraged to solve this task according to the task description, using any language you may know.
21 is a two player game, the game is played by choosing
a number (1, 2, or 3) to be added to the running total.

The game is won by the player whose chosen number causes the running total
to reach exactly 21.

The running total starts at zero.
One player will be the computer.

Players alternate supplying a number to be added to the running total.



Task

Write a computer program that will:

 do the prompting (or provide a button menu), 
 check for errors and display appropriate error messages, 
 do the additions (add a chosen number to the running total), 
 display the running total, 
 provide a mechanism for the player to quit/exit/halt/stop/close the program,
 issue a notification when there is a winner, and
 determine who goes first (maybe a random or user choice, or can be specified when the game begins). 


 
type Player =
    | Computer
    | Person
type Play = {runningTotal:int; nextTurn:Player}
type Win =
    | ByExact of Player
    | ByOtherExceeded of Player
type Status =
    | Start
    | Playing of Play
    | Winner of Win
    | Exit
 
let rnd = System.Random ()
let randomFirstPlayer () =
    let selection = rnd.Next 2
    if selection = 0 then Computer else Person
let computerChose current =
    if current > 17 then 21-current // win strategy
    else if current > 13 && current < 17 then 17-current // naive thwart opponent strategy
    else rnd.Next(1, (min 4 (21-current+1)))
 
let (|Exact|Exceeded|Under|) i = if i = 21 then Exact else if i > 21 then Exceeded else Under
let (|ValidNumber|InvalidInput|Quit|) (s:string) =
    let trimmed = s.Trim().ToLower()
    if trimmed = "1" || trimmed = "2" || trimmed = "3" then ValidNumber 
    else if trimmed = "q" then Quit
    else InvalidInput
 
let readable = function | Computer -> "Computer is" | Person -> "You are"
 
let rec looper = function
    | Start ->
        let firstPlayer = randomFirstPlayer ()
        printfn $"{readable firstPlayer} randomly selected to go first."
        looper (Playing {runningTotal=0; nextTurn=firstPlayer})
    | Playing play ->
        match play with
        | {runningTotal=Exact; nextTurn=Person} -> looper (Winner (ByExact Computer))
        | {runningTotal=Exact; nextTurn=Computer} -> looper (Winner (ByExact Person))
        | {runningTotal=Exceeded; nextTurn=player} -> looper (Winner (ByOtherExceeded player))
        | {runningTotal=r; nextTurn=player} ->
            match player with
            | Computer ->
                let computerChoice = computerChose r
                let total = r+computerChoice
                printfn $"Computer entered {computerChoice}. Current total now: {total}."
                looper (Playing {runningTotal=total; nextTurn=Person})
            | Person ->
                let input = printf "Enter number 1, 2, or 3 (or q to exit): "; System.Console.ReadLine ()
                match input with
                | ValidNumber ->
                    let playerChoice = System.Int32.Parse input
                    let total = r+playerChoice
                    printfn $"Player entered {playerChoice}. Current total now: {total}."
                    looper (Playing {runningTotal=total; nextTurn=Computer})
                | Quit -> looper Exit
                | InvalidInput -> printfn "Invalid input. Try again."; looper (Playing play)
    | Winner win ->
        match win with
        | ByExact player -> printfn $"{readable player} the winner by getting to 21."; looper Exit
        | ByOtherExceeded player -> printfn $"{readable player} the winner by not exceeding 21."; looper Exit
    | Exit -> printfn "Thanks for playing!"
 
let run () = looper Start
 