Last active
March 4, 2017 12:29
-
-
Save pmbanka/8f0f201fac5f07eb7825c1e999b3f79a to your computer and use it in GitHub Desktop.
Game of Life in F#
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
open System | |
let cartesian xs ys = | |
xs |> List.collect (fun x -> ys |> List.map (fun y -> x, y)) | |
type Coords = { X:int; Y:int } with | |
static member FromTuple (x, y) = | |
{ X = x; Y = y } | |
type State = { Alive:Set<Coords>; XMax:int; YMax:int } with | |
member this.AllCells = | |
cartesian [ 0 .. this.XMax ] [ 0 .. this.YMax ] | |
|> List.map Coords.FromTuple | |
type Input = { AliveMarker:char; Grid:string } | |
type OutputConfig = { AliveMarker:char; DeadMarker:char; MaxIter: int; Delay:TimeSpan } | |
let fromString input = | |
let lines = input.Grid.Split ([| "\n"; "\r\n" |], StringSplitOptions.RemoveEmptyEntries) |> List.ofSeq | |
let coords = | |
lines | |
|> Seq.mapi (fun lineIdx line -> | |
line | |
|> Seq.mapi (fun chIdx ch -> if ch = input.AliveMarker then Some { X = chIdx; Y = lineIdx } else None) | |
|> Seq.choose id) | |
|> Seq.concat | |
|> Set.ofSeq | |
let xMax = (lines |> Seq.maxBy (fun line -> line.Length)).Length-1 | |
{ Alive = coords; XMax = xMax; YMax = lines.Length-1 } | |
let toString (aliveMarker:char) (deadMarker:char) state = | |
let createLine y = | |
[| 0 .. state.XMax |] | |
|> Array.map (fun x -> if state.Alive.Contains { X=x; Y=y } then aliveMarker else deadMarker) | |
|> String | |
let lines = | |
[| 0 .. state.YMax |] | |
|> Array.map createLine | |
String.concat Environment.NewLine lines | |
let step state = | |
let aliveNeighbors coord = | |
cartesian [coord.X-1 .. coord.X+1] [coord.Y-1 .. coord.Y+1] | |
|> List.map Coords.FromTuple | |
|> List.where (fun c -> c.X >= 0 && c.X <= state.XMax && c.Y >= 0 && c.Y <= state.YMax && c <> coord) | |
|> List.where state.Alive.Contains | |
|> List.length | |
let isAliveInNextRound coord = | |
let isAliveNow = state.Alive.Contains coord | |
let aliveNeighbors = aliveNeighbors coord | |
match isAliveNow, aliveNeighbors with | |
| true, 2 | true, 3 | false, 3 -> true | |
| _ -> false | |
let newAlive = | |
state.AllCells | |
|> Seq.where isAliveInNextRound | |
|> Set.ofSeq | |
{ state with Alive = newAlive } | |
let animate input cfg = | |
let rec loop currentState currentIter = async { | |
System.Console.Clear () | |
printfn "%s" <| toString cfg.AliveMarker cfg.DeadMarker currentState | |
do! Async.Sleep (int cfg.Delay.TotalMilliseconds) | |
if currentIter < cfg.MaxIter then | |
let nextState = step currentState | |
return! loop nextState (currentIter+1) | |
else | |
return () | |
} | |
let initialState = fromString input | |
loop initialState 0 | |
let grid = """ | |
........................................................................O.................... | |
......................................................................OO.O................... | |
......................................................................OO...O................. | |
....................................................................O...OO.O.....O........... | |
....................................................................OOOO.OO...OOOO.......O.O. | |
..................................................................O......O....OOO.....O.O..O. | |
..................................................................OOOOOOO...O...O....O..O.... | |
...............................................................O.O......OO..O...O.O.OO....O.. | |
..............................................................OOOOOOOOO.....O..OO........O... | |
.............................................................OO..............O.OO.OOOO...O..O | |
............................................................OO....OO.O..........O...O..O.O... | |
.............................................................OO....O........OOO......O.O.O..O | |
.....................................................................O......OO......O....OO.. | |
.............................................................OO....O........OOO......O.O.O..O | |
............................................................OO....OO.O..........O...O..O.O... | |
.............................................................OO..............O.OO.OOOO...O..O | |
..............................................................OOOOOOOOO.....O..OO........O... | |
...............................................................O.O......OO..O...O.O.OO....O.. | |
..................................................................OOOOOOO...O...O....O..O.... | |
..................................................................O......O....OOO.....O.O..O. | |
....................................................................OOOO.OO...OOOO.......O.O. | |
....................................................................O...OO.O.....O........... | |
......................................................................OO...O................. | |
......................................................................OO.O................... | |
........................................................................O....................""" | |
let input = { AliveMarker = 'O'; Grid = grid } | |
let cfg = { AliveMarker = 'o'; DeadMarker = '.'; Delay = TimeSpan.FromMilliseconds 10.; MaxIter = 300 } | |
animate input cfg |> Async.RunSynchronously |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment