评论

收藏

[Html/CSS] 豆包写的【九九乘法表消消乐】小游戏

开发技术 开发技术 发布于:2025-11-23 14:37 | 阅读数:1 | 评论:0

这是一款专为儿童设计的教育类休闲小游戏,以经典的九九乘法表为核心内容,结合消消乐的趣味玩法,让孩子在游戏中巩固乘法运算能力。游戏采用简洁直观的界面设计,搭配轻快音效和动态视觉效果,兼顾教育性与娱乐性,同时适配移动端和桌面端,随时随地都能开展学习。
Snipaste_2025-11-23_14-36-48.png
核心功能
  • 趣味乘法匹配:游戏开局展示多对乘法表达式与结果卡片,玩家点击选择匹配的卡片(如 “3×4” 和 “12”),匹配成功后卡片消失,消除所有卡片即可通关。
  • 关卡进阶机制:每通关一次解锁下一关,关卡难度随等级提升(每关新增 2 对卡片),逐步增加运算挑战难度。
  • 实时游戏数据:界面实时显示游戏时间、当前得分和关卡进度,时间越短完成通关,额外奖励分数越高,激发玩家高效解题。
  • 人性化提示功能:内置提示按钮,点击后可高亮一对匹配卡片(扣除少量分数),帮助玩家突破解题瓶颈,同时培养思考能力。
  • 沉浸式音效体验:点击 “开始游戏” 后自动播放循环背景音乐,匹配成功 / 失败有对应提示音,通关时自动播放胜利音乐,搭配彩色纸屑庆祝效果,提升游戏氛围感。
  • 防误触引导:未点击 “开始游戏” 时,点击卡片会弹出 “请先点开始游戏” 提示,避免误操作,优化新手体验。

技术特点
  • 无外部依赖:移除了 Font Awesome 等外部图标库,通过纯 CSS 绘制图标,本地化所有样式资源,加载速度更快,运行更稳定。
  • 响应式设计:采用 Tailwind CSS 构建界面,自动适配不同屏幕尺寸,从手机到电脑都能获得流畅的操作体验。
  • 轻量化开发:基于原生 HTML、CSS、JavaScript 开发,无需框架,代码结构清晰,可直接在浏览器中打开运行,无需额外安装。
  • 视觉交互优化:卡片 hover 效果、时间闪烁动画、按钮点击反馈等细节设计,提升操作手感和视觉体验。

适用场景
  • 家长辅导孩子学习乘法运算的趣味工具;
  • 小学低年级课堂的互动教学辅助材料;
  • 儿童课余时间的益智休闲活动,替代纯娱乐游戏,实现 “玩中学”。

游戏亮点
  • 教育性优先:聚焦九九乘法表核心知识点,游戏玩法完全服务于乘法运算练习,避免无关干扰内容;
  • 操作门槛低:规则简单易懂,无需复杂教程,儿童可独立上手,界面文字和图标清晰,适配低龄儿童认知水平;
  • 体验流畅完整:从开局引导、游戏进行到通关庆祝,形成完整的游戏闭环,搭配音效和动画,提升沉浸感。

html代码展示:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>九九乘法表消消乐</title>

    <link rel="stylesheet" href="https://www.codeae.com/data/attachment/forum/202511/23/codeae/css/min.css">
  <script src="www.codeae.com"></script>
  
  <!-- 配置Tailwind自定义样式 -->
  <script>
    tailwind.config = {
      theme: {
        extend: {
          colors: {
            primary: '#4F46E5',
            secondary: '#10B981',
            accent: '#EC4899',
            warning: '#F59E0B'
          },
          fontFamily: {
            title: ['"Comic Sans MS"', '"Marker Felt"', 'sans-serif'],
          }
        }
      }
    }
  </script>
  

