/// m13 life app: the same wiring as Snake and Pong, with a third kind of input, the mouse. module Snake.Life.App open Snake open Snake.Model open Snake.IO open Snake.Stream open Snake.Life.Model open Snake.Life.Rules open Snake.Life.View let GEN_MS = 150L let lifeKey (key: string) : LifeAction option = match key with | " " -> Some LifeAction.TogglePause | "n" | "N" -> Some LifeAction.Step | "c" | "C" -> Some LifeAction.Clear | "r" | "R" -> Some LifeAction.Randomize | _ -> None // Integer division rounds towards zero (-1 / 16 = 0), so the pixel is checked before dividing. let lifeClick (x: int) (y: int) : LifeAction option = if x >= 0 && y >= 0 && x / LCELL < LW && y / LCELL < LH then Some(LifeAction.Toggle { X = x / LCELL; Y = y / LCELL }) else None let lifeActions: Stream = Stream.merge (Stream.merge (Stream.interval GEN_MS |> Stream.map (fun _ -> LifeAction.Frame)) (Stream.keyDowns |> Stream.choose lifeKey)) (Stream.clicks |> Stream.choose (fun (x, y) -> lifeClick x y)) let lifeStates (seed: uint32) (actions: Stream) : Stream = actions |> Stream.scan lifeUpdate (initialLife seed) |> Stream.startWith (initialLife seed) let lifeMain () : IO>> = IO.randomSeed () |> IO.map (fun seed -> lifeStates seed lifeActions |> Stream.map (fun state -> IO.drawScene (lifeView state)) |> Stream.clampTime App.MAX_GAP_MS)