Rustle

Build a Wordle Clone in Rust

Herbert Wolverson
The Pragmatic Programmers
8 min readFeb 24, 2022

--

https://pragprog.com/newsletter/

If you’re on social media, you’ve probably seen lots of Wordle results. Wordle is a simple, addictive game — and many people wish they could play more often. Let’s indulge our word-guessing cravings and build Rustle: a Wordle clone written in Rust.

Getting Started

To get started, we’ll create a new project and set up some dependencies.

Create a New Project

Open a terminal/command prompt, and cd to your preferred directory for creating new Rust projects. Enter the following to create a new project:

Cargo will build an empty “Hello, World!” project for you.

Set Up Some Dependencies

Rustle will require two dependencies:

  • bracket-random will be used to generate random numbers. It’s a friendly wrapper over the rand ecosystem.
  • colored will be used to print terminal text in multiple colors. Colored makes it easy to print colorful text on your terminal. For example "this is blue".blue() will automatically add the ANSI commands to the string to render the string in blue — and correctly restore your default color afterward.

In your newly created rustle project, open Cargo.toml and add these dependencies:

Let’s make these dependencies available within our program. Open src/main.rs and add the following to the top:

You can now access the colored’s commands (such as red()) directly by name. RandomNumberGenerator is available without qualifying it with a full path. We’ve also included HashSet from the Rust standard library — we’ll need it later.

Build a Word List

There are lots of word lists for games available for free, online. We’ll use one commonly found in a lot of different Scrabble-like games: https://www.wordgamedictionary.com/twl06/download/twl06.txt

Download the words file, and save it as src/words.txtin your project directory.

Take a quick look at the file:

Notice a few things:

  • Not all of the words are five letters long
  • The top of the file contains a heading and an empty line
  • The words are all in lower-case

All of that’s ok — there are plenty of valid words in there, and we’ll filter the words when we load them.

Let’s start by embedding the word list into our Rust program. Open src/main.rs. At the top of the file, add:

This line creates a constant — a variable that cannot change. It is loaded with the include_str! macro (built into Rust). include_str! loads the named file at compile-time, and embeds it into your source code. This step lets you ship the resulting executable file without also having to include the text file.

Sanitizing Input

Next, we need to create a function named sanitize_word. It’s never a good idea to trust user input (whether typed or from a word list), so we’ll perform a few steps to ensure that a string is a valid Rustle word:

This function operates as a function chain — each function reads the results of the previous function, gradually building a result:

  1. trim() removes any spaces and non-printing characters (such as \n indicating the end of a line). It’s always a good idea to trim input — you never know what hidden data may be in there!
  2. to_uppercase() converts the word to capital letters (U.S. English).
  3. chars() creates an iterator over the string, returning each character in turn. Rust strings may contain Unicode, so it isn’t guaranteed that each character is of a fixed length. For that reason, it’s preferred to iterate over chars() rather than accessing the underlying data directly.
  4. We call filter with a test that each character is_ascii_alphabetic. This step ensures that if some Kanji, Cyrillic, numbers of other characters have slipped into the input — they will be ignored.
  5. Finally we collect the results back into a String and return it.

Now that we can sanitize potential words, let’s read them into a word list.

Building a Word List

It’s possible that you would prefer a Wordle-like game that uses a different length of word. Let’s support that! Next to the const you just created in src/main.rs, create a new constant specifying the desired word length:

Let’s make a function that examines every line in the word.txt file and returns a list of possible words in a vector:

This function also operates as a function chain: each line takes the result of the previous line and operates on it:

  1. We start with ALL_WORDS — the entire words.txt file.
  2. Split creates an iterator over the string, divided by a delimiter. We’ve picked \n — the newline marker. Subsequent calls are iterating over each line in the words.txt file.
  3. We use skip to pass over the title and empty line at the beginning of the file.
  4. Map is used to transform data. Calling map(sanitize_word) calls sanitize_word on every word we’re iterating. This step ensures that they are valid words.
  5. We then filter with a closure (inline function). Filter retains entries for whom the closure returns true — in this case, if the sanitized word length equals our WORD_LENGTH constant.
  6. Finally, collect places all of the remaining words into a vector of strings. Rust is able to deduce the type of vector Vec<String> from the function signature.

