[007.2] Etch-A-Sketch

Exploring more of Elm's Graphics capabilities by building an Etch-A-Sketch application.

Subscribe now

Etch-A-Sketch [05.13.2016]

We're going to continue exploring building applications with Elm's Graphics capabilities. To facilitate this exploration, we'll build a small application and extend it: An Etch-A-Sketch. Let's get started.

Project

We'll start off by kicking off a new application:

mkdir etchasketch
cd etchasketch
vim Main.elm

We're going to simulate an Etch-A-Sketch. Consequently, we know we need to bring in Graphics, Color, and the Keyboard:

import Color exposing (..)
import Collage exposing (..)
import Element exposing (..)
import Keyboard
import Html exposing (..)
import Html.App as App

Next, let's identify the model. An Etch-A-Sketch has a current drawing, which is a list of points, and the current x and y position for the head.

type alias Model =
  { points : List Point
  , x : Int
  , y : Int
  }

...where a point is just a 2-tuple of Ints:

type alias Point = (Int, Int)

Our initial model will have a single point in the middle and the head will be in the middle of the canvas:

initialModel : Model
initialModel =
  { points = [(0, 0)]
  , x = 0
  , y = 0
  }

Our view will just be a traced path along those points:

view : Model -> Html Msg
view model =
  collage 800 800
    [ (drawLine model.points) ]
    |> Element.toHtml

drawLine is a function that takes a list of points and produces a traced path along them:

drawLine : List Point -> Form
drawLine points =
  let
    -- Our points are integers, but a path needs a list of floats.  We'll make a
    -- function to turn a 2-tuple of ints into a 2-tuple of floats
    intsToFloats : (Int, Int) -> (Float, Float)
    intsToFloats (x, y) =
      (toFloat x, toFloat y)

    -- Then we'll map our points across that function
    shape = path (List.map intsToFloats points)
  in
    -- Finally, we'll trace that list of points in solid red
    shape
    |> traced (solid red)

We'll build out a Program that wires everything together:

main : Program Never
main =
  App.program
    { init = initialModel ! []
    , update = update
    , view = view
    , subscriptions = subscriptions
    }

We want to know about keyboard keys being released:

subscriptions : Model -> Sub Msg
subscriptions model =
  Keyboard.ups KeyUp

We'll respond to the arrow keys being released with our own messages:


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    KeyUp keyCode ->
      ( keyUp keyCode model, Cmd.none )


keyUp : Keyboard.KeyCode -> Model -> Model
keyUp keyCode model =
  case keyCode of
    38 -> -- up
      { model | y = model.y + 1, points = (model.x, model.y + 1) :: model.points }
    40 -> -- down
      { model | y = model.y - 1, points = (model.x, model.y - 1) :: model.points }
    37 -> -- left
      { model | x = model.x - 1, points = (model.x - 1, model.y) :: model.points }
    39 -> -- right
      { model | x = model.x + 1, points = (model.x + 1, model.y) :: model.points }
    _ -> model

With that, we've modeled everything fundamental about an Etch-A-Sketch. Let's fire up the reactor and try it out:

elm-reactor

And it works! You have to press the key and release it every time you want the update function to fire, but it's completely usable.

Summary

So that's it. In 88 lines of code, including spacing and type annotations, we've got a functioning Etch-A-Sketch in the browser. In the next few episodes we'll add some features and see how to evolve a simple prototype like this to include more interesting functionality. See you soon!

Resources