Memory games, also known as concentration or pairs, are classic for a reason. They’re simple to understand, fun to play, and a great way to exercise your memory. In this tutorial, we’ll build a simple, interactive Memory game using TypeScript. This project is perfect for beginners and intermediate developers looking to deepen their understanding of TypeScript, learn about event handling, and practice DOM manipulation. We’ll cover everything from setting up the project to implementing game logic and handling user interactions.
Why Build a Memory Game?
Creating a Memory game offers several advantages for learning TypeScript:
- Practical Application: You’ll apply fundamental TypeScript concepts to a real-world, interactive project.
- Event Handling: You’ll learn how to handle user clicks and other events, a crucial skill in web development.
- DOM Manipulation: You’ll practice manipulating HTML elements dynamically, which is essential for creating interactive web applications.
- Code Organization: You’ll see how to structure your code for readability and maintainability.
- Fun! Building a game is an engaging way to learn and solidify your skills.
Setting Up the Project
Before we dive into the code, let’s set up our development environment. We’ll need:
- Node.js and npm (or yarn): For managing dependencies and running our development server.
- A Code Editor: Such as Visual Studio Code, Sublime Text, or Atom.
- TypeScript Compiler: We’ll install this using npm.
Step 1: Create a Project Directory
Create a new directory for your project and navigate into it using your terminal:
mkdir memory-game
cd memory-game
Step 2: Initialize npm
Initialize a new npm project:
npm init -y
This creates a package.json file, which will manage our project’s dependencies.
Step 3: Install TypeScript
Install the TypeScript compiler as a development dependency:
npm install --save-dev typescript
Step 4: Create TypeScript Configuration File
Create a tsconfig.json file in your project root. This file configures the TypeScript compiler. You can generate a default configuration using the following command:
npx tsc --init
This will create a tsconfig.json file. You can customize this file to suit your needs. For this project, a basic configuration will suffice. Here’s a sample tsconfig.json file:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
Step 5: Create HTML and TypeScript Files
Create the following files in your project directory:
index.html: This file will contain the HTML structure of our game.src/index.ts: This file will contain our TypeScript code. Create a folder namedsrcand placeindex.tsinside it.style.css: This will hold the styling for our game (optional, but recommended).
Building the HTML Structure
Let’s create the basic HTML structure for our Memory game in index.html. This will include a container for the game board, and potentially a score display and a reset button.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Memory Game</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>Memory Game</h1>
<div id="game-board" class="game-board"></div>
<div class="score-board">
<p>Score: <span id="score">0</span></p>
<button id="reset-button">Reset</button>
</div>
</div>
<script src="dist/index.js"></script>
</body>
</html>
This HTML provides:
- A container for the entire game.
- A heading for the game title.
- A
game-boarddiv where the cards will be displayed. - A
score-boardto display the player’s score and a reset button. - A link to a
style.cssfile for styling. - A link to the compiled JavaScript file (
dist/index.js).
Styling the Game with CSS
Let’s add some basic styling to style.css to make our game visually appealing. This is optional, but it significantly improves the user experience. Here’s a basic example. Feel free to customize it to your liking!
body {
font-family: sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f0f0f0;
}
.container {
text-align: center;
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.game-board {
display: grid;
grid-template-columns: repeat(4, 100px);
grid-gap: 10px;
margin-bottom: 20px;
}
.card {
width: 100px;
height: 100px;
background-color: #ccc;
border-radius: 8px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
font-size: 2em;
}
.card.flipped {
background-color: #fff;
border: 2px solid #333;
}
.score-board {
margin-bottom: 20px;
}
button {
padding: 10px 20px;
font-size: 1em;
cursor: pointer;
border: none;
background-color: #4CAF50;
color: white;
border-radius: 4px;
}
This CSS provides basic styling for the game container, the game board (using a grid layout), the cards, and the score board. It also includes styling for the flipped state of the cards and the reset button.
Writing the TypeScript Code
Now, let’s write the core logic for our Memory game in src/index.ts. We will break this down into smaller, manageable parts.
1. Defining Card Data
First, we’ll define an interface for our cards and create an array of card objects. Each card will have an ID, a value (the content it displays), and a flag to indicate if it’s been matched or flipped.
interface Card {
id: number;
value: string;
matched: boolean;
flipped: boolean;
}
const cardValues = [
"A", "B", "C", "D", "E", "F", "G", "H"
];
let cards: Card[] = [];
function initializeCards() {
cards = [];
const cardData = [...cardValues, ...cardValues]; // Duplicate the values for pairs
cardData.sort(() => 0.5 - Math.random()); // Shuffle the cards
cards = cardData.map((value, index) => ({
id: index,
value: value,
matched: false,
flipped: false,
}));
}
Here, we define a Card interface and then initialize the cards array. We duplicate the values in cardValues to create pairs. We shuffle the cards using the Fisher-Yates shuffle algorithm (simplified by using sort(() => 0.5 - Math.random())). The `initializeCards` function ensures the cards are correctly set up at the start of the game and when resetting.
2. Creating the Game Board
Next, we’ll create the HTML elements for each card and add them to the game board.
const gameBoard = document.getElementById('game-board') as HTMLElement;
const scoreDisplay = document.getElementById('score') as HTMLElement;
const resetButton = document.getElementById('reset-button') as HTMLButtonElement;
let score = 0;
let flippedCards: Card[] = [];
function createCardElement(card: Card): HTMLDivElement {
const cardElement = document.createElement('div');
cardElement.classList.add('card');
cardElement.dataset.id = card.id.toString();
cardElement.addEventListener('click', () => cardClickHandler(card));
return cardElement;
}
function renderCards() {
if (!gameBoard) return;
gameBoard.innerHTML = ''; // Clear the board
cards.forEach(card => {
const cardElement = createCardElement(card);
if (card.flipped) {
cardElement.classList.add('flipped');
cardElement.textContent = card.value;
}
gameBoard.appendChild(cardElement);
});
}
function updateScore(points: number) {
score += points;
if (scoreDisplay) {
scoreDisplay.textContent = score.toString();
}
}
function resetGame() {
score = 0;
updateScore(0);
initializeCards();
renderCards();
}
This code does the following:
- Gets references to the game board, score display, and reset button from the HTML.
- Defines variables for the score and the currently flipped cards.
- Creates a
createCardElementfunction to generate a div element for each card and adds a click event listener. - The
renderCardsfunction clears the game board and then iterates through thecardsarray, creating and appending card elements to the board. It also checks if a card should be initially flipped (when the game starts). - The
updateScoreandresetGamefunctions handle score updates and resetting the game state, respectively.
3. Handling Card Clicks and Game Logic
This is where the main game logic resides. We’ll handle card clicks, check for matches, and update the game state.
function cardClickHandler(card: Card) {
if (card.flipped || flippedCards.length === 2 || card.matched) return; // Prevent clicking the same card twice, or more than 2 cards.
card.flipped = true;
flippedCards.push(card);
renderCards();
if (flippedCards.length === 2) {
setTimeout(checkForMatch, 1000); // Check for match after a short delay
}
}
function checkForMatch() {
const [card1, card2] = flippedCards;
if (card1.value === card2.value) {
// Match found
card1.matched = true;
card2.matched = true;
updateScore(10);
} else {
// No match
card1.flipped = false;
card2.flipped = false;
}
flippedCards = []; // Reset the flipped cards array
renderCards();
}
This code does the following:
cardClickHandlerhandles the click event. It prevents clicking on the same card twice, or more than two cards at once. It sets the card’sflippedproperty to true, adds the card to theflippedCardsarray, and callsrenderCardsto update the display. If two cards are flipped, it callscheckForMatchafter a delay.checkForMatchchecks if the two flipped cards match. If they match, it sets theirmatchedproperties to true and updates the score. If they don’t match, it flips the cards back over. Finally, it clears theflippedCardsarray and callsrenderCardsto update the display.
4. Initializing and Running the Game
Finally, we need to initialize the game and add an event listener for the reset button.
function initializeGame() {
initializeCards();
renderCards();
if (resetButton) {
resetButton.addEventListener('click', resetGame);
}
}
initializeGame();
This code calls the initializeGame function, which initializes the cards, renders the initial game board, and attaches the resetGame function to the reset button’s click event.
5. Compiling and Running the Code
To compile your TypeScript code, run the following command in your terminal:
tsc
This will compile your src/index.ts file into dist/index.js. If you encounter errors, double-check your code for typos and ensure you’ve followed the steps correctly.
Now, open index.html in your browser. You should see the Memory game board. Click on cards to flip them over and try to find matching pairs!
Common Mistakes and How to Fix Them
Here are some common mistakes and how to avoid them:
- Incorrect File Paths: Double-check that your file paths in
index.htmland your import/export statements in your TypeScript code are correct. A common mistake is forgetting the./prefix when linking to local files. - Typographical Errors: TypeScript is case-sensitive. Carefully check your variable names, function names, and property names for any typos.
- Uninitialized Variables: Make sure all your variables are initialized before you use them. TypeScript can help catch this with the
strictflag in yourtsconfig.json. - Incorrect Event Handling: Ensure you’re attaching event listeners correctly and that your event handler functions are properly defined. Check that you are passing the correct arguments to your event handler.
- DOM Element Selection Errors: Make sure the elements you are trying to select with
document.getElementById()actually exist in your HTML. Use the browser’s developer tools to check for errors. Also, remember to use type assertions (e.g.,as HTMLElement) when selecting DOM elements to ensure type safety. - Incorrect Logic in
checkForMatch: A common mistake is not correctly handling the cases where cards do and do not match. Review the code carefully to ensure the game logic is correctly implemented. - Forgetting to Clear the
flippedCardsarray: Make sure you clear theflippedCardsarray after checking for a match or after the cards are flipped back over.
Key Takeaways
This tutorial has covered the following key concepts:
- Setting up a TypeScript project.
- Creating HTML structure and CSS styling.
- Defining interfaces for data structures.
- Using arrays and loops to manipulate data.
- Handling user events (click events).
- Manipulating the DOM to display and update the game.
- Implementing game logic (matching cards).
By building this Memory game, you’ve gained practical experience with fundamental TypeScript concepts and learned how to build a simple interactive web application.
FAQ
Q: How can I add more card values?
A: Simply add more strings to the cardValues array in your TypeScript code. Remember to duplicate the values when initializing the cards to create pairs.
Q: How can I change the number of cards on the board?
A: The number of cards is determined by the number of values in your cardValues array. To change the board size, modify the number of elements in the array. You’ll also want to adjust the grid-template-columns property in your CSS to fit the new number of cards.
Q: How can I add a timer to the game?
A: You can add a timer using the setInterval function. Create a variable to store the timer’s ID, and then within the checkForMatch function (or other appropriate places), start and stop the timer, and update a display element with the remaining time.
Q: How can I make the cards more visually appealing?
A: You can enhance the visual appeal by:
- Adding images to the cards instead of text.
- Using CSS transitions and animations to create a smoother flipping effect.
- Adding hover effects to the cards.
- Using a more visually appealing color scheme.
Q: How can I deploy this game online?
A: You can deploy the game by:
- Using a static site hosting service like Netlify or GitHub Pages.
- Building your project (
tsc), and then uploading theindex.html,dist/index.js, andstyle.cssfiles to the hosting service.
This simple Memory game provides a solid foundation for understanding the fundamentals of TypeScript and web development. As you continue to learn, you can expand upon this project, adding features, improving the user interface, and refining the game logic. Experiment with different features, such as adding a timer, score tracking, or different difficulty levels. The possibilities are endless, and the more you practice, the more confident you’ll become in your ability to build interactive web applications with TypeScript. Keep coding, keep learning, and enjoy the process of creating!