</head>
<body class="bg-gradient-to-br from-indigo-50 to-purple-50 min-h-screen">
  <div class="container mx-auto px-4 py-8 max-w-5xl">
    <!-- 游戏标题 -->
    <h1 class="text-[clamp(1.8rem,5vw,3.5rem)] font-title text-primary font-bold text-center mb-2" style="font-weight: 900;">九九乘法表消消乐</h1>
    <p class="text-gray-600 text-center mb-6">找到匹配的乘法表达式和结果,消除所有方块获胜!</p>
    
    <!-- 游戏信息栏 -->
    <div class="bg-white rounded-xl shadow-lg p-4 mb-6 flex flex-wrap justify-between items-center border-2 border-primary/20">
      <div class="flex items-center text-gray-800 mb-2 sm:mb-0">
        <span class="icon icon-clock text-red-500 text-xl"></span>
        <span class="font-bold mr-1 text-lg">时间:</span>
        <span id="time" class="font-bold text-red-600 text-xl px-2 py-1 rounded">00:00</span>
      </div>
      <div class="flex items-center text-gray-800 mb-2 sm:mb-0">
        <span class="icon icon-star text-yellow-500 text-xl"></span>
        <span class="font-bold mr-1 text-lg">分数:</span>
        <span id="score" class="font-bold text-yellow-600 text-xl">0</span>
      </div>
      <div class="flex items-center text-gray-800">
        <span class="icon icon-trophy text-primary text-xl"></span>
        <span class="font-bold mr-1 text-lg">关卡:</span>
        <span id="level" class="font-bold text-primary text-xl">1</span>
      </div>
    </div>
    
    <!-- 游戏规则 -->
    <div class="bg-white rounded-lg shadow-md p-4 mb-6">
      <h2 class="text-lg font-semibold text-gray-800 mb-2">游戏规则</h2>
      <ul class="list-disc pl-5 text-gray-600 space-y-1">
        <li>所有卡片一开始就是翻开的明牌</li>
        <li>点击选择匹配的乘法表达式和结果 (如"3×4"和"12")</li>
        <li>成功匹配后方块消失</li>
        <li>消除所有方块即可通关</li>
        <li>用时越短得分越高</li>
      </ul>
    </div>
    
    <!-- 游戏区域 -->
    <div class="bg-white rounded-lg shadow-md p-6 mb-6">
      <div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4" id="game-board"></div>
    </div>
    
    <!-- 控制按钮 - 修复后 -->
    <div class="flex flex-wrap justify-center gap-4">
      <button id="start-btn" class="bg-primary hover:bg-primary/90 text-white px-6 py-3 rounded-lg font-medium btn-effect">
        <span class="icon icon-play"></span> 开始游戏
      </button>
      <button id="reset-btn" class="bg-gray-600 hover:bg-gray-700 text-white px-6 py-3 rounded-lg font-medium btn-effect">
        <span class="icon icon-refresh"></span> 重新开始
      </button>
      <button id="hint-btn" class="bg-warning hover:bg-warning/90 text-white px-6 py-3 rounded-lg font-medium btn-effect">
        <span class="icon icon-lightbulb"></span> 提示
      </button>
    </div>
  </div>
  
  <!-- 通关弹窗 -->
  <div id="result-modal" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 hidden">
    <div id="confetti-container" class="absolute inset-0 pointer-events-none"></div>
    <div class="bg-white rounded-2xl shadow-2xl p-8 max-w-md w-full mx-4 transform transition-all scale-100">
      <div class="text-center">
        <h2 class="text-3xl font-bold text-primary mb-6">恭喜通关!</h2>
        <div class="bg-gray-50 rounded-lg p-4 mb-6 text-left">
          <p class="text-lg mb-2"><span class="font-semibold">关卡:</span> <span id="result-level">1</span></p>
          <p class="text-lg mb-2"><span class="font-semibold">用时:</span> <span id="result-time">00:00</span></p>
          <p class="text-lg"><span class="font-semibold">得分:</span> <span id="result-score">0</span></p>
        </div>
        <div class="flex flex-col sm:flex-row justify-center gap-4">
          <button id="next-level-btn" class="bg-primary hover:bg-primary/90 text-white font-bold py-3 px-8 rounded-full shadow-lg transition-all duration-300 btn-effect">
            下一关
          </button>
          <button id="close-modal-btn" class="bg-gray-600 hover:bg-gray-700 text-white font-bold py-3 px-8 rounded-full shadow-lg transition-all duration-300 btn-effect">
            关闭
          </button>
        </div>
      </div>
    </div>
  </div>

  <script>
    // 游戏状态变量
    const gameState = {
      isPlaying: false,
      timer: null,
      seconds: 0,
      score: 0,
      level: 1,
      selectedCards: [],
      matchedPairs: 0,
      totalPairs: 12,
      cardsData: [],
      backgroundMusicPlaying: false // 标记背景音乐是否正在播放
    };
    
    // DOM元素
    const timeDisplay = document.getElementById('time');
    const scoreDisplay = document.getElementById('score');
    const levelDisplay = document.getElementById('level');
    const startBtn = document.getElementById('start-btn');
    const resetBtn = document.getElementById('reset-btn');
    const hintBtn = document.getElementById('hint-btn');
    const gameBoard = document.getElementById('game-board');
    const resultModal = document.getElementById('result-modal');
    const resultLevel = document.getElementById('result-level');
    const resultTime = document.getElementById('result-time');
    const resultScore = document.getElementById('result-score');
    const nextLevelBtn = document.getElementById('next-level-btn');
    const closeModalBtn = document.getElementById('close-modal-btn');
    const confettiContainer = document.getElementById('confetti-container');
    
    // 音效配置(新增背景音乐、优化胜利音乐播放时机)
    const sounds = {
      background: new Audio('https://www.codeae.com/data/attachment/forum/202511/23/codeae/audio/01.mp3'), // 轻快背景音乐
      match: new Audio('https://www.codeae.com/data/attachment/forum/202511/23/codeae/audio/02.mp3'), // 匹配成功音效
      wrong: new Audio('https://www.codeae.com/data/attachment/forum/202511/23/codeae/audio/03.mp3'), // 匹配失败音效
      hint: new Audio('https://www.codeae.com/data/attachment/forum/202511/23/codeae/audio/04.mp3'), // 提示音效
      victory: new Audio('https://www.codeae.com/data/attachment/forum/202511/23/codeae/audio/05.mp3') // 通关胜利音乐
    };
    
    // 初始化音效设置
    function initAudioSettings() {
      // 背景音乐设置:循环播放、音量适中
      sounds.background.loop = true;
      sounds.background.volume = 0.3;
      
      // 胜利音乐音量优化
      sounds.victory.volume = 0.7;
    }
    
    // 播放音效(通用函数,避免重复播放)
    function playSound(audio) {
      try {
        if (audio && audio.play) {
          // 重置播放位置(避免音效重叠)
          audio.currentTime = 0;
          audio.play().catch(e => console.log(`音效播放失败: ${e.message}`));
        }
      } catch (e) {
        console.log(`音效播放异常: ${e.message}`);
      }
    }
    
    // 生成乘法表达式对
    function generateUniquePairs(count) {
      const pairs = [];
      const usedExpressions = new Set();
      
      while (pairs.length < count) {
        const a = Math.floor(Math.random() * 9) + 1;
        const b = Math.floor(Math.random() * 9) + 1;
        const expression = `${a}×${b}`;
        
        if (!usedExpressions.has(expression)) {
          usedExpressions.add(expression);
          pairs.push({
            expression: expression,
            result: (a * b).toString(),
            color: cardColors[pairs.length % cardColors.length]
          });
        }
      }
      
      return pairs;
    }
    
    // 卡片颜色列表
    const cardColors = [
      'bg-green-400 text-gray-800',
      'bg-blue-400 text-white',
      'bg-purple-400 text-white',
      'bg-pink-400 text-white',
      'bg-yellow-400 text-gray-800',
      'bg-indigo-400 text-white',
      'bg-teal-400 text-white',
      'bg-rose-400 text-white',
      'bg-amber-400 text-gray-800',
      'bg-emerald-400 text-white',
      'bg-sky-400 text-white',
      'bg-violet-400 text-white'
    ];
    
    // 创建游戏卡片
    function createGameCards() {
      gameBoard.innerHTML = '';
      gameState.cardsData = [];
      
      const pairsCount = 12 + (gameState.level - 1) * 2;
      gameState.totalPairs = pairsCount;
      
      const pairs = generateUniquePairs(pairsCount);
      
      pairs.forEach(pair => {
        gameState.cardsData.push({
          id: `exp-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
          content: pair.expression,
          type: 'expression',
          value: pair.expression,
          result: pair.result,
          color: pair.color,
          matched: false
        });
        
        gameState.cardsData.push({
          id: `res-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
          content: pair.result,
          type: 'result',
          value: pair.result,
          expression: pair.expression,
          color: pair.color,
          matched: false
        });
      });
      
      shuffleArray(gameState.cardsData);
      
      gameState.cardsData.forEach(card => {
        const cardElement = document.createElement('div');
        cardElement.id = card.id;
        cardElement.className = `${card.color} rounded-lg p-4 text-center text-lg font-semibold shadow-md card-hover`;
        cardElement.textContent = card.content;
        cardElement.dataset.type = card.type;
        cardElement.dataset.value = card.value;
        
        gameBoard.appendChild(cardElement);
      });
    }
    
    // 初始化游戏
    function initGame() {
      // 停止背景音乐(重置游戏时)
      if (gameState.backgroundMusicPlaying) {
        sounds.background.pause();
        gameState.backgroundMusicPlaying = false;
      }
      
      gameState.isPlaying = false;
      gameState.seconds = 0;
      gameState.selectedCards = [];
      gameState.matchedPairs = 0;
      
      timeDisplay.className = 'font-bold text-red-600 text-xl px-2 py-1 rounded';
      
      createGameCards();
      
      updateTimeDisplay();
      updateScoreDisplay();
      levelDisplay.textContent = gameState.level;
      
      // 为卡片添加点击事件
      document.querySelectorAll('#game-board > div').forEach(card => {
        card.addEventListener('click', () => handleCardClick(card));
      });
    }
    
    // 处理卡片点击
    function handleCardClick(card) {
      // 未开始游戏时,点击卡片弹出提示
      if (!gameState.isPlaying) {
        alert('请先点开始游戏');
        return;
      }
      
      // 忽略已匹配的卡片
      const cardData = gameState.cardsData.find(c => c.id === card.id);
      if (cardData && cardData.matched) return;
      
      // 已经选中两张卡片时不处理
      if (gameState.selectedCards.length >= 2) return;
      
      // 选中当前卡片
      card.classList.add('ring-4', 'ring-yellow-400', 'scale-105');
      gameState.selectedCards.push(card);
      
      // 当选中两张卡片时检查是否匹配
      if (gameState.selectedCards.length === 2) {
        setTimeout(checkForMatch, 500);
      }
    }
    
    // 检查匹配
    function checkForMatch() {
      const [card1, card2] = gameState.selectedCards;
      const card1Data = gameState.cardsData.find(c => c.id === card1.id);
      const card2Data = gameState.cardsData.find(c => c.id === card2.id);
      
      card1.classList.remove('ring-4', 'ring-yellow-400', 'scale-105');
      card2.classList.remove('ring-4', 'ring-yellow-400', 'scale-105');
      
      let isMatch = false;
      
      if (card1Data && card2Data) {
        if ((card1Data.type === 'expression' && card2Data.type === 'result' && 
           card1Data.result === card2Data.value) ||
          (card2Data.type === 'expression' && card1Data.type === 'result' && 
           card2Data.result === card1Data.value)) {
          isMatch = true;
        }
      }
      
      if (isMatch) {
        playSound(sounds.match);
        
        if (card1Data) card1Data.matched = true;
        if (card2Data) card2Data.matched = true;
        
        card1.classList.add('opacity-0', 'scale-90', 'pointer-events-none', 'transition-all', 'duration-300');
        card2.classList.add('opacity-0', 'scale-90', 'pointer-events-none', 'transition-all', 'duration-300');
        
        const timeBonus = Math.max(10, 50 - Math.floor(gameState.seconds / 10));
        gameState.score += 10 + timeBonus;
        gameState.matchedPairs++;
        updateScoreDisplay();
        
        // 所有卡片匹配完成 → 立即通关(自动播放胜利音乐)
        if (gameState.matchedPairs === gameState.totalPairs) {
          endGame(true);
        }
      } else {
        playSound(sounds.wrong);
        
        if (gameState.score > 0) {
          gameState.score = Math.max(0, gameState.score - 5);
          updateScoreDisplay();
        }
      }
      
      gameState.selectedCards = [];
    }
    
    // 开始游戏(新增背景音乐播放)
    function startGame() {
      if (gameState.isPlaying) return;
      
      gameState.isPlaying = true;
      startBtn.disabled = true;
      startBtn.classList.add('opacity-70', 'cursor-not-allowed');
      
      // 播放背景音乐(仅当未播放时)
      if (!gameState.backgroundMusicPlaying) {
        playSound(sounds.background);
        gameState.backgroundMusicPlaying = true;
      }
      
      timeDisplay.classList.add('time-flash-strong');
      
      gameState.timer = setInterval(() => {
        gameState.seconds++;
        updateTimeDisplay();
        
        if (gameState.seconds > 30) {
          timeDisplay.classList.remove('time-flash-strong');
          timeDisplay.classList.add('time-flash-urgent', 'text-red-800');
        } else if (gameState.seconds > 15) {
          timeDisplay.classList.add('text-red-700');
        }
      }, 1000);
    }
    
    // 重置游戏
    function resetGame() {
      clearInterval(gameState.timer);
      timeDisplay.classList.remove('time-flash-strong', 'time-flash-urgent', 'text-red-700', 'text-red-800');
      
      initGame();
      
      startBtn.disabled = false;
      startBtn.classList.remove('opacity-70', 'cursor-not-allowed');
      
      resultModal.classList.add('hidden');
    }
    
    // 下一关
    function nextLevel() {
      // 停止胜利音乐,继续播放背景音乐
      sounds.victory.pause();
      
      gameState.level++;
      gameState.score += 100;
      
      initGame();
      // 重新开始游戏时自动播放背景音乐
      gameState.isPlaying = true;
      startBtn.disabled = true;
      startBtn.classList.add('opacity-70', 'cursor-not-allowed');
      
      if (!gameState.backgroundMusicPlaying) {
        playSound(sounds.background);
        gameState.backgroundMusicPlaying = true;
      }
      
      timeDisplay.classList.add('time-flash-strong');
      gameState.timer = setInterval(() => {
        gameState.seconds++;
        updateTimeDisplay();
        
        if (gameState.seconds > 30) {
          timeDisplay.classList.remove('time-flash-strong');
          timeDisplay.classList.add('time-flash-urgent', 'text-red-800');
        } else if (gameState.seconds > 15) {
          timeDisplay.classList.add('text-red-700');
        }
      }, 1000);
      
      resultModal.classList.add('hidden');
    }
    
    // 结束游戏(通关时自动播放胜利音乐)
    function endGame(isWin) {
      clearInterval(gameState.timer);
      gameState.isPlaying = false;
      
      // 停止背景音乐,播放胜利音乐
      sounds.background.pause();
      gameState.backgroundMusicPlaying = false;
      
      // 停止时间动画
      timeDisplay.classList.remove('time-flash-strong', 'time-flash-urgent', 'text-red-700', 'text-red-800');
      
      startBtn.disabled = false;
      startBtn.classList.remove('opacity-70', 'cursor-not-allowed');
      
      if (isWin) {
        resultLevel.textContent = gameState.level;
        resultTime.textContent = formatTime(gameState.seconds);
        resultScore.textContent = gameState.score;
        
        createConfetti();
        // 通关时立即播放胜利音乐(无需点击弹窗)
        playSound(sounds.victory);
        
        // 显示通关弹窗
        setTimeout(() => {
          resultModal.classList.remove('hidden');
        }, 800);
      }
    }
    
    // 提示功能
    function showHint() {
      if (!gameState.isPlaying) {
        alert('请先点击"开始游戏"!');
        return;
      }
      
      playSound(sounds.hint);
      
      const unmatchedCards = gameState.cardsData.filter(card => !card.matched);
      
      if (unmatchedCards.length === 0) {
        alert('所有卡片都已匹配完成!');
        return;
      }
      
      let matchingPair = null;
      for (let i = 0; i < unmatchedCards.length; i++) {
        for (let j = i + 1; j < unmatchedCards.length; j++) {
          const card1 = unmatchedCards[i];
          const card2 = unmatchedCards[j];
          
          if (
            (card1.type === 'expression' && card2.type === 'result' && card1.result === card2.value) ||
            (card2.type === 'expression' && card1.type === 'result' && card2.result === card1.value)
          ) {
            matchingPair = [card1, card2];
            break;
          }
        }
        if (matchingPair) break;
      }
      
      if (matchingPair) {
        const [card1, card2] = matchingPair;
        const elem1 = document.getElementById(card1.id);
        const elem2 = document.getElementById(card2.id);
        
        if (elem1 && elem2) {
          elem1.classList.add('animate-pulse', 'ring-4', 'ring-red-500');
          elem2.classList.add('animate-pulse', 'ring-4', 'ring-red-500');
          
          setTimeout(() => {
            elem1.classList.remove('animate-pulse', 'ring-4', 'ring-red-500');
            elem2.classList.remove('animate-pulse', 'ring-4', 'ring-red-500');
          }, 3000);
        }
        
        gameState.score = Math.max(0, gameState.score - 10);
        updateScoreDisplay();
      } else {
        alert('没有找到可匹配的卡片!');
      }
    }
    
    // 创建庆祝效果
    function createConfetti() {
      confettiContainer.innerHTML = '';
      const colors = ['#4F46E5', '#EC4899', '#F59E0B', '#10B981', '#8B5CF6'];
      
      for (let i = 0; i < 100; i++) {
        const confetti = document.createElement('div');
        confetti.className = 'confetti';
        confetti.style.left = `${Math.random() * 100}%`;
        confetti.style.width = `${5 + Math.random() * 10}px`;
        confetti.style.height = `${5 + Math.random() * 10}px`;
        confetti.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
        confetti.style.animationDelay = `${Math.random() * 2}s`;
        confetti.style.animationDuration = `${2 + Math.random() * 3}s`;
        confettiContainer.appendChild(confetti);
      }
    }
    
    // 格式化时间
    function formatTime(seconds) {
      const mins = Math.floor(seconds / 60);
      const secs = seconds % 60;
      return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
    }
    
    // 更新时间显示
    function updateTimeDisplay() {
      timeDisplay.textContent = formatTime(gameState.seconds);
    }
    
    // 更新分数显示
    function updateScoreDisplay() {
      scoreDisplay.textContent = gameState.score;
    }
    
    // 打乱数组
    function shuffleArray(array) {
      for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
      }
      return array;
    }
    
    // 页面加载完成后初始化音效和游戏
    document.addEventListener('DOMContentLoaded', () => {
      initAudioSettings();
      initGame();
    });
    
    // 事件监听
    startBtn.addEventListener('click', startGame);
    resetBtn.addEventListener('click', resetGame);
    hintBtn.addEventListener('click', showHint);
    nextLevelBtn.addEventListener('click', nextLevel);
    closeModalBtn.addEventListener('click', () => {
      // 关闭弹窗时停止胜利音乐
      sounds.victory.pause();
      resultModal.classList.add('hidden');
    });
  </script>
</body>
</html>
该项目基于 原生 HTML+CSS+JavaScript 开发,搭配 Tailwind CSS 实现样式快速构建,整体代码结构清晰、轻量化,无复杂框架依赖,核心分为「界面布局、样式设计、游戏逻辑、音效控制」四大模块,以下是详细说明:
一、整体文件结构
九九乘法表消消乐/
├─ index.html  # 核心文件(界面布局+样式+逻辑一体化)
└─ 无额外依赖文件  # 所有资源本地化,直接浏览器打开即可运行
二、核心代码模块拆解
1. 界面布局(HTML 部分)采用语义化标签构建,结构分层清晰,主要包含 5 个核心区域:
  • 标题与规则区:游戏名称、玩法说明,采用响应式文字适配不同屏幕;
  • 游戏信息栏:显示时间、分数、关卡数据,用图标 + 文字组合提升可读性;
  • 游戏主区域:通过 CSS Grid 布局卡片容器,动态渲染乘法卡片;
  • 控制按钮区:开始游戏、重新开始、提示 3 个核心操作按钮,支持移动端点击;
  • 通关弹窗:隐藏状态,通关后显示战绩并触发庆祝效果。
关键布局代码示例:
<!-- 游戏卡片容器(响应式网格布局) -->
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4" id="game-board"></div>

