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!