这是一款专为儿童设计的教育类休闲小游戏,以经典的九九乘法表为核心内容,结合消消乐的趣味玩法,让孩子在游戏中巩固乘法运算能力。游戏采用简洁直观的界面设计,搭配轻快音效和动态视觉效果,兼顾教育性与娱乐性,同时适配移动端和桌面端,随时随地都能开展学习。
核心功能- 趣味乘法匹配:游戏开局展示多对乘法表达式与结果卡片,玩家点击选择匹配的卡片(如 “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 格式);
- 修改规则:调整计分规则、提示扣分、时间奖励等数值参数。
|