SameGame

The famous tile-matching puzzle

This demo shows a Fable implementation of SameGame. The functional implementation of the game follows the type-first design approach and consists of three main components: types, game logic and front end. This sample has been contributed by Leif Battermann. You can find the full source code on GitHub.

width

height

colors

select game

0 point(s).

Rules

SameGame is a single player game. It is played on a two-dimensional board filled with stones of different colors.

The player may remove groups of stones from the board.

A group of stones is defined by two or more orthogonally connected identical-colored stones.

After a group is removed, all the stones above will fall down.

If a column is cleared completely, the columns to the right will slide to the left to close the gap.

The game ends when the board is either empty or the remaining stones cannot be removed.

Scoring

Removing a group of n stones will result in a score of (n-2)2 points.

If all stones are removed from the board, the player receives a bonus of 1000 points.

If the game ends without clearing the board, the player will receive a penalty. The penalty is computed according to (n-2)2 where n is the number of stones left on the board.

Type-first design

The module SameGameTypes contains the definitions of all the types needed for the game. There is no implementation yet.

It is the goal to enforce the rules of the game through the types and make invalid state unrepresentable as much as possible.

 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: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
module SameGameTypes =

    type Position = {
        Col:int
        Row:int } with

        member this.Left = { this with Col = this.Col  - 1 }
        member this.Right = { this with Col = this.Col  + 1 }
        member this.Up = { this with Row = this.Row  + 1 }
        member this.Down = { this with Row = this.Row  - 1 }

    type Color = Color of int

    type CellState =
        | Stone of Color
        | Empty

    type Column = CellState list

    type Board = Column list

    type Cell = {
        Position:Position
        State:CellState }

    type Group = {
        Color:Color
        Positions: Position list } 

    type Game = 
        | InProgress of GameState
        | Finished of GameState

    and GameState = {
        Board:Board
        Score:int }

    /// This is usually a function that produces (pseudo) random colors.
    /// It can also be used to create a specific initial board position.
    type StoneGenerator = unit-> CellState

    type GameConfig = {
        NumberOfColumns:int
        NumberOfRows:int
        StoneGenerator:StoneGenerator }

    type SameGameApi = {
        NewGame: GameConfig -> Game option
        Play: Game -> Position -> Game }

Implementation of the game logic

The module SameGameDomain contains the actual implementation of the game logic.

It exposes a public property api of type SameGameApi that provides an API for player interactions.

Note that the implementation of the game logic doesn't contain any front end code.

  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: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
