/// 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 ``. let gameCanvas () : obj = window?document?getElementById ("game")