<!-- 控制按钮(Flex布局居中,支持换行) -->
<div class="flex flex-wrap justify-center gap-4">
  <button id="start-btn" class="bg-primary ...">开始游戏</button>
  <button id="reset-btn" class="bg-gray-600 ...">重新开始</button>
  <button id="hint-btn" class="bg-warning ...">提示</button>
</div>

2. 样式设计(CSS+Tailwind)
  • Tailwind 配置:扩展自定义颜色(主题色、辅助色)和字体,适配儿童视觉偏好;
  • 本地图标实现:无需外部图标库,通过 CSS 伪元素 + Unicode 符号绘制时钟、星星、播放等图标,解决依赖问题;
  • 交互样式:卡片 hover 放大、按钮点击反馈、时间闪烁动画(紧急时加速闪烁),提升操作手感;
  • 响应式适配:通过 Tailwind 的栅格系统(grid-cols-*)和媒体查询,自动适配手机 / 平板 / 电脑屏幕。

3. 游戏核心逻辑(JavaScript 部分)采用面向过程设计,核心逻辑清晰,无复杂类封装,主要分为 6 个功能模块:
(1)游戏状态管理用全局对象gameState存储关键数据,便于统一维护:
const gameState = {
  isPlaying: false, // 是否游戏中
  timer: null, // 计时定时器
  seconds: 0, // 已用时间
  score: 0, // 分数
  level: 1, // 当前关卡
  selectedCards: [], // 选中的卡片
  matchedPairs: 0, // 已匹配对数
  totalPairs: 12, // 总匹配对数(随关卡递增)
  backgroundMusicPlaying: false // 背景音乐状态
};
(2)卡片数据生成
  • 随机生成不重复的乘法表达式(1×1 到 9×9);
  • 每对包含「表达式卡片」和「结果卡片」,绑定对应颜色和唯一 ID;
  • shuffleArray函数打乱卡片顺序,保证游戏随机性。
