Part 1 – Build a Wordle Helper Using Elm and A Little Bit of Logic

druchan

druchan

Posted on August 7, 2023

Part 1 – Build a Wordle Helper Using Elm and A Little Bit of Logic

By now, there are a gazillion Wordle clones. This post is not about building a clone.

Instead, this is about building a program that can be used to help solve Wordle.

(Disclaimer: this can take away the fun of solving Wordle without any external help but the prospect of doing some functional programming logic is too exciting to miss).


I'm trying out a slightly different format for this post.

Each section has:

  • a short intro that answers "what's the goal here?"
  • the full piece of code that tries to achieve the goal – I want you to read this section before moving to the next. Try and force yourself to make sense of the code as best as you can. It's okay to be wrong, okay to not understand some bits etc.
  • and finally a breakdown of the code.

You'd need to know some basics of Elm in order to grok this post. The Elm Guide is a good place to start.


The scope of the program

Generally good to scope out what we're going to build.

We're building a program that:

  • has a frontend (duh, obviously, I can hear you say),
  • lets you input 5-letter words and also mark them as - Not in the word, in word but in a different position, in the exact position,
  • and computes and shows a "candidate" list of 5-letter words that could potentially be the answer.

The way to use this would be:

  • you enter a word in the actual Wordle game. The game system gives you clues about the word by marking the typed letters some color.
  • you then enter the same word in our program and then click on each letter to set it to the same color as the one in the Wordle game window.
  • as you do this, the program outputs a shortening list of words one of which could be potential solution to the Wordle game.

Let's build the giant dictionary

To get a "candidate" short-list of 5-letter words that could have the potential solution, we'll first need a giant "master list" of 5-letter words.

There are two ways to get this into our program.

  1. We could just save it as a constant in an Elm module (something like MasterList.elm) and import it in our program.
  2. Or we could grab it by making an HTTP request.

Not much of a pro/con variation between the two so I'm going to go with #2.

import Browser
import Html exposing (Html, div, text)
import Http
import Result

-- defining the program
main : Program () Model Msg
main =
    Browser.element
        { init = init
        , view = view
        , update = update
        , subscriptions = \_ -> Sub.none
        }

-- defining our Model
type alias Model =
    { words : List String }

-- defining our Msg
type Msg
    = GotWords (Result String String)

-- the function that makes the API call and gets the master word list
getWords : Cmd Msg
getWords =
    Http.get
        { url = wordsUrl
        , expect = Http.expectString (\res -> GotWords (Result.mapError (\_ -> "Error") res))
        }


wordsUrl : String
wordsUrl = "https://gist.githubusercontent.com/shmookey/b28e342e1b1756c4700f42f17102c2ff/raw/ed4c33a168027aa1e448c579c8383fe20a3a6225/WORDS"

-- init of the program
init : () -> ( Model, Cmd Msg )
init _ =
    ( { words = [] }, getWords )

-- a dummy view for now. this does nothing.
view : Model -> Html Msg
view model =
    div [] [ text "Hi" ]

-- the update function. this is where we process the results of the HTTP
-- request made by `gotWords` in `init`
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        GotWords res ->
            case res of
                Err e ->
                    let
                        _ =
                            Debug.log "error" e
                    in
                    ( model, Cmd.none )

                Ok wrds ->
                    ( { model
                        | words = String.lines wrds
                      }
                    , Cmd.none
                    )
Enter fullscreen mode Exit fullscreen mode

So, in this piece of code, we are:

  • setting up the basics (Model, Msg, init etc.)
  • we're creating a function getWords that (when called) makes an HTTP GET request to fetch the list of words from the link described by wordsUrl
  • and we're handling the result of that call in our update function's GotWords branch.

Some things to note here:

In the HTTP GET request, I'm simply using expectString instead of something like expectJson because the return value (of the GET call) is just plain string.

-- the function that makes the API call and gets the master word list
getWords : Cmd Msg
getWords =
    Http.get
        { url = wordsUrl
        , expect = Http.expectString (\res -> GotWords (Result.mapError (\_ -> "Error") res))
        }


wordsUrl : String
wordsUrl = "https://gist.githubusercontent.com/shmookey/b28e342e1b1756c4700f42f17102c2ff/raw/ed4c33a168027aa1e448c579c8383fe20a3a6225/WORDS"
Enter fullscreen mode Exit fullscreen mode

Notice that I'm also transforming the error via Result.mapError because I'm trying to keep it simple and skim over the edge-case scenarios (like show an accurate error msg if the GET request fails.)

expect = Http.expectString (\res -> GotWords (Result.mapError (\_ -> "Error") res))
Enter fullscreen mode Exit fullscreen mode

We make the call to get the word list in our init function.

-- init of the program
init : () -> ( Model, Cmd Msg )
init _ =
    ( { words = [] }, getWords )
Enter fullscreen mode Exit fullscreen mode

You can visit the URL for the words list (ie, wordsUrl) directly and see how the word-list is structured.

It's one line per word.

We want to convert this into a list of strings (hence, List String).

Therefore, in the Ok wrds branch of the case statement, you'll find String.ines wrds:

case msg of
        GotWords res ->
            case res of
                Err e ->
                    let
                        _ =
                            Debug.log "error" e
                    in
                    ( model, Cmd.none )

                Ok wrds ->
                    ( { model
                        | words = String.lines wrds
                      }
                    , Cmd.none
                    )
Enter fullscreen mode Exit fullscreen mode

With the master list, we can now move to the next bit of our program: a way to input words/letters and mark them as being in the word, not in the word and in/not-in position.

💖 💪 🙅 🚩
druchan
druchan

Posted on August 7, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related