Category Archives: Fable

Two Tetromino Tetris with Fable and F#

In my last post, I talked about my early adventures Fable and building a memory tiles game.

Recently I have been exploring Fable further and, in particular, using it to interact with the Javascript canvas API. I’ve been wanting to try out building a version of Tetris for a while now. I used to love playing it on the Gameboy many years ago. Fable afforded me the perfect tool to build this game in the beautiful language of F#.

There is a video snippet above of my implemented game in action. This blog won’t go into every detail of the code as it is available here but I wanted to go through a few code snippets here that may help other people looking to build a game involving game animation frames and using the canvas API with Fable and F#.

What’s a tetromino and why two tetrominoes and not the full thing?

A tetromino is the name given to each of the shapes in the Tetris game. Each tetromino also has a number or rotations that it can go through. There are more details about Tetris tetrominoes here.

I think of this version as a version 1. I wanted to get as much of a fully featured game implemented as I could in a pretty short time frame – while I had some summer vacation time. I hope to return to this at some point and add more shapes and also improve the rotation algorithm – unlike the original game, my rotation algorithm simply prevents rotations when blocks are in the way although it does handle wall kicks. I chose the I-Block along with its rotations and the T-Block along with its rotations as the two tetrominoes that I would use in this initial Tetris implementation.

The domain modelling

I modeled the gameboard as a map of row indexes to rows with each row itself being a map of the block left X position indexes to blocks. Blocks are the primitives that tetrominoes are built from. I wanted to capture the states that a gameboard can be in using the F# type system. A gameboard that is in a resting state has no moving tetromino and a gameboard in motion has a moving tetromino. The main game engine processes these gameboard states so I made them distinct using a Discriminated Union – a DU.

60   type LeftBlockPosition = float 
61    
.......
65   and RowData = Map<LeftBlockPosition,Block> 
66    
67   type RowBottomPosition = float 
68    
69   type GameboardInMotion = { 
70       Height : float 
71       Width : float 
72       BlockSize : float 
73       MovingTetromino : Tetromino 
74       Rows : Map<RowBottomPosition, RowData> 
75   } 
76    
77   type RestingGameboard = { 
78       Height : float 
79       Width : float 
80       BlockSize : float 
81       PlacedTetromino : Tetromino 
82       Rows : Map<RowBottomPosition, RowData> 
83   } 
84    
85   type Gameboard =  
86   | GameboardInMotion of GameboardInMotion 
87   | RestingGameboard of RestingGameboard 
88    

A block is modeled as a an F# record.

3    type Block = { 
4        BottomX : float 
5        BottomY : float 
6        Color : string 
7    } 

The tetrominoes are modelled as a DU also with each case representing a tetromino rotated in a particular direction. Each tetromino also has tetromino detail about the rows of blocks that it comprises of.

19   type TetrominoRow = { Blocks : Block list } 
...........
36   type TetrominoDetail = { TetrominoRows : TetrominoRow list } 
37    
38   type Tetromino =  
39   | StraightUp of TetrominoDetail 
40   | StraightRight of TetrominoDetail 
41   | StraightDown of TetrominoDetail 
42   | StraightLeft of TetrominoDetail 
43   | TShapeUp of TetrominoDetail 
44   | TShapeRight of TetrominoDetail 
45   | TShapeDown of TetrominoDetail 
46   | TShapeLeft of TetrominoDetail 

Valid key presses are also modeled using a DU and using designing with capabilities to prevent the creation of an invalid key press. I learned about designing with capabilities from Scott Wlaschin – Designing with Capabilities and from reading Scott’s new book:
Domain Modeling Made Functional

89   type KeyCode = float 
90    
91   type GameControl =  
92       | Up 
93       | Left 
94       | Right 
95    
96   type ValidKeyPress = private ValidKeyPress of GameControl * KeyCode  
97    
98   let (|ValidKeyPress|) validKeyPress =  
99       match validKeyPress with  
100      | ValidKeyPress(control,keyCode) -> ValidKeyPress (control, keyCode) 
101   
102  module ValidKeyPress =  
103       
104      let toValidKeyPress keyCode = 
105          match keyCode with 
106          | 37. -> Some <| ValidKeyPress (Left, 37.) 
107          | 38. -> Some <| ValidKeyPress (Up, 38.) 
108          | 39. -> Some <| ValidKeyPress (Right, 39.) 
109          | _ -> None

Working with the canvas API

The Fable.Import namespace provides a thin wrapper over the Javascript DOM API.

I get a reference to my actual canvas div with the following:

9    let tetrisView = Browser.document.getElementById("tetris-view") :?> Browser.HTMLCanvasElement 
10   let ctx = tetrisView.getContext_2d() 

To render rows of blocks, I used fillRect as follows:

27       let renderRow blockSize (blocks:Map<LeftBlockPosition,Block>) =  
28           blocks |> Map.toSeq |> Seq.map snd |> Seq.iter (fun block ->  
29               ctx.fillStyle <- U3.Case1 block.Color 
30               ctx.fillRect(block.BottomX, block.BottomY - blockSize, blockSize, blockSize)) 

To clear the canvas area before each re-render of the gameboard, I used clearRect:

 ctx.clearRect(0., 0., tetrisView.width, tetrisView.height) 

Getting the game in motion!

Using .Net events and also the Javascript interval API (wrapped nicely by Fable), I was able to set up a repeating game frame clock. So, each time the time interval passes, an event is triggered causing a new gameboard to be evaluated from the current gameboard and from any user game control input and this new game board is then rendered.

46   let frameChangeEvent =  new Event<Gameboard>() 
47    
48   let mutable private frameClockId = 0. 
49    
50   let startFrameClock() =  
51       frameClockId <- Browser.window.setInterval((fun() ->  
52               match lastRenderedGameBoard with  
53               | GameboardInMotion _ 
54               | RestingGameboard _ -> frameChangeEvent.Trigger lastRenderedGameBoard 
55               )  
56           , 150.) 

I handle user input in a separate UserGameController module using the wrapper that Fable gives over DOM event handling:

1    module Tetris.UserGameController 
2     
3    open Fable.Core 
4    open Fable.Import 
5    open Tetris.Definitions 
6     
7    Node.require.Invoke("core-js") |> ignore 
8     
9    let private keyPressed= ref (None:ValidKeyPress option) 
10    
11   let private handleKeyDown keyCode =  
12       match ValidKeyPress.toValidKeyPress keyCode with 
13       | Some (ValidKeyPress _ as vkp) -> 
14           keyPressed := Some vkp 
15       | None -> () 
16    
17   let private handleKeyUp keyCode =  
18       match !keyPressed with 
19       | Some (ValidKeyPress(_, currentlyPressed)) when keyCode = currentlyPressed ->  
20           keyPressed := None 
21       | _ -> () 
22     
23     
24   Browser.window.addEventListener_keydown (fun e -> handleKeyDown e.keyCode :> obj) 
25   Browser.window.addEventListener_keyup (fun e -> handleKeyUp e.keyCode :> obj) 
26    
27   let getKeyPressed() = !keyPressed 
28           

The game engine

The game engine is essentially a state machine. From the gameboard it is given and the current user input (if any), it works out what the next gameboard should be. There is a fair bit of logic in there around things like:
– whether a rotation is allowed,
– what the next rotation is for the current tetromino,
– whether the tetromino can move left or right,
– whether the tetromino should rest on blocks below
etc.

With the domain modeled using the F# type system and immutable DUs and records, these transitions became simpler to reason about. As an example, after it is determined if a rotation is possible (if the up arrow is currently pressed), the engine works out which way the tetromino can move. I created what I called a TransitionReferee for this. This is somewhat verbose but I wanted the logic and rules to be self documenting as much as possible. The snippet from the TransitionReferee is below to give an example of what I mean.

21   module TransitionReferee =  
22    
23       type RefereeDecision =  
24           | MoveHorizontallyAndVertically of GameboardInMotion 
25           | MoveVerticallyOnly of GameboardInMotion 
26           | MoveVerticallyOnlyAndRestOnBottom of GameboardInMotion 
27           | MoveVerticallyOnlyAndRestOnBlockBelow of GameboardInMotion 
28           | MoveAndRestOnBlockBelow of GameboardInMotion 
29           | MoveAndRestOnBottom of GameboardInMotion 
30           | CheckForCompletedRowsAndReleaseAnotherBlock of RestingGameboard 
31            
32       let blocksOverlapHorizontally block1XPos block2XPos blockSize = 
33           block1XPos > block2XPos - blockSize && block1XPos < block2XPos + blockSize 
34        
35       let tetrominoRowOverlapsWithExistingBlocks (horizontalTransitionDirection:HorizontalTransitionDirection) blockSize (tetrominoRow:TetrominoRow) row =  
36            
37           tetrominoRow.Blocks 
38           |> List.exists (fun tetrominoBlock -> 
39               row  
40               |> Map.exists (fun existingX  _ ->  
41                   blocksOverlapHorizontally existingX (tetrominoBlock |> nextXPosition horizontalTransitionDirection) blockSize)) 
42    
43       let otherRowsInRangeContainingBlocksInTheWay direction (gameboard:GameboardInMotion)  =  
44           //compare gameboard rows when all tetromino blocks have been removed 
45           gameboard.MovingTetromino.TetrominoRows 
46           |> List.fold (fun gameboardRows tetrominoRow -> 
47                gameboardRows 
48                |> Map.tryFind tetrominoRow.BottomY  
49                |> Option.map (fun gameboardRow ->  
50                   tetrominoRow.Blocks 
51                   |> List.fold (fun row b -> row |> Map.remove b.BottomX) gameboardRow) 
52                |> function | Some gameboardRowWithTetrominoBlocksRemoved -> gameboardRows |> Map.add tetrominoRow.BottomY gameboardRowWithTetrominoBlocksRemoved | None -> gameboardRows 
53           ) gameboard.Rows 
54           |> fun gameboardRows -> 
55               gameboardRows 
56               |> Map.toSeq 
57               |> Seq.exists (fun (rowY, gameboardRow) -> 
58                   gameboard.MovingTetromino.TetrominoRows  
59                   |> Seq.exists (fun tetrominoRow ->  
60                       (rowY > ((tetrominoRow.TopY gameboard.BlockSize) + transitionDistance) &&  
61                        rowY < (tetrominoRow.BottomY + transitionDistance) + (tetrominoRow.Height gameboard.BlockSize) && 
62                        tetrominoRowOverlapsWithExistingBlocks direction gameboard.BlockSize tetrominoRow gameboardRow))) 
63    
64       let decideTransition (direction:HorizontalTransitionDirection) (gameboard:Gameboard) =  
65           match gameboard with 
66           | GameboardInMotion gameboard -> 
67               let otherRowsInRangeContainingBlocksInTheWay direction  = otherRowsInRangeContainingBlocksInTheWay direction gameboard                     
68                
69               let tetrominoShouldMoveToRestOnBlocksBelow direction (gameboard:GameboardInMotion) =  
70                   gameboard.MovingTetromino.TetrominoRows 
71                   |> Seq.exists (fun tetrominoRow -> 
72                       gameboard.Rows 
73                       |> Map.tryFind (tetrominoRow.BottomY + transitionDistance + gameboard.BlockSize)  
74                       |> Option.map (fun row ->  
75                           tetrominoRowOverlapsWithExistingBlocks direction gameboard.BlockSize tetrominoRow row)  
76                       |> function | Some b -> b | None -> false 
77                   ) 
78                    
79               if otherRowsInRangeContainingBlocksInTheWay direction then 
80                   if (gameboard.MovingTetromino.TetrominoRows.[0].BottomY + transitionDistance) = gameboard.Height then  
81                       MoveVerticallyOnlyAndRestOnBottom gameboard 
82                   elif tetrominoShouldMoveToRestOnBlocksBelow NoHorizontalTransition gameboard then 
83                           MoveVerticallyOnlyAndRestOnBlockBelow gameboard 
84                   else MoveVerticallyOnly gameboard 
85               else  
86                   if (gameboard.MovingTetromino.TetrominoRows.[0].BottomY + transitionDistance) = gameboard.Height then  
87                       MoveAndRestOnBottom gameboard 
88                   elif tetrominoShouldMoveToRestOnBlocksBelow direction gameboard then     
89                       MoveAndRestOnBlockBelow gameboard 
90                   else MoveHorizontallyAndVertically gameboard 
91                    
92           | RestingGameboard gameboard -> CheckForCompletedRowsAndReleaseAnotherBlock gameboard 

Conclusion

I hope the code snippets that I have shown here can be of some help to others who are exploring Fable. The full source code is available here:
Also, the current version of this Two Tetromino Tetris game can be played here.

Early Fable Adventures – Building A Memory Tiles Game

Getting aquianted with fable

Just about a month ago, I had the great pleasure of attending F# eXchange 2017 in London. It was 2 days of wonderful F# indulgence with fantastic talks and workshops. It was also a great experience meeting some of the people who have pretty much been virtual mentors to me on my F# journey like Kit Eason and Scott Wlaschin. I did Kit’s pluralsight F# courses and Scott’s fsharp for fun and profit site is a constant source of learning for me so to get chatting with these guys in person was really cool.

Among all of these great talks and workshops was a talk by Alfonso García-Caro, about his Fable compiler. This is an F# to javascript compiler that hooks in really well with the javascript ecosystem. I’m lucky that in my job I am coding in F# across the full stack currently using websharper for our F# to javascript needs. So Alfonso’s talk about Fable really caught my attention. I couldn’t wait to get a bit of free time to dive in and try it out.

Getting up and running with Fable was very quick and easy thanks to this extremely helpful blog post by Krzysztof Cieslak. I started by simply trying out simple dom event handling. Out of the box, Fable has an API that appears to be a light F# wrapper around the javascript dom API.
This is accessed simply with

open Fable.Import

From here, the dom is at your finger tips through the eyes of F# through the module Fable.Import.Browser.

A little game to get my teeth into Fable

After playing around with some dom operations through Fable, I wanted something a bit more substantial to get my teeth into. Recently I was playing this common memory game with my 3 year old daughter. Its a game where you have a grid of cards with pictures on them where every picture will appear on exactly 2 cards. You lay them out randomly upside down. Then you take turns turning 2 cards over at a time. If you get matching pictures, you keep the cards but if there is no match, you turn the cards upside down again. At the end, the player with the most mathed cards wins.

I thought this would be a great game to implement with Fable so I could get started properly with this wonderful F# to javascript compiler. I simplified the game for my first version and built just a one player game. You get a grid of blacked out tiles which have hidden colors where every color will exist twice on the grid. Then you click (or tap if on a mobile device) on 2 tiles to uncover their colors. If they match, the tiles stay open permently until you start a fresh game. If they are not a match, the tiles are blacked out again the next time you click a new tile. This first version was enough to get me building something with Fable. In future versions, I plan to make it a bit more interesting by adding time challenges and levels with bigger grids among other ideas.

Dom operations through Fable to get up and running

There are a few basic things that I wanted to do with the dom for my game interaction. The following were enough for the first version of the game.

Creating a dom element with attributes

let tileDiv = Browser.document.createElement("div")
             tileDiv.className <- "col-3"
             let bc = match tile.Status with
                      | UnMatched -> tile.CoverColor
                      | AttemptingMatch -> tile.HiddenColor
                      | Matched -> tile.HiddenColor
             tileDiv.style.backgroundColor <- bc
             tileDiv.style.border <- "white solid 4px"

In the above, the Browser module is coming from the earlier Fable.Import. From this module, there is a wealth of dom related functions closely matching the standard javascript document API. Here I’m creating a div, setting its class name and a couple other properties including its border and also its background color which is a result of a pattern match to check the status of the corresponding tile in the backing model.

Adding a click event

Adding events is straight forward too. I found if I got stuck because of not knowing the function or type to use from Fable.Import.Browser, I could just look up the corresponding javascript dom function/element on the Mozilla Dom API reference and the corresponding Fable module/type/function was named similarly.
For the memory tiles game, I needed a way to add a click event to a, “Start Fresh Game” button to refresh the grid with new randomized tiles. This is shown below:

let startNewGameButton = Browser.document.createElement("button")
      startNewGameButton.addEventListener_click(fun _ -> startNewGameCallback() :> obj)
      startNewGameButton.innerText <- "Start Fresh Game"
      gameContainer.appendChild startNewGameButton |> ignore

Here, I create the button to start a new game of randomized tiles. I then add a click event which can take a regular F# function as a parameter. Here I’m passing a lambda which, when executed, will call a function called startNewGameCallback. The block of code shown here exists inside a render function in my View module and this render function takes the startNewGameCallback function as a parameter. The full source code for this is on my github MemoryTiles.fsx

I use the same Fable addEventListener_click function for handling the click event on each tile as shown below.

let eventHandler r c d = Func<Browser.MouseEvent, obj>(fun _ -> tileClickCallback r c gameBoard :> obj)
        tileDiv.addEventListener_click(eventHandler rowIndex cIndex tileDiv)

This takes a lambda wrapping another callback which has been passed to the render function in my View module. This eventually calls back to the main driver of the game – the tileClick function in my Controller module, but first let me just show the data modelling from the Model module to put this tileClick function in context.

F# typing goodness with Fable

Fable supports the standard F# data modelling constructs bringing us the F# type safety that we love so much!
For the games’ backing model, I used standard F# records and disciminated unions to represent the immutable game board which serves as a point in time state to render a point in time view and also to enable the generation of a another game board depending on user input and the value of its tiles. A tile itself is modelled as a record and the status of a tile along with the the current selection of tiles are modelled as DUs. So even though this is a game running as javascript, I have still been able to use F# types and F# immutability to express the domain and behaviour of the game. This is shown below. I added a GameBoard helper module for the state transistions of updating tiles and the selection. There is some localized mutability within the updateTile function but the function is still pure with an immutable game board going in and another one coming out. The updateSelected is also returning a new immutable game board.

type GameBoard = {
    Selection : Selection
    Board : Tile list list
}
and Tile = {
    Row : int
    Col : int
    HiddenColor : string
    CoverColor : string
    Status : TileStatus
}
and TileStatus = UnMatched | AttemptingMatch | Matched
and Selection = NoneSelected | OneSelected of Tile | TwoSelected of Tile * Tile
 
module GameBoard = 
    let updateTile r c t gb = 
        let updatedRow = gb.Board.[r] |> List.toArray |> (fun arr -> arr.[c] <- t; arr) |> List.ofArray
        {
            gb with
                Board = gb.Board |> List.toArray |> (fun arr -> arr.[r] <- updatedRow; arr) |> List.ofArray
        }
 
    let updateSelected s gb = { gb with Selection = s}

Using standard .Net events for to trigger view rendering

.Net event handling is one of my favourite features of the .Net platform and, in particular, the way it is implemented in F#. Fable allows us to use standard .Net event triggering and handling. I designed the game using a version of the model, view, controller pattern. I wanted to keep my view decoupled from the model but I still needed a way for it to be rendered whenever the game board changed. I also wanted it to remain as decoupled as possible from the controller module. The controller module passes the main engine of the game, its tileClick function, as a function parameter to the view but the view doesn’t know anything about its implementation. This is similar for the callback to start a fresh game. In order for the view to be rendered and re-rendered, I set up a an event in the in the Model module as follows.

let modelChangeEvent = (new Event<GameBoard>())

In the Controller module, there is a an initialise function which starts everything off. It publishs an observation to the above event and adds a listener, which takes a game board as a parameter and which will call render on the View whenever the a modelChangeEvent is triggered.
A startGame() function, called by initialise, simply triggers a modelChangeEvent to trigger the initial rendering of the game.

let startGame() = modelChangeEvent.Trigger (Model.generateRandomModel 4)
 
let initialise() = 
    Model.modelChangeEvent.Publish.Add(fun gameBoard -> View.render tileClick startGame gameBoard)
    startGame()

Main game logic with F# pattern matching

The main game logic is in the tileClick function of the Controller module. It receives a game board and a row and column for the clicked tile and, based on these, it determines a new game board to be created and triggers the modelChangeEvent so that the View.render function is called to re-render with the new game state.For some cases, no event is triggered and the view is not re-rendered e.g. if the same tile is clicked multiple times in succession. Standard F# pattern matching can be used here and the full function is below.

let tileClick tileRow tileCol (gameBoard: GameBoard) = 
    let board = gameBoard.Board
    let tile = board.[tileRow].[tileCol] 
    let lastSelection = gameBoard.Selection
    if tile.Status = Matched then ()
    else
        match lastSelection with
        | TwoSelected(t1, t2) when t1.Status = Matched && t2.Status = Matched && tile <> t1 && tile <> t2->
            gameBoard
            |> GameBoard.updateTile tile.Row tile.Col { tile with Status = AttemptingMatch }
            |> GameBoard.updateSelected (OneSelected { tile with Status = AttemptingMatch })
            |> modelChangeEvent.Trigger
        | TwoSelected(t1, t2) when t1.Status = Matched && tile <> t1 && tile <> t2 -> 
            gameBoard
            |> GameBoard.updateTile tile.Row tile.Col { tile with Status = AttemptingMatch }
            |> GameBoard.updateTile t2.Row t2.Col { t2 with Status = UnMatched }
            |> GameBoard.updateSelected (OneSelected { tile with Status = AttemptingMatch })
            |> modelChangeEvent.Trigger
        | TwoSelected(t1, t2) when t2.Status = Matched && tile <> t1 && tile <> t2 -> 
            gameBoard
            |> GameBoard.updateTile tile.Row tile.Col { tile with Status = AttemptingMatch }
            |> GameBoard.updateTile t1.Row t1.Col { t1 with Status = UnMatched }
            |> GameBoard.updateSelected (OneSelected ({ tile with Status = AttemptingMatch }))
            |> modelChangeEvent.Trigger
        | TwoSelected(t1, t2) when tile <> t1 && tile <> t2 -> 
            gameBoard 
            |> GameBoard.updateTile t1.Row t1.Col { t1 with Status = UnMatched }
            |> GameBoard.updateTile t2.Row t2.Col { t2 with Status = UnMatched }
            |> GameBoard.updateTile tile.Row tile.Col { tile with Status = AttemptingMatch }
            |> GameBoard.updateSelected (OneSelected ({ tile with Status = AttemptingMatch }))
            |> modelChangeEvent.Trigger           
        | OneSelected t1 when t1.HiddenColor = tile.HiddenColor && tile <> t1 -> 
            gameBoard
            |> GameBoard.updateTile t1.Row t1.Col { t1 with Status = Matched }
            |> GameBoard.updateTile tile.Row tile.Col { tile with Status = Matched }              
            |> GameBoard.updateSelected (TwoSelected ({ t1 with Status = Matched },{ tile with Status = Matched }))
            |> modelChangeEvent.Trigger
        | OneSelected t1 when tile <> t1 -> 
            gameBoard
            |> GameBoard.updateTile tile.Row tile.Col { tile with Status = AttemptingMatch }              
            |> GameBoard.updateSelected (TwoSelected (t1, { tile with Status = AttemptingMatch }))
            |> modelChangeEvent.Trigger 
        | NoneSelected when tile.Status <> Matched -> 
            gameBoard
            |> GameBoard.updateTile tile.Row tile.Col { tile with Status = AttemptingMatch }              
            |> GameBoard.updateSelected (OneSelected ({ tile with Status = AttemptingMatch }))
            |> modelChangeEvent.Trigger
        | _ -> ()

Conclusion

I had loads of fun making this game and definitely want to add more to it. My daughter has been playing away with it and having great fun!
I’ll definitely be continuing on my Fable journey.
The full source code for this game is on my github:
MemoryTiles.fsx
Also the current version of the game can be played here