# Super Fable Mario

Mario clone using HTML5 canvas

Mario clone, based on functional reactive sample written in Elm. The Fable version is using HTML 5 canvas to render the background and an `img` tag showing the Mario (using animated GIFs). You can find the full source code on GitHub.

To see how it works, use the left, right and up buttons to play. Our Mario respects no boundaries!

## Mario and composable physics

We keep information about Mario in a single record type with fields that represent the current x and y coordinates (`x` and `y`), current velocity (`vx` and `vy`) and the current direction (`dir`). The direction is used to pick the correct Mario image when rendering:

 ```1: 2: 3: 4: ``` ``````type Mario = { x:float; y:float; vx:float; vy:float; dir:string } ``````

The step function of the game takes previvous `Mario` value and returns a new one. It is composed from 4 functions that represent different aspects of the game.

The functions that depend on keyboard take the current keyboard state as the first argument. This is represented as a tuple `int*int` consisting of x and y directions. For example, when the left key is pressed, the value is `(-1, 0)`.

 ``` 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: ``` ``````// If the Up key is pressed (y > 0) and Mario is on the ground, // then create Mario with the y velocity 'vy' set to 5 let jump (_,y) m = if y > 0 && m.y = 0. then { m with vy = 5. } else m // If Mario is in the air, then his "up" velocity is decreasing let gravity m = if m.y > 0. then { m with vy = m.vy - 0.1 } else m // Apply physics - move Mario according to the current velocities let physics m = { m with x = m.x + m.vx; y = max 0. (m.y + m.vy) } // When Left/Right keys are pressed, change 'vx' and direction let walk (x,_) m = let dir = if x < 0 then "left" elif x > 0 then "right" else m.dir { m with vx = float x; dir = dir } ``````

The `step` function takes a `dir` parameter representing the keyboard status and a current `Mario` state. It simply runs 4 components in a pipeline:

 ```1: 2: ``` ``````let step dir mario = mario |> physics |> walk dir |> gravity |> jump dir ``````

## Rendering Mario with HTML5

Now we're ready to render Mario using HTML 5 canvas! To do that, we need the width and height of the canvas and the current state of Mario. The following function fills the bottom half of the canvas with green, upper half with blue and then chooses the right Mario image. It uses helpers from the `Win` module, which are discussed below:

 ``` 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: ``` ``````/// Render mario on canvas let render (w,h) (mario:Mario) = // Render background (0.,0.,w,h) |> Win.filled (Win.rgb 174 238 238) (0.,h-50.,w,50.) |> Win.filled (Win.rgb 74 163 41) // Select and position Mario // (walking is represented as an animated gif) let verb = if mario.y > 0. then "jump" elif mario.vx <> 0. then "walk" else "stand" "images/mario" + verb + mario.dir + ".gif" |> Win.image |> Win.position (w/2.-16.+mario.x, h-50.-31.-mario.y) ``````

## Driving the game

The last thing that needs to be done is to write the `main` function that drives the game. The function does some initialization and then starts a recursive `update` function that calculates a new game state using `step` and renders it in a loop. The `Keyboard` helper module is discussed below.

 ``` 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: ``` ``````// Some initialization Keyboard.init() let w,h = Win.dimensions() // Recursive function that updates the state & renders it let rec update mario () = let mario = mario |> step (Keyboard.arrows()) render (w,h) mario window.setTimeout(update mario, 1000. / 60.) |> ignore // Start the game with Mario in the center let mario = { x=0.; y=0.; vx=0.; vy=0.; dir="right" } update mario () ``````

## Keyboard helpers

The `Keyboard` module handles keydown and keyup events of the window and exposes them using the `arrows` property (which is a tuple `int*int` with `-1` if the left/up key is pressed, `1` if right/down key is pressed and `0` otherwise).

 ``` 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: ``` ``````module Keyboard = /// Set of currently pressed keys let mutable keysPressed = Set.empty /// Returns 1 if key with given code is pressed let code x = if keysPressed.Contains(x) then 1 else 0 /// Update the state of the set for given key event let update (e : KeyboardEvent, pressed) = let keyCode = int e.keyCode let op = if pressed then Set.add else Set.remove keysPressed <- op keyCode keysPressed null /// Returns pair with -1 for left or down and +1 /// for right or up (0 if no or both keys are pressed) let arrows () = (code 39 - code 37, code 38 - code 40) let init () = document.addEventListener_keydown(fun e -> update(e, true)) document.addEventListener_keyup(fun e -> update(e, false)) ``````

