This file contains 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 Pins = int | |
let [<Literal>] MaxPins : Pins = 10 | |
type Frame = | |
| Strike of Pins | |
| Spare of Pins * Pins | |
| Open of Pins * Pins | |
| Pending of Pins | |
| Final of Pins * Pins option * Pins option | |
override this.ToString() = | |
let strikeOrNormal i = if i = MaxPins then "X" else string i | |
let opt = Option.map strikeOrNormal >> Option.defaultValue "" | |
match this with | |
| Strike _ -> "X -" | |
| Spare (first, _) -> $"{first} /" | |
| Open (first, seconde) -> $"{first} {seconde}" | |
| Pending first -> $"{first}" | |
| Final (MaxPins, Some s, Some t) when s + t = MaxPins -> $"X {s} /" | |
| Final (f, Some s, Some t) when f + s = MaxPins -> $"{f} / {strikeOrNormal t}" | |
| Final (first, seconde, third) -> $"{strikeOrNormal first} {opt seconde} {opt third}" | |
module Frame = | |
let start num pins = | |
match num, pins with | |
| 10, _ -> Final (pins, None, None) | |
| _, MaxPins -> Strike MaxPins | |
| _ -> Pending pins | |
let close first seconde = | |
(first, seconde) | |
|> if first + seconde = MaxPins then Spare else Open | |
let final pins = function | |
| (f, None, None) -> Final (f, Some pins, None) | |
| (f, Some s, None) when f + s >= MaxPins -> Final (f, Some s, Some pins) | |
| _ -> failwith "Final frame already closed." | |
module Game = | |
let roll pins frames = | |
match List.rev frames with | |
| Pending f :: tail -> Frame.close f pins :: tail | |
| Final (f, s, t) :: tail -> Frame.final pins (f, s, t) :: tail | |
| tail -> Frame.start (frames.Length + 1) pins :: tail | |
|> List.rev | |
let rollSeq pinsSeq = | |
let mutable frames = [] | |
for pins in pinsSeq do | |
frames <- roll pins frames | |
frames | |
let spareBonus = function | |
| Strike f | Spare (f, _) | Open (f, _) | Pending f | Final (f, _, _) -> f | |
let strikeBonus = function | |
| Spare (f, s) | Open (f, s) | Final (f, Some s, _) -> f + s | |
| _ -> 0 | |
let scoring frames = | |
let rec loop total scores rest = | |
match rest with | |
| [] -> | |
List.rev scores | |
| Open (f, s) :: tail -> | |
let score = total + f + s | |
loop score (score :: scores) tail | |
| Spare (f, s) :: (next :: _ as tail) -> | |
let score = total + f + s + (spareBonus next) | |
loop score (score :: scores) tail | |
| Strike f1 :: (Strike f2 :: (next :: _) as tail) -> | |
let score = total + f1 + f2 + (spareBonus next) | |
loop score (score :: scores) tail | |
| Strike f :: (next :: _ as tail) -> | |
let score = total + f + (strikeBonus next) | |
loop score (score :: scores) tail | |
| Final (f, s, t) :: _ -> | |
let opt v = defaultArg v 0 | |
let score = total + f + (opt s) + (opt t) | |
loop score (score :: scores) [] | |
| Pending f :: _ -> | |
loop (total + f) (f :: scores) [] | |
| _ :: tail -> | |
loop total scores tail | |
loop 0 [] frames | |
module Printer = | |
let concat s = String.concat " " s | |
let frames frames = | |
frames |> List.map string |> concat |> printfn "%s" | |
let scoring scoring = | |
scoring |> List.map (sprintf "%3i") |> concat |> printfn "%s" | |
let frames = | |
[ 5; 4; 4; 6; 7; 0; 10; 10; 10; 5; 3; 6; 4; 4; 6; 10; 10; 10 ] | |
|> Game.rollSeq | |
frames |> Printer.frames | |
frames |> Game.scoring |> Printer.scoring | |
// 5 4 4 / 7 0 X - X - X - 5 3 6 / 4 / X X X | |
// 9 26 33 63 88 106 114 128 148 178 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment