Wordle Force: How I built the Wordle game from scratch in Salesforce with Lightning Web Components and a random word generator API

Posted by

Hello readers! Happy New Year! Pretty much every morning, while waking up and grabbing my first cup of coffee, I open up the New York Times, read a bit of news and play the new game of Wordle for the day.

For those of you that don’t know Wordle, here is everything you need to know about the game:

  • There are 6 rows and 5 cells in each row
  • For each cell, you are able to use 1 word and try to guess the correct 5 letter word
  • If you guess a letter in a cell that is in the position of the word that is chosen, the cell will turn green
  • If you guess a letter in a cell that is not in the position of the word but is still included in the word, the cell will turn yellow
  • If you guess a letter that isn’t in the word, the cell will turn grey
  • You get 6 chances to guess the right word

After playing it so much, learning the rules of the game, figuring out different strategies and noticing the subtle quirks in the design and experience, I started to wonder what it would be like to build the game from scratch in Salesforce. Here is everything that is included:

  • HTTP GET callout to a random word generator service that allows me to limit responses to words that are 5 letters only
  • Custom Lightning Web Component
  • Lightning Message Channel

First, I wanted to design a simple but powerful component structure that would allow me to replicate the capability in each row while also knowing where the user is at any given moment to support the various requirements for the game. Here are the user requirements I came up with for it:

  • User Requirements for Cells
    • When a user types into the cell or clicks on the keyboard at the bottom of the screen…
      • Only allow alphanumeric characters to be typed in and prevent any special characters
      • Limit the cell to only have one letter
      • Shift the HTML focus to the <input> element cell that is right-adjacent to it
      • all cells must be disabled unless:
        • the user is on an active cell
        • there are inputs on previous cells
        • if the enter key has not been selected yet for a given row that is being filled out to process matches on the daily word
    • When a user hits the ‘Backspace’ key or clicks on the ‘Backspace’ button at the bottom of the screen, the following should happen…
      • current active cell should become inactive
      • the previous cell should become active and that value should be cleared
      • if the user is on cell #1, do not allow backspace as this will disable the entire row for the user
    • When a user hits the ‘Enter’ key or clicks the ‘Enter’ button at the bottom of the screen, the following should happen…
      • there should be a check to make sure that all cells within the active row have values in them that can be assessed for the given word
      • if there are not five cell values in the row, an error should pop up letting the user know that they need to submit a five letter word before hitting enter
      • if there are five cell values in the row, the component should check each word submitted against the word of the day to see where the guesses fall within position of the row
      • once assessed, if the word has not been correctly guessed, the previously active row should now be disabled and the users focus will be taken to the first cell of the new row to continue guessing
      • once a word has been assessed, the keyboard below and the corresponding cells will have the color that represents the rules stated above
      • once assessed, if the word has been successfully guessed, all cells will appear as green and a success notification will pop up letting the user know that they have won the game
      • once assessed, the entire row should be disabled and each color should show up one after the other to create a visual effect rather than all cells showcasing their color status all at once.
  • User Requirements for Rows
    • When a user clicks or types ‘Enter’ and the entire row is assessed as mentioned above, the entire row should then be locked down and unavailable for edit to the user
    • A new row should become activated and the first cell of that next row should become available for user input and follow the pattern of the previous row before it

An idea that came to me was to create the component structure just like a grid and assign each cell a value (or Id) that could correspond to its placement in the grid. This way, if I ever needed to focus on the next cell, travel back to the previous one or collect the cells in the submitted row, I could always rely on the current placement of the user to drive that experience. Please see an image of the idea below:

Event Listeners and Standard HTML Elements

When building this, I tried to adhere to as many core Salesforce concepts and components that I could. There are three areas where I had to break the mold a bit on standard Salesforce usage:

  • Usage of <input/> tags…
    • Due to the fact that some of my requirements involved changing the background color, width, and height of the cells that users were guessing letters in, I had a limitation with using a lightning-input tag and the Shadow DOM restrictions that come with it. Having said that, if anyone reading this knows how they would have solved this using a standard OOTB input component, I would love to know!
  • Usage of this.template.querySelector in a constructor() context…
    • constructor() { super(); this.template.addEventListener('keyup', this.handleChange.bind(this)); }
    • this approach was to accommodate the limitation around <input/> tags
  • Usage of <button/> tags…
    • Very similar to the input tag requirements of DOM manipulation made me navigate away from using OOTB button components, however, I was still able to use the onclick() event from the custom button which prevented me from having to set up an event listener on the Javascript side.

The remainder of functionality was used with the following OOTB features of the platform:

  • Lightning Layout
  • Lightning Layout Item
  • Custom Events
  • Lightning Message Channel

Packaged JSON Structure that is carried in Events

Here is an example of the packaged up JSON that carries a lot of the capability across events that get passed around for the various use cases

Everything within this object is fairly standard with data that corresponds to the cell, the color of what that cell should be after the logic has done the “word of the day” comparison and the entry that it corresponds to.

However, one attribute you might be curious about is called offset. In order to create the delayed feature of cell color changes that occur when ‘Enter’ is selected based on the match, I used an offset property and a setTimeout() JavaScript function on the subscribing end of the Lightning Message Channel publish from the ‘Enter’ event. When the cell component ingests the event from the ‘Enter’ select, the mutation of the input element is held within a setTimeout() to execute at the time we tell it which is governed by the offset. I thought it was a fairly clever solution that I really like!

The color changes of the keyboard also adhere to a similar structure of abiding by the offset to know when to execute. The keyboard uses the binding variable of “entry” to know what to match the color on while the cells use the binding attribute of “cell” to know which color to apply.

Random Word Generator API call from Apex

To get the “word of the day”, I found a public API that would generate a five letter word for you and on each start of the game, I am calling out to that service to get back the word that is in question for the game. Here is the code below:

@AuraEnabled
    public static String performWordCallout(){
        
        HttpRequest req = new HttpRequest();
        req.setEndpoint('https://random-word-api.herokuapp.com/word?length=5');
        req.setMethod('GET');

        Http http = new Http();
        HTTPResponse res = http.send(req);
        System.debug(res.getBody());
        return res.getBody();
    }

This service is being called on the connectedCallback() webhook in the parent component so that it can notify children of the word to work off of for the requirements above.

Keyboard below the Rows

As stated above, the keyboard below the rows is made up of HTML buttons. For each click of the buttons, a Lightning Message Channel event is published and injected into the cell that is active. When the ‘Enter’ key is selected, the same event listener for the cells to react to the match is also consumed by the keyboard to know how to alter the corresponding buttons to match the user experience of the row. It heavily reduced clutter since I was able to use a very similar structure.

Overall, I thought this exercise was a ton of fun and I learned a lot in the process. I would love to look at the source code from the original Wordle application to see how my logic compares. Please see the video demo below with the game in action and happy coding!

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s