Build a Card Memory Game with React
Tags: react, javascript, webdev
Published: 2021-06-21 06:58:41


Very often we come across small games and wonder how complex is it? Can we build it? More often than not we do not go beyond it. In this post however we will build a simple memory game which is easy to play and also easy to develop.

The card memory game is a simple game to test the player’s memory. In a deck of paired cards, the player needs to select a matching pair in consecutive turns. The player wins the game when all matching pairs are selected.

A simple UI of it may look like this:

![UI reference for our final demo](https://cdn.hashnode.com/res/hashnode/image/upload/v1623414079982/7GmyKmELK.png)

### Let us define the Rules of the Game

We can’t make a game unless we know the rules. So lets state them here:

* We need a shuffled set of cards. There must be a pair of each card in our deck.

* The game must flip the cards clicked by the player. Maximum of two cards will show at a time.

* The game will handle matched and unmatched cards. Unmatched cards are flipped back after a short duration. Matched cards are removed from the deck.

* Every time a player selects a pair, the game will increment the current move count

* Once all pairs are found out, players sees a confirmation dialog with the score.

* Game provides a functionality to restart.

### So what are we waiting for... Lets get into it.

We first define our card structure. For a card we create an object with the type attribute and an image source.

```json
{
   type: 'Pickachu',
   image: require('../images/Pickachu.png')
}
```


Now the next step is to shuffle the deck of cards. **Ahh yes, this is the most important step. It is not really a memory game if we don’t shuffle**

### **1. Shuffle**

I will use [**Fisher-Yates shuffle algorithm**](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) for shuffling an array of cards.

```javascript
// Fisher Yates Shuffle
function swap(array, i, j) {
   const temp = array[i];
   array[i] = array[j];
   array[j] = temp;
}
function shuffleCards(array) {
   const length = array.length;
   for (let i = length; i > 0; i--) {
      const randomIndex = Math.floor(Math.random() * i);
      const currentIndex = i - 1;
      swap(array, currIndex, randomIndex)
   }
   return array;
}
```

### 2. Render board for the deck of cards

In this example we are using 12 cards(6 pairs). After shuffling our cards, we render them as a grid of 3x4. You can either choose to split your card deck into 3 arrays of 4 items each and render using a nested map or use CSS flexbox or grid. I will be using CSS Grid to render it since it is easier to handle updates with a one dimension array.

```javascript

export default function App({ uniqueCardsArray }) {
  const [cards, setCards] = useState(
    () => shuffleCards(uniqueCardsArray.concat(uniqueCardsArray))
  );
  
  const handleCardClick = (index) => {
    // We will handle it later
  };


  return (
    <div className="App">
      <header>
        <h3>Play the Flip card game</h3>
        <div>
          Select two cards with same content consequtively to make them vanish
        </div>
      </header>
      <div className="container">
        {cards.map((card, index) => {
          return (
            <Card
              key={index}
              card={card}
              index={index}
              onClick={handleCardClick}
            />
          );
        })}
      </div>
   </div>
  )
}
```

```css
  .container {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-template-rows: repeat(3, 1fr);
    justify-items: center;
    align-items: stretch;
    gap: 1rem;
  }
```

### 3. Flip Cards, evaluate match and count moves

The next step is to provide an interaction for the user to flip cards and evaluate if there is a match. For it we maintain the following states

* **openCards** to track the cards that have been flipped by the player

* **clearedCards** to track the cards that have matched and need to be removed from the deck

* **moves** to keep track of the moves made by the player.

```javascript
import { useEffect, useState, useRef } from "react";
import Card from "./card";
import uniqueElementsArray from './data';
import "./app.scss";

export default function App() {
  const [cards, setCards] = useState(
    () => shuffleCards(uniqueCardsArray.concat(uniqueCardsArray))
  );
  const [openCards, setOpenCards] = useState([]);
  const [clearedCards, setClearedCards] = useState({});
  const [moves, setMoves] = useState(0);
  const [showModal, setShowModal] = useState(false);
  const timeout = useRef(null);

  // Check if both the cards have same type. If they do, mark them inactive
  const evaluate = () => {
    const [first, second] = openCards;
    if (cards[first].type === cards[second].type) {
      setClearedCards((prev) => ({ ...prev, [cards[first].type]: true }));
      setOpenCards([]);
      return;
    }
    // Flip cards after a 500ms duration
    timeout.current = setTimeout(() => {
      setOpenCards([]);
    }, 500);
  };
  
  const handleCardClick = (index) => {
    // Have a maximum of 2 items in array at once.
    if (openCards.length === 1) {
      setOpenCards((prev) => [...prev, index]);
      // increase the moves once we opened a pair
      setMoves((moves) => moves + 1);
    } else {
      // If two cards are already open, we cancel timeout set for flipping cards back
      clearTimeout(timeout.current);
      setOpenCards([index]);
    }
  };

  useEffect(() => {
    if (openCards.length === 2) {
      setTimeout(evaluate, 500);
    }
  }, [openCards]);

  const checkIsFlipped = (index) => {
    return openCards.includes(index);
  };

  const checkIsInactive = (card) => {
    return Boolean(clearedCards[card.type]);
  };

  return (
    <div className="App">
      <header>
        <h3>Play the Flip card game</h3>
        <div>
          Select two cards with same content consequtively to make them vanish
        </div>
      </header>
      <div className="container">
        {cards.map((card, index) => {
          return (
            <Card
              key={index}
              card={card}
              index={index}
              isDisabled={shouldDisableAllCards}
              isInactive={checkIsInactive(card)}
              isFlipped={checkIsFlipped(index)}
              onClick={handleCardClick}
            />
          );
        })}
      </div>
    </div>
  );
}

```

At a time we shall only keep a maximum of two cards in **openCards** state. Since we have a static array and we aren’t actually deleting anything from our original cards array we can just store the index of the opened card in **openCards** state. Based on **openCards and clearedCards** state we pass a prop **isFlipped** or **isInactive** respectively to our Card component which it will then use to add the respective class.

> Do look at [this wonderful blog](https://3dtransforms.desandro.com/card-flip) which explains how to handle Flip Card Animation.
> **Note**: Since we add an animation to our cards for flipping, we evaluate a match after few seconds to allow for the flip transition.

### 4. Check for game completion

Every time we evaluate for a match, we check if all pairs have been found. If yes, we show the player a completion modal.

```javascript
  const checkCompletion = () => {
    // We are storing clearedCards as an object since its more efficient 
    //to search in an object instead of an array
    if (Object.keys(clearedCards).length === uniqueCardsArray.length) {
      setShowModal(true);
    }
  };
```

### **5. And finally, our restart functionality**

Well restarting is simple, we just **reset** our states and reshuffle our cards.

```javascript
<Button onClick={handleRestart} color="primary" variant="contained">
    Restart
</Button>
```

```javascript
  const handleRestart = () => {
    setClearedCards({});
    setOpenCards([]);
    setShowModal(false);
    setMoves(0);
    // set a shuffled deck of cards
    setCards(shuffleCards(uniqueCardsArray.concat(uniqueCardsArray)));
  };

```

Hurray! There we have our basic memory card game.

You can find the CodeSandbox Playground for the demo below

{% codesandbox clever-smoke-77b6s %}

### **Conclusion**

I am so glad we’ve reached this point. We created a shuffled deck, rendered it on a board, added a flip functionality and evaluated for a matching pair. We can extend this example to add a timer, add best score of the player and support level for higher numbers of cards as well.

You can check [this Github repository](https://github.com/mayankshubham/memory-card-game) for the full code.

If you liked this post do share it with your friends and if you have any suggestions or feedbacks feel free to add comments or [DM me on Twitter](https://twitter.com/ShubhamReacts). 

**Thank you for Reading**