Creation chat
ClarkZhang
帮我做一个帮助小学生学习九九乘法表的小游戏
05-02 09:14
暂无回复
No comments yet
登录 后可以评论
Assistant
这是为您小学生学习九九乘法表设计的一个交互式小游戏HTML文件。它通过配对答题的方式,帮助巩固乘法口诀。
(HTML code has been generated and updated; see above for the current version.)
### 核心功能与设计亮点
游戏通过“看算式、选答案”的互动形式,让学习过程更轻松有趣。您可以这样操作:
1. **核心玩法**:页面中央会显示一道乘法题(如“3 × 4”),下方有四个选项按钮。您需要点击您认为正确的答案。
2. **即时反馈**:点击选项后,页面会立即给出“正确”或“再想想”的提示。答对时,选项按钮会变为绿色并锁定,同时得分增加;答错时,按钮会短暂变红,您可以继续尝试。
3. **学习进度**:页面顶部会清晰显示您的“得分”和“总答题次数”。每道题答对后,点击“下一题”按钮即可继续挑战,系统会随机生成新的乘法题目。
▼ Code
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>九九乘法表 · 小游戏</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>tailwind.config = { darkMode: 'class' }</script>
</head>
<body class="min-h-screen bg-slate-50 dark:bg-slate-900 text-slate-700 dark:text-slate-200 flex flex-col items-center justify-start font-sans select-none">
<!-- 主题切换按钮 (固定右上角) -->
<button id="theme-toggle-btn" class="fixed top-4 right-4 z-10 p-2 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 transition-colors flex items-center justify-center" aria-label="Toggle theme" onclick="toggleTheme()">
<svg id="icon-moon" class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998z"/></svg>
<svg id="icon-sun" class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true" style="display:none"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"></path></svg>
</button>
<!-- 自定义弹窗 (用于提示/确认) -->
<div id="custom-modal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4 hidden" aria-hidden="true">
<div class="rounded-xl bg-white dark:bg-slate-800 p-6 shadow-xl max-w-sm w-full border border-slate-200 dark:border-slate-700">
<p id="custom-modal-msg" class="text-slate-700 dark:text-slate-200 mb-4"></p>
<button type="button" onclick="hideModal()" class="w-full rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600">确定</button>
</div>
</div>
<!-- 主内容容器:显式高度,保证在iframe中可见 -->
<main class="w-full max-w-xl px-4 py-6 flex flex-col items-center min-h-[500px] h-[80vh] max-h-[700px]">
<!-- 标题与得分 -->
<header class="w-full flex items-center justify-between mb-4">
<h1 class="text-2xl font-bold text-slate-900 dark:text-slate-100 tracking-tight">✖️ 乘法小英雄</h1>
<div class="flex items-center gap-3 text-sm">
<span class="bg-white dark:bg-slate-800 px-3 py-1 rounded-full shadow-sm border border-slate-200 dark:border-slate-700">
✅ <span id="scoreDisplay">0</span>
</span>
<span class="bg-white dark:bg-slate-800 px-3 py-1 rounded-full shadow-sm border border-slate-200 dark:border-slate-700">
🔄 <span id="attemptDisplay">0</span>
</span>
</div>
</header>
<!-- 游戏卡片区域:显示算式 & 选项 -->
<section class="flex-1 w-full bg-white dark:bg-slate-800 rounded-2xl shadow-lg border border-slate-200 dark:border-slate-700 p-6 flex flex-col items-center justify-center gap-6 touch-none">
<!-- 当前算式 -->
<div class="text-center">
<p class="text-xs uppercase tracking-wider text-slate-500 dark:text-slate-400 mb-1">点击下方正确答案</p>
<p id="questionDisplay" class="text-5xl font-extrabold text-slate-900 dark:text-slate-100 bg-slate-100 dark:bg-slate-700 px-8 py-4 rounded-2xl inline-block min-w-[200px]">
3 × 4
</p>
</div>
<!-- 选项网格 (2x2) -->
<div class="grid grid-cols-2 gap-4 w-full max-w-xs">
<button class="option-btn bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white text-2xl font-bold py-5 rounded-xl shadow-md active:scale-95 transition-transform touch-action-manipulation" data-value="">?</button>
<button class="option-btn bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white text-2xl font-bold py-5 rounded-xl shadow-md active:scale-95 transition-transform touch-action-manipulation" data-value="">?</button>
<button class="option-btn bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white text-2xl font-bold py-5 rounded-xl shadow-md active:scale-95 transition-transform touch-action-manipulation" data-value="">?</button>
<button class="option-btn bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white text-2xl font-bold py-5 rounded-xl shadow-md active:scale-95 transition-transform touch-action-manipulation" data-value="">?</button>
</div>
<!-- 反馈信息 & 下一题按钮 -->
<div class="flex items-center gap-3">
<span id="feedbackMsg" class="text-lg font-medium text-slate-600 dark:text-slate-300 min-w-[80px]">🤔 选一个吧</span>
<button id="nextBtn" class="bg-emerald-600 hover:bg-emerald-700 dark:bg-emerald-500 dark:hover:bg-emerald-600 text-white px-5 py-2 rounded-xl shadow-md font-semibold transition-all active:scale-95 touch-action-manipulation disabled:opacity-40 disabled:pointer-events-none">
下一题 ➜
</button>
</div>
</section>
<!-- 底部小提示 -->
<footer class="mt-3 text-xs text-slate-400 dark:text-slate-500 text-center">
点击选项答题 · 答对得分 💪
</footer>
</main>
<script>
// ========== 主题切换 (由外部提供, 但需要内部函数) ==========
function toggleTheme(){ var el=document.documentElement; var isDark=!el.classList.contains('dark'); el.classList.toggle('dark',isDark); localStorage.setItem('theme',isDark?'dark':'light'); applyTheme(isDark); }
function applyTheme(isDark){ document.getElementById('icon-moon').style.display=isDark?'none':'block'; document.getElementById('icon-sun').style.display=isDark?'block':'none'; }
document.addEventListener('DOMContentLoaded',function(){ var saved=localStorage.getItem('theme'); var isDark=saved==='dark'||(!saved&&window.matchMedia('(prefers-color-scheme: dark)').matches); document.documentElement.classList.toggle('dark',isDark); applyTheme(isDark); });
// ========== 自定义弹窗 ==========
function showModal(msg){ var el=document.getElementById('custom-modal'); document.getElementById('custom-modal-msg').textContent=msg||''; el.classList.remove('hidden'); }
function hideModal(){ document.getElementById('custom-modal').classList.add('hidden'); }
// ========== 乘法表游戏逻辑 ==========
(function() {
'use strict';
// DOM 元素
const questionDisplay = document.getElementById('questionDisplay');
const optionBtns = document.querySelectorAll('.option-btn');
const feedbackMsg = document.getElementById('feedbackMsg');
const nextBtn = document.getElementById('nextBtn');
const scoreSpan = document.getElementById('scoreDisplay');
const attemptSpan = document.getElementById('attemptDisplay');
// 状态
let currentAnswer = 0; // 当前题目的正确答案
let score = 0; // 答对次数
let attempts = 0; // 总答题次数 (点击选项即算)
let isAnswered = false; // 当前题目是否已经答过 (防止重复得分)
let currentQuestionStr = ''; // 用于日志
// 辅助:生成随机整数 [min, max]
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// 生成一道新的乘法题 (1~9)
function generateQuestion() {
const a = randomInt(1, 9);
const b = randomInt(1, 9);
currentAnswer = a * b;
currentQuestionStr = `${a} × ${b}`;
questionDisplay.textContent = currentQuestionStr;
console.log(`[乘法] 新题目: ${currentQuestionStr} = ${currentAnswer}`);
return { a, b, answer: currentAnswer };
}
// 生成4个选项 (包含正确答案, 且尽量不重复)
function generateOptions(correct) {
// 用 Set 保证选项不重复
const opts = new Set([correct]);
// 生成3个干扰项: 在 correct±10 范围内随机, 且大于0, 且不与 correct 重复
while (opts.size < 4) {
// 随机偏移 -8 ~ +8, 但至少为1
let offset = randomInt(-8, 8);
let candidate = correct + offset;
if (candidate <= 0) candidate = randomInt(1, 20);
// 避免与正确答案相同 (Set自动去重)
opts.add(candidate);
}
// 转为数组并打乱顺序 (Fisher–Yates)
const arr = Array.from(opts);
for (let i = arr.length - 1; i > 0; i--) {
const j = randomInt(0, i);
[arr[i], arr[j]] = [arr[j], arr[i]];
}
// 确保正确答案在选项中 (Set已包含)
console.log(`[乘法] 选项: [${arr.join(', ')}] (正确答案: ${correct})`);
return arr;
}
// 刷新题目与选项
function refreshQuestion() {
// 重置答题状态
isAnswered = false;
feedbackMsg.textContent = '🤔 选一个吧';
nextBtn.disabled = true;
// 生成新题
const q = generateQuestion();
const options = generateOptions(q.answer);
// 更新按钮
optionBtns.forEach((btn, idx) => {
if (idx < options.length) {
btn.textContent = options[idx];
btn.dataset.value = options[idx];
// 恢复按钮样式 (移除正确/错误样式)
btn.className = 'option-btn bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white text-2xl font-bold py-5 rounded-xl shadow-md active:scale-95 transition-transform touch-action-manipulation';
btn.disabled = false;
}
});
console.log(`[乘法] 题目已刷新,等待作答`);
}
// 处理选项点击 (事件委托)
function handleOptionClick(e) {
const btn = e.currentTarget;
// 如果已答题 或 按钮被禁用,忽略
if (isAnswered || btn.disabled) return;
const selectedValue = parseInt(btn.dataset.value, 10);
if (isNaN(selectedValue)) return;
// 增加尝试次数
attempts++;
attemptSpan.textContent = attempts;
console.log(`[乘法] 用户选择: ${selectedValue}, 正确答案: ${currentAnswer}`);
// 判断正确
const isCorrect = (selectedValue === currentAnswer);
if (isCorrect) {
// 答对加分 (仅首次)
score++;
scoreSpan.textContent = score;
feedbackMsg.textContent = '🎉 完全正确!太棒啦!';
// 标记已答
isAnswered = true;
// 高亮正确按钮 (绿色)
btn.className = 'option-btn bg-emerald-500 hover:bg-emerald-600 dark:bg-emerald-400 dark:hover:bg-emerald-500 text-white text-2xl font-bold py-5 rounded-xl shadow-md active:scale-95 transition-transform touch-action-manipulation';
// 禁用所有选项
optionBtns.forEach(b => { b.disabled = true; });
// 启用下一题
nextBtn.disabled = false;
console.log(`[乘法] ✅ 答对了! 当前得分: ${score}`);
} else {
// 答错: 显示错误反馈, 但不禁用按钮, 可继续尝试
feedbackMsg.textContent = '❌ 再想想… 换一个试试?';
// 闪烁红色提示 (临时变红)
btn.className = 'option-btn bg-red-500 hover:bg-red-600 dark:bg-red-400 dark:hover:bg-red-500 text-white text-2xl font-bold py-5 rounded-xl shadow-md active:scale-95 transition-transform touch-action-manipulation';
// 0.3秒后恢复蓝色
setTimeout(() => {
if (!isAnswered) { // 如果还没答对, 恢复蓝色
btn.className = 'option-btn bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white text-2xl font-bold py-5 rounded-xl shadow-md active:scale-95 transition-transform touch-action-manipulation';
}
}, 300);
console.log(`[乘法] ❌ 答错了, 用户选了 ${selectedValue}`);
}
}
// 下一题
function nextQuestion() {
if (nextBtn.disabled) return; // 安全防护
refreshQuestion();
}
// 初始化: 绑定事件, 生成第一题
function initGame() {
// 绑定选项点击
optionBtns.forEach(btn => {
btn.addEventListener('click', handleOptionClick);
// 移动端触摸优化
btn.style.touchAction = 'manipulation';
});
// 绑定下一题按钮
nextBtn.addEventListener('click', nextQuestion);
// 生成第一题
refreshQuestion();
// 从 localStorage 恢复数据 (如有)
try {
const saved = localStorage.getItem('multiplicationGame');
if (saved) {
const data = JSON.parse(saved);
if (typeof data.score === 'number' && typeof data.attempts === 'number') {
score = data.score;
attempts = data.attempts;
scoreSpan.textContent = score;
attemptSpan.textContent = attempts;
console.log(`[存储] 恢复进度: 得分 ${score}, 尝试 ${attempts}`);
}
}
} catch (e) {
console.warn('[存储] 读取失败', e);
}
console.log('[乘法] 游戏已启动!');
}
// 保存进度到 localStorage (每次得分/尝试变化时调用)
function saveProgress() {
try {
localStorage.setItem('multiplicationGame', JSON.stringify({ score, attempts }));
} catch (e) {
console.warn('[存储] 保存失败', e);
}
}
// 在得分或尝试变化时保存 (通过 Proxy 或手动调用)
// 简单: 在 score/attempts 变更后手动调用
const origUpdateScore = (newScore) => { score = newScore; scoreSpan.textContent = score; saveProgress(); };
const origUpdateAttempts = (newAttempts) => { attempts = newAttempts; attemptSpan.textContent = attempts; saveProgress(); };
// 但为了保持代码简洁, 我们直接在 handleOptionClick 中更新后调用 saveProgress
// 重写部分: 在 handleOptionClick 中增加保存
// 由于上面已经定义, 我们利用闭包重新定义
// 实际上我们可以在 handleOptionClick 最后调用 saveProgress
// 但为了不破坏之前逻辑, 采用 patch 方式:
const originalHandle = handleOptionClick;
// 替换 handleOptionClick 以加入保存
// 但事件绑定已经用了原来的函数, 我们需要重新绑定?
// 更干净: 在 initGame 中重新绑定新函数, 但为了可读, 直接重写 handleOptionClick 的引用.
// 由于上面是函数声明, 我们可以覆盖:
// 但为了不混乱, 在 initGame 尾部重写监听.
// 最简单: 在 initGame 最后, 移除原有监听, 重新绑定一个新的 handleOptionClickWithSave.
// 我们就这样做:
// 新处理函数 (包含保存)
function handleOptionClickWithSave(e) {
const btn = e.currentTarget;
if (isAnswered || btn.disabled) return;
const selectedValue = parseInt(btn.dataset.value, 10);
if (isNaN(selectedValue)) return;
attempts++;
attemptSpan.textContent = attempts;
console.log(`[乘法] 用户选择: ${selectedValue}, 正确答案: ${currentAnswer}`);
const isCorrect = (selectedValue === currentAnswer);
if (isCorrect) {
score++;
scoreSpan.textContent = score;
feedbackMsg.textContent = '🎉 完全正确!太棒啦!';
isAnswered = true;
btn.className = 'option-btn bg-emerald-500 hover:bg-emerald-600 dark:bg-emerald-400 dark:hover:bg-emerald-500 text-white text-2xl font-bold py-5 rounded-xl shadow-md active:scale-95 transition-transform touch-action-manipulation';
optionBtns.forEach(b => { b.disabled = true; });
nextBtn.disabled = false;
console.log(`[乘法] ✅ 答对了! 当前得分: ${score}`);
} else {
feedbackMsg.textContent = '❌ 再想想… 换一个试试?';
btn.className = 'option-btn bg-red-500 hover:bg-red-600 dark:bg-red-400 dark:hover:bg-red-500 text-white text-2xl font-bold py-5 rounded-xl shadow-md active:scale-95 transition-transform touch-action-manipulation';
setTimeout(() => {
if (!isAnswered) {
btn.className = 'option-btn bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white text-2xl font-bold py-5 rounded-xl shadow-md active:scale-95 transition-transform touch-action-manipulation';
}
}, 300);
console.log(`[乘法] ❌ 答错了, 用户选了 ${selectedValue}`);
}
// 保存进度
saveProgress();
}
// 重新绑定
function rebindEvents() {
optionBtns.forEach(btn => {
btn.removeEventListener('click', handleOptionClick);
btn.removeEventListener('click', handleOptionClickWithSave);
btn.addEventListener('click', handleOptionClickWithSave);
});
// 下一题保存
const origNext = nextBtn._listeners ? null : null;
// 简单: 在 nextQuestion 中保存
const originalNextFn = nextQuestion;
nextBtn.removeEventListener('click', nextQuestion);
const newNext = function() {
if (nextBtn.disabled) return;
refreshQuestion();
saveProgress(); // 切换题目时也保存 (进度不变但无妨)
};
nextBtn.addEventListener('click', newNext);
// 保存引用避免GC
window._nextHandler = newNext;
}
// 覆盖 initGame
const originalInit = initGame;
initGame = function() {
// 调用原始逻辑 (但事件绑定会重复, 我们直接重写)
// 手动设置初始
refreshQuestion();
// 绑定新事件
optionBtns.forEach(btn => {
btn.addEventListener('click', handleOptionClickWithSave);
btn.style.touchAction = 'manipulation';
});
nextBtn.addEventListener('click', function() {
if (nextBtn.disabled) return;
refreshQuestion();
saveProgress();
});
// 恢复存储
try {
const saved = localStorage.getItem('multiplicationGame');
if (saved) {
const data = JSON.parse(saved);
if (typeof data.score === 'number' && typeof data.attempts === 'number') {
score = data.score;
attempts = data.attempts;
scoreSpan.textContent = score;
attemptSpan.textContent = attempts;
console.log(`[存储] 恢复进度: 得分 ${score}, 尝试 ${attempts}`);
}
}
} catch (e) { console.warn(e); }
console.log('[乘法] 游戏已启动!');
};
// 启动
initGame();
// 暴露一些变量便于调试
window.__game = { score, attempts, refreshQuestion, saveProgress };
})();
</script>
<!-- 提示用户 Console 位置 (可忽略) -->
<div class="fixed bottom-2 left-2 text-[10px] text-slate-400 dark:text-slate-600 opacity-60 pointer-events-none">Console 在右下角</div>
</body>
</html>
05-02 09:17
暂无回复
No comments yet
登录 后可以评论
Comments
暂无回复
No comments yet
登录 后可以评论