Memory Match Challenge
The Memory Match Challenge is a fully interactive card-flipping game implemented as a single HTML file. Players match pairs of cards while enjoying smooth animations and responsive design. The app includes various difficulty levels and accessible game mechanics.
Prompt
Write a fully complete web app as a single HTML file. The app should be a memory matching game where players flip cards to find matching pairs of images. The app should include the following features:
- Card Layout:
- Display cards in a grid layout appropriate to the difficulty level:
- Easy: 4x2 grid (8 cards, 4 pairs)
- Medium: 4x4 grid (16 cards, 8 pairs)
- Hard: 6x4 grid (24 cards, 12 pairs)
 
- Ensure that each image has an exact matching pair (no more, no less).
 
- Display cards in a grid layout appropriate to the difficulty level:
- Images:
- Use a set of placeholder images such as emojis or Font Awesome icons for the card faces.
- Make sure that all pairs are correctly matched and no card is left without a matching pair.
 
- Card Appearance:
- The face-down cards (before flipping) should be visible with a distinct color or design (e.g., a patterned background or an icon) so that players can see the cards on the grid.
- When flipped, the card reveals its face image.
 
- Difficulty Levels:
- Allow the player to select a difficulty level before starting the game.
- Provide an option to return to the difficulty selection screen at any time.
 
- Game Mechanics:
- Shuffle the cards randomly at the start of each game.
- When a player clicks on a card, it flips to reveal the image.
- Only two cards can be flipped face-up at the same time.
- If the two flipped cards match, they remain face-up, and their appearance changes (e.g., fade out, change color) to indicate a successful match.
- If they do not match, they flip back after a short delay (e.g., 1 second).
 
- Scoring and Timing:
- Include a timer that starts when the first card is flipped and stops when all pairs are found.
- Display a counter for the number of attempts (each attempt counts when two cards have been flipped).
 
- User Interface:
- Provide a start/restart button.
- Include a message or modal that congratulates the player upon completing the game, displaying the total time taken and the number of attempts.
- Offer an option to play again or select a different difficulty level after the game ends.
- Use Tailwind CSS for styling to create a visually appealing and responsive design.
- Include smooth flip animations for the cards using CSS transitions.
- Ensure the app is mobile-friendly and responsive on different screen sizes.
 
- Accessibility:
- Use semantic HTML5 elements.
- Ensure accessibility features such as ARIA labels for better screen reader support.
 
Please provide the complete HTML file with embedded JavaScript and Tailwind CSS styling, all contained within a single file. Ensure that all features work correctly, that the face-down cards are visible before flipping, and that there are no unmatched cards in any difficulty level.
Expected Output
Below is the complete HTML file implementing the Memory Match Challenge:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Memory Match Challenge</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <style>
    /* Flip card styles */
    .flip-card {
      perspective: 1000px;
      width: 100%;
      height: 100%;
    }
    .flip-card-inner {
      transition: transform 0.6s;
      transform-style: preserve-3d;
      position: relative;
      width: 100%;
      height: 100%;
    }
    .flipped .flip-card-inner {
      transform: rotateY(180deg);
    }
    .matched .flip-card-inner {
      opacity: 0.6; /* Fade-out effect */
    }
    .flip-card-front, .flip-card-back {
      backface-visibility: hidden;
      position: absolute;
      width: 100%;
      height: 100%;
      top: 0;
      left: 0;
      border-radius: 0.5rem;
    }
    .flip-card-back {
      transform: rotateY(180deg);
    }
    .card-container {
      height: 80px;
    }
  </style>
</head>
<body class="bg-gray-100 flex flex-col items-center justify-center min-h-screen">
  <div class="w-full max-w-md mx-auto p-4">
    <h1 class="text-3xl font-bold mb-4 text-center">Memory Match Challenge</h1>
    <div id="start-screen" class="text-center">
      <p class="mb-4">Select Difficulty Level:</p>
      <button class="difficulty-btn bg-blue-500 text-white px-4 py-2 m-2 rounded" data-level="easy">Easy</button>
      <button class="difficulty-btn bg-yellow-500 text-white px-4 py-2 m-2 rounded" data-level="medium">Medium</button>
      <button class="difficulty-btn bg-red-500 text-white px-4 py-2 m-2 rounded" data-level="hard">Hard</button>
    </div>
    <div id="game-stats" class="hidden mb-4 flex justify-between items-center">
      <div>Time: <span id="timer">0</span>s</div>
      <div>Attempts: <span id="attempts">0</span></div>
      <button id="restart-btn" class="bg-gray-500 text-white px-2 py-1 rounded">Restart</button>
    </div>
    <div id="game-grid" class="grid gap-2"></div>
  </div>
  <script>
    const startScreen = document.getElementById('start-screen');
    const gameGrid = document.getElementById('game-grid');
    const timerEl = document.getElementById('timer');
    const attemptsEl = document.getElementById('attempts');
    const restartBtn = document.getElementById('restart-btn');
    let difficulty, cardSymbols, firstCard, secondCard, matchesFound, attempts, timer, timeElapsed;
    const symbols = ['🍎','🍌','🍒','🍇','🍉','🍓','🍍','🥝'];
    document.querySelectorAll('.difficulty-btn').forEach(button => {
      button.addEventListener('click', () => {
        difficulty = button.dataset.level;
        startGame();
      });
    });
    restartBtn.addEventListener('click', resetGame);
    function startGame() {
      startScreen.classList.add('hidden');
      resetGame();
    }
    function resetGame() {
      clearInterval(timer);
      [matchesFound, attempts, timeElapsed] = [0, 0, 0];
      timerEl.textContent = timeElapsed;
      attemptsEl.textContent = attempts;
      gameGrid.innerHTML = '';
      setupGame();
      startTimer();
    }
    function setupGame() {
      const pairs = difficulty === 'easy' ? 4 : difficulty === 'medium' ? 8 : 12;
      cardSymbols = symbols.slice(0, pairs).concat(symbols.slice(0, pairs)).sort(() => 0.5 - Math.random());
      gameGrid.className = `grid gap-2 grid-cols-${difficulty === 'hard' ? 6 : 4}`;
      cardSymbols.forEach(symbol => gameGrid.appendChild(createCard(symbol)));
    }
    function createCard(symbol) {
      const card = document.createElement('div');
      card.className = 'flip-card bg-blue-500 text-white rounded flex items-center justify-center text-4xl';
      card.textContent = '❓';
      card.dataset.symbol = symbol;
      card.addEventListener('click', () => flipCard(card));
      return card;
    }
    function flipCard(card) {
      if (firstCard && secondCard) return;
      card.textContent = card.dataset.symbol;
      if (!firstCard) return firstCard = card;
      secondCard = card;
      attemptsEl.textContent = ++attempts;
      checkMatch();
    }
    function checkMatch() {
      if (firstCard.dataset.symbol === secondCard.dataset.symbol) {
        matchesFound++;
        [firstCard, secondCard] = [null, null];
        if (matchesFound === cardSymbols.length / 2) endGame();
      } else {
        setTimeout(() => {
          firstCard.textContent = secondCard.textContent = '❓';
          [firstCard, secondCard] = [null, null];
        }, 1000);
      }
    }
    function startTimer() {
      timer = setInterval(() => timerEl.textContent = ++timeElapsed, 1000);
    }
    function endGame() {
      clearInterval(timer);
      alert(`You won! Time: ${timeElapsed}s, Attempts: ${attempts}`);
      resetGame();
    }
  </script>
</body>
</html>