Wordle. The daily word puzzle that has captivated millions. Its simple premise – guess a five-letter word in six tries – belies its addictive nature. But have you ever wondered how such a game is built? This tutorial will guide you through creating your own interactive Wordle game using TypeScript, equipping you with valuable skills in web development and programming logic. We’ll break down the process step-by-step, explaining each concept clearly, providing code examples, and highlighting common pitfalls to avoid. By the end, you’ll not only have a working Wordle clone but also a solid understanding of TypeScript fundamentals.
Why TypeScript?
TypeScript, a superset of JavaScript, adds static typing to your code. This means you define the data types of your variables, function parameters, and return values. Why is this important? Because it helps you catch errors early in the development process. TypeScript can identify potential issues during compilation, before you even run your code, saving you time and headaches. It also improves code readability and maintainability, especially in larger projects. Furthermore, TypeScript provides excellent tooling support, including autocompletion and refactoring capabilities, making your development workflow more efficient.
Setting Up Your Project
Let’s get started! First, you’ll need to set up a new TypeScript project. You’ll need Node.js and npm (Node Package Manager) installed on your system. Open your terminal or command prompt and navigate to your desired project directory. Then, run the following commands:
mkdir wordle-typescript
cd wordle-typescript
npm init -y
npm install typescript --save-dev
This creates a new directory, initializes a new npm project, and installs TypeScript as a development dependency. Next, create a `tsconfig.json` file in your project root. This file configures the TypeScript compiler. You can generate a basic `tsconfig.json` by running:
npx tsc --init
This command creates a `tsconfig.json` file with default settings. You can customize these settings to fit your project’s needs. For our Wordle game, you might want to adjust the following options:
target: Sets the JavaScript language version (e.g., “es6”, “esnext”).module: Specifies the module system (e.g., “es2020”, “commonjs”).outDir: Defines the output directory for compiled JavaScript files (e.g., “dist”).strict: Enables strict type-checking options. It’s generally a good idea to set this to `true`.
Here’s a sample `tsconfig.json` file:
{
"compilerOptions": {
"target": "es6",
"module": "es2020",
"outDir": "dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}
Create a `src` directory in your project root, and within it, create a file called `index.ts`. This is where we’ll write the code for our Wordle game.
Game Logic: Core Concepts
Before we dive into the code, let’s outline the core components of our Wordle game:
- Word List: A collection of valid five-letter words that the player can guess.
- Secret Word: The word the player needs to guess, chosen randomly from the word list.
- Guess Input: A field where the player enters their guess.
- Validation: Checks if the guess is a valid word and of the correct length.
- Feedback: Provides feedback on each guess, indicating which letters are correct (green), present in the word but in the wrong position (yellow), or not in the word (gray).
- Game Board: A visual representation of the guesses and feedback.
- Game State: Tracks the number of guesses remaining and the game’s outcome (win or lose).
Implementing the Game Logic in TypeScript
Let’s start by defining some basic types and variables in `index.ts`:
// Define a type for a single letter feedback
type LetterFeedback = 'correct' | 'present' | 'absent';
// Define a type for a guess result
type GuessResult = LetterFeedback[];
// Constants
const WORD_LENGTH = 5;
const MAX_GUESSES = 6;
// Sample word list (replace with a more comprehensive list)
const wordList: string[] = ['apple', 'grape', 'table', 'chair', 'house', 'world'];
// Get a random word from the word list
const getRandomWord = (): string => {
const randomIndex = Math.floor(Math.random() * wordList.length);
return wordList[randomIndex].toUpperCase();
};
let secretWord: string = getRandomWord();
let guessesRemaining: number = MAX_GUESSES;
let gameWon: boolean = false;
let gameBoard: string[][] = Array(MAX_GUESSES).fill(null).map(() => Array(WORD_LENGTH).fill(''));
let guessResults: GuessResult[] = [];
Let’s break down this code:
- `LetterFeedback` Type: Defines the possible feedback for each letter in a guess: ‘correct’, ‘present’, or ‘absent’.
- `GuessResult` Type: An array of `LetterFeedback` values, representing the feedback for an entire guess.
- `WORD_LENGTH` and `MAX_GUESSES`: Constants to make the code more readable and maintainable.
- `wordList`: An array of valid words. In a real-world application, this would likely be a much larger list.
- `getRandomWord()`: A function that selects a random word from the `wordList` and converts it to uppercase. This is our secret word.
- `secretWord`: The secret word the player is trying to guess.
- `guessesRemaining`: The number of guesses the player has left.
- `gameWon`: A boolean flag indicating whether the player has won.
- `gameBoard`: A 2D array (an array of arrays) to store the player’s guesses. Each inner array represents a row on the game board, and each element within is a single letter of the guess. We initialize it with empty strings.
- `guessResults`: An array to store the feedback for each guess.
Now, let’s create the core game logic function to evaluate the player’s guess:
const evaluateGuess = (guess: string): GuessResult => {
const guessResult: GuessResult = Array(WORD_LENGTH).fill('absent');
const secretWordLetters = secretWord.split('');
const guessLetters = guess.split('');
// Check for correct letters (green)
for (let i = 0; i < WORD_LENGTH; i++) {
if (guessLetters[i] === secretWordLetters[i]) {
guessResult[i] = 'correct';
secretWordLetters[i] = ''; // Prevent double-counting
}
}
// Check for present letters (yellow)
for (let i = 0; i -1) {
secretWordLetters[index] = '';
}
}
}
return guessResult;
};
Here’s how `evaluateGuess` works:
- It takes the player’s guess as a string.
- It initializes a `guessResult` array with ‘absent’ for each letter.
- It splits both the secret word and the guess into arrays of letters.
- It iterates through the letters, checking for correct matches (green). If a letter is correct, it sets the corresponding element in `guessResult` to ‘correct’ and replaces the letter in `secretWordLetters` with an empty string to prevent it from being counted again if it appears multiple times in the guess.
- It then iterates again, checking for letters present in the word but in the wrong position (yellow). If a letter is present and the corresponding element in `guessResult` is still ‘absent’, it sets the element to ‘present’. It also removes the first occurrence of the letter from `secretWordLetters` to prevent double-counting.
- Finally, it returns the `guessResult` array.
Next, we need a function to validate the guess:
const isValidGuess = (guess: string): boolean => {
guess = guess.toLowerCase();
return guess.length === WORD_LENGTH && wordList.includes(guess);
};
This function simply checks if the guess is the correct length and if it exists in our `wordList`. We convert the guess to lowercase to make the comparison case-insensitive.
Building the User Interface (UI) with HTML and JavaScript
Now, let’s create the HTML and JavaScript to build the UI of our game. Create an `index.html` file in the project root:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wordle in TypeScript</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>Wordle</h1>
<div id="game-board"></div>
<input type="text" id="guess-input" maxlength="5">
<button id="guess-button">Guess</button>
<p id="message"></p>
<p id="guesses-remaining">Guesses Remaining: 6</p>
</div>
<script src="dist/index.js"></script>
</body>
</html>
This HTML sets up the basic structure of the game:
- A title.
- A game board (`<div id=”game-board”>`).
- An input field (`<input type=”text” id=”guess-input”>`) for the player to enter their guess.
- A button (`<button id=”guess-button”>`) to submit the guess.
- A message area (`<p id=”message”>`) to display feedback to the player.
- A display of remaining guesses (`<p id=”guesses-remaining”>`).
- A link to our stylesheet, `style.css`.
- A link to our compiled JavaScript file, `dist/index.js`.
Create a `style.css` file in your project root to style the game:
body {
font-family: sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f0f0f0;
}
.container {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
text-align: center;
}
#game-board {
display: grid;
grid-template-columns: repeat(5, 40px);
grid-gap: 5px;
margin-bottom: 15px;
}
.row {
display: flex;
justify-content: center;
margin-bottom: 5px;
}
.square {
width: 40px;
height: 40px;
border: 1px solid #ccc;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.5rem;
font-weight: bold;
text-transform: uppercase;
}
.correct {
background-color: #6aaa64;
color: white;
border-color: #6aaa64;
}
.present {
background-color: #c9b458;
color: white;
border-color: #c9b458;
}
.absent {
background-color: #787c7e;
color: white;
border-color: #787c7e;
}
input[type="text"] {
padding: 10px;
font-size: 1rem;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
text-transform: uppercase;
width: 200px;
}
button {
padding: 10px 20px;
font-size: 1rem;
background-color: #538d4e;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #6aaa64;
}
This CSS provides basic styling for the game board, input field, and button. It also defines the colors for the different feedback states (correct, present, absent).
Now, let’s add the JavaScript code to `index.ts` to interact with the HTML elements, handle user input, and update the game board:
// Get HTML elements
const gameBoardElement = document.getElementById('game-board') as HTMLDivElement;
const guessInputElement = document.getElementById('guess-input') as HTMLInputElement;
const guessButtonElement = document.getElementById('guess-button') as HTMLButtonElement;
const messageElement = document.getElementById('message') as HTMLParagraphElement;
const guessesRemainingElement = document.getElementById('guesses-remaining') as HTMLParagraphElement;
// Function to render the game board
const renderGameBoard = () => {
if (!gameBoardElement) return;
gameBoardElement.innerHTML = ''; // Clear the board
for (let i = 0; i < MAX_GUESSES; i++) {
const row = document.createElement('div');
row.classList.add('row');
for (let j = 0; j < WORD_LENGTH; j++) {
const square = document.createElement('div');
square.classList.add('square');
square.textContent = gameBoard[i][j];
if (guessResults[i]) {
square.classList.add(guessResults[i][j]); // Apply feedback class
}
row.appendChild(square);
}
gameBoardElement.appendChild(row);
}
};
// Function to handle a new guess
const handleGuess = () => {
const guess = guessInputElement.value.trim().toUpperCase();
if (!isValidGuess(guess)) {
messageElement.textContent = 'Invalid guess. Please enter a valid 5-letter word.';
return;
}
if (guessesRemaining > 0 && !gameWon) {
// Update the game board with the guess
for (let i = 0; i < WORD_LENGTH; i++) {
gameBoard[MAX_GUESSES - guessesRemaining][i] = guess[i];
}
const result = evaluateGuess(guess);
guessResults.push(result);
renderGameBoard();
if (result.every(letterFeedback => letterFeedback === 'correct')) {
gameWon = true;
messageElement.textContent = 'Congratulations! You guessed the word!';
} else {
guessesRemaining--;
guessesRemainingElement.textContent = `Guesses Remaining: ${guessesRemaining}`;
if (guessesRemaining === 0) {
messageElement.textContent = `You ran out of guesses. The word was ${secretWord}.`;
}
}
guessInputElement.value = ''; // Clear the input field
}
};
// Event listener for the guess button
guessButtonElement.addEventListener('click', handleGuess);
// Initial render
renderGameBoard();
Let’s break down the JavaScript code:
- Get HTML Elements: We retrieve references to the HTML elements we need to interact with (the game board, input field, button, message area, and guesses remaining display). The `as HTML…` part is a type assertion, telling TypeScript the type of the element.
- `renderGameBoard()`: This function is responsible for dynamically creating and updating the game board in the HTML. It clears the board, then loops through the `gameBoard` 2D array, creating a row and square for each letter. It also applies the correct CSS classes (‘correct’, ‘present’, ‘absent’) based on the `guessResults`.
- `handleGuess()`: This function is the core of the game’s interaction. It’s called when the player clicks the “Guess” button. It performs the following steps:
- Gets the player’s guess from the input field.
- Validates the guess using `isValidGuess()`. If the guess is invalid, it displays an error message.
- If the guess is valid and the game is ongoing, it updates the `gameBoard` array with the guess.
- Calls `evaluateGuess()` to get the feedback for the guess.
- Adds the feedback to the `guessResults` array.
- Renders the updated game board using `renderGameBoard()`.
- Checks if the player has won (all letters are correct). If so, it displays a congratulatory message.
- If the player hasn’t won, it decrements the `guessesRemaining` counter and updates the display.
- If the player runs out of guesses, it reveals the secret word.
- Clears the input field for the next guess.
- Event Listener: We add an event listener to the “Guess” button, so that when the player clicks it, the `handleGuess()` function is executed.
- Initial Render: We call `renderGameBoard()` at the beginning to display an empty game board.
Compiling and Running the Game
Now it’s time to compile your TypeScript code and run the game. Open your terminal and run the following command in your project directory:
tsc
This command compiles your `index.ts` file into `index.js` and places the output in the `dist` folder, as defined by your `tsconfig.json`. Open `index.html` in your web browser. You should see the Wordle game interface. Try entering a five-letter word and clicking the “Guess” button. You should see feedback on your guess, and the game board will update. Keep guessing until you either guess the word correctly or run out of guesses.
Common Mistakes and How to Fix Them
Here are some common mistakes and how to avoid them:
- Incorrect File Paths: Make sure your HTML file correctly references the CSS and JavaScript files. Double-check the file paths in your `<link>` and `<script>` tags.
- Typos: TypeScript can help catch typos, but double-check your code for any spelling errors, especially in variable names and function calls.
- Case Sensitivity: Remember that JavaScript and TypeScript are case-sensitive. Make sure you use the correct capitalization for variable names, function names, and HTML element IDs.
- Incorrect CSS Classes: Ensure you’ve defined the CSS classes (‘correct’, ‘present’, ‘absent’) in your `style.css` file and that you are applying them correctly in your JavaScript code when updating the game board.
- Not Using `trim()`: The `trim()` method removes whitespace from the beginning and end of a string. Make sure to use it on the user’s input to remove any leading or trailing spaces that might cause validation issues.
- Forgetting to Clear the Input Field: After each guess, remember to clear the input field so the player can enter their next guess.
- Incorrect Logic in `evaluateGuess()`: Carefully review your logic in the `evaluateGuess()` function to ensure it correctly provides feedback. Pay close attention to preventing double-counting of letters.
- Not Handling Edge Cases: Consider edge cases, such as an empty word list or invalid input. Implement appropriate error handling.
Enhancements and Next Steps
This is a basic implementation of Wordle. Here are some ideas for enhancements:
- More Comprehensive Word List: Use a much larger word list for a more challenging game.
- Keyboard Input: Allow the player to enter letters using the keyboard.
- Error Handling: Implement more robust error handling (e.g., handling invalid input).
- Game Difficulty: Add options for different difficulty levels (e.g., longer words).
- User Interface Improvements: Enhance the user interface with animations and visual effects.
- Persistence: Save and load the game state so players can resume their games later. You could use local storage for this.
- Statistics: Track the player’s win/loss ratio, number of guesses per game, etc.
- Accessibility: Consider accessibility features for players with disabilities.
Key Takeaways
- TypeScript Fundamentals: You’ve learned how to use TypeScript to define types, write functions, and work with HTML elements.
- Game Logic: You’ve built the core game logic for Wordle, including guess validation and feedback.
- UI Development: You’ve created a simple user interface using HTML, CSS, and JavaScript.
- Problem-Solving: You’ve learned how to break down a problem (Wordle) into smaller, manageable parts and implement a solution.
- Development Workflow: You’ve learned the basic steps of setting up a TypeScript project, compiling code, and running a web application.
This tutorial provides a solid foundation for building your own Wordle game in TypeScript. Remember that the journey of a thousand lines of code begins with a single step. Keep experimenting, exploring new features, and refining your skills. The more you practice, the more confident and proficient you will become in TypeScript and web development. With each line of code, you’re not just building a game; you’re building your skills, your understanding, and your future in the world of software development. So, go forth and code!
