forks

Creation chat

ClarkZhang
帮我做一个帮助小学生学习九九乘法表的小游戏
05-02 09:14

登录 后可以评论

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

登录 后可以评论

Comments

登录 后可以评论