107: 
108: 
109: 
110: 
111: 
112: 
113: 
114: 
115: 
116: 
117: 
118: 
119: 
120: 
121: 
122: 
123: 
124: 
125: 
126: 
127: 
128: 
129: 
130: 
131: 
132: 
133: 
134: 
135: 
136: 
137: 
138: 
139: 
module SameGameDomain =

    open System
    open SameGameTypes

    let private square x = x * x

    let private bonus = 1000

    let private calcScore groupSize =
        square (groupSize - 2)

    let private penalty stonesLeft =
        -(square (stonesLeft - 2))

    let private getCellState (board:Board) pos =
        let colCount = board |> List.length
        if pos.Col < colCount
            && pos.Col >= 0
            && pos.Row < board.[pos.Col].Length
            && pos.Row >= 0
        then board.[pos.Col].[pos.Row]
        else Empty

    let private findAdjacentWithSameColor board col (pos:Position) =
        [pos.Up; pos.Right; pos.Down; pos.Left]
        |> List.map (fun p ->  getCellState board p, p)
        |> List.filter (fun cell -> fst cell = Stone col)
        |> List.map snd

    let private hasValidMoves board = 
        board
        |> Seq.mapi (fun i col -> 
            col 
            |> Seq.mapi (fun j cell ->
                { Position = { Col = i; Row = j }; State = cell}))
        |> Seq.exists (fun col -> 
            col 
            |> Seq.exists (fun cell -> 
                match cell.State with 
                | Stone c ->
                    cell.Position
                    |> findAdjacentWithSameColor board c
                    |> (not << List.isEmpty) 
                | _ -> false))

    let private numberOfStones board =
        let numOfStonesInCol =
            List.sumBy (function Stone c -> 1 | Empty -> 0)
        board |> List.map numOfStonesInCol |> List.sum

    let private isEmpty (board:Board) =
        board |> List.forall (List.head >> ((=) Empty))

    let private evaluateGameState gameState =
        if gameState.Board |> hasValidMoves
        then InProgress gameState 
        elif gameState.Board |> isEmpty
        then Finished { gameState with Score = gameState.Score + bonus }
        else
            let score =
                gameState.Score
                + (gameState.Board |> numberOfStones |> penalty)
            Finished { gameState with Score = score }

    let private getGroup board position =
        let rec find (ps:Position list) col (group:Position list) =
            match ps with
            | [] -> group
            | x::xs -> 
                let cells =
                    x
                    |> findAdjacentWithSameColor board col
                    |> List.filter (fun pos ->
                        not (List.exists ((=) pos) (xs @ group) ))
                find (cells @ xs) col (x :: group)

        getCellState board position
        |> function 
            | Stone c -> 
                let positions = find [position] c []
                if positions |> List.length > 1
                then Some { Color = c; Positions = positions }
                else None
            | _ -> None

    let private removeGroup group board =
        board
        |> List.mapi (fun i col -> 
            col 
            |> List.mapi (fun j cell ->
                { Position = { Col = i; Row = j }; State = cell}) 
            |> List.filter (fun cell ->
                group.Positions
                |> (not << List.exists ((=) cell.Position)))
            |> List.map (fun cell -> cell.State)
            |> fun col' ->
                col' @ List.replicate (col.Length - col'.Length) Empty)
        |> List.filter (List.head >> ((<>) Empty))
        |> fun cols -> cols @ List.replicate
                        (board.Length - cols.Length)
                        (List.replicate (board.[0].Length) Empty)

    let private play gameState pos = 
        getGroup gameState.Board pos
        |> function 
            | Some g -> 
                let newBoard = gameState.Board |> removeGroup g
                { Board = newBoard
                  Score = gameState.Score + calcScore g.Positions.Length }
            | _ -> gameState

    let private playIfRunning game pos =
        match game with
        | InProgress gameState ->
            play gameState pos |> evaluateGameState
        | _ -> game

    let private isValid conf =
        if conf.NumberOfColumns < 3 || conf.NumberOfColumns > 15
        then false
        elif conf.NumberOfRows < 3 || conf.NumberOfRows > 15
        then false
        else true

    let private newGame config = 
        let createBoard config =
            List.init config.NumberOfColumns (fun _ ->
                List.init config.NumberOfRows (fun _ ->
                    config.StoneGenerator()))
            |> fun board -> { Board = board; Score = 0 }
            |> evaluateGameState |> Some
        if config |> isValid
        then createBoard config
        else None

    let api = {
        NewGame = newGame
        Play = playIfRunning }

Front end with Fable

The UI implementation is based on HTML and style sheets. The SameGame board e.g. is rendered as an HTML table.

This is the function that renders a board to an HTML string:

 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: 
open Fable.Core 
open Fable.Import.Browser
open SameGameTypes

let api = SameGameDomain.api

// val renderBoardToHtmlString : board:Board -> string
let renderBoardToHtmlString (board:Board) =
    let renderCell x y col =
        "<td class='sg-td'>"
        + sprintf "<a href='javaScript:void(0);' id='cell-%d-%d'>" x y 
        + sprintf "<div class='sg-cell sg-color%d'>" col
        + "</div></a></td>"

    let makeBoard (board: int list list) = 
        "<table class='sg-table horiz-centered'>"
        + String.concat "" [
            for y in [board.[0].Length - 1 .. -1 .. 0] do
                yield "<tr class='sg-tr'>"
                    + ([0..(board.Length - 1)]
                        |> List.map (fun x -> renderCell x y board.[x].[y])
                        |> String.concat "")
                    + "</tr>"
            ]
        + "</table>"

    makeBoard (board |> List.map (fun col ->
        col |> List.map (function Stone (Color c) -> c | Empty -> 0)))

