🔐 数字解码器
by SparkStar82816 lines27.2 KB
<!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">
🧩 猜测次数 <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.