(3)交互逻辑
  • 卡片点击:未开始游戏时弹出提示,已选中 2 张时禁止继续选择,匹配成功则卡片消失;
  • 匹配判断:通过卡片类型(expression/result)和数值对比,验证是否匹配;
  • 提示功能:遍历未匹配卡片,高亮一对正确组合,扣除少量分数;
  • 通关判断:当已匹配对数等于总对数时,触发通关逻辑。
(4)计时与计分
  • 计时:游戏开始后启动定时器,实时更新时间显示,超过 15 秒 / 30 秒触发不同闪烁效果;
  • 计分:匹配成功 + 基础分 + 时间奖励(用时越短奖励越高),匹配失败 / 使用提示扣除少量分数。
(5)关卡进阶
  • 每通关一次,关卡 + 1,总匹配对数 + 2(提升难度);
  • 下一关自动初始化新卡片,保留当前分数并添加通关奖励。
(6)庆祝效果
  • 通关时生成彩色纸屑动画(通过动态创建 DOM 元素 + CSS 动画实现);
  • 自动播放胜利音乐,弹窗显示战绩。

4. 音效控制
  • 内置 4 类音效:背景音乐、匹配成功、匹配失败、提示、通关胜利;
  • 音效优化:重置播放位置避免重叠,控制音量比例(背景 30%、胜利 70%),关键操作音效优先;
  • 状态联动:开始游戏自动播放背景音乐,通关时暂停背景、播放胜利音乐,关闭弹窗停止胜利音乐。

三、代码核心亮点
  • 轻量化:无任何外部依赖(除 Tailwind CDN,可本地替换),代码总行数约 500 行,便于修改和二次开发;
  • 易维护:功能模块拆分明确,变量命名语义化,无冗余代码,新手也能快速理解;
  • 兼容性强:基于原生 API 开发,支持所有现代浏览器,无需担心兼容性问题;
  • 体验友好:添加防误触提示、操作反馈、动画过渡,细节处理到位,适配低龄用户。

四、运行与修改说明
  • 运行方式:直接用浏览器打开index.html文件,无需额外配置;
  • 自定义修改:
    • 调整关卡难度:修改generateUniquePairs函数中的初始对数或递增数量;
    • 更换颜色:修改cardColors数组中的 Tailwind 颜色类;
    • 调整音效:替换sounds对象中的音频链接(支持 MP3 格式);
    • 修改规则:调整计分规则、提示扣分、时间奖励等数值参数。