## Window helpers

The `Window` module contains basic functionality for creating and rendering window using HTML5 canvas and moving images around. The functions below fill the window, set position of image and create image.

 ``` 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: ``` ``````module Win = // Get the canvas context for drawing let canvas = document.getElementsByTagName_canvas().[0] let context = canvas.getContext_2d() // Format RGB color as "rgb(r,g,b)" let (\$) s n = s + n.ToString() let rgb r g b = "rgb(" \$ r \$ "," \$ g \$ "," \$ b \$ ")" /// Fill rectangle with given color let filled color rect = let ctx = context ctx.fillStyle <- U3.Case1 color ctx.fillRect rect /// Move element to a specified X Y position let position (x,y) (img : HTMLImageElement) = img.style.left <- x.ToString() + "px" img.style.top <- (canvas.offsetTop + y).ToString() + "px" let dimensions () = canvas.width, canvas.height /// Get the first element and set `src` (do /// nothing if it is the right one to keep animation) let image (src:string) = let image = document.getElementsByTagName_img().[0] if image.src.IndexOf(src) = -1 then image.src <- src image ``````
namespace Fable
namespace Fable.Core
namespace Fable.Import
module Browser

from Fable.Import
val max : a:'a -> b:'a -> 'a (requires comparison)

Full name: Mario.max
val a : 'a (requires comparison)
val b : 'a (requires comparison)
val mutable keysPressed : Set<int>

Full name: Mario.Keyboard.keysPressed

Set of currently pressed keys
Multiple items
module Set

from Microsoft.FSharp.Collections

--------------------
type Set<'T (requires comparison)> =
interface IComparable
interface IEnumerable
interface IEnumerable<'T>
interface ICollection<'T>
new : elements:seq<'T> -> Set<'T>
member Add : value:'T -> Set<'T>
member Contains : value:'T -> bool
override Equals : obj -> bool
member IsProperSubsetOf : otherSet:Set<'T> -> bool
member IsProperSupersetOf : otherSet:Set<'T> -> bool
...

Full name: Microsoft.FSharp.Collections.Set<_>

--------------------
new : elements:seq<'T> -> Set<'T>
val empty<'T (requires comparison)> : Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Set.empty
val code : x:int -> int

Full name: Mario.Keyboard.code

Returns 1 if key with given code is pressed
val x : int
member Set.Contains : value:'T -> bool
val update : e:KeyboardEvent * pressed:bool -> 'a (requires 'a : null)

Full name: Mario.Keyboard.update

Update the state of the set for given key event
val e : KeyboardEvent
Multiple items
val KeyboardEvent : KeyboardEventType

Full name: Fable.Import.Browser.KeyboardEvent

--------------------
type KeyboardEvent =
interface
inherit UIEvent
abstract member getModifierState : keyArg:string -> bool
abstract member DOM_KEY_LOCATION_JOYSTICK : float
abstract member DOM_KEY_LOCATION_LEFT : float
abstract member DOM_KEY_LOCATION_MOBILE : float
abstract member DOM_KEY_LOCATION_RIGHT : float
abstract member DOM_KEY_LOCATION_STANDARD : float
abstract member altKey : bool
abstract member char : string
...
end

Full name: Fable.Import.Browser.KeyboardEvent
val pressed : bool
val keyCode : int
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
property KeyboardEvent.keyCode: float
val op : (int -> Set<int> -> Set<int>)
val add : value:'T -> set:Set<'T> -> Set<'T> (requires comparison)

val remove : value:'T -> set:Set<'T> -> Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Set.remove
val arrows : unit -> int * int

Full name: Mario.Keyboard.arrows

Returns pair with -1 for left or down and +1
for right or up (0 if no or both keys are pressed)
val init : unit -> unit

Full name: Mario.Keyboard.init
val document : Document

Full name: Fable.Import.Browser.document
abstract member Document.addEventListener_keydown : listener:System.Func<KeyboardEvent,obj> * ?useCapture:bool -> unit
abstract member Document.addEventListener_keyup : listener:System.Func<KeyboardEvent,obj> * ?useCapture:bool -> unit
val canvas : HTMLCanvasElement

