「gumi Inc. が Erlang & Elixir Fest 2018 の会場で配布しているクイズ」をF#で

元の問題（Elixir） https://gist.github.com/cooldaemon/35acccd20d226ee33b639b17156242dc

## Mission2

`walk` 関数を隠ぺいする方法もありますが、今回はそのまま。

```fsharp:Gumimaze.fs
module Gumimaze =
    let read =
        let withIndex xs = Array.mapi (fun i x -> x, i) xs
        let lines = "maze.txt" |> System.IO.File.ReadAllLines
        seq {
            for line, y in withIndex lines do
                for c, x in withIndex (line.ToCharArray ()) ->
                    (x, y), c
        }
        |> Map.ofSeq

    let rec walk maze p walked pt =
        let walked = Map.add p true walked
        let aroundP =
            let x, y = p
            [x+1, y; x, y+1; x-1, y; x, y-1]
        let checkAndWalk p =
            match Map.containsKey p walked, Map.find p maze with
            | true, _   -> []
            | _   , 'W' -> []
            | _   , '1' -> walk maze p walked (pt + 1)
            | _   , 'G' -> [pt]
            | _   , _   -> walk maze p walked pt
        List.collect checkAndWalk aroundP

    let solve maze =
        let p = maze |> Map.pick (fun k v -> if v = 'S' then Some k else None)
        walk maze p Map.empty 0 |> List.max

    [<EntryPoint>]
    let main argv = 
        read |> solve |> printfn "%A"
        0
```

## Mission3

`MailboxProcessor` クラスでアクターモデルにするのが、お題に沿った回答になるでしょうか。ただし、以下の実装では余計に遅くなります。

```fsharp:Gumimaze.fs
module GumimazeAgent
    open Microsoft.FSharp.Control
    open System.Threading.Tasks

    type ReducerMessage = Point of int | End
    type CounterMessage = Inc | Dec
    type WalkerMessage = Map<int*int, char> * (int * int) * Map<int*int, bool> * int;

    let answer = new TaskCompletionSource<int>()

    let reducer = MailboxProcessor<_>.Start(fun mb ->
        let rec mainLoop max = async {
            let! message = mb.Receive()
            match message with
            | Point pt -> return! mainLoop (if pt > max then pt else max)
            | _        -> return answer.SetResult(max)
        }
        mainLoop 0
    )

    let counter = MailboxProcessor<_>.Start(fun mb ->
        let rec mainLoop count = async {
            let! message = mb.Receive()
            let count =
                match message with
                | Inc -> count + 1
                | Dec -> count - 1
            if count > 0 then
                return! mainLoop count
            else
                End |> reducer.Post(ReducerMessage.End)
        }
        mainLoop 0
    )

    let rec walkerBody = fun (mb: MailboxProcessor<_>) -> async {
        let! msg = mb.Receive()
        let walked = Map.add msg.Position true msg.Walked
        let aroundP =
            let x, y = msg.Position
            [x+1, y; x, y+1; x-1, y; x, y-1]
        for p in aroundP do
            match Map.containsKey p walked, Map.find p msg.Maze with
            | true, _   -> ()
            | _   , 'W' -> ()
            | _   , '1' ->
                Inc |> counter.Post
                let walker = MailboxProcessor<_>.Start(walkerBody)
                (msg.Maze, p, walked, msg.Point + 1) |> walker.Post
            | _   , 'G' ->
                Point msg.Point |> reducer.Post
            | _   , _   ->
                Inc |> counter.Post
                let walker = MailboxProcessor<_>.Start(walkerBody)
                (msg.Maze, p, walked, msg.Point) |> walker.Post
        Dec |> counter.Post
    }
    let walker = MailboxProcessor<_>.Start(walkerBody)

    let withIndex xs =
        Array.mapi (fun i x -> x, i) xs

    let read =
        let lines = "maze.txt" |> System.IO.File.ReadAllLines
        seq {
            for line, y in withIndex lines do
                for c, x in withIndex (line.ToCharArray ()) ->
                    (x, y), c
        }
        |> Map.ofSeq

    let solve maze =
        let p = maze |> Map.pick (fun k v -> if v = 'S' then Some k else None)
        Inc |> counter.Post
        (maze, p, Map.empty, 0) |> walker.Post

    [<EntryPoint>]
    let main argv =
        read |> solve
        answer.Task.Result |> printfn "%A"
        0
```