The function updateUi is responsible for displaying the game and integrating the user interactions. These are the steps for updating the UI:

  1. The HTML elements for displaying the board and the score are obtained.
  2. A nested function addListeners for adding listeners for click events for all table cells is defined. The handlers will play a move and then recursively call updateUi again to update the UI with the new game state.
  3. A pattern match of the game state is performed. Depending on the state, the board will be updated, event listeners will be added, and the score will be updated.
 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: 
30: 
31: 
32: 
33: 
34: 
35: 
let getById<'T when 'T :> HTMLElement> id =
    document.getElementById(id) :?> 'T

// val updateUi : game:Game option -> unit
let rec updateUi game =
    let boardElement = getById<HTMLDivElement>("sg-board")
    let scoreElement = getById<HTMLDivElement> ("sg-score")

    let play game (x,y) =
        game
        |> Core.Option.map (fun g ->
            api.Play g { Col = x; Row = y })
        |> updateUi

    let addListeners maxColIndex maxRowIndex  =
        [0..maxColIndex] |> List.iter (fun x ->
            [0..maxRowIndex] |> List.iter (fun y -> 
                let cellId = sprintf "cell-%d-%d" x y
                let el = getById<HTMLButtonElement>(cellId)
                el.addEventListener_click(fun _ ->
                    play game (x,y); null)))
    
    match game with
    | Some (InProgress gs) -> 
        let board = renderBoardToHtmlString gs.Board
        boardElement.innerHTML <- board
        addListeners (gs.Board.Length - 1) (gs.Board.[0].Length - 1)
        scoreElement.innerText <- sprintf "%i point(s)." gs.Score
    | Some (Finished gs) -> 
        let board = renderBoardToHtmlString gs.Board
        boardElement.innerHTML <- board
        scoreElement.innerText <- "No more moves. " +
            sprintf "Your final score is %i point(s)." gs.Score
    | _ -> boardElement.innerText <-
            "Sorry, an error occurred while rendering the board."

The configuration of the board is obtained by parsing the class attribute of the div element that contains the board. E.g. <div id="sg-board" class="15-15-5"></div> will be parsed to a 15 x 15 board with 5 different colors.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
let rndColorGtor i = 
    let rnd = new System.Random()
    fun () -> rnd.Next(i) + 1 |> Color |> Stone 

let defaultConfig =  
    (getById<HTMLDivElement>("sg-board")).className
    |> fun className -> className.Split('-') 
    |> Array.map int
    |> fun arr ->
        { NumberOfColumns = arr.[0]
          NumberOfRows = arr.[1]
          StoneGenerator = rndColorGtor arr.[2] }

The handlers for starting a new game and for selecting a game from a list of preset initial game positions are defined and added.

 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: 
30: 
31: 
32: 
let buttonNewGame = getById<HTMLButtonElement>("new-game") 
let selectGame = getById<HTMLSelectElement>("sg-select-game") 
let selectWidth = getById<HTMLSelectElement>("sg-select-w") 
let selectHeight = getById<HTMLSelectElement>("sg-select-h") 
let selectColors = getById<HTMLSelectElement>("sg-select-col") 

let config() =
    { NumberOfColumns = int selectWidth.value
      NumberOfRows = int selectHeight.value
      StoneGenerator = int selectColors.value |> rndColorGtor }

let newGameOnClick() =
    let game = config() |> api.NewGame
    selectGame.selectedIndex <- 0.0 
    updateUi game
    
let selectGameOnChange () =
    let presetGtor gameNum =
        let mutable index = 0;
        let game = PresetGames.games.[gameNum]
        fun () ->
            index <- index + 1
            game.[index-1] |> Color |> Stone

    let gameIndex = int selectGame.value
    if gameIndex >= 0 then
        { config() with StoneGenerator = presetGtor gameIndex }
        |> api.NewGame
        |> updateUi

selectGame.addEventListener_change(fun _ -> selectGameOnChange(); null)
buttonNewGame.addEventListener_click(fun _ -> newGameOnClick(); null)

Finally the game is initialized with:

1: 
api.NewGame defaultConfig |> updateUi 
val games : int list list

Full name: Samegame.PresetGames.games
type Position =
  {Col: int;
   Row: int;}
  member Down : Position
  member Left : Position
  member Right : Position
  member Up : Position

Full name: Samegame.SameGameTypes.Position
Position.Col: 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<_>
Position.Row: int
val this : Position
member Position.Left : Position

Full name: Samegame.SameGameTypes.Position.Left
member Position.Right : Position

Full name: Samegame.SameGameTypes.Position.Right
member Position.Up : Position

Full name: Samegame.SameGameTypes.Position.Up
member Position.Down : Position

Full name: Samegame.SameGameTypes.Position.Down
Multiple items
union case Color.Color: int -> Color

--------------------
type Color = | Color of int

Full name: Samegame.SameGameTypes.Color
type CellState =
  | Stone of Color
  | Empty

Full name: Samegame.SameGameTypes.CellState
union case CellState.Stone: Color -> CellState
union case CellState.Empty: CellState
type Column = CellState list

Full name: Samegame.SameGameTypes.Column
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
type Board = Column list

Full name: Samegame.SameGameTypes.Board
type Cell =
  {Position: Position;
   State: CellState;}

Full name: Samegame.SameGameTypes.Cell
Multiple items
Cell.Position: Position

--------------------
type Position =
  {Col: int;
   Row: int;}
  member Down : Position
  member Left : Position
  member Right : Position
  member Up : Position

Full name: Samegame.SameGameTypes.Position
Cell.State: CellState
type Group =
  {Color: Color;
   Positions: Position list;}

Full name: Samegame.SameGameTypes.Group
Multiple items
Group.Color: Color

--------------------
type Color = | Color of int

Full name: Samegame.SameGameTypes.Color
Group.Positions: Position list
type Game =
  | InProgress of GameState
  | Finished of GameState

Full name: Samegame.SameGameTypes.Game
union case Game.InProgress: GameState -> Game
type GameState =
  {Board: Board;
   Score: int;}

Full name: Samegame.SameGameTypes.GameState
union case Game.Finished: GameState -> Game
Multiple items
GameState.Board: Board

--------------------
type Board = Column list

Full name: Samegame.SameGameTypes.Board
GameState.Score: int
type StoneGenerator = unit -> CellState

Full name: Samegame.SameGameTypes.StoneGenerator


 This is usually a function that produces (pseudo) random colors.
 It can also be used to create a specific initial board position.
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
type GameConfig =
  {NumberOfColumns: int;
   NumberOfRows: int;
   StoneGenerator: StoneGenerator;}

Full name: Samegame.SameGameTypes.GameConfig
GameConfig.NumberOfColumns: int
GameConfig.NumberOfRows: int
Multiple items
GameConfig.StoneGenerator: StoneGenerator

--------------------
type StoneGenerator = unit -> CellState

Full name: Samegame.SameGameTypes.StoneGenerator


 This is usually a function that produces (pseudo) random colors.
 It can also be used to create a specific initial board position.
type SameGameApi =
  {NewGame: GameConfig -> Game option;
   Play: Game -> Position -> Game;}

Full name: Samegame.SameGameTypes.SameGameApi
SameGameApi.NewGame: GameConfig -> Game option
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>
SameGameApi.Play: Game -> Position -> Game
namespace System
module SameGameTypes

from Samegame
val private square : x:int -> int