Full name: Mario.Win.canvas
abstract member Document.getElementsByTagName_canvas : unit -> NodeListOf<HTMLCanvasElement>
val context : CanvasRenderingContext2D

Full name: Mario.Win.context
abstract member HTMLCanvasElement.getContext_2d : unit -> CanvasRenderingContext2D
val s : string
val n : 'a
System.Object.ToString() : string
val rgb : r:'a -> g:'b -> b:'c -> string

Full name: Mario.Win.rgb
val r : 'a
val g : 'b
val b : 'c
val filled : color:string -> float * float * float * float -> unit

Full name: Mario.Win.filled

Fill rectangle with given color
val color : string
val rect : float * float * float * float
val ctx : CanvasRenderingContext2D
type U3<'a,'b,'c> =
| Case1 of 'a
| Case2 of 'b
| Case3 of 'c

Full name: Fable.Core.U3<_,_,_>
union case U3.Case1: 'a -> U3<'a,'b,'c>
abstract member CanvasRenderingContext2D.fillRect : x:float * y:float * w:float * h:float -> unit
val position : x:'a * y:float -> img:HTMLImageElement -> unit

Full name: Mario.Win.position

Move element to a specified X Y position
val x : 'a
val y : float
val img : HTMLImageElement
Multiple items
val HTMLImageElement : HTMLImageElementType

Full name: Fable.Import.Browser.HTMLImageElement

--------------------
type HTMLImageElement =
interface
inherit HTMLElement
abstract member align : string
abstract member alt : string
abstract member border : string
abstract member complete : bool
abstract member crossOrigin : string
abstract member currentSrc : string
abstract member height : float
abstract member hspace : float
abstract member isMap : bool
...
end

Full name: Fable.Import.Browser.HTMLImageElement
property HTMLElement.style: CSSStyleDeclaration
property CSSStyleDeclaration.left: string
property CSSStyleDeclaration.top: string
property HTMLElement.offsetTop: float
val dimensions : unit -> float * float

Full name: Mario.Win.dimensions
property HTMLCanvasElement.width: float
property HTMLCanvasElement.height: float
val image : src:string -> HTMLImageElement

Full name: Mario.Win.image

Get the first <img /> element and set `src` (do
nothing if it is the right one to keep animation)
val src : string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
val image : HTMLImageElement
abstract member Document.getElementsByTagName_img : unit -> NodeListOf<HTMLImageElement>
property HTMLImageElement.src: string
System.String.IndexOf(value: string) : int
System.String.IndexOf(value: char) : int
System.String.IndexOf(value: string, comparisonType: System.StringComparison) : int
System.String.IndexOf(value: string, startIndex: int) : int
System.String.IndexOf(value: char, startIndex: int) : int
System.String.IndexOf(value: char, startIndex: int, count: int) : int
System.String.IndexOf(value: string, startIndex: int, comparisonType: System.StringComparison) : int
System.String.IndexOf(value: string, startIndex: int, count: int) : int
System.String.IndexOf(value: string, startIndex: int, count: int, comparisonType: System.StringComparison) : int
type Mario =
{x: float;
y: float;
vx: float;
vy: float;
dir: string;}

Full name: Mario.Mario
Mario.x: float
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = System.Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
Mario.y: float
Mario.vx: float
Mario.vy: float
Mario.dir: string
val jump : 'a * y:int -> m:Mario -> Mario

Full name: Mario.jump
val y : int
val m : Mario
val gravity : m:Mario -> Mario

Full name: Mario.gravity
val physics : m:Mario -> Mario

Full name: Mario.physics
val walk : x:int * 'a -> m:Mario -> Mario

Full name: Mario.walk
val dir : string
val step : int * int -> mario:Mario -> Mario

Full name: Mario.step
val dir : int * int
val mario : Mario
val render : w:float * h:float -> mario:Mario -> unit

Full name: Mario.render

Render mario on canvas
val w : float
val h : float
module Win

from Mario
val verb : string
module Keyboard

from Mario
val w : float

Full name: Mario.w
val h : float

Full name: Mario.h
val update : mario:Mario -> unit -> unit

Full name: Mario.update
val window : Window

Full name: Fable.Import.Browser.window
abstract member WindowTimers.setTimeout : handler:obj * ?timeout:obj * [<System.ParamArray>] args:obj [] -> float
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
val mario : Mario

Full name: Mario.mario