2
\$\begingroup\$

During a discussion in a development chat, a user suggested to another (in the context of C# originally),

UserA: Challenge: Create an implementation of RPS, and then show how it can be extended to include an unlockable option Bomb (blows up paper and rock, but scissors cuts the fuse - basically Paper+; AI that has Bomb uses it instead of Paper) with a minimum of fuss.

UserA: Bonus: Make it so the game can be thus modified changing only data (constants, serialized fields, whatever).

UserB: how would one unlock the Bomb?

UserA: Basically "elsewhere" however you want. Could be an external property, could be an Interface or Serialized Field or parameter or whatever.

and since I'm interested in learning F# I decided to pick up the exercise. Along with that, I also tried to clean up the code for I/O handling, such that I could potentially swap a "player vs computer" game to "player vs player" or even "computer vs computer". I have published the script in GitHub, but please find it pasted below too for convenience:

open System

type MatchResult =
    | Won
    | Lost
    | Draw

type Element =
    | Rock
    | Paper
    | Scissors
    | Bomb

type Player = {
    Pick: Element[] -> Element;
    AnnounceResult: MatchResult -> unit;
}

let victoriesOf element =
    match element with
    | Rock -> [Scissors]
    | Paper -> [Rock]
    | Scissors -> [Paper; Bomb]
    | Bomb -> [Rock; Paper]

let (!) (result: MatchResult) =
    match result with
    | Won -> Lost
    | Lost -> Won
    | x -> x

let evaluate player rival =
    let winsOver a b =
        victoriesOf a
        |> List.contains b
    match (winsOver player rival, winsOver rival player) with
    | (true, false) -> Won
    | (false, true) -> Lost
    | _ -> Draw

let parse options move =
    let cleanup (input: string) = input.ToLowerInvariant().Trim()
    let compare =
        string
        >> cleanup
        >> (=) move
    Seq.tryFind compare options

let runRound (a: Player) (b: Player) options =
    let aChoice = a.Pick options
    let bChoice = b.Pick options
    let result = evaluate aChoice bChoice
    a.AnnounceResult result
    b.AnnounceResult !result

let player = {
    Pick = fun options ->
        printfn "Choose one of %s:" (String.Join(", ", options))
        match parse options (Console.ReadLine()) with
        | Some(x) -> x
        | None -> failwith "Unknown choice!";
    AnnounceResult = fun result ->
        match result with
        | Won -> printfn "You won!"
        | Lost -> printfn "You lost!"
        | Draw -> printfn "It's a draw!"
}

let computer = 
    let rng = Random()
    {
        Pick = fun options -> 
            let choice = Array.item (rng.Next(options.Length)) options
            printfn "Computer chose %A" choice
            choice;
        AnnounceResult = fun _ -> ();
    }

[<EntryPoint>]
let main argv =
    let validMoves = [|Rock; Paper; Scissors; Bomb|]

    while true do
        runRound player computer validMoves
    0

Since I'm new to ML-like languages, I'm looking for feedback on how I could better use the F# libraries, better model my state and data, and even how to make it more point-free. And if there are more idiomatic ways of writing any piece of code, please tell!

\$\endgroup\$

Your Answer

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

Browse other questions tagged or ask your own question.