Full name: Samegame.SameGameDomain.square
val x : int
val private bonus : int

Full name: Samegame.SameGameDomain.bonus
val private calcScore : groupSize:int -> int

Full name: Samegame.SameGameDomain.calcScore
val groupSize : int
val private penalty : stonesLeft:int -> int

Full name: Samegame.SameGameDomain.penalty
val stonesLeft : int
val private getCellState : board:Board -> pos:Position -> CellState

Full name: Samegame.SameGameDomain.getCellState
val board : Board
val pos : Position
val colCount : int
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val length : list:'T list -> int

Full name: Microsoft.FSharp.Collections.List.length
val private findAdjacentWithSameColor : board:Board -> col:Color -> pos:Position -> Position list

Full name: Samegame.SameGameDomain.findAdjacentWithSameColor
val col : Color
property Position.Up: Position
property Position.Right: Position
property Position.Down: Position
property Position.Left: Position
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
val p : Position
val filter : predicate:('T -> bool) -> list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.filter
val cell : CellState * Position
val fst : tuple:('T1 * 'T2) -> 'T1

Full name: Microsoft.FSharp.Core.Operators.fst
val snd : tuple:('T1 * 'T2) -> 'T2

Full name: Microsoft.FSharp.Core.Operators.snd
val private hasValidMoves : board:Board -> bool

Full name: Samegame.SameGameDomain.hasValidMoves
module Seq

from Microsoft.FSharp.Collections
val mapi : mapping:(int -> 'T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.mapi
val i : int
val col : Column
val j : int
val cell : CellState
val exists : predicate:('T -> bool) -> source:seq<'T> -> bool

Full name: Microsoft.FSharp.Collections.Seq.exists
val col : seq<Cell>
val cell : Cell
val c : Color
Cell.Position: Position
val not : value:bool -> bool

Full name: Microsoft.FSharp.Core.Operators.not
val isEmpty : list:'T list -> bool

Full name: Microsoft.FSharp.Collections.List.isEmpty
val private numberOfStones : board:CellState list list -> int

Full name: Samegame.SameGameDomain.numberOfStones
val board : CellState list list
val numOfStonesInCol : (CellState list -> int)
val sumBy : projection:('T -> 'U) -> list:'T list -> 'U (requires member ( + ) and member get_Zero)

Full name: Microsoft.FSharp.Collections.List.sumBy
val sum : list:'T list -> 'T (requires member ( + ) and member get_Zero)

Full name: Microsoft.FSharp.Collections.List.sum
val private isEmpty : board:Board -> bool

Full name: Samegame.SameGameDomain.isEmpty
val forall : predicate:('T -> bool) -> list:'T list -> bool

Full name: Microsoft.FSharp.Collections.List.forall
val head : list:'T list -> 'T

Full name: Microsoft.FSharp.Collections.List.head
val private evaluateGameState : gameState:GameState -> Game

Full name: Samegame.SameGameDomain.evaluateGameState
val gameState : GameState
GameState.Board: Board
val score : int
val private getGroup : board:Board -> position:Position -> Group option

Full name: Samegame.SameGameDomain.getGroup
val position : Position
val find : (Position list -> Color -> Position list -> Position list)
val ps : Position list
val group : Position list
val x : Position
val xs : Position list
val cells : Position list
val exists : predicate:('T -> bool) -> list:'T list -> bool

Full name: Microsoft.FSharp.Collections.List.exists
val positions : Position list
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
val private removeGroup : group:Group -> board:CellState list list -> CellState list list

Full name: Samegame.SameGameDomain.removeGroup
val group : Group
val mapi : mapping:(int -> 'T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.mapi
val col : CellState list
val col' : CellState list
val replicate : count:int -> initial:'T -> 'T list

Full name: Microsoft.FSharp.Collections.List.replicate
property List.Length: int
val cols : CellState list list
val private play : gameState:GameState -> pos:Position -> GameState

Full name: Samegame.SameGameDomain.play
val g : Group
val newBoard : CellState list list
val private playIfRunning : game:Game -> pos:Position -> Game

Full name: Samegame.SameGameDomain.playIfRunning
val game : Game
val private isValid : conf:GameConfig -> bool

Full name: Samegame.SameGameDomain.isValid
val conf : GameConfig
val private newGame : config:GameConfig -> Game option

Full name: Samegame.SameGameDomain.newGame
val config : GameConfig
val createBoard : (GameConfig -> Game option)
val init : length:int -> initializer:(int -> 'T) -> 'T list

Full name: Microsoft.FSharp.Collections.List.init
GameConfig.StoneGenerator: StoneGenerator
val api : SameGameApi

Full name: Samegame.SameGameDomain.api
namespace Fable
namespace Fable.Core
namespace Fable.Import
module Browser

from Fable.Import
val api : SameGameApi

Full name: Samegame.api
module SameGameDomain

from Samegame
val renderBoardToHtmlString : board:Board -> string

Full name: Samegame.renderBoardToHtmlString
val renderCell : (int -> int -> int -> string)
val y : int
val col : int
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val makeBoard : (int list list -> string)
val board : int list list
module String

from Microsoft.FSharp.Core
val concat : sep:string -> strings:seq<string> -> string

Full name: Microsoft.FSharp.Core.String.concat
val c : int
val getById : id:string -> #HTMLElement

Full name: Samegame.getById
Multiple items
val HTMLElement : HTMLElementType

Full name: Fable.Import.Browser.HTMLElement

--------------------
type HTMLElement =
  interface
    inherit Element
    abstract member addEventListener : type:string * listener:EventListenerOrEventListenerObject * ?useCapture:bool -> unit
    abstract member addEventListener_MSContentZoom : listener:Func<UIEvent,obj> * ?useCapture:bool -> unit
    abstract member addEventListener_MSGestureChange : listener:Func<MSGestureEvent,obj> * ?useCapture:bool -> unit
    abstract member addEventListener_MSGestureDoubleTap : listener:Func<MSGestureEvent,obj> * ?useCapture:bool -> unit
    abstract member addEventListener_MSGestureEnd : listener:Func<MSGestureEvent,obj> * ?useCapture:bool -> unit
    abstract member addEventListener_MSGestureHold : listener:Func<MSGestureEvent,obj> * ?useCapture:bool -> unit
    abstract member addEventListener_MSGestureStart : listener:Func<MSGestureEvent,obj> * ?useCapture:bool -> unit
    abstract member addEventListener_MSGestureTap : listener:Func<MSGestureEvent,obj> * ?useCapture:bool -> unit
    abstract member addEventListener_MSGotPointerCapture : listener:Func<MSPointerEvent,obj> * ?useCapture:bool -> unit
    ...
  end

Full name: Fable.Import.Browser.HTMLElement
val id : string
val document : Document

Full name: Fable.Import.Browser.document
abstract member Document.getElementById : elementId:string -> HTMLElement
val updateUi : game:Game option -> unit

Full name: Samegame.updateUi
val game : Game option
val boardElement : HTMLDivElement
Multiple items
val HTMLDivElement : HTMLDivElementType

Full name: Fable.Import.Browser.HTMLDivElement

--------------------
type HTMLDivElement =
  interface
    inherit HTMLElement
    abstract member align : string
    abstract member noWrap : bool
    abstract member align : string with set
    abstract member noWrap : bool with set
  end

Full name: Fable.Import.Browser.HTMLDivElement
val scoreElement : HTMLDivElement
val play : (Game option -> int * int -> unit)
namespace Microsoft.FSharp.Core
module Option

from Microsoft.FSharp.Core
val map : mapping:('T -> 'U) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.map
val g : Game
val addListeners : (int -> int -> unit)
val maxColIndex : int
val maxRowIndex : int
val iter : action:('T -> unit) -> list:'T list -> unit

Full name: Microsoft.FSharp.Collections.List.iter
val cellId : string
val el : HTMLButtonElement
Multiple items
val HTMLButtonElement : HTMLButtonElementType

Full name: Fable.Import.Browser.HTMLButtonElement

--------------------
type HTMLButtonElement =
  interface
    inherit HTMLElement
    abstract member checkValidity : unit -> bool
    abstract member createTextRange : unit -> TextRange
    abstract member autofocus : bool
    abstract member disabled : bool
    abstract member form : HTMLFormElement
    abstract member formAction : string
    abstract member formEnctype : string
    abstract member formMethod : string
    abstract member formNoValidate : string
    ...
  end

Full name: Fable.Import.Browser.HTMLButtonElement
abstract member HTMLElement.addEventListener_click : listener:System.Func<MouseEvent,obj> * ?useCapture:bool -> unit
val gs : GameState
val board : string
property HTMLElement.innerHTML: string
property HTMLElement.innerText: string
val rndColorGtor : i:int -> (unit -> CellState)

Full name: Samegame.rndColorGtor
val rnd : System.Random
Multiple items
type Random =
  new : unit -> Random + 1 overload
  member Next : unit -> int + 2 overloads
  member NextBytes : buffer:byte[] -> unit
  member NextDouble : unit -> float

Full name: System.Random

--------------------
System.Random() : unit
System.Random(Seed: int) : unit
System.Random.Next() : int
System.Random.Next(maxValue: int) : int
System.Random.Next(minValue: int, maxValue: int) : int
val defaultConfig : GameConfig

Full name: Samegame.defaultConfig
val className : string
System.String.Split([<System.ParamArray>] separator: char []) : string []
System.String.Split(separator: string [], options: System.StringSplitOptions) : string []
System.String.Split(separator: char [], options: System.StringSplitOptions) : string []
System.String.Split(separator: char [], count: int) : string []
System.String.Split(separator: string [], count: int, options: System.StringSplitOptions) : string []
System.String.Split(separator: char [], count: int, options: System.StringSplitOptions) : string []
module Array

from Microsoft.FSharp.Collections
val map : mapping:('T -> 'U) -> array:'T [] -> 'U []

Full name: Microsoft.FSharp.Collections.Array.map
val arr : int []
val buttonNewGame : HTMLButtonElement

Full name: Samegame.buttonNewGame
val selectGame : HTMLSelectElement

Full name: Samegame.selectGame
Multiple items
val HTMLSelectElement : HTMLSelectElementType

Full name: Fable.Import.Browser.HTMLSelectElement

--------------------
type HTMLSelectElement =
  interface
    inherit HTMLElement
    abstract member add : element:HTMLElement * ?before:U2<HTMLElement,float> -> unit
    abstract member checkValidity : unit -> bool
    abstract member Item : name:string -> obj with get
    abstract member autofocus : bool
    abstract member disabled : bool
    abstract member form : HTMLFormElement
    abstract member length : float
    abstract member multiple : bool
    abstract member name : string
    ...
  end

Full name: Fable.Import.Browser.HTMLSelectElement
val selectWidth : HTMLSelectElement

Full name: Samegame.selectWidth
val selectHeight : HTMLSelectElement

Full name: Samegame.selectHeight
val selectColors : HTMLSelectElement

Full name: Samegame.selectColors
val config : unit -> GameConfig

Full name: Samegame.config
property HTMLSelectElement.value: string
val newGameOnClick : unit -> unit

Full name: Samegame.newGameOnClick
property HTMLSelectElement.selectedIndex: float
val selectGameOnChange : unit -> unit

Full name: Samegame.selectGameOnChange
val presetGtor : (int -> unit -> CellState)
val gameNum : int
val mutable index : int
val game : int list
module PresetGames

from Samegame
val gameIndex : int
abstract member HTMLElement.addEventListener_change : listener:System.Func<Event,obj> * ?useCapture:bool -> unit
Fork me on GitHub