🎮ArcadeLab

🔐 数字解码器

by SparkStar82
816 lines27.2 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>🔐 数字解码器</title>
    <style>
        /* ===== 全局重置 & 基础 ===== */
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            background: linear-gradient(145deg, #0b1a2e 0%, #1a2f44 100%);
            font-family: 'Segoe UI', Roboto, system-ui, -apple-system, sans-serif;
            padding: 20px;
            margin: 0;
        }

        /* ===== 主卡片 ===== */
        .game-container {
            max-width: 720px;
            width: 100%;
            background: rgba(255, 255, 255, 0.07);
            backdrop-filter: blur(16px);
            -webkit-backdrop-filter: blur(16px);
            border-radius: 48px;
            padding: 36px 32px 40px;
            box-shadow: 0 30px 60px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.08);
            border: 1px solid rgba(255, 255, 255, 0.05);
            transition: all 0.3s;
        }

        /* ===== 头部 ===== */
        .header {
            text-align: center;
            margin-bottom: 28px;
        }

        .header h1 {
            font-size: 28px;
            font-weight: 700;
            letter-spacing: 1px;
            color: #f0f7ff;
            text-shadow: 0 2px 12px rgba(0, 20, 40, 0.6);
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
            flex-wrap: wrap;
        }

        .header h1 span {
            background: linear-gradient(135deg, #ffd700, #f5a623);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
        }

        .header .sub {
            color: rgba(255, 255, 255, 0.55);
            font-size: 14px;
            letter-spacing: 2px;
            margin-top: 4px;
            font-weight: 300;
        }

        /* ===== 信息栏 ===== */
        .info-bar {
            display: flex;
            justify-content: space-between;
            align-items: center;
            background: rgba(0, 0, 0, 0.3);
            border-radius: 60px;
            padding: 10px 20px;
            margin-bottom: 24px;
            border: 1px solid rgba(255, 255, 255, 0.06);
            flex-wrap: wrap;
            gap: 8px;
        }

        .info-bar .attempts {
            color: rgba(255, 255, 255, 0.85);
            font-size: 15px;
            font-weight: 500;
        }

        .info-bar .attempts strong {
            color: #ffd700;
            font-size: 20px;
            font-weight: 700;
            margin: 0 2px;
        }

        .info-bar .status {
            font-size: 15px;
            font-weight: 600;
            padding: 4px 16px;
            border-radius: 40px;
            background: rgba(255, 255, 255, 0.06);
            color: rgba(255, 255, 255, 0.6);
            transition: all 0.4s ease;
            min-height: 34px;
            display: flex;
            align-items: center;
        }

        .info-bar .status.success {
            background: rgba(46, 204, 113, 0.25);
            color: #2ecc71;
            box-shadow: 0 0 24px rgba(46, 204, 113, 0.15);
        }

        .info-bar .status.error {
            background: rgba(231, 76, 60, 0.18);
            color: #e74c3c;
        }

        /* ===== 提示图例 ===== */
        .legend {
            display: flex;
            justify-content: center;
            gap: 20px;
            margin-bottom: 18px;
            font-size: 13px;
            color: rgba(255, 255, 255, 0.5);
            flex-wrap: wrap;
        }

        .legend-item {
            display: flex;
            align-items: center;
            gap: 6px;
        }

        .legend-dot {
            width: 18px;
            height: 18px;
            border-radius: 6px;
            display: inline-block;
        }

        .legend-dot.green {
            background: #2ecc71;
        }
        .legend-dot.blue {
            background: #3498db;
        }
        .legend-dot.red {
            background: #e74c3c;
        }

        /* ===== 网格 ===== */
        .grid {
            display: grid;
            grid-template-columns: repeat(5, 1fr);
            gap: 12px;
            margin-bottom: 28px;
        }

        .cell-wrapper {
            position: relative;
            aspect-ratio: 1 / 1;
        }

        .cell-wrapper .index {
            position: absolute;
            top: -8px;
            left: 50%;
            transform: translateX(-50%);
            font-size: 10px;
            font-weight: 600;
            color: rgba(255, 255, 255, 0.18);
            pointer-events: none;
            letter-spacing: 0.5px;
            background: rgba(0, 0, 0, 0.3);
            padding: 0 6px;
            border-radius: 10px;
            line-height: 18px;
            white-space: nowrap;
        }

        .cell {
            width: 100%;
            height: 100%;
            border: 2px solid rgba(255, 255, 255, 0.12);
            border-radius: 16px;
            background: rgba(255, 255, 255, 0.05);
            color: #fff;
            font-size: clamp(28px, 5vw, 42px);
            font-weight: 700;
            text-align: center;
            outline: none;
            transition: all 0.2s ease;
            caret-color: #ffd700;
            font-variant-numeric: tabular-nums;
            box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.2);
        }

        .cell:focus {
            border-color: rgba(255, 215, 0, 0.5);
            background: rgba(255, 255, 255, 0.10);
            box-shadow: 0 0 24px rgba(255, 215, 0, 0.08), inset 0 2px 8px rgba(0, 0, 0, 0.2);
            transform: scale(1.02);
        }

        .cell::placeholder {
            color: rgba(255, 255, 255, 0.12);
            font-weight: 300;
        }

        /* ===== 颜色状态 ===== */
        .cell.state-default {
            background: rgba(255, 255, 255, 0.05);
            border-color: rgba(255, 255, 255, 0.12);
            color: #fff;
        }

        .cell.state-green {
            background: #2ecc71;
            border-color: #2ecc71;
            color: #fff;
            box-shadow: 0 0 28px rgba(46, 204, 113, 0.25);
        }

        .cell.state-blue {
            background: #3498db;
            border-color: #3498db;
            color: #fff;
            box-shadow: 0 0 28px rgba(52, 152, 219, 0.20);
        }

        .cell.state-red {
            background: #e74c3c;
            border-color: #e74c3c;
            color: #fff;
            box-shadow: 0 0 28px rgba(231, 76, 60, 0.20);
        }

        /* ===== 按钮组 ===== */
        .actions {
            display: flex;
            gap: 14px;
            justify-content: center;
            flex-wrap: wrap;
            margin-top: 4px;
        }

        .btn {
            padding: 14px 40px;
            border: none;
            border-radius: 60px;
            font-size: 16px;
            font-weight: 600;
            letter-spacing: 1px;
            cursor: pointer;
            transition: all 0.25s ease;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            min-width: 130px;
            flex: 1 1 auto;
        }

        .btn-primary {
            background: linear-gradient(135deg, #ffd700, #f5a623);
            color: #1a2f44;
            box-shadow: 0 8px 24px rgba(255, 215, 0, 0.25);
        }

        .btn-primary:hover {
            transform: translateY(-2px);
            box-shadow: 0 12px 32px rgba(255, 215, 0, 0.35);
        }

        .btn-primary:active {
            transform: translateY(0px);
            box-shadow: 0 4px 12px rgba(255, 215, 0, 0.20);
        }

        .btn-primary:disabled {
            opacity: 0.4;
            cursor: not-allowed;
            transform: none;
            box-shadow: none;
        }

        .btn-secondary {
            background: rgba(255, 255, 255, 0.07);
            color: rgba(255, 255, 255, 0.7);
            border: 1px solid rgba(255, 255, 255, 0.10);
            backdrop-filter: blur(4px);
        }

        .btn-secondary:hover {
            background: rgba(255, 255, 255, 0.14);
            color: #fff;
            transform: translateY(-2px);
        }

        .btn-secondary:active {
            transform: translateY(0px);
        }

        /* ===== 成功弹窗(内嵌) ===== */
        .success-overlay {
            display: none;
            margin-top: 20px;
            padding: 18px 20px;
            border-radius: 20px;
            background: rgba(46, 204, 113, 0.12);
            border: 1px solid rgba(46, 204, 113, 0.25);
            text-align: center;
            animation: successPulse 1.2s ease-in-out infinite alternate;
        }

        .success-overlay.show {
            display: block;
        }

        .success-overlay .emoji {
            font-size: 48px;
            display: block;
            margin-bottom: 2px;
        }

        .success-overlay .msg {
            font-size: 22px;
            font-weight: 700;
            color: #2ecc71;
            letter-spacing: 2px;
        }

        .success-overlay .sub-msg {
            font-size: 14px;
            color: rgba(255, 255, 255, 0.4);
            margin-top: 4px;
        }

        @keyframes successPulse {
            0% {
                box-shadow: 0 0 20px rgba(46, 204, 113, 0.05);
            }
            100% {
                box-shadow: 0 0 50px rgba(46, 204, 113, 0.18);
            }
        }

        /* ===== 响应式 ===== */
        @media (max-width: 560px) {
            .game-container {
                padding: 24px 16px 28px;
                border-radius: 32px;
            }

            .header h1 {
                font-size: 22px;
            }

            .grid {
                gap: 8px;
            }

            .cell {
                font-size: clamp(22px, 6vw, 32px);
                border-radius: 12px;
                border-width: 1.5px;
            }

            .cell-wrapper .index {
                font-size: 8px;
                top: -6px;
                line-height: 14px;
                padding: 0 4px;
            }

            .info-bar {
                padding: 8px 14px;
                border-radius: 40px;
                font-size: 13px;
            }

            .info-bar .attempts strong {
                font-size: 17px;
            }

            .btn {
                padding: 12px 24px;
                font-size: 14px;
                min-width: 100px;
            }

            .legend {
                font-size: 11px;
                gap: 12px;
            }

            .legend-dot {
                width: 14px;
                height: 14px;
            }

            .success-overlay .msg {
                font-size: 18px;
            }
            .success-overlay .emoji {
                font-size: 38px;
            }
        }

        @media (max-width: 400px) {
            .grid {
                gap: 5px;
            }
            .cell {
                font-size: clamp(18px, 5vw, 26px);
                border-radius: 10px;
            }
            .game-container {
                padding: 18px 10px 20px;
            }
            .btn {
                padding: 10px 16px;
                font-size: 12px;
                min-width: 70px;
            }
            .info-bar .status {
                font-size: 12px;
                padding: 2px 12px;
            }
        }
    </style>
</head>
<body>
    <div class="game-container" role="main" aria-label="数字解码游戏">

        <!-- 头部 -->
        <div class="header">
            <h1>
                🔐 数字
                <span>解码器</span>
            </h1>
            <div class="sub">破解 20 位隐藏数字序列</div>
        </div>

        <!-- 信息栏 -->
        <div class="info-bar">
            <div class="attempts">
                🧩 猜测次数 &nbsp;<strong id="attemptDisplay">0</strong>
            </div>
            <div class="status" id="statusDisplay">等待输入…</div>
        </div>

        <!-- 图例 -->
        <div class="legend">
            <span class="legend-item">
                <span class="legend-dot green"></span> 正确
            </span>
            <span class="legend-item">
                <span class="legend-dot blue"></span> 偏大
            </span>
            <span class="legend-item">
                <span class="legend-dot red"></span> 偏小
            </span>
            <span class="legend-item" style="opacity:0.4;">
                <span class="legend-dot" style="background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.1);"></span> 未猜
            </span>
        </div>

        <!-- 20 个格子:4行 × 5列 -->
        <div class="grid" id="gridContainer" role="grid" aria-label="20个数字输入框">
            <!-- JavaScript 动态生成 -->
        </div>

        <!-- 按钮组 -->
        <div class="actions">
            <button class="btn btn-primary" id="confirmBtn">✓ 确认</button>
            <button class="btn btn-secondary" id="resetBtn">⟳ 重置</button>
        </div>

        <!-- 成功消息 -->
        <div class="success-overlay" id="successOverlay">
            <span class="emoji">🎉</span>
            <div class="msg">解码成功!</div>
            <div class="sub-msg">你破解了所有数字 🧠</div>
        </div>
    </div>

    <script>
        (function() {
            'use strict';

            // ============================================================
            //  1. DOM 引用
            // ============================================================
            const gridContainer = document.getElementById('gridContainer');
            const attemptDisplay = document.getElementById('attemptDisplay');
            const statusDisplay = document.getElementById('statusDisplay');
            const confirmBtn = document.getElementById('confirmBtn');
            const resetBtn = document.getElementById('resetBtn');
            const successOverlay = document.getElementById('successOverlay');

            const TOTAL = 20;
            const DIGITS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
            const REPEAT = 2; // 每个数字出现 2 次

            // ============================================================
            //  2. 游戏状态
            // ============================================================
            let answer = []; // 正确答案 (长度 20)
            let attempts = 0;
            let isGameWon = false;

            // 每个格子对应的 input 元素
            let inputs = [];

            // ============================================================
            //  3. 工具函数
            // ============================================================
            function shuffleArray(arr) {
                for (let i = arr.length - 1; i > 0; i--) {
                    const j = Math.floor(Math.random() * (i + 1));
                    [arr[i], arr[j]] = [arr[j], arr[i]];
                }
                return arr;
            }

            // 生成答案:0-9 各出现 2 次,打乱
            function generateAnswer() {
                const pool = [];
                for (const d of DIGITS) {
                    for (let r = 0; r < REPEAT; r++) {
                        pool.push(d);
                    }
                }
                return shuffleArray(pool);
            }

            // ============================================================
            //  4. 渲染网格
            // ============================================================
            function renderGrid() {
                gridContainer.innerHTML = '';
                inputs = [];

                for (let i = 0; i < TOTAL; i++) {
                    const wrapper = document.createElement('div');
                    wrapper.className = 'cell-wrapper';

                    // 序号标签
                    const indexLabel = document.createElement('span');
                    indexLabel.className = 'index';
                    indexLabel.textContent = String(i + 1).padStart(2, '0');
                    wrapper.appendChild(indexLabel);

                    // 输入框
                    const input = document.createElement('input');
                    input.type = 'text';
                    input.inputMode = 'numeric';
                    input.maxLength = 1;
                    input.className = 'cell state-default';
                    input.dataset.index = i;
                    input.placeholder = '?';
                    input.autocomplete = 'off';
                    input.spellcheck = false;

                    // 只允许 0-9
                    input.addEventListener('input', function(e) {
                        // 如果游戏已胜利,不允许修改
                        if (isGameWon) {
                            this.value = '';
                            return;
                        }
                        // 过滤:只保留数字字符
                        let val = this.value.replace(/\D/g, '');
                        if (val.length > 1) val = val.slice(0, 1);
                        this.value = val;

                        // 如果该格子当前有颜色状态,清空(表示用户修改了)
                        if (this.dataset.state && this.dataset.state !== 'default') {
                            // 但如果已经胜利,不要清除状态
                            if (!isGameWon) {
                                this.className = 'cell state-default';
                                this.dataset.state = 'default';
                            }
                        }

                        // 输入后自动跳到下一个框 (非退格/删除操作)
                        if (val.length === 1) {
                            const next = this.nextElementSibling;
                            if (next && next.tagName === 'INPUT') {
                                next.focus();
                            }
                        }
                    });

                    // 键盘事件:退格键跳回上一个
                    input.addEventListener('keydown', function(e) {
                        if (e.key === 'Backspace' && this.value === '') {
                            const prev = this.previousElementSibling;
                            if (prev && prev.tagName === 'INPUT') {
                                prev.focus();
                                e.preventDefault();
                            }
                        }
                        // 阻止输入非数字字符 (但用 input 过滤已经够了)
                    });

                    // 粘贴保护
                    input.addEventListener('paste', function(e) {
                        e.preventDefault();
                        const text = (e.clipboardData || window.clipboardData).getData('text');
                        const num = text.replace(/\D/g, '');
                        if (num.length > 0) {
                            this.value = num.slice(0, 1);
                            // 触发 input 事件
                            this.dispatchEvent(new Event('input', { bubbles: true }));
                        }
                    });

                    wrapper.appendChild(input);
                    gridContainer.appendChild(wrapper);
                    inputs.push(input);
                }
            }

            // ============================================================
            //  5. 核心游戏逻辑
            // ============================================================

            // 重置游戏
            function resetGame() {
                answer = generateAnswer();
                attempts = 0;
                isGameWon = false;
                successOverlay.classList.remove('show');
                statusDisplay.textContent = '等待输入…';
                statusDisplay.className = 'status';
                attemptDisplay.textContent = '0';

                // 清空所有输入框并重置样式
                for (const input of inputs) {
                    input.value = '';
                    input.className = 'cell state-default';
                    input.dataset.state = 'default';
                    input.disabled = false;
                    input.placeholder = '?';
                }

                // 聚焦到第一个框
                if (inputs.length > 0) {
                    inputs[0].focus();
                }

                confirmBtn.disabled = false;
            }

            // 检查是否所有框都已填写
            function isAllFilled() {
                for (const input of inputs) {
                    if (input.value === '') return false;
                }
                return true;
            }

            // 获取用户输入的数字数组
            function getUserGuess() {
                const guess = [];
                for (const input of inputs) {
                    const val = input.value.trim();
                    if (val === '') {
                        guess.push(null);
                    } else {
                        guess.push(parseInt(val, 10));
                    }
                }
                return guess;
            }

            // 确认猜测
            function handleConfirm() {
                // 如果已胜利,不允许再确认
                if (isGameWon) {
                    statusDisplay.textContent = '🎉 已经解码成功,点击重置再玩一局!';
                    statusDisplay.className = 'status success';
                    return;
                }

                // 检查是否全部填写
                if (!isAllFilled()) {
                    statusDisplay.textContent = '⚠️ 请填满所有 20 个框';
                    statusDisplay.className = 'status error';
                    // 闪烁提示
                    setTimeout(() => {
                        if (!isGameWon) {
                            statusDisplay.className = 'status';
                            statusDisplay.textContent = '等待输入…';
                        }
                    }, 1800);
                    return;
                }

                const guess = getUserGuess();
                // 检查是否有 null (安全)
                for (let i = 0; i < TOTAL; i++) {
                    if (guess[i] === null) {
                        statusDisplay.textContent = '⚠️ 请填满所有框';
                        statusDisplay.className = 'status error';
                        return;
                    }
                }

                // 增加猜测次数
                attempts++;
                attemptDisplay.textContent = attempts;

                // 比较并着色
                let allCorrect = true;
                for (let i = 0; i < TOTAL; i++) {
                    const userVal = guess[i];
                    const correctVal = answer[i];
                    const input = inputs[i];

                    if (userVal === correctVal) {
                        input.className = 'cell state-green';
                        input.dataset.state = 'green';
                    } else if (userVal > correctVal) {
                        input.className = 'cell state-blue';
                        input.dataset.state = 'blue';
                        allCorrect = false;
                    } else { // userVal < correctVal
                        input.className = 'cell state-red';
                        input.dataset.state = 'red';
                        allCorrect = false;
                    }
                }

                // 更新状态
                if (allCorrect) {
                    isGameWon = true;
                    statusDisplay.textContent = '🎉 全部正确!解码成功!';
                    statusDisplay.className = 'status success';
                    successOverlay.classList.add('show');
                    confirmBtn.disabled = true;
                    // 禁用所有输入框
                    for (const input of inputs) {
                        input.disabled = true;
                    }
                } else {
                    statusDisplay.textContent = `🧐 已猜 ${attempts} 次,继续努力!`;
                    statusDisplay.className = 'status';
                    // 不锁定输入框,用户可以继续修改
                }
            }

            // ============================================================
            //  6. 初始化 & 事件绑定
            // ============================================================

            function init() {
                renderGrid();
                resetGame();

                // 确认按钮
                confirmBtn.addEventListener('click', handleConfirm);

                // 重置按钮
                resetBtn.addEventListener('click', function() {
                    resetGame();
                });

                // 键盘快捷键: Enter 触发确认
                document.addEventListener('keydown', function(e) {
                    if (e.key === 'Enter') {
                        const active = document.activeElement;
                        // 如果焦点在输入框上,触发确认
                        if (active && active.tagName === 'INPUT' && active.closest('.game-container')) {
                            e.preventDefault();
                            confirmBtn.click();
                        }
                    }
                });

                // 点击容器空白处聚焦到第一个空框
                gridContainer.addEventListener('click', function(e) {
                    if (e.target === gridContainer) {
                        // 找第一个空框或第一个框
                        for (const input of inputs) {
                            if (input.value === '' && !input.disabled) {
                                input.focus();
                                return;
                            }
                        }
                        if (inputs.length > 0 && !inputs[0].disabled) {
                            inputs[0].focus();
                        }
                    }
                });
            }

            // ============================================================
            //  7. 启动
            // ============================================================
            document.addEventListener('DOMContentLoaded', init);
        })();
    </script>

</body>
</html>

Game Source: 🔐 数字解码器

Creator: SparkStar82

Libraries: none

Complexity: complex (816 lines, 27.2 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-sparkstar82" to link back to the original. Then publish at arcadelab.ai/publish.