<div id="app"></div>
.memory-game {
  height: 300px;
  width: 300px;
  margin: auto;
  h1 {
    text-align: center;
  }
  @media screen and (min-width: 480px) {
    height: 400px;
    width: 400px;
  }
  @media screen and (min-width: 720px) {
    height: 500px;
    width: 500px;
  }
}

.game {
  text-align: center;
  background-color: #2196F3;
  height: 100%;
  &__board {
    display: grid;
    grid-template-columns: auto auto auto auto auto auto;
    padding: 5px;
    gap: 5px;
    margin-bottom: 1rem;
    &__card {
      background-color: rgba(255, 255, 255, 0.8);
      border: 1px solid transparent;
      border-radius: 4px;
      aspect-ratio: 1;
      position: relative;
      &--content {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        font-size: 30px;
      }
      &:hover {
        cursor: pointer;
        opacity: 0.8;
      }
    }
  }
}

.show {
  visibility: visible;
}

.hide {
  visibility: hidden;
}
View Compiled
/*
* https://frontendeval.com/questions/memory-game
*
* Create a card-matching memory game
*/

const Card = ({ key, num, choose, matched }) => {
  
  handleClick = (e) => {
    const isPairChosen = choose();
    e.target.firstChild.classList.remove('hide');
    e.target.classList.add('show');
  }
  
  return (
    <div>
      {
        matched ? <></> : (
          <div 
            className={`game__board__card`}
            onClick={ handleClick }
          >
            <div className='game__board__card--content hide'>{ num }</div>
          </div>
        )
      }
    </div>
  )
}

const Board = ({ rows, cols }) => {
  const totalCards = rows * cols;
  const [board, setBoard] = React.useState([]);
  const [c1, setC1] = React.useState(null);
  const [c2, setC2] = React.useState(null);
  const [currentKey, setCurrentKey] = React.useState(null);
  const [gameFinished, setGameFinished] = React.useState(false);
  
  React.useEffect(() => {
    const cards = shuffle(generateCards());
    setBoard(cards);
  }, [])
  
  React.useEffect(() => {
    // c1 & c2, c1 != c2 (hide and reset turn)
    // c1 & c2, c1 == c2 (disable and reset turn)
    
    if (c1 && c2) {
      document.querySelector('.game').style.pointerEvents = 'none';
      if (c1 !== c2) {
        setTimeout(() => {
          hideCards();
          resetTurn();
          document.querySelector('.game').style.pointerEvents = 'auto';
        }, 3000);
      } else { // they are equal, now remove them from the game
        setTimeout(() => {
          disableCards();
          resetTurn();
          checkWin();
          document.querySelector('.game').style.pointerEvents = 'auto';
        }, 3000);
      }
    }
  }, [c1, c2])
  
  hideCards = () => {
    document.querySelectorAll('.show').forEach(el => {
      el.firstChild.classList.remove('show');
      el.firstChild.classList.add('hide');
    });
  }
  
  disableCards = () => {
    // console.log('disabling cards');
    setBoard(prevBoard => {
      return prevBoard.map(card => {
        const { key, num, choose } = card.props;
        if (num === c1) {
          return <Card key={key} num={ num } choose={ choose } show={ false } matched={ true } />;
        } else {
          return card;
        }
      })
    })
  }
  
  resetTurn = () => {
    // console.log('resetting turn', c1, c2);
    setC1(null);
    setC2(null);
  }
  
  generateNumber = (i) => {
    return i % 2 == 0 ? Math.ceil((i+1)/2) : Math.ceil(i/2);
  }
  
  choose = (key, num) => {
    // console.log('clicked', num, 'key', key);
    if (key === currentKey || c2) {
      return;
    }
    
    setCurrentKey(key);
    c1 ? setC2(num) : setC1(num);
  }
  
  checkWin = () => {
    let isFinished = true;
    for (const card of board) {
      if (!card.props.matched) {
        isFinished = false;
        break;
      }
    }
    if (isFinished) { setGameFinished(true); }
  }
  
  resetGame = () => {
    setCurrentKey(null);
    setC1(null);
    setC2(null);
    setGameFinished(false);
    setBoard(shuffle(generateCards()));
  }
  
  generateCards = () => {
    return Array.from({ length: totalCards }, (_, i) => {
      const value = generateNumber(i);
      return (
        <Card 
          key={i} 
          num={ value } 
          choose={ () => choose(i, value) }
          matched={ false }
        />
      )
    })
  }
  
  shuffle = (unsortedArray) => {
    const shuffled = unsortedArray
      .map(value => ({ value, sort: Math.random() }))
      .sort((a, b) => a.sort - b.sort)
      .map(({ value }) => value);
    
    return shuffled;
  }
  
  return (
    <div className='game'>
      <div className='game__board'>
        { board.map(card => card) }
      </div>
      { gameFinished && <button onClick={ resetGame }>Play Again</button> }
    </div>
  )
}

const MemoryGame = () => {
  return (
    <div className='memory-game'>
      <h1>Memory Game 🧠</h1>
      <Board rows={6} cols={6} />
    </div>
  )
}

ReactDOM.render(<MemoryGame />, document.getElementById('app'));
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js