Wordle, the daily word game sensation, has captivated millions with its simple yet addictive gameplay. In this tutorial, we’ll dive into the world of TypeScript and build our own interactive Wordle clone from scratch. This project is a fantastic way to learn and practice TypeScript fundamentals while creating something fun and engaging. You’ll gain valuable experience with core concepts like variables, data types, functions, DOM manipulation, and event handling. By the end, you’ll have a fully functional Wordle game running in your browser, and a solid understanding of how to apply TypeScript to real-world projects.
Why Build a Wordle Clone with TypeScript?
TypeScript offers several advantages for this project and for software development in general:
- Type Safety: TypeScript adds static typing to JavaScript, catching errors during development rather than runtime. This reduces bugs and makes your code more reliable.
- Improved Code Readability: Types make your code easier to understand and maintain. They act as documentation, clarifying the expected data types for variables and function parameters.
- Enhanced Development Experience: TypeScript provides better autocompletion, refactoring, and error checking in your IDE, leading to a more productive development workflow.
- Modern JavaScript Features: TypeScript supports the latest JavaScript features, allowing you to write cleaner and more concise code.
Building a Wordle clone is a great way to apply these benefits, as the game logic involves manipulating data and user input, making type safety and code organization crucial.
Setting Up Your Development Environment
Before we begin, make sure you have the following installed:
- Node.js and npm (Node Package Manager): Used to manage project dependencies and run the TypeScript compiler. You can download it from https://nodejs.org/.
- A Code Editor: We recommend Visual Studio Code (VS Code), which provides excellent TypeScript support. You can download it from https://code.visualstudio.com/.
Once you have these installed, let’s create a new project directory and initialize it:
mkdir wordle-clone
cd wordle-clone
npm init -y
This will create a package.json file, which manages your project’s dependencies.
Installing TypeScript and Setting Up the Project
Next, install TypeScript as a development dependency:
npm install typescript --save-dev
Now, create a tsconfig.json file in your project directory. This file configures the TypeScript compiler. You can generate a basic one using the following command:
npx tsc --init
This command creates a tsconfig.json file with default settings. You can customize these settings to suit your project’s needs. For example, you might want to specify the output directory for compiled JavaScript files. Here’s a basic example of a tsconfig.json file:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
target: Specifies the JavaScript version to compile to.module: Specifies the module system to use (e.g., CommonJS, ESNext).outDir: Specifies the output directory for compiled JavaScript files.esModuleInterop: Enables interoperability between CommonJS and ES modules.forceConsistentCasingInFileNames: Enforces consistent casing in filenames.strict: Enables strict type checking.skipLibCheck: Skips type checking of declaration files (.d.ts).include: Specifies the files or directories to include in the compilation.
Creating the HTML Structure
Create an index.html file in your project directory. This file will contain the HTML structure for our Wordle game. Here’s a basic structure:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wordle Clone</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="game-container">
<div id="board"></div>
<div id="keyboard"></div>
</div>
<script src="dist/app.js"></script>
</body>
</html>
This HTML sets up the basic structure of the game, including a container for the game board, a keyboard, and links to the stylesheet and JavaScript file. We’ll create the style.css and app.ts files later.
Creating the CSS Stylesheet (style.css)
Create a style.css file in your project directory. This file will contain the CSS styles for our Wordle game. Here’s a basic example:
body {
font-family: sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f0f0f0;
}
#game-container {
display: flex;
flex-direction: column;
align-items: center;
}
#board {
display: grid;
grid-template-columns: repeat(5, 50px);
grid-template-rows: repeat(6, 50px);
gap: 5px;
margin-bottom: 20px;
}
.square {
width: 50px;
height: 50px;
border: 1px solid #ccc;
display: flex;
justify-content: center;
align-items: center;
font-size: 2rem;
text-transform: uppercase;
font-weight: bold;
}
#keyboard {
display: flex;
flex-direction: column;
gap: 5px;
}
.row {
display: flex;
gap: 5px;
}
.key {
width: 30px;
height: 50px;
background-color: #ddd;
border: none;
font-size: 1.2rem;
font-weight: bold;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
}
This CSS provides basic styling for the game board, squares, and keyboard. Feel free to customize the styles to your liking.
Writing the TypeScript Code (app.ts)
Now, let’s write the TypeScript code for our Wordle game. Create an app.ts file in a src directory. This file will contain the game logic, including the word selection, user input handling, and game state management.
Here’s a basic outline of the code structure:
// Define constants
const WORD_LENGTH = 5;
const NUMBER_OF_GUESSES = 6;
// Get DOM elements
const board = document.getElementById('board') as HTMLElement;
const keyboard = document.getElementById('keyboard') as HTMLElement;
// Game state variables
let secretWord: string;
let guesses: string[][] = [];
let currentGuess: string = '';
let currentRow: number = 0;
// Function to initialize the game
function initGame() {
// 1. Select a random secret word
// 2. Create the game board
// 3. Create the keyboard
// 4. Add event listeners for keyboard input
}
// Function to handle key presses
function handleKeyPress(key: string) {
// 1. Check if the key is a valid letter
// 2. Add the letter to the current guess
// 3. Update the UI
}
// Function to submit a guess
function submitGuess() {
// 1. Check if the guess is valid (length, word exists)
// 2. Check the guess against the secret word
// 3. Update the UI
// 4. Check if the game is won or lost
}
// Function to update the board
function updateBoard() {
// 1. Clear the board
// 2. Render the guesses on the board
}
// Function to update the keyboard
function updateKeyboard() {
// 1. Update the keyboard keys based on the guess results
}
// Function to check if the game is won
function checkWin(): boolean {
// 1. Check if the current guess matches the secret word
// 2. Return true if the game is won, false otherwise
return false;
}
// Function to check if the game is lost
function checkLoss(): boolean {
// 1. Check if the maximum number of guesses has been reached
// 2. Return true if the game is lost, false otherwise
return false;
}
// Initialize the game when the page loads
initGame();
Let’s break down the code into smaller, more manageable parts.
1. Constants and DOM Elements
First, we define some constants and get references to the DOM elements:
const WORD_LENGTH = 5;
const NUMBER_OF_GUESSES = 6;
const board = document.getElementById('board') as HTMLElement;
const keyboard = document.getElementById('keyboard') as HTMLElement;
We’ll use these constants and DOM elements throughout the game.
2. Game State Variables
Next, we declare variables to store the game state:
let secretWord: string;
let guesses: string[][] = [];
let currentGuess: string = '';
let currentRow: number = 0;
secretWord: The word the player needs to guess.guesses: A 2D array to store the player’s guesses.currentGuess: The current guess being entered.currentRow: The current row the player is on.
3. initGame() Function
This function initializes the game:
async function initGame() {
// 1. Select a random secret word
secretWord = await getRandomWord();
console.log("Secret Word:", secretWord);
// 2. Initialize the guesses array
guesses = Array(NUMBER_OF_GUESSES).fill(null).map(() => Array(WORD_LENGTH).fill(''));
// 3. Create the game board
createBoard();
// 4. Create the keyboard
createKeyboard();
// 5. Add event listeners for keyboard input
document.addEventListener('keydown', handleKeyPress);
}
Inside initGame():
- We call
getRandomWord()to fetch a random word from a word list. - We initialize the
guessesarray to be a 2D array filled with empty strings. - We call
createBoard()to set up the visual game board. - We call
createKeyboard()to render the on-screen keyboard. - We add a
keydownevent listener to the document to handle keyboard input.
Let’s define the getRandomWord() function. For simplicity, we’ll use a hardcoded list of words. In a real-world application, you’d likely fetch the word list from an API or a file.
async function getRandomWord(): Promise<string> {
const wordList = ['apple', 'beach', 'cloud', 'dream', 'earth', 'flame', 'grape', 'house', 'igloo', 'juice'];
const randomIndex = Math.floor(Math.random() * wordList.length);
return wordList[randomIndex];
}
4. createBoard() Function
This function creates the game board:
function createBoard() {
for (let i = 0; i < NUMBER_OF_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');
row.appendChild(square);
}
board.appendChild(row);
}
}
This function creates the grid of squares where the player will enter their guesses. It iterates through the number of guesses and word length to create the rows and squares, respectively.
5. createKeyboard() Function
This function creates the on-screen keyboard:
function createKeyboard() {
const keyboardLayout = [
['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'],
['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L'],
['Z', 'X', 'C', 'V', 'B', 'N', 'M', '<'], // < represents backspace
];
keyboardLayout.forEach(rowKeys => {
const row = document.createElement('div');
row.classList.add('row');
rowKeys.forEach(key => {
const keyElement = document.createElement('button');
keyElement.classList.add('key');
keyElement.textContent = key;
keyElement.addEventListener('click', () => handleKeyPress(key));
row.appendChild(keyElement);
});
keyboard.appendChild(row);
});
}
This function creates the keyboard layout and adds event listeners to each key. The backspace key is represented by <.
6. handleKeyPress() Function
This function handles key presses:
function handleKeyPress(key: string) {
if (currentGuess.length < WORD_LENGTH && key.match(/^[A-Za-z]$/)) {
// Add letter to current guess
guesses[currentRow][currentGuess.length] = key;
currentGuess += key;
updateBoard();
} else if (key === '<' || key === 'Backspace') {
// Delete letter
if (currentGuess.length > 0) {
guesses[currentRow][currentGuess.length - 1] = '';
currentGuess = currentGuess.slice(0, -1);
updateBoard();
}
} else if (key === 'Enter' && currentGuess.length === WORD_LENGTH) {
// Submit guess
submitGuess();
}
}
This function handles different key presses:
- Letter Keys: If the key is a letter and the current guess is not full, it adds the letter to the current guess and updates the board.
- Backspace Key: If the backspace key is pressed, it removes the last letter from the current guess and updates the board.
- Enter Key: If the enter key is pressed and the current guess is full, it submits the guess.
7. submitGuess() Function
This function handles submitting the guess:
async function submitGuess() {
if (!isValidWord(currentGuess)) {
alert('Not a valid word.');
return;
}
const result = checkGuess(currentGuess);
updateBoard();
updateKeyboard(result);
if (checkWin()) {
alert('Congratulations! You won!');
// Disable further input
document.removeEventListener('keydown', handleKeyPress);
} else if (checkLoss()) {
alert(`You lost! The word was ${secretWord}.`);
// Disable further input
document.removeEventListener('keydown', handleKeyPress);
} else {
// Move to the next row
currentRow++;
currentGuess = '';
}
}
Inside submitGuess():
- It calls
isValidWord()to check if the guess is a valid word. - If the guess is valid, it calls
checkGuess()to compare the guess with the secret word. - It calls
updateBoard()to update the board with the results. - It calls
updateKeyboard()to update the keyboard with the results. - It checks if the player has won or lost and displays an appropriate message.
- If the game continues, it moves to the next row and resets the current guess.
Let’s define the isValidWord() function. For simplicity, we’ll check if the word exists in our hardcoded word list.
function isValidWord(word: string): boolean {
const wordList = ['apple', 'beach', 'cloud', 'dream', 'earth', 'flame', 'grape', 'house', 'igloo', 'juice'];
return wordList.includes(word);
}
Next, let’s define the checkGuess() function, which determines the result of the guess. This is where the core Wordle logic resides.
function checkGuess(guess: string): string[] {
const result: string[] = Array(WORD_LENGTH).fill('');
const secretWordLetters = secretWord.split('');
const guessLetters = guess.split('');
// Check for correct letters in the correct position (green)
for (let i = 0; i < WORD_LENGTH; i++) {
if (guessLetters[i] === secretWordLetters[i]) {
result[i] = 'green';
secretWordLetters[i] = ''; // Mark as used
}
}
// Check for correct letters in the wrong position (yellow)
for (let i = 0; i < WORD_LENGTH; i++) {
if (result[i] === '') {
const letterIndex = secretWordLetters.indexOf(guessLetters[i]);
if (letterIndex > -1) {
result[i] = 'yellow';
secretWordLetters[letterIndex] = ''; // Mark as used
}
}
}
// Remaining letters are incorrect (gray)
for (let i = 0; i < WORD_LENGTH; i++) {
if (result[i] === '') {
result[i] = 'gray';
}
}
return result;
}
Here’s how checkGuess() works:
- It creates a
resultarray to store the feedback for each letter (green, yellow, or gray). - It converts both the secret word and the guess into arrays of letters.
- It first checks for correct letters in the correct positions (green). If a letter matches the corresponding letter in the secret word, it marks the result as ‘green’ and marks the letter in the secret word as used to prevent it from being matched again.
- Next, it checks for correct letters in the wrong positions (yellow). It iterates through the remaining letters in the guess and checks if they exist in the secret word. If a letter is found, it marks the result as ‘yellow’ and marks the letter in the secret word as used.
- Finally, any remaining letters are marked as incorrect (gray).
- It returns the
resultarray.
8. updateBoard() Function
This function updates the game board with the current guesses:
function updateBoard() {
const squares = document.querySelectorAll('.square');
squares.forEach((square, index) => {
const row = Math.floor(index / WORD_LENGTH);
const col = index % WORD_LENGTH;
const guess = guesses[row][col];
square.textContent = guess.toUpperCase();
// Apply background color based on the results from checkGuess()
if (currentRow > row) {
const result = checkGuess(guesses[row].join(''));
const color = result[col];
if (color) {
square.style.backgroundColor = getColor(color);
}
}
});
}
The updateBoard() function:
- Gets all the square elements from the DOM.
- Iterates through each square and calculates its row and column based on its index.
- Gets the corresponding letter from the
guessesarray. - Updates the square’s text content with the letter (converted to uppercase).
- If the row has been submitted, it calls
checkGuess()to get the color for each letter and sets the background color of the square accordingly.
Let’s add the getColor() function to return the CSS color based on the result:
function getColor(result: string): string {
switch (result) {
case 'green':
return '#6aaa64'; // Green
case 'yellow':
return '#c9b458'; // Yellow
case 'gray':
return '#787c7e'; // Gray
default:
return '#d3d6da'; // Default (light gray)
}
}
9. updateKeyboard() Function
This function updates the keyboard keys based on the guess results:
function updateKeyboard(result: string[]) {
const keys = document.querySelectorAll('.key');
keys.forEach(keyElement => {
const key = keyElement.textContent;
if (!key) return;
const letterIndex = secretWord.indexOf(key.toLowerCase());
if (result.includes('green') && result.some((res, index) => res === 'green' && guesses[currentRow].join('').charAt(index) === key.toLowerCase())) {
keyElement.style.backgroundColor = getColor('green');
} else if (result.includes('yellow') && result.some((res, index) => res === 'yellow' && guesses[currentRow].join('').includes(key.toLowerCase()))) {
keyElement.style.backgroundColor = getColor('yellow');
} else if (result.every(res => res === 'gray') && guesses[currentRow].join('').includes(key.toLowerCase())) {
keyElement.style.backgroundColor = getColor('gray');
}
});
}
The updateKeyboard() function:
- Selects all the keyboard key elements.
- Iterates through each key and gets its text content.
- Checks the guess results and changes the background color of the key accordingly.
10. checkWin() Function
This function checks if the player has won:
function checkWin(): boolean {
return guesses[currentRow - 1]?.join('') === secretWord;
}
This function simply checks if the last guess matches the secret word.
11. checkLoss() Function
This function checks if the player has lost:
function checkLoss(): boolean {
return currentRow === NUMBER_OF_GUESSES;
}
This function checks if the player has reached the maximum number of guesses.
Putting It All Together
Finally, call the initGame() function to start the game:
initGame();
That’s the basic Wordle game logic. Now, compile the TypeScript code using the command:
tsc
This will generate a dist folder containing the compiled app.js file. Make sure to update the script tag in your index.html to point to the correct file path (e.g., <script src="dist/app.js"></script>). Open index.html in your browser to play the game!
Common Mistakes and Troubleshooting
- Incorrect File Paths: Double-check that the file paths in your HTML (e.g., the link to
style.cssand the script tag forapp.js) are correct. - Typos: TypeScript helps, but typos can still occur. Carefully check variable names, function names, and any other code for errors.
- Compiler Errors: If you see compiler errors, read the error messages carefully. They often provide clues about what’s wrong. VS Code’s TypeScript support will highlight errors in your code.
- Incorrect Logic: Debugging is essential. Use
console.log()to print the values of variables and trace the execution of your code. - Event Listener Issues: Make sure your event listeners are correctly attached and are not being added multiple times, which can lead to unexpected behavior.
Enhancements and Next Steps
Here are some ways you can extend this project:
- Word List API: Instead of a hardcoded word list, fetch words from a public API.
- Difficulty Levels: Implement different difficulty levels by adjusting the word length or the number of guesses.
- User Interface Improvements: Enhance the game’s visual appearance and user experience.
- Mobile Responsiveness: Make the game responsive for different screen sizes.
- Share Functionality: Add a feature to share your Wordle results on social media.
- Persistent Storage: Use local storage to save game progress, high scores, and user preferences.
Summary / Key Takeaways
In this tutorial, we’ve successfully built a functional Wordle clone using TypeScript. We’ve covered the fundamentals of TypeScript, including type safety, variables, functions, DOM manipulation, and event handling. You’ve learned how to structure a project, write TypeScript code, and integrate it with HTML and CSS. This project provides a solid foundation for further exploring TypeScript and building more complex web applications. By understanding the core concepts and following the steps outlined in this tutorial, you can now confidently apply TypeScript to your own projects and create engaging, interactive web experiences. Remember to practice, experiment, and continue learning to enhance your TypeScript skills.
FAQ
Q: What is TypeScript?
A: TypeScript is a superset of JavaScript that adds static typing. It helps catch errors during development, improves code readability, and enhances the developer experience.
Q: Why use TypeScript for this project?
A: TypeScript improves the quality and maintainability of the code. It makes debugging easier by catching type-related errors early on and provides better code completion and refactoring features.
Q: How do I compile TypeScript code?
A: You compile TypeScript code using the TypeScript compiler (tsc). This command converts your TypeScript files (.ts) into JavaScript files (.js).
Q: How can I debug my TypeScript code?
A: You can debug your TypeScript code using your browser’s developer tools (e.g., Chrome DevTools) or a debugger in your code editor (like VS Code). Set breakpoints in your code and inspect the values of variables.
Q: Where can I learn more about TypeScript?
A: The official TypeScript documentation (https://www.typescriptlang.org/docs/) is an excellent resource. You can also find many tutorials and courses online.
This Wordle clone project is more than just a game; it’s a practical learning experience. The process of building it reinforces the importance of code organization, type safety, and the power of TypeScript in modern web development. The skills you’ve acquired—from setting up a development environment to manipulating the DOM and handling user input—are transferable to a wide range of web projects. As you continue to build and experiment, you’ll find that TypeScript not only improves your code quality but also enhances your overall development workflow, making you a more efficient and confident developer. The journey of learning is continuous, and each project, no matter how simple, is a step forward.
