# 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 = "
" + sprintf "" x y + sprintf "
" col + "
" let makeBoard (board: int list list) = "" + String.concat "" [ for y in [board.[0].Length - 1 .. -1 .. 0] do yield "" + ([0..(board.Length - 1)] |> List.map (fun x -> renderCell x y board.[x].[y]) |> String.concat "") + "" ] + "" 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("sg-board") let scoreElement = getById ("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(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("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("new-game") let selectGame = getById("sg-select-game") let selectWidth = getById("sg-select-w") let selectHeight = getById("sg-select-h") let selectColors = getById("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

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

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