This function builds an entire list of valid words. If you’re sticking with the default of 5, it will build a list of 8,938 possible words.

Customize Your Rustle Game

You may not want to play strictly by the Wordle rules. Maybe you’d like more guesses or words of a different length. Let’s accommodate this feature by adding a few constants to define the game parameters:

You can change these values and recompile the game to play Wordle according to your own rules.

Building a RustleGame Type

We’ll encapsulate much of the Rustle game inside its own type. Let’s start by defining the type itself, which needs to hold the data we’ll need for the game logic:

This is a pretty straightforward structure. It contains the dictionary (available words), the chosen word, a set of guessed letters, and a list of all of the guesses the player has tried so far.

Setting Up the Game

Let’s create a constructor for RustleGame that sets up a dictionary and selects a word. You’ve already written the functions required to make this work; this step wraps them and sets up the RustleGame state:

This function creates a RandomNumberGenerator from bracket-random. It then builds the dictionary, using the word_listfunction you created earlier. A word is randomly selected from the dictionary and placed into the word variable.

Displaying Guesses

One of the joys of Worlde is the clear, attractive set of colored blocks that display your progress as you try to guess the chosen word. Some simple rules are applied to guesses:

  • Letters that are both in the chosen word, and in the correct location are displayed in green.
  • Letters that are in the word, but are not in the correct place are displayed in yellow.
  • Letters that aren’t in the word are printed in grey and added to the list of letters you don’t need.

The following function (part of RustleGame) accomplishes this:

This function starts by iterating all of the stored guesses (from self.guesses). It uses enumerate to add numbering, and each display line starts with “1:” to indicate the guess number.

Each guess is then broken into characters with char, and each character is evaluated. If the word is in the right place (checked by using nth against the selected word), it prints in green. If it is in the word but not in the right place (evaluated with any), it prints in yellow. Otherwise, it prints in regular console colors.

Letters that aren’t in the target word are added to guessed_letters. Because this is a HashSet, you don’t need to worry about duplicates: the set stores a given value only once.

Printing the List of Wasted Letters

Wordle helps you out by showing you which letters you’ve used that aren’t in the target word. Since we’re storing these in guessed_letters, we can do the same. Add this function to RustleGame's implementation:

This function starts by checking if there are any invalid letters. If there aren’t, it does nothing — there’s no need to display an empty list. Otherwise, it prints a list of letters from the guessed_letters set.

Asking the User to Make a Guess

Let’s create a function that asks the user to make a guess at the word. It needs to check that a guess is of a valid length and in the dictionary. Add the following to RustleGame's implementation:

The function begins by prompting the user to enter a word of WORD_LENGTH letters. It’s always a good idea to prompt the user before waiting for input; otherwise, they may sit and stare at the program unsure of what to do.

Then the function calls the display_invalid_letters function we created above. We’re not constraining the user’s ability to reuse letters, but this should give them a useful hint.

Next, we loop on valid_guess. We’ll set the variable to true when a guess is of the right length and in the dictionary. We use stdin().read_line to read a guess from the terminal and immediately call sanitize_word on it. Finally, we check that the sanitized word is of the correct length — and in the dictionary. If those are true, we set valid_guess to true and continue; otherwise, we ask the user to try again.

Did We Win?

Our last function in RustleGamewill determine if the game is over:

The is_game_over function returns true if the game is over, and false if it should continue. It checks to see if the most recent guess is correct, and congratulates the player if they won. If the player has run out of tries, it ends the game with a message telling the player the correct word. Otherwise, it returns false.

The Game Loop

Finally, let’s make our main function glue all of this together:

We use a loop and break out of it if the game is over. The loop is running the functions we’ve built: it displays the guess board, asks for a guess, and checks to see if the game is over.

Run the game with cargo run and you can play Rustle!

Not bad for 120 lines of code! This game is a good example of how Rust’s iterator function and function chaining can help you create a fun game with very little code.

The full source code for the game is:

🧩 Have fun playing Rustle!

If you enjoyed this article and want to learn more about Rust, pick up Herbert Wolverson’s books from The Pragmatic Bookshelf. You can save 35 percent with promo code rust_2022 now through March 31, 2022. Promo codes are not valid on prior purchases.

--

--