Up/Down arrows (or W/S) move your paddle. First to 5 wins. R restarts.
+
+
+
diff --git a/fsharp/web/BrowserPlatform.fs b/fsharp/web/BrowserPlatform.fs
new file mode 100644
index 0000000..92554b4
--- /dev/null
+++ b/fsharp/web/BrowserPlatform.fs
@@ -0,0 +1,64 @@
+/// Browser glue: turns a canvas into a Platform. Impure by nature; not part of the exercises' logic.
+/// `?` (Fable.Core.JsInterop dynamic access) calls browser APIs by name, without type bindings or extra packages.
+module Snake.Web.BrowserPlatform
+
+open Fable.Core
+open Fable.Core.JsInterop
+open Snake.Runtime
+
+[]
+let private window: obj = jsNative
+
+[]
+let private crypto: obj = jsNative
+
+// Keys that scroll the page should only steer the game.
+let private prevent = [ "ArrowUp"; "ArrowDown"; "ArrowLeft"; "ArrowRight"; " " ]
+
+let private listen (kind: string) (cb: string -> unit) : unit =
+ window?addEventListener (kind,
+ fun (e: obj) ->
+ let key: string = e?key
+
+ if List.contains key prevent then
+ e?preventDefault ()
+
+ cb key)
+
+// t is the number of milliseconds since the first animation frame.
+let private startClock (onTime: int64 -> unit) : unit =
+ let rec frame (start: float option) : float -> unit =
+ fun now ->
+ let t0 = defaultArg start now
+ onTime (int64 (now - t0))
+ window?requestAnimationFrame (frame (Some t0))
+
+ window?requestAnimationFrame (frame None)
+
+// Fable compiles a uint32 array to a Uint32Array, which is what crypto.getRandomValues fills.
+let private randomSeed () : uint32 =
+ let buf: uint32[] = Array.zeroCreate 1
+ crypto?getRandomValues (buf)
+ buf[0]
+
+/// A Platform drawing on `canvas` (an HTML canvas element) and listening to the window's keys and frames.
+let create (canvas: obj) : Platform =
+ let ctx = canvas?getContext ("2d")
+
+ { FillRect =
+ fun x y w h color ->
+ ctx?fillStyle <- color
+ ctx?fillRect (x, y, w, h)
+ FillText =
+ fun x y text color size align ->
+ ctx?fillStyle <- color
+ ctx?font <- $"{size}px monospace"
+ ctx?textAlign <- align
+ ctx?fillText (text, x, y)
+ RandomSeed = randomSeed
+ StartClock = startClock
+ OnKeyDown = listen "keydown"
+ OnKeyUp = listen "keyup" }
+
+/// The page's `