Nazanin De's Blog

Projects, Ideas and thoughts

Start your first game with Elm

Elm is a fully functional language and it is in your browser that is the thing I love about it! UI side Functional development.

It is pretty fast comparing to other available UI frameworks out there. Here is the performance result from "TodoMVCBenchmark":

alt You may ask your self why Elm is so fast they are many reason one is leveraging the virtual DOM.
The new elm-html library enabled using the CSS and HTML directly in Elm and uses and optimizes the code by doing some lazy rendering.

All these awesome features made me explore Elm and make a simple memory game with it.

The game simply has two modules Card and GAME.

The logic of every Elm program will break up into three cleanly separated parts:

  • model
  • view
  • update

Having the view separated from the update part makes the code easier to test and it is architecturally great!

Model

Each Card has three property: status, image and id. Status is a type defined below which you can compare types in Elm to Enums in other languages.

You need to define initial model as the initial status of your model which is the Card here:

type alias Model =  
  { 
   status : Status,
   image : String,
   id : Int
  }

type Status  
    = Opened
    | Closed
    | Locked

initialModel: String -> Int -> Model  
initialModel img id =  
  { status = Closed,
    image = img,
    id =  id
  }
View

The next part is to have the HTML defined in the view function and update the view inside the update function. The view function gets a Singnal which is the status of the Card and the Model and returns Html.

view : Signal.Address Status -> Model -> Html.Html  
view address model =  
  div [Html.Attributes.class "flipper",Html.Attributes.id "flipper"] [          Html.span [imageContainerStyle] [ Html.span [onClick address model.status] [ Html.img [ toImage model, imageStyle] [] ] ] ]
Update

Now lastly we have to update the view based on the status of the card and our Model. In this case in the Card is matched it is locked so we do nothing and return the Model. If the card is Closed we change its status to open and update the Model and if it is Opened we do otherwise.

update: Status -> Model -> Model  
update action model =  
  case action of
    Locked -> model
    Closed -> { model | status <- Opened}
    Opened -> { model | status <- Closed}
Main

I used the StartApp package to wire together the initial model with the update and view functions. It is a small wrapper around Elm's signals you can check the code out for more understanding.

The key to wiring up your application is the concept of an Address. Every event handler in our view function reports to a particular address. It just sends chunks of data along. The StartApp package monitors all the messages coming in to this address and feeds them into the update function. The model gets updated and elm-html takes care of rendering the changes efficiently.[from elm-architecture]

Lastly we have our main function defined in Game.elm .

module MemoryGame where

import Html exposing (div, button, text, span, p, img)  
import Html.Events exposing (onClick)  
import Signal.Time exposing (..)  
import Html.Attributes  
import StartApp.Simple exposing (start)

main =  
  start { model = init, view = view, update = update }

type alias Model = {  
   cards: List Card.Model,
   matched_pair: Int,
   score: Int,
   rows: Int,
   columns: Int
}

type Action  
  = Do Int Card.Status
  | Restart

Out inital model is randomly generated list of Cards with some other properties used to keep track of matches and game status.
For generating random list objects I used Random.Array.shuffle function from Random package.

type alias Time = Float

initSeed =  
   round Now.loadTime

shuffle: List Card.Model -> List Card.Model  
shuffle list =  
  case Random.Array.shuffle (Random.initialSeed initSeed) (Array.fromList list) of
    (x, y) -> (Array.toList x)

init: Model  
init =  
  {
    cards = shuffle <|
              List.map (\index -> Card.initialModel ("images/" ++ (toString (index % 8)) ++ ".png") index) [1..16],
    score = 0,
    matched_pair = 0,
    rows = 4,
    columns = 4
  }

followings are last part of memoryGame.elm which includes view and update functions.
You can see how well you can separate various views based on the status of your program.

view: Signal.Address Action -> Model -> Html.Html  
view address model =  
  let
    maxCount = List.length model.cards
  in
    if model.matched_pair == maxCount then
      div [Html.Attributes.class "winContainer"]
      [
        p [] [
          text "You Won!",
          img [Html.Attributes.src "images/halloween178.svg"] []
        ],
        p [] [
          span [] [(Html.text ("Score: " ++ toString (model.matched_pair * 50 - model.score)))]
        ],
        button [onClick address Restart, Html.Attributes.class "btn btn-default btn-circle btn-lg"] [Html.text "Restart"]
      ]
    else
      div [Html.Attributes.class  "col-xs-12 main"] [
       div [Html.Attributes.class  "infoContainer col-xs-4"] [
        p [] [
          Html.text "Moves: ",
          span [] [(Html.text (toString model.score))]
        ],
        p [] [
          Html.text "Matched: ",
            span [] [(Html.text (toString ((toFloat model.matched_pair) / 2)  ++ " / " ++ toString ( toFloat model.rows * toFloat model.columns / 2)))]
        ]
       ],
       div [Html.Attributes.class "cardsContainer col-xs-8"]
        [
          div [Html.Attributes.style  [("width", toString (model.columns * 80) ++ "px")]]
          (List.map (\cModel -> Card.view (Signal.forwardTo address (Do cModel.id)) cModel) model.cards)
        ]

      ]

update: Action -> Model -> Model  
update action model =  
      case action of
        Restart -> init
        --Do Int Card.status
        Do y x -> model |> updateCardById x y
                        |> checkAndLock
                        |> closeAllCards y

I think Elm is super powerful and it is the language of the next generation web applications.

You can try the game out online here