DEV Community

Cup of Code
Cup of Code

Posted on • Updated on • Originally published at cupofcode.blog

Wordle in React: Picking Up Where The Others Left Off!

Learn how to create the guess distribution, the landing page, and all the cool features that are in the original Wordle game!

main image

I’ve recently decided I would like to improve my React skills. Sure, I’ve followed the official React tic-tac-toe tutorial before, but things still didn’t click for me. I knew the solution was to create a fun project, so I found a Wordle tutorial! It’s called Make a Wordle Clone with React by Net Ninja, and that was a great start.

Unfortunately, this project wasn’t close enough to the real game. Don’t get me wrong, it had the main functionality, which is very cool to implement, but I was passionate about adding the missing features that appear in the original Wordle!

In this tutorial, we will grab the Wordle-clone project that many people on YouTube created, and take it to the finish line! In a future blog post, I will show you how to theme your game with more exotic topics than ‘5 letter words’ :D

*So many React Wordle tutorials and they all stop at the same stage*
So many React Wordle tutorials and they all stop at the same stage

To make things clear, I will refer to the code I produced by following Net Ninja’s tutorial as “the base code”. Here is a link, in case you want to check it out.

The agenda for this blog post is:

  1. Useful React tips for beginners

  2. Quick overview of the base code we are starting with

  3. The virtual keyboard

  4. The post-game modal

  5. The landing page

  6. Calculating game index

Shall we begin?

Yay!


Useful React tips for beginners

I know it all looks overwhelming when you are just starting with React, so let me make things a bit clearer!

  1. There are two Reacts out there: React Native and React/ReactJs. ReactJS is for making front-end websites, and this is the one we are using today.

  2. Creating a new project: Super simple, follow this paragraph and you are good to go. If everything goes well, your browser will open a new tab for localhost:3000 with the default starting page:

*The starting page*
The starting page
  1. Running the app locally: You can either use npm start in the command line or hover above start in package.json and then click Run Script.
Running the app locally
Running the app locally
  • You don’t need to restart when you want to see your code changes, it will update automatically.

  • You should try refreshing the browser page when you are fixing an error — because those sometimes stay on the screen even when the issue is fixed.

  1. There is a useful VS code extension called ES7+ React/Redux/React-Native snippets. With that installed, you can create a new component file, type in rfc and it will auto-complete the structure of the component.

Here are some solutions for problems you will probably encounter:

  1. Double rendering: When you start printing to the console you will quickly notice that everything is rendering twice. This is not a bug in your code! This is a React thing. This does not happen in Production, just locally in the development environment. You can read more about it here.

  2. Syntax tips and tricks:

  • The error “JSX expressions must have one parent element” means you need to wrap your HTML elements with empty tags: <><your code></>.

  • The syntax for mapping over a JS array to produce HTML elements is as follows:

  • The syntax for styling inside the HTML: <h1 style={{paddingLeft: “70px”}}> . Notice the double curly brackets, the camelCase, and the stringifying of the value only (but not the key).
  1. Updates via the UseState hook do not reflect immediately, which means setting a value and then immediately getting it, won’t work as you intended. You can read more about it here.
setMovies(result);
console.log(movies) // movies!==result
Enter fullscreen mode Exit fullscreen mode

Deploying to an App Deployment Platform

Excitement to share your final product is an appropriate outcome of following this tutorial, and Netlify is the way to do so. Netlify is a website that hosts your project for free!

  1. Uploading to Netlify:

You can watch the full tutorial here, but the TLDR is:
build the project (you can find it in the package.json file, just below start). This will create a build directory with all the needed files. Then, sign up to Netlify, and upload the build directory. It’s that simple!

I recommend you give it a try now and deploy the project you have as it is currently, before adding code. This way, you’d know if the blank page you see is a code issue or a deployment issue due to it being your first time using the platform.

  1. Speaking of a blank page, let me introduce you to the white screen of death:

This refers to a situation where our React application appears to have crashed and only displays a blank white screen. This is very frustrating, especially when the app works fine on the laptop but not on the phone (and how do you debug there?!).

This is usually caused by JS code such as Date formatting, regex expressions, etc. I quickly learned that Chrome and Android phones are more forgiving than iPhones and some browsers on Mac (Safari, Firefox) so I highly recommend checking your website there periodically. If you see an error when opening the website on an iPhone, you will probably encounter the issue in Firefox on the Mac as well, where you have developer tools and can see the error.

  1. The definition of done of any feature should include testing the iPhone view. Checking it via the developer tools’ responsive view isn’t enough, because it doesn’t give an accurate display. You’ll see examples of that later in the blog post.

Now you are ready to start!

meme


The Base Code

No need to show here how to do those steps because the YouTube tutorial does it very well. I’ll detail the state of the project post the tutorial, to connect you to the rest of the blog post.

The base code from Net Ninja’s GitHub can be found here. You can either fork his code and come join me now, or revisit this blog post after following his tutorial!

If you want to pull the base code:

  1. Pull the code from Net Ninja: a. Open a terminal in your projects directory. b. Run the following commands one by one:
git clone https://github.com/iamshaunjp/React-Wordle.git
cd .\React-Wordle\
git pull origin lesson-16 --allow-unrelated-histories
Enter fullscreen mode Exit fullscreen mode
  • You might need to resolve conflicts in README.md

c. Edit the README.md to be yours, and don't forget to credit Net Ninja for the base code!

d. Now you have the code locally!

  1. Make sure it runs smoothly: a. Open your React-Wordle project in your IDE and open a new terminal b. Install dependencies by running the following commands one by one:
npm i
npm i json-server
Enter fullscreen mode Exit fullscreen mode

c. Run the json-server:
i. Open a new terminal
ii. Run the command json-server .\data\db.json --port 3001

d. Open package.json, hover above "build" and click "Run Script"

e. A new tab will open and you’ll see the game!

  1. Connect the project to your GitHub repository: a. Create a new repo at GitHub. b. Open the terminal in your project directory. c. Run the following commands one by one:
git remote set-url origin <URL_TO_GITHUB_REPO>
git add .
git commit -m "Starting with NetNinja's base code"
git push origin main
Enter fullscreen mode Exit fullscreen mode

d. Now your code is in your GitHub repository!

Run the base code locally
Run the base code locally

Overview of the base code

When you run the code you will see the page with the grid and the keyboard. The keyboard is not clickable and is here to show you all the letters and their colors. Above the grid there is the solution for this round, and the current guess the user is typing (for testing purposes).

Every time the user types a guess and presses enter, the tiles flip and color with green, yellow, or grey. The game ends when the user guesses the solution word or when the user does not manage to guess the solution word within 6 tries. Then, a modal pops up with a winning/losing message, the solution word, and the number of guesses it took.

Please note that every time you refresh the page, there will be a different solution word. The original Wordle game gives one word per day, and we will be coding this feature in today’s blog post! :D

My favorite part was the flipping tiles — I’ve never made CSS animations before — and now I know how! :D

This won’t work on Netlify!

If you try to deploy the base code as it is now in Netlify it won’t run! This is because the base code is pulling the solution words from the JSON file by running json-server in a separate terminal, which simulates a RESTful API. Then, the line fetch(‘http://localhost:3001/solutions') breaks in Netlify.

Using the json-server package doesn’t work in Netlify because Netlify can only host static pages (with no servers running on parallel terminals).

The solution: Uninstall this package (npm uninstall json-server) and modify the code to simply call the local file: import solutions from './data/db.json';


The Virtual Keyboard

The keyboard in the original game has two roles: Visual and functional.

First, show the color of the letters:

  • Letters you haven’t tried yet — light grey

  • Letters you guessed correctly in the right place — green

  • Letters you guessed that are in the solution word but not in the right place — yellow

  • Letters you have tried but do not appear in the word at all — dark grey

The second purpose of the keyboard is to populate the grid tiles. The user can click the virtual keyboard and it will function the same way as a regular keyboard, which is useful for users on mobile and Padlets.

The keyboard we start with from the base code does not have any functionality, and it doesn’t look like a regular keyboard, so we are now going to fix that!

keyboard before and after

Making It Look Like A Real Keyboard

First thing first — the keyboard is in lowercase letters! The original Wordle’s keyboard (and any keyboard in the world) has uppercase letters, so let’s change that!

In the base code, the keyboard is created using a JSON object called letters:

{
  "letters": [
    {"key": "a"},
    {"key": "b"},
  ...
}
Enter fullscreen mode Exit fullscreen mode

Then the Keypad component fetches that object:



We can simplify this! Remove it altogether and create an array of letters inside the component. No need for fetching and no need for useEffect hook! Tip: Don’t forget to order them in the qwerty keyboard layout.

Note that even though it’s tempting, you shouldn’t change the letters to uppercase. The user is typing in lowercase (like the letters you are reading now), and the function will compare them to what would now be uppercase letters, and will always return a mismatch. So how do we get the uppercase visuals? With the CSS file!

.keypad > div {
  ...
  text-transform: uppercase;
}
Enter fullscreen mode Exit fullscreen mode

If you run your app now, you might see the keyboard’s keys spilling to the wrong places: P goes to the second row, and L goes to the third row, depending on the width of the window. I fixed that by dividing my array of letters into three.

Now, to build the keyboard, I’m looping over the 3 arrays. This solution makes it very easy to add “Enter” and “Backspace” buttons: I just add them separately before and after the third loop!

Making the Virtual Keyboard Clickable

The most straightforward approach here is to add “handleKeyup” function as the keys’ “onClick” event. Unfortunately, it didn’t work.

I couldn’t get the virtual keyboard to give the “handleKeyup” function the right parameters to make it work (something about it being an event handler and this {key} parameter). This caused me to use a workaround:

I took the content of handleKeyup and wrapped it in a new function, one that doesn’t have a weird {key} parameter. That way I had one source of truth to handling the user typings!

const useWordle = (solution) => {
  ...
  const handleKeyup = ({ key }) => {
    handleKeyupVirtual(key)
    return
  }
  ...
  return {
    ...
    handleKeyupVirtual
  };
Enter fullscreen mode Exit fullscreen mode

To use this new function, we need to output it from the useWordle hook and pass it to the Keypad component:

export default function Wordle({...}) {
  const { ..., handleKeyupVirtual} = useWordle(...);
  ...
  <Keypad usedKeys={usedKeys} handleKeyupVirtual={handleKeyupVirtual} />
  ...
}
Enter fullscreen mode Exit fullscreen mode

And then in Keypad.js:

return (
  ...
  <div key="Enter" style={{width: "15%", fontSize:"13px"}}
     onClick={() => handleKeyupVirtual("Enter")}>Enter</div>
...
Enter fullscreen mode Exit fullscreen mode

Fixing a Tiny Bug

When I opened the game on the iPhone I discovered a problem: The backspace character is very small, regardless of the font size.

keyboard bug

How do we solve this? I checked how the original game did it: I opened the NY Times Wordle with the developer tools and saw they used an image instead of a character, so I did the same.

After finding an appropriate image online and placing it on my keyboard, the image was stuck at the top of the key and I wasn’t able to resolve it with direct CSS modification: When I tried to modify the margin or the padding, it affected the entire row. It was not as easy as it initially looked!

*When I added a margin to the backspace image — it affected the entire keyboard row*
When I added a margin to the backspace image — it affected the entire keyboard row

Eventually, I did find a trick on Stack overflow: Add the “no wrap” white-space property to the backspace div, and add inside it a helper span. Now it’s in a perfect size!

Final Touches

The keyboard is functioning and it looks good, but there are a few more things we can do to make it perfect!

Adding delay to the key color change

You might have noticed that when you play the original game, the keyboard key color updates only after the grid row is colored. In our game at the moment, the keyboard updates quicker because of the tiles’ animation.

I was looking into delaying components in react when I suddenly realized there must be an easier approach via CSS — and my hunch was right! All you need to do is go to the .keypad > div.green (and yellow and grey) and add transition-delay: 1s; — That’s it! :D

Addressing CapsLock in handleKey

At some point, I’ve noticed that if the CapsLock is on, it messes up the entered guess. To solve this, I added an if condition to catch that key, and ignore it (just return):

const useWordle = (solution) => {
  ...
  const handleKeyupVirtual = ( key ) => { 
    if (key === 'CapsLock') {
      return;
    }
    if(key === 'Enter'){
      ...
Enter fullscreen mode Exit fullscreen mode

Position the keyboard at the bottom of the page

When the user is on the mobile, it’s more comfortable to have the keyboard lower on the screen (closer to the thumbs). The keyboard is currently right below the grid, which is closer to the center.

*Positioning the keyboard at the bottom is better for mobile users*
Positioning the keyboard at the bottom is better for mobile users

With that said, I like that the keyboard is just below the grid when it’s the desktop view. This means we need different styles for different screen sizes!

We start by adding a @media rule with the condition of max-width: 700px, so it would affect only a narrow screen view. Inside that, we add the second version of the keypad style.

We need two modifications to make it work: First is adding to the keypad position: absolute; along with bottom, right, and left = 0. Then, we need to add to the body’s style position: relative; and set the height to be 90vh.

If you set it to 100vh, it will look beautiful on your screen, in any size, but will require scrolling on the iPhone (I encourage you to check for yourself).



Now, after all these modifications, we have a beautiful functioning keyboard:

The new keyboard — both pretty and smart!
The new keyboard — both pretty and smart!

Post Game Modal

When the game is over, pops up a modal with some interesting information.

In the original game, there are statistics including the number of games played and the percentage of games won.

Below that, you can find past games’ guess distribution: How many times did the user guess the right word in how many tries.

Lastly, there is a share button that produces a string of squares, representing the game, that the user can share with their friends. We will create all those cool features!

before and after

Add a Close Button

I’ve started with the thing that annoyed me most: I couldn’t close the modal. I had to add an exit button.

This is super easy to implement in pure HTML and JS but I wasn’t sure how to do it correctly in React. According to the base code, the modal shows up with the boolean showModal, and its value is set in the “Wordle” component:

Add Close Button

I’ve started with the thing that annoyed me most: I couldn’t close the modal. I had to add an exit button. This is super easy to implement in pure HTML and JS but I wasn’t sure how to do it correctly in React. According to the base code, the modal shows up with the boolean showModal and its value is set in the “Wordle” component:


Even if I add an X button, how do I change the value of that showModal boolean from within the Modal component? The trick is to pass setShowModal as a parameter to the component!

Fun fact: &times; is a multiplication sign in HTML. It looks a bit different than “x”.

Now the grid page is accessible post-game

It’s important to mention that adding the functionality to close the modal means the user can see the grid of guesses after the game is over. This is fine, but we need to make sure he can’t type in any more guesses, both on the physical and virtual keyboards.

For the physical keyboard, we can simply add else to the “if-user-won” inside the useEffect, and only there add the keyup listener.


Please note that in the base code, there are different modal versions for winning and losing (and therefore different if conditions), but I merged them — so in my code, it’s one if condition: if the guess is correct or if it’s not correct but we reached the 6th try (if (isCorrect || turn > 5))

For the virtual keyboard, we want to override the clickability of the keys on that last turn. Let’s see it in the code:


We start by passing an argument notifying the keypad component that it should lock the keys. Considering we want to indicate the game is over, we can use the same boolean we use in the if-the-game-ended statement: isLocked = (isCorrect || turn > 5). Now, the trick is to use pointerEvents property: We set it as “none” or “auto” based on the value of isLocked. Lastly, we add it to the style of the keyboard’s keys (you should have a total of 5 occurrences: top, middle, and bottom rows, Enter, and Backspace).

Guess Distribution

Now we can move on to the coolest feature of the modal: Let’s show the user their statistics! In the total games they played, what was their guess distribution? This is divided into 2 parts: collecting the statistics, and presenting them.

Collect the statistics

The base code doesn’t save how many guesses the player used, so we need to add that ourselves… but how?

We want this information to be saved even when the user closes the website. An easy way to do so is to use local storage.

Local storage’s stored data is saved across browser sessions, with no expiration time. So, if there is no statistics object at all — create one. Then, read the previous statistics, update them with this game’s result, and save them in the local storage.

Important tip! When coding with local storage, you need the ability to restart it, for testing purposes. I have this line of code commented out and easy to reach in App.js: //localStorage.removeItem(“wordlestruckStatistics”);. Comment it in, refresh, comment it out, refresh — and you get a clean slate.

Let’s write some code!

We will create an array of length 7, to represent the possible statistics: User guessed the solution after 1,2,3,4,5,6 tries, or they lost. 0 will represent the loss.

The tricky part is that when the user loses, the turn is set to 6 (because it was their 6th try), so we need to adjust the index based on whether the user was correct: let index = isCorrect ? turn : 0;

Now — where should the code sit? It can sit in the Modal component, or in the Wordle component. It’s best to update the statistics when the game is finished, so we will add our new code in the “If the game finished” condition in the Wordle component.

Present the statistics

I encourage you to create a new React component dedicated to this section (< Statistics />) and call it from the Modal component.

First, you need to pull the data from localStorage. Then, save the number of total games in a variable. I present the data using a table element: The left column is the possible number of guesses (0–6), and the right column is the grey bar charts representing the distribution.

Now, those aren’t just any boring bar charts! In the original game, the distribution is written inside the bar, and the bar’s size depends on the distribution amount (but 0 isn’t empty). Also, the bar representing the number of guesses it took the user for the current game is colored green, not grey.

How do we achieve all that? For our bar charts, we’ll be using divs, so it’s a no-brainer to write in them. For the bar length, here is the math I did: Math.floor((stat / totalGames) * 80) + 8.

I divided stat-for-specific-guess-number by the total number of games to reach a proportional size. I multiplied by 80 because I didn’t want the biggest size to reach 100% width (but this is my personal preference). Lastly, I added 8 because I set the size of the 0 distribution to be 4, and I wanted 1 to be visibly bigger than that, even if there were many games played.

What about the green color? That property depends only on the turn number: let barColor = (index === turn) ? "green" : "rgb(107, 107, 109)";

Full code for the guess distribution section:


Screenshot:

screenshot

In the screenshot above you can see I also presented information on total games played and % of wins. After completing the guess distribution, this part will be easy for you to add by yourselves.

Maybe we shouldn’t show the user his losses

Later in the development, I decided I didn’t want to present the number of losses. This can be achieved by wrapping the code inside the map in if(index!==0). Not the prettiest but the most minor change. If you do insist on prettier, there is another solution: Create a copy of objStatistics and .shift() it — this will remove the first item. There is no critical harm in shifting the original array, but it’s not a good practice because it can confuse the human eye reading this code. objStatisticsWithout0 is not confusing anyone :)

// to remove first element - number of loses.
let objStatisticsWithout0 = objStatistics;
objStatisticsWithout0.shift();
Enter fullscreen mode Exit fullscreen mode

If you choose to go this route, you’ll need another index variable: We shift our array, which means array[turn] does not match the guess distribution in turn. I called mine guessDistIndex and set it to be index +1.



Lastly, before we continue, I found it useful to make the modal scrollable. You can easily achieve that by adding in index.css:

.modal {
  ...
  overflow-x: hidden;
  overflow-y: auto;
}
Enter fullscreen mode Exit fullscreen mode

This is it for now. We will come back to the Modal after we go over the features that the rest of it depends on :D


Ok, remember when I said that in a future blog post, I’ll show you how to give your Wordle a cool theme? In the next section, I expose the theme of my Wordle because I can’t hide it anymore — it will appear in the screenshots.

meme2

My Wordle game is about Taylor Swift songs! The juicy part of a themed Wordle is creating the data set and adjusting the functionality to also work with solutions longer than 5 characters, and solutions containing more than one word. I will touch on those cool features in a future blog post, so stay tuned!


The Landing Page

All the Wordle clone projects start the game with the main game page (the one with the grid and the keyboard), but the true Wordlers (“Hi Wordler”) among you know that there is a grey landing page with “How to play” and “Play” buttons. This is what we will work on in this section!

*The landing page*
The landing page

The structure of the page is pretty clear: Image, title, text, 2 buttons, more text. The tricky part is clicking the Play button and “moving between pages” while staying on the same page with the same URL, without routing!

We’ll start by creating a new component, which I called <Welcome />, and we add all the needed data there. You can present the date in this format by using:

var today = new Date();
var displayDate = today.toLocaleDateString("en-US", options);
Enter fullscreen mode Exit fullscreen mode

Calculating the index, on the other hand, is a tricky part which I will touch on later! Keep it hard-coded for now.

Adding this new component requires some refactoring because it sits between App.js and Wordle.js.
App.js used to call Wordle.js, but now it will call Welcome.js, and Welcome.js will call Wordle.js.

Now, we want the page to show the Wordle component if the button was clicked. This means the landing page content disappears and the Wordle content appears. We achieve that by using React’s useState. That way we trigger a re-render to replace a value:

const [showWordle, setShowWordle] = useState(false)
Enter fullscreen mode Exit fullscreen mode

So, on button click we set showWordle to be true, and we wrap the corresponding code sections so that they are dependent on the boolean value!

{!showWordle && (<landing page code>)} {showWordle && (<Wordle component>)}

Let’s see it in the Welcome component:

The Instructions Modal

It’s time to address the second button on the landing page. The modal itself is nothing we haven’t already seen, but the tricky part is showing it above the grid page!

In the original game, clicking the how to play button leads you to the same page as the play button, just with the extra instructions modal. Once you click the X button you’ll see the grid and the keyboard.

*Wordlestruck’s instructions modal*
Wordlestruck’s instructions modal

So, you create a new component (<Instructions />) and put your code there. Where should you call it from? The structure will be similar to the landing page/wordle page structure, but from inside the wordle component!

We established earlier that both buttons will lead to the grid page, it’s just a question of whether or not the instructions modal will be shown on top. To achieve that, when we call the wordle component, we will pass a boolean parameter for the instructions page: {showWordle && <Wordle solution={solution} showInstructions={showInstructions} />}

That’s a good start, but it’s not enough. We also need to update the condition of showing the landing page vs showing the wordle component to take into consideration that new boolean:


Note that the initial state is to see the landing page, which means not showing the Wordle component, and not showing the instructions modal on top of the Wordle component. Then, if one of those booleans is set to true, we want to remove the landing page code and show the Wordle component.

So, we finished with the changes in the Welcome component, and we passed an additional parameter to the Wordle component. Let’s adjust the code to consider that new parameter!

Inside the Wordle component:

This time, different from the landing page logic, we don’t need to attach the Wordle code (grid, keyboard) to the !showInstructionsModal condition because we want it to exist underneath the instructions modal. That way, when we close the modal we will see the game!


I didn’t show any code for the Instructions modal, because it’s easy to implement considering it’s not our first modal in the project. You can do it! :D

Calculating the Game Number (Index)

At first glance, it may seem like the index is a tiny feature that appears only once on the landing page. In reality, this is a crucial part of this game, and it will be used throughout the whole game flow!

The index is accessed on the landing page, in the data set (to pull the word of the day), in the share button, and in various components to indicate if the user has played today or not. I know, most of those we didn’t do yet — which is why we need the index at this point!

When I started planning this function, I got overwhelmed: Every month has a different number of days, some years are leap years, and it even gets more complicated — Date.now() function returns the number of milliseconds since January 1, 1970, but there is no function to calculate the number of milliseconds since January 1, 1970, to another date that is not now!

At this point, I used the successful method “drop it for now and come back to it later”, and it worked! I googled it, understood all the parts (I can’t find the exact link but it was from Geeks for Geeks), and was determined to write it myself!

How to calculate the number of days between two dates?

First of all, the trick is not to calculate days between two dates, but to calculate the number of days in each date (# of days in 2023 years, # of days in 3 months, etc) and subtract the two!


Note that when you calculate years and months, you don’t use the direct value, but subtract 1: If the date is March 13th, 2023, we don’t have all days of March, nor all days of 2023. This is why you see in the code above year-1 and month-1. The for loop that is calculating full months in days is running from 0 to month-1 (excluding), which matches the monthDays array: January’s amount of days is in cell number 0, not 1.

Addressing Leap Years

The leap years aspect is not as complicated as it initially looks. A leap year is a year that has one additional day at the end of February. So, once you know how many years are leap years — that’s the amount of days you add.

What makes a year a leap year? According to Wikipedia,

“This extra leap day occurs in each year that is an integer multiple of 4 (except for years evenly divisible by 100, but not by 400)”.

In code, it simply means if(year % 4 === 0 && !(year % 100 === 0 && year % 400 !== 0)).

This is how I wrote the function:


Don’t forget that leap years have their extra day in February, so keep the last year out of this calculation, and add a day only if the month is after February (month > 2).

Just to make sure we tackled all possible edge cases, let’s think about what would happen on Feb 28th and Feb 29th.

Feb 28th: month is not >2 then leap year doesn’t impact our calculation.

Feb 29th: month is not >2 then leap year doesn’t impact our calculation in this part. When we add the day to the sum of total days, we will add 29, so that’s ok.

This caused a bug!

I’ve discovered the hard way that Mac’s Safari and Firefox browsers do not agree to render the starting date in the current format. Chrome is more forgiving, which is why it took me a while to discover it.

The acceptable format is new Date(2023,11,8) — This means the month range is 0–11! (and only the month, not the other variables, I double-checked!)

It’s more human-friendly to address the months as starting from 1 so it’s better to adjust the month variable (and leave yourself a comment as a reminder!):

let startingDate = new Date(2023, 11, 8); // YYYY-(MM-1)-DD
...
let month = date.getMonth() - 1;
Enter fullscreen mode Exit fullscreen mode

Now, you can optimize this by hard coding the start date. We set it only once, no need to calculate it again and again. I chose to keep the calculation because I haven’t launched my game yet, so the starting day is about to update.

If you are passionate about optimizing the program, you can calculate the first 2023 years in days only once and save those in variables! I won’t do it here, so let’s call this your homework ;)

Now that you have the game index calculated, you can modify the word pulling so that there will be only one word per day, based on the date!

The base code has the line: const randomSolution = solutions[Math.floor(Math.random() * solutions.length)], which gives a new word every refresh of the page. If we change it to be const todaysSolution = solutions[game_id], where game_id is the index calculated by our function, you could add the exciting sentence “A new puzzle is released daily at midnight” to your Instructions modal! :D

Last but not least

We need to take a closer look at the index ranges: Let’s say we have 200 solution words. The solutions data set indexes will range between 0–199, because of the convention that an array starts at 0. The game indexes (numbers) should range between 1-infinity. This means we have a mismatch both at the start and at the end of the index ranges. Also, if we want the game index to start at 1, we need to revisit our getGameIndexofToday() function. We will fix it all in this section!

App.js:

This is our code at the moment:

const game_id = getGameIndexofToday(); // 0-infinity, but we don't want "game no.0"
...
const todaysSolution = solutions[game_id]; // 0-199
Enter fullscreen mode Exit fullscreen mode

At some point, your game index will be higher than your dataset size. If you have 200 words in your data set, and it’s day 201, your app will crash. You can cover this case simply by adding const solution_id = game_id % solutions.length; instead of using the value coming from the index calculation function directly.

That is not enough. Once we fix game_id to start at 1, the first solution word will be the second item in the array (solutions[1]). We need to correct that! Therefore, we'll use const solution_id = (game_id-1) % solutions.length; instead.

num_of_days_from_date.js:

Lastly, let’s look at getGameIndexofToday() function. We built it so that we calculate amount of days in each date, and then subtract. What happens on the first day? The result will be 0, which is not a good starting index for our game (game number 0?). This happened because usually when we calculate something from start to finish, we exclude the end value. In our case, we need to include it. Let’s fix the code!

Not only do I add +1 before I return the value, but I make sure to rename the function to make it explicit that it includes the end day.

Now our indexes are matching!

const game_id = getGameIndexofToday(); // 1-infinity
...
const solution_id = game_id % solutions.length; // 1-200
const todaysSolution = solutions[solution_id]; // 1-200
Enter fullscreen mode Exit fullscreen mode

This is a good place to close today’s tutorial. There are more features to add, but we’ll do them in the next blog post!

Let’s give a quick reminder of what we created today:

  • A beautiful and functional virtual keyboard

  • A cool statistics modal with the user’s guess distribution

  • An instructions modal

  • A landing page with the real date and game index!

This is a lot! But a lot of fun as well. 😀

In the next blog post, we will create the following features:

  • The share functionality

  • Different screens for whether the user played today or not

  • Header buttons for statistics and instructions

Exciting news to all the swifties out there — my Wordlestruck game will launch at the end of this blog post series!


Blogging is my hobby, so I happily spend time and money on it. If you enjoyed this blog post, putting 1 euro in my tipping jar will let me know :)

Thank you for your support!

Top comments (0)