模拟器开源教学网页

将鼠标悬停在函数名上查看完整源代码

使用说明

将鼠标悬停在函数名,配套css上,会显示该函数和配套css的完整源代码。

每个函数下方添加了配套CSS部分,同样可以通过悬停查看CSS源代码。

一、核心游戏功能
initializeDeck()
initializeDeck() - 初始化牌堆 function initializeDeck() { // 清空日志 document.getElementById('log').innerHTML = ''; log("牌堆初始化开始"); // 重置所有数组 deck = []; drawnCards = []; discardPile = []; hand = []; // 清空UI显示 document.getElementById('deck').innerHTML = ''; // 重置游戏状态 reshuffleUsed = 0; phase = ''; lastUsedCard = null; peachCount = 1; wineCount = 1; // 使用预定义的standardDeck生成牌堆 standardDeck.forEach(card => { // 处理每张卡牌的花色和点数 card.suits.forEach(suitStr => { // 使用正则匹配花色和点数 const match = suitStr.match(/^([♠♥♣♦]️?)([JQKA]|\d+)$/); if (match) { const suitPart = match[1]; let point = match[2]; // 点数转换 if (point === 'J') point = 11; else if (point === 'Q') point = 12; else if (point === 'K') point = 13; else if (point === 'A') point = 1; else point = parseInt(point); // 根据卡牌数量生成多张牌 for (let i = 0; i < card.count; i++) { // 生成唯一ID并添加到牌堆 deck.push({ name: card.name, suit: suitPart, point: point, uid: CryptoJS.lib.WordArray.random(16).toString() }); } } }); }); // 重置武器状态 currentWeapon=null; killCount=2; hasZhugeLianNu=false; updateStatus(); // 更新状态显示 // 洗牌并开始游戏 shuffleDeck(); generateDeckMD5(); updateMD5Display(); generateDeckAES(); updateAESDisplay(); updateRemaining(); startGame(); log("牌堆初始化完成"); }
初始化游戏牌堆,重置所有游戏状态变量,生成新的牌堆并开始游戏。
配套CSS
牌堆管理系统CSS /* 卡牌基础样式 */ .card { background-color: #f0f0f0; border: 1px solid #ccc; padding: 8px; margin: 4px; display: inline-block; width: 130px; text-align: center; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); transition: all 0.2s ease; } .card:hover { transform: translateY(-3px); box-shadow: 0 4px 8px rgba(0,0,0,0.15); } /* 牌堆区域样式 */ #deck { margin-top: 15px; padding: 10px; background: #f8f9fa; border: 1px solid #e0e0e0; border-radius: 6px; min-height: 100px; } /* 剩余牌堆区域样式 */ #remaining { margin-top: 15px; padding: 10px; background: #fff8e1; border: 1px solid #ffd54f; border-radius: 6px; min-height: 100px; } /* 手牌区域样式 */ #hand { margin: 15px 0; padding: 15px; background: #e8f5e9; border: 1px solid #c8e6c9; border-radius: 6px; min-height: 150px; } /* 高亮状态 */ .highlight { background-color: #fff9c4; border: 2px solid #ffd600; box-shadow: 0 0 10px rgba(255, 214, 0, 0.5); } /* 禁用状态 */ .disabled { opacity: 0.5; pointer-events: none; cursor: not-allowed; }

关键技术点

shuffleDeck()
shuffleDeck() - 洗牌函数 function shuffleDeck() { log("开始洗牌..."); // 使用Fisher-Yates洗牌算法 for (let i = deck.length - 1; i > 0; i--) { // 使用加密API获取随机索引 const randomBuffer = new Uint32Array(1); window.crypto.getRandomValues(randomBuffer); const j = randomBuffer[0] % (i + 1); // 交换位置 [deck[i], deck[j]] = [deck[j], deck[i]]; } // 添加洗牌动画效果 const deckElement = document.getElementById('deck'); deckElement.classList.add('shuffling'); // 更新UI显示 updateDeckDisplay(); // 生成新的加密标识 generateDeckMD5(); generateDeckAES(); // 移除动画类 setTimeout(() => { deckElement.classList.remove('shuffling'); log("洗牌完成!牌堆已重新随机排序"); }, 1200); }
使用Fisher-Yates算法随机打乱牌堆顺序,更新加密标识和状态显示。
配套CSS
洗牌动画CSS /* 洗牌动画容器 */ .shuffling { position: relative; animation: deckShuffle 1.2s cubic-bezier(0.4, 0, 0.2, 1); filter: drop-shadow(0 0 12px rgba(255, 215, 64, 0.6)); } /* 单张卡牌的洗牌动画 */ .card.shuffle-item { animation: cardShuffle 0.8s ease-in-out both; transform-origin: 50% 100%; } /* 牌堆整体洗牌动画 */ @keyframes deckShuffle { 0% { transform: translateY(0) rotateZ(0deg); opacity: 1; } 30% { transform: translateY(-20px) rotateZ(-5deg); opacity: 0.9; } 70% { transform: translateY(10px) rotateZ(5deg); opacity: 0.95; } 100% { transform: translateY(0) rotateZ(0deg); opacity: 1; } } /* 单张卡牌洗牌动画 */ @keyframes cardShuffle { 0% { transform: rotateZ(0deg) scale(1); z-index: 1; } 25% { transform: rotateZ(-15deg) scale(1.05); z-index: 2; } 50% { transform: rotateZ(15deg) scale(0.95); filter: brightness(1.2); } 75% { transform: rotateZ(-10deg) scale(1.02); filter: brightness(1.1); } 100% { transform: rotateZ(0deg) scale(1); z-index: 1; } } /* 减少运动偏好设置 */ @media (prefers-reduced-motion: no-preference) { .shuffling, .card.shuffle-item { will-change: transform, filter; backface-visibility: hidden; } }

关键技术点

drawCards(count)
drawCards(count) - 抽牌函数 function drawCards(count) { log(`开始抽取 ${count} 张牌...`); // 检查牌堆是否足够 if (deck.length < count) { log("牌堆数量不足,自动重新洗牌弃牌堆"); reshuffleDiscardPile(); } const drawn=[]; for (let i=0; i < count; i++) { if (deck.length===0) { log("牌堆已空,无法继续抽牌"); break; } // 从牌堆顶部取牌 const card=deck.shift(); drawn.push(card); drawnCards.push(card); // 添加到手牌 hand.push(card); // 添加动画效果 animateCardDraw(card); } // 更新UI和状态 updateHandDisplay(); updateRemaining(); generateDeckMD5(); updateMD5Display(); log(`成功抽取 ${drawn.length} 张牌`); // 检查是否抽到诸葛连弩 const zhugeCard=drawn.find(card=> card.name === "诸葛连弩"); if (zhugeCard) { hasZhugeLianNu = true; log("★ 抽到诸葛连弩,杀次数上限+1"); updateStatus(); } return drawn; } // 辅助函数:卡片抽取动画 function animateCardDraw(card) { const cardElement = document.createElement('div'); cardElement.className = 'card draw-animation'; cardElement.textContent = `${card.name} (${card.suit}${card.point})`; cardElement.dataset.uid = card.uid; // 添加到动画容器 const animationContainer = document.getElementById('animation-container'); animationContainer.appendChild(cardElement); // 动画结束后移除元素 setTimeout(() => { cardElement.remove(); }, 1200); }
从牌堆顶部抽取指定数量的卡牌,添加到玩家手牌中。
配套CSS
抽牌动画CSS /* 抽牌动画容器 */ #animation-container { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 2000; overflow: visible; } /* 抽牌动画 */ .card.draw-animation { position: absolute; top: 50%; left: 50%; z-index: 2001; animation: drawCard 1.2s cubic-bezier(0.68, -0.55, 0.27, 1.55) forwards; transform-origin: center; box-shadow: 0 0 20px rgba(255, 215, 0, 0.7); } /* 抽牌动画关键帧 */ @keyframes drawCard { 0% { transform: translate(-50%, -50%) scale(0.8) rotateY(0deg); opacity: 0; filter: blur(2px); } 30% { transform: translate(-50%, -150%) scale(1.2) rotateY(180deg); opacity: 1; filter: blur(0); box-shadow: 0 0 30px rgba(255, 215, 0, 0.9); } 70% { transform: translate(calc(-50% + 200px), -50%) scale(1.1) rotateY(360deg); opacity: 0.9; } 100% { transform: translate(calc(-50% + 400px), 100px) scale(1) rotateY(720deg); opacity: 0; } } /* 特殊卡牌高亮 */ .card.draw-animation[data-card-type="weapon"] { animation: drawWeapon 1.2s ease-in-out forwards; background: linear-gradient(45deg, #ffeb3b, #ff9800); box-shadow: 0 0 25px rgba(255, 152, 0, 0.8); } @keyframes drawWeapon { 0% { transform: translate(-50%, -50%) scale(0.8); opacity: 0; } 50% { transform: translate(-50%, -50%) scale(1.3); opacity: 1; box-shadow: 0 0 40px rgba(255, 152, 0, 0.9); } 100% { transform: translate(calc(-50% + 400px), 100px) scale(1); opacity: 0; } } /* 减少运动设置 */ @media (prefers-reduced-motion: reduce) { .card.draw-animation { animation: none !important; opacity: 0 !important; } }

关键技术点

startGame()
startGame() - 开始游戏 function startGame() { log("游戏开始!"); // 重置游戏状态 phase = 'preparation'; killCount = 2; // 初始杀次数 hasZhugeLianNu = false; currentWeapon = null; // 清除手牌和弃牌堆 hand = []; discardPile = []; // 更新状态显示 updateStatus(); // 添加游戏开始动画 const gameArea = document.getElementById('game-area'); gameArea.classList.add('game-starting'); // 显示开始动画 setTimeout(() => { // 抽取起始手牌(4张) drawCards(4); // 进入准备阶段 setPhase('preparation'); log("准备阶段,请等待..."); // 移除动画类 gameArea.classList.remove('game-starting'); }, 1500); } // 设置游戏阶段 function setPhase(newPhase) { phase = newPhase; const phaseElement = document.getElementById('phase-indicator'); // 更新阶段显示 phaseElement.textContent = `当前阶段: ${getPhaseName(newPhase)}`; phaseElement.className = `phase ${newPhase}`; // 根据阶段更新按钮状态 updateButtonStates(); log(`进入${getPhaseName(newPhase)}阶段`); } // 获取阶段名称 function getPhaseName(phaseCode) { const phases = { 'preparation': '准备', 'draw': '摸牌', 'play': '出牌', 'discard': '弃牌', 'end': '结束' }; return phases[phaseCode] || phaseCode; } // 更新按钮状态 function updateButtonStates() { const drawBtn = document.getElementById('drawBtn'); const endBtn = document.getElementById('endBtn'); // 根据阶段启用/禁用按钮 drawBtn.disabled = phase !== 'draw'; endBtn.disabled = phase !== 'play'; // 添加视觉反馈 if (phase === 'draw') { drawBtn.classList.add('phase-active'); } else { drawBtn.classList.remove('phase-active'); } }
初始化游戏状态,进入准备阶段,抽取起始手牌。
配套CSS
游戏阶段CSS /* 游戏区域开始动画 */ .game-starting { position: relative; overflow: hidden; } .game-starting::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(45deg, #ff5722, #ff9800, #ffeb3b, #4caf50); z-index: 100; animation: gameStartGradient 1.5s ease-in-out forwards; opacity: 0.8; } @keyframes gameStartGradient { 0% { transform: scale(0) rotate(0deg); opacity: 0; } 50% { transform: scale(2) rotate(180deg); opacity: 0.9; } 100% { transform: scale(4) rotate(360deg); opacity: 0; } } /* 阶段指示器 */ .phase { display: inline-block; padding: 6px 12px; border-radius: 20px; font-weight: bold; margin: 10px 0; transition: all 0.3s ease; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } /* 不同阶段的颜色 */ .phase.preparation { background: #2196F3; color: white; } .phase.draw { background: #4CAF50; color: white; } .phase.play { background: #FF9800; color: white; } .phase.discard { background: #F44336; color: white; } .phase.end { background: #9C27B0; color: white; } /* 阶段按钮 */ .phase-button { padding: 8px 16px; border-radius: 4px; font-weight: bold; cursor: pointer; transition: all 0.2s; margin: 0 5px; border: none; box-shadow: 0 2px 4px rgba(0,0,0,0.2); } /* 激活阶段的按钮 */ .phase-active { transform: scale(1.05); box-shadow: 0 0 15px rgba(255, 193, 7, 0.8); animation: pulse 1.5s infinite; } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.7); } 70% { box-shadow: 0 0 0 12px rgba(255, 193, 7, 0); } 100% { box-shadow: 0 0 0 0 rgba(255, 193, 7, 0); } } /* 禁用按钮样式 */ .phase-button:disabled { opacity: 0.5; cursor: not-allowed; transform: none; box-shadow: none; animation: none; }

关键技术点

drawPhase()
drawPhase() - 进入摸牌阶段 function drawPhase() { // 确保在正确的阶段 if (phase !== 'preparation') { log("无法进入摸牌阶段:当前阶段错误"); return; } log("进入摸牌阶段..."); setPhase('draw'); // 添加阶段动画效果 const phaseIndicator = document.getElementById('phase-indicator'); phaseIndicator.classList.add('phase-transition'); // 抽取3张牌 setTimeout(() => { drawCards(3); // 更新阶段按钮状态 updateButtonStates(); // 进入出牌阶段 setTimeout(() => { setPhase('play'); log("摸牌完成,进入出牌阶段"); phaseIndicator.classList.remove('phase-transition'); }, 1000); }, 500); // 更新状态显示 updateStatus(); }
进入摸牌阶段,抽取3张牌并更新游戏阶段状态。
配套CSS
阶段按钮CSS /* 摸牌阶段按钮 */ #drawBtn { background: linear-gradient(to bottom, #4CAF50, #2E7D32); color: white; border: none; padding: 10px 20px; border-radius: 4px; font-weight: bold; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 3px 6px rgba(0,0,0,0.16); position: relative; overflow: hidden; } #drawBtn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0,0,0,0.2); background: linear-gradient(to bottom, #66BB6A, #388E3C); } #drawBtn:active:not(:disabled) { transform: translateY(1px); box-shadow: 0 2px 4px rgba(0,0,0,0.2); } #drawBtn:disabled { opacity: 0.5; cursor: not-allowed; background: linear-gradient(to bottom, #A5D6A7, #81C784); } /* 按钮波纹效果 */ #drawBtn::after { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0; background: rgba(255, 255, 255, 0.3); border-radius: 50%; transform: translate(-50%, -50%); opacity: 0; } #drawBtn:active:not(:disabled)::after { animation: ripple 0.6s linear; } @keyframes ripple { 0% { width: 0; height: 0; opacity: 1; } 100% { width: 300px; height: 300px; opacity: 0; } } /* 阶段过渡动画 */ .phase-transition { animation: phaseTransition 1s ease-in-out; } @keyframes phaseTransition { 0% { transform: scale(1); box-shadow: 0 0 0 rgba(76, 175, 80, 0); } 50% { transform: scale(1.1); box-shadow: 0 0 20px rgba(76, 175, 80, 0.7); } 100% { transform: scale(1); box-shadow: 0 0 0 rgba(76, 175, 80, 0); } } /* 按钮图标 */ #drawBtn::before { content: '🃏'; margin-right: 8px; font-size: 18px; } /* 按钮状态提示 */ #drawBtn.active-phase { animation: pulse 1.5s infinite; border: 2px solid #FFEB3B; } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(255, 235, 59, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(255, 235, 59, 0); } 100% { box-shadow: 0 0 0 0 rgba(255, 235, 59, 0); } }

关键技术点

useCard(card, index)
useCard(card, index) - 使用卡牌 function useCard(card, index) { // 检查是否在出牌阶段 if (phase !== 'play') { log(`当前是${getPhaseName(phase)}阶段,不能出牌`); return; } // 检查卡牌是否可用 if (card.disabled) { log(`${card.name} 已被禁用,无法使用`); return; } log(`使用卡牌: ${card.name} (${card.suit}${card.point})`); // 根据卡牌类型执行不同效果 switch (card.name) { case '杀': handleKillCard(card); break; case '闪': handleDodgeCard(card); break; case '桃': handlePeachCard(card); break; case '酒': handleWineCard(card); break; case '诸葛连弩': handleZhugeLianNu(card); break; // 其他卡牌类型... default: handleGenericCard(card); } // 从手牌中移除 hand.splice(index, 1); // 添加到弃牌堆 discardPile.push(card); // 更新UI updateHandDisplay(); updateDiscardDisplay(); // 添加使用动画 animateCardUse(card); // 记录最后使用的卡牌 lastUsedCard = card; // 更新杀次数 if (card.name === '杀') { killCount--; updateStatus(); } // 检查游戏结束条件 checkGameEnd(); } // 处理杀牌 function handleKillCard(card) { if (killCount <= 0 && !hasZhugeLianNu) { log("杀次数已用完,无法使用杀"); return; } log("★ 使用杀,对目标造成1点伤害"); // 实际游戏逻辑:选择目标并造成伤害 } // 处理闪牌 function handleDodgeCard(card) { log("★ 使用闪,抵消一次杀的攻击"); // 实际游戏逻辑:抵消攻击 } // 处理桃牌 function handlePeachCard(card) { log("★ 使用桃,回复1点体力"); // 实际游戏逻辑:回复体力 peachCount--; updateRemaining(); } // 处理酒牌 function handleWineCard(card) { log("★ 使用酒,本回合杀伤害+1"); // 实际游戏逻辑:增加杀伤害 wineCount--; updateRemaining(); } // 处理诸葛连弩 function handleZhugeLianNu(card) { log("★ 装备诸葛连弩,杀次数上限+1"); hasZhugeLianNu=true; currentWeapon=card; updateStatus(); } // 卡牌使用动画 function animateCardUse(card) { const cardElement=document.querySelector(`.card[data-uid="${card.uid}" ]`); if (!cardElement) return; // 添加使用动画类 cardElement.classList.add('card-used'); // 创建动画副本 const clone=cardElement.cloneNode(true); clone.classList.add('card-use-animation'); clone.style.position='fixed' ; clone.style.zIndex='1000' ; // 添加到动画容器 const animationContainer=document.getElementById('animation-container'); animationContainer.appendChild(clone); // 获取目标位置(弃牌堆) const discardPile=document.getElementById('discard-pile'); const targetRect=discardPile.getBoundingClientRect(); // 动画效果 anime({ targets: clone, top: targetRect.top + 'px' , left: targetRect.left + 'px' , scale: 0.5, opacity: 0, duration: 800, easing: 'easeInOutQuad' , complete: ()=> { clone.remove(); } }); // 移除原始元素的动画类 setTimeout(() => { cardElement.classList.remove('card-used'); }, 300); }
处理卡牌使用逻辑,包括杀、闪、桃等基本牌和锦囊牌的效果。
配套CSS
卡牌使用状态CSS /* 卡牌使用状态 */ .card-used { transform: scale(0.9); opacity: 0.7; filter: grayscale(80%); transition: all 0.3s ease; } /* 卡牌使用动画 */ .card-use-animation { animation: cardUse 0.8s ease-in-out forwards; box-shadow: 0 0 20px rgba(255, 215, 0, 0.7); z-index: 1000; } @keyframes cardUse { 0% { transform: scale(1); opacity: 1; filter: none; } 50% { transform: scale(1.2) rotate(10deg); box-shadow: 0 0 30px rgba(255, 87, 34, 0.8); filter: brightness(1.5); } 100% { transform: scale(0.5) rotate(20deg); opacity: 0; } } /* 不同类型卡牌的特殊效果 */ .card[data-card-type="kill"] { border: 2px solid #f44336; } .card[data-card-type="dodge"] { border: 2px solid #2196F3; } .card[data-card-type="peach"] { border: 2px solid #4CAF50; } .card[data-card-type="wine"] { border: 2px solid #FF9800; } .card[data-card-type="weapon"] { border: 2px solid #9C27B0; } /* 卡牌禁用状态 */ .card.disabled { opacity: 0.5; filter: grayscale(100%); cursor: not-allowed; } /* 卡牌悬停效果 */ .card:not(.disabled):hover { transform: translateY(-5px); box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2); z-index: 10; } /* 特殊卡牌悬停效果 */ .card[data-card-type="kill"]:hover { box-shadow: 0 8px 15px rgba(244, 67, 54, 0.3); } .card[data-card-type="dodge"]:hover { box-shadow: 0 8px 15px rgba(33, 150, 243, 0.3); } /* 卡牌激活效果 */ .card.active { animation: cardPulse 1.5s infinite; border: 2px solid #FFEB3B; } @keyframes cardPulse { 0% { box-shadow: 0 0 0 0 rgba(255, 235, 59, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(255, 235, 59, 0); } 100% { box-shadow: 0 0 0 0 rgba(255, 235, 59, 0); } }

关键技术点

二、辅助功能系统
updateRemainingCounts()
updateRemainingCounts() - 更新记牌器 function updateRemainingCounts() { // 重置计数器 const counts = { '杀': 0, '闪': 0, '桃': 0, '酒': 0, '诸葛连弩': 0, '万箭齐发': 0, '南蛮入侵': 0, '顺手牵羊': 0, '过河拆桥': 0 }; // 统计牌堆中各类卡牌数量 deck.forEach(card => { if (counts.hasOwnProperty(card.name)) { counts[card.name]++; } }); // 更新UI显示 const container = document.getElementById('remaining-counts'); container.innerHTML = ''; // 按类别分组显示 const categories = { '基本牌': ['杀', '闪', '桃', '酒'], '装备牌': ['诸葛连弩'], '锦囊牌': ['万箭齐发', '南蛮入侵', '顺手牵羊', '过河拆桥'] }; // 创建类别分组 for (const [category, cards] of Object.entries(categories)) { const categoryDiv = document.createElement('div'); categoryDiv.className = 'count-category'; const header = document.createElement('h3'); header.textContent = category; categoryDiv.appendChild(header); const cardList = document.createElement('div'); cardList.className = 'count-list'; cards.forEach(cardName => { const countItem = document.createElement('div'); countItem.className = 'count-item'; const nameSpan = document.createElement('span'); nameSpan.className = 'card-name'; nameSpan.textContent = cardName; const countSpan = document.createElement('span'); countSpan.className = 'card-count'; countSpan.textContent = counts[cardName]; // 添加特殊样式 if (counts[cardName] === 0) { countSpan.classList.add('zero-count'); } countItem.appendChild(nameSpan); countItem.appendChild(countSpan); cardList.appendChild(countItem); }); categoryDiv.appendChild(cardList); container.appendChild(categoryDiv); } // 添加折叠/展开功能 const toggleBtn = document.createElement('button'); toggleBtn.id = 'toggleCounts'; toggleBtn.textContent = '收起记牌器'; toggleBtn.addEventListener('click', () => { container.classList.toggle('collapsed'); toggleBtn.textContent = container.classList.contains('collapsed') ? '展开记牌器' : '收起记牌器'; }); container.parentNode.insertBefore(toggleBtn, container); log("记牌器已更新"); }
统计并显示各类卡牌的剩余数量,按类别分组展示。
配套CSS
记牌器CSS /* 记牌器容器 */ #remaining-counts { background: #fffde7; border: 2px solid #ffd54f; border-radius: 8px; padding: 15px; margin: 15px 0; transition: all 0.3s ease; overflow: hidden; } /* 折叠状态 */ #remaining-counts.collapsed { max-height: 0; padding: 0; border: none; opacity: 0; } /* 类别标题 */ .count-category h3 { color: #5d4037; margin: 10px 0 5px; padding-bottom: 5px; border-bottom: 1px dashed #ffd54f; font-size: 16px; } /* 卡片列表 */ .count-list { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 15px; } /* 单张卡片计数 */ .count-item { display: flex; align-items: center; background: #fff8e1; border: 1px solid #ffd54f; border-radius: 4px; padding: 6px 12px; min-width: 100px; transition: all 0.2s ease; } .count-item:hover { transform: translateY(-2px); box-shadow: 0 3px 6px rgba(0,0,0,0.1); background: #ffecb3; } /* 卡片名称 */ .card-name { flex: 1; font-weight: bold; color: #5d4037; } /* 卡片数量 */ .card-count { background: #ffd54f; color: #5d4037; font-weight: bold; width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-left: 8px; transition: all 0.3s ease; } /* 零张特殊样式 */ .card-count.zero-count { background: #ffcdd2; color: #c62828; animation: pulseWarning 1.5s infinite; } @keyframes pulseWarning { 0% { box-shadow: 0 0 0 0 rgba(198, 40, 40, 0.4); } 70% { box-shadow: 0 0 0 8px rgba(198, 40, 40, 0); } 100% { box-shadow: 0 0 0 0 rgba(198, 40, 40, 0); } } /* 折叠按钮 */ #toggleCounts { background: #ffd54f; color: #5d4037; border: none; border-radius: 4px; padding: 6px 12px; cursor: pointer; font-weight: bold; transition: all 0.2s ease; margin-bottom: 10px; } #toggleCounts:hover { background: #ffc107; transform: translateY(-2px); box-shadow: 0 3px 6px rgba(0,0,0,0.1); } #toggleCounts:active { transform: translateY(0); } /* 响应式布局 */ @media (max-width: 768px) { .count-list { gap: 8px; } .count-item { min-width: 85px; padding: 5px 8px; font-size: 14px; } .card-count { width: 25px; height: 25px; font-size: 14px; } }

关键技术点

log(message)
log(message) - 记录操作日志 function log(message, type = 'info') { const logContainer = document.getElementById('log'); const now = new Date(); // 格式化时间 HH:mm:ss const timeStr = now.toTimeString().substring(0, 8); // 创建日志条目 const logEntry = document.createElement('div'); logEntry.className = `log-entry log-${type}`; // 添加时间戳 const timestamp = document.createElement('span'); timestamp.className = 'log-timestamp'; timestamp.textContent = `[${timeStr}] `; logEntry.appendChild(timestamp); // 添加消息内容 const content = document.createElement('span'); content.className = 'log-content'; content.textContent = message; logEntry.appendChild(content); // 添加到日志容器 logContainer.appendChild(logEntry); // 自动滚动到底部 logContainer.scrollTop = logContainer.scrollHeight; // 添加动画效果 logEntry.style.opacity = '0'; setTimeout(() => { logEntry.style.transition = 'opacity 0.3s ease'; logEntry.style.opacity = '1'; }, 10); // 特殊类型日志的额外处理 if (type === 'warning' || type === 'error') { logEntry.classList.add('log-highlight'); animateLogHighlight(logEntry); } } // 日志高亮动画 function animateLogHighlight(element) { element.animate([ { backgroundColor: 'transparent' }, { backgroundColor: '#fff9c4', offset: 0.5 }, { backgroundColor: 'transparent' } ], { duration: 2000, iterations: 1 }); }
添加带时间戳的操作日志条目到日志容器。
配套CSS
日志容器CSS /* 日志容器 */ #log { max-height: 300px; overflow-y: auto; padding: 10px; background: #f5f5f5; border: 1px solid #e0e0e0; border-radius: 6px; font-family: 'Consolas', 'Monaco', monospace; font-size: 14px; line-height: 1.5; margin-top: 15px; } /* 滚动条样式 */ #log::-webkit-scrollbar { width: 8px; } #log::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; } #log::-webkit-scrollbar-thumb { background: #bdbdbd; border-radius: 4px; } #log::-webkit-scrollbar-thumb:hover { background: #9e9e9e; } /* 日志条目 */ .log-entry { padding: 8px 12px; margin-bottom: 6px; border-radius: 4px; transition: all 0.3s ease; animation: fadeIn 0.3s ease; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } /* 不同日志类型 */ .log-info { background: #e3f2fd; border-left: 4px solid #2196F3; } .log-warning { background: #fff8e1; border-left: 4px solid #FFC107; } .log-error { background: #ffebee; border-left: 4px solid #F44336; } /* 时间戳样式 */ .log-timestamp { color: #616161; font-weight: bold; margin-right: 8px; } /* 日志内容 */ .log-content { color: #212121; } /* 高亮日志 */ .log-highlight { animation: highlightPulse 2s ease-in-out; } @keyframes highlightPulse { 0% { box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.7); } 50% { box-shadow: 0 0 0 8px rgba(255, 193, 7, 0); } 100% { box-shadow: 0 0 0 0 rgba(255, 193, 7, 0); } } /* 日志分隔线 */ .log-entry:not(:last-child) { border-bottom: 1px dashed #e0e0e0; } /* 响应式设计 */ @media (max-width: 768px) { #log { max-height: 200px; font-size: 13px; } .log-entry { padding: 6px 8px; } .log-timestamp { display: block; margin-bottom: 4px; } }

关键技术点

handleReshuffle()
handleReshuffle() - 脚气卡功能 function handleReshuffle() { // 检查手牌数量 if (hand.length < 4) { log("手牌不足4张,无法使用脚气卡"); return; } log("使用脚气卡..."); // 添加动画效果 const handArea=document.getElementById('hand'); handArea.classList.add('reshuffling'); // 获取最后4张手牌 const cardsToReshuffle=hand.slice(-4); // 创建回收动画 animateReshuffle(cardsToReshuffle); // 延迟执行实际逻辑 setTimeout(()=> { // 从手牌中移除最后4张 hand.splice(-4, 4); // 添加到弃牌堆 discardPile.push(...cardsToReshuffle); // 重新洗牌 shuffleDeck(); // 抽取4张新牌 drawCards(4); // 更新显示 updateHandDisplay(); updateDiscardDisplay(); // 移除动画类 handArea.classList.remove('reshuffling'); log("脚气卡使用完成:回收4张牌,重新洗牌并抽取4张新牌"); }, 1500); } // 脚气卡动画效果 function animateReshuffle(cards) { const animationContainer = document.getElementById('animation-container'); cards.forEach((card, index) => { // 找到对应的卡牌元素 const cardElement = document.querySelector(`.card[data-uid="${card.uid}"]`); if (!cardElement) return; // 创建动画副本 const clone = cardElement.cloneNode(true); clone.classList.add('reshuffle-animation'); clone.style.position = 'fixed'; clone.style.zIndex = '1000'; // 设置初始位置 const rect = cardElement.getBoundingClientRect(); clone.style.left = `${rect.left}px`; clone.style.top = `${rect.top}px`; animationContainer.appendChild(clone); // 动画效果 anime({ targets: clone, translateX: [0, -100 + index * 50], translateY: [0, -200], rotate: [0, 360], scale: [1, 1.5], opacity: [1, 0], duration: 1200, delay: index * 100, easing: 'easeInOutQuad', complete: () => { clone.remove(); } }); }); }
使用脚气卡功能,回收最后4张手牌,重新洗牌并抽取新牌。
配套CSS
脚气卡按钮CSS /* 脚气卡按钮 */ #reshuffleBtn { background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); color: white; border: none; border-radius: 30px; padding: 12px 24px; font-size: 16px; font-weight: bold; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 6px 12px rgba(106, 17, 203, 0.3); position: relative; overflow: hidden; text-transform: uppercase; letter-spacing: 1px; } #reshuffleBtn:hover:not(:disabled) { transform: translateY(-3px); box-shadow: 0 8px 15px rgba(106, 17, 203, 0.4); background: linear-gradient(135deg, #7a21db 0%, #3585ff 100%); } #reshuffleBtn:active:not(:disabled) { transform: translateY(1px); box-shadow: 0 3px 6px rgba(106, 17, 203, 0.3); } #reshuffleBtn:disabled { opacity: 0.6; cursor: not-allowed; } /* 按钮发光效果 */ #reshuffleBtn::after { content: ''; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%; background: rgba(255, 255, 255, 0.1); transform: rotate(45deg); transition: all 0.6s ease; } #reshuffleBtn:hover:not(:disabled)::after { top: -10%; left: -10%; } /* 按钮图标 */ #reshuffleBtn::before { content: '🔄'; margin-right: 8px; font-size: 18px; } /* 脚气卡动画 */ .reshuffle-animation { animation: reshuffleFloat 1.2s ease-in-out forwards; box-shadow: 0 0 20px rgba(106, 17, 203, 0.7); z-index: 1000; } @keyframes reshuffleFloat { 0% { transform: translate(0, 0) rotate(0deg) scale(1); opacity: 1; } 50% { transform: translate(0, -100px) rotate(180deg) scale(1.2); box-shadow: 0 0 30px rgba(106, 17, 203, 0.9); } 100% { transform: translate(0, -200px) rotate(360deg) scale(0.5); opacity: 0; } } /* 手牌区域动画 */ #hand.reshuffling { position: relative; overflow: visible; } #hand.reshuffling::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(106, 17, 203, 0.1); border-radius: 8px; animation: reshuffleGlow 1.5s infinite alternate; z-index: -1; } @keyframes reshuffleGlow { from { box-shadow: 0 0 10px rgba(106, 17, 203, 0.3); } to { box-shadow: 0 0 30px rgba(106, 17, 203, 0.7); } } /* 按钮状态提示 */ #reshuffleBtn.active-reshuffle { animation: reshufflePulse 1.5s infinite; } @keyframes reshufflePulse { 0% { box-shadow: 0 0 0 0 rgba(106, 17, 203, 0.7); } 70% { box-shadow: 0 0 0 15px rgba(106, 17, 203, 0); } 100% { box-shadow: 0 0 0 0 rgba(106, 17, 203, 0); } }

关键技术点

三、安全与验证系统
generateDeckMD5()
generateDeckMD5() - 生成牌堆MD5 function generateDeckMD5() { // 创建牌堆数据的字符串表示 const deckData = deck.map(card => `${card.name}|${card.suit}|${card.point}|${card.uid}` ).join(';'); // 使用CryptoJS计算MD5哈希 const hash = CryptoJS.MD5(deckData).toString(); // 存储哈希值 deckMD5 = hash; // 更新显示 updateMD5Display(); // 添加安全日志 securityLog.push({ timestamp: new Date().toISOString(), action: 'generate_deck_md5', hash: hash, deckSize: deck.length }); log(`牌堆MD5已更新: ${hash.substring(0, 8)}...`); return hash; } // 更新MD5显示 function updateMD5Display() { const md5Element = document.getElementById('md5Value'); if (!md5Element) return; // 显示前8位和后8位 const displayHash = deckMD5 ? `${deckMD5.substring(0, 8)}...${deckMD5.substring(24)}` : '未生成'; md5Element.textContent = displayHash; // 添加复制按钮功能 md5Element.onclick = function() { navigator.clipboard.writeText(deckMD5) .then(() => { const originalText = this.textContent; this.textContent = '已复制!'; this.classList.add('copied'); setTimeout(() => { this.textContent = originalText; this.classList.remove('copied'); }, 2000); }) .catch(err => { console.error('复制失败:', err); }); }; }
生成牌堆的MD5标识符,用于安全验证。
配套CSS
加密面板CSS /* MD5验证面板 */ .md5-panel { background: #e8f5e9; border: 1px solid #4CAF50; border-radius: 8px; padding: 15px; margin: 15px 0; position: relative; overflow: hidden; } .md5-panel::before { content: '安全验证'; position: absolute; top: -10px; left: 15px; background: #4CAF50; color: white; padding: 2px 10px; border-radius: 4px; font-size: 12px; font-weight: bold; } /* MD5值显示 */ #md5Value { display: inline-block; background: #2d2d2d; color: #4CAF50; font-family: 'Consolas', 'Monaco', monospace; padding: 8px 15px; border-radius: 4px; margin: 10px 0; cursor: pointer; transition: all 0.3s ease; border: 1px solid #4CAF50; box-shadow: 0 2px 5px rgba(0,0,0,0.1); min-width: 200px; text-align: center; overflow: hidden; text-overflow: ellipsis; } #md5Value:hover { background: #3d3d3d; box-shadow: 0 4px 8px rgba(0,0,0,0.15); } #md5Value.copied { background: #388E3C; color: white; animation: pulseSuccess 1s; } @keyframes pulseSuccess { 0% { box-shadow: 0 0 0 0 rgba(56, 142, 60, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(56, 142, 60, 0); } 100% { box-shadow: 0 0 0 0 rgba(56, 142, 60, 0); } } /* 验证结果样式 */ #verifyResult { padding: 8px 12px; border-radius: 4px; font-weight: bold; margin-top: 10px; display: inline-block; } .verify-success { background: #E8F5E9; color: #2E7D32; border: 1px solid #4CAF50; } .verify-failure { background: #FFEBEE; color: #C62828; border: 1px solid #F44336; } /* 验证按钮 */ #verifyBtn { background: #4CAF50; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; transition: all 0.3s ease; margin-top: 10px; font-weight: bold; } #verifyBtn:hover { background: #388E3C; transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.2); } #verifyBtn:active { transform: translateY(0); } /* 安全图标 */ .md5-icon { display: inline-block; margin-right: 8px; font-size: 18px; } /* 响应式设计 */ @media (max-width: 768px) { .md5-panel { padding: 12px; } #md5Value { min-width: 180px; font-size: 14px; padding: 6px 12px; } }

关键技术点

generateDeckAES()
generateDeckAES() - 生成牌堆AES加密 function generateDeckAES() { // 创建牌堆数据的JSON字符串 const deckData = JSON.stringify({ timestamp: new Date().toISOString(), deck: deck.map(card => ({ name: card.name, suit: card.suit, point: card.point, uid: card.uid })) }); // 使用CryptoJS进行AES-256加密 const encrypted = CryptoJS.AES.encrypt( deckData, AES_SECRET_KEY, { mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 } ).toString(); // 存储加密结果 deckAES = encrypted; // 更新显示 updateAESDisplay(); // 添加安全日志 securityLog.push({ timestamp: new Date().toISOString(), action: 'generate_deck_aes', encrypted: encrypted.substring(0, 50) + '...' // 只存储部分 }); log(`牌堆AES加密完成: ${encrypted.substring(0, 15)}...`); return encrypted; } // 更新AES显示 function updateAESDisplay() { const aesElement = document.getElementById('aesValue'); if (!aesElement) return; // 显示前15位和后15位 const displayText = deckAES ? `${deckAES.substring(0, 15)}...${deckAES.substring(deckAES.length - 15)}` : '未生成'; aesElement.textContent = displayText; // 添加复制功能 aesElement.onclick = function() { navigator.clipboard.writeText(deckAES) .then(() => { const originalText = this.textContent; this.textContent = '已复制!'; this.classList.add('copied'); setTimeout(() => { this.textContent = originalText; this.classList.remove('copied'); }, 2000); }) .catch(err => { console.error('复制失败:', err); }); }; // 添加解密按钮功能 const decryptBtn = document.getElementById('decryptBtn'); if (decryptBtn) { decryptBtn.disabled = !deckAES; } } // 解密牌堆数据 function decryptDeckData(encryptedData) { try { const decrypted = CryptoJS.AES.decrypt( encryptedData, AES_SECRET_KEY, { mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 } ).toString(CryptoJS.enc.Utf8); if (!decrypted) { throw new Error('解密失败或密钥错误'); } return JSON.parse(decrypted); } catch (error) { log(`解密错误: ${error.message}`, 'error'); return null; } }
使用AES-256算法加密牌堆数据,生成安全编码。
配套CSS
AES显示CSS /* AES加密面板 */ .aes-panel { background: #e3f2fd; border: 1px solid #2196F3; border-radius: 8px; padding: 15px; margin: 15px 0; position: relative; overflow: hidden; } .aes-panel::before { content: '高级加密'; position: absolute; top: -10px; left: 15px; background: #2196F3; color: white; padding: 2px 10px; border-radius: 4px; font-size: 12px; font-weight: bold; } /* AES值显示 */ #aesValue { display: inline-block; background: #2d2d2d; color: #2196F3; font-family: 'Consolas', 'Monaco', monospace; padding: 8px 15px; border-radius: 4px; margin: 10px 0; cursor: pointer; transition: all 0.3s ease; border: 1px solid #2196F3; box-shadow: 0 2px 5px rgba(0,0,0,0.1); min-width: 300px; text-align: center; overflow: hidden; text-overflow: ellipsis; font-size: 14px; } #aesValue:hover { background: #3d3d3d; box-shadow: 0 4px 8px rgba(0,0,0,0.15); } #aesValue.copied { background: #1976D2; color: white; animation: pulseSuccessBlue 1s; } @keyframes pulseSuccessBlue { 0% { box-shadow: 0 0 0 0 rgba(25, 118, 210, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(25, 118, 210, 0); } 100% { box-shadow: 0 0 0 0 rgba(25, 118, 210, 0); } } /* 解密按钮 */ #decryptBtn { background: #2196F3; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; transition: all 0.3s ease; margin-top: 10px; font-weight: bold; display: inline-flex; align-items: center; } #decryptBtn:disabled { opacity: 0.5; cursor: not-allowed; } #decryptBtn:not(:disabled):hover { background: #1976D2; transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.2); } #decryptBtn:not(:disabled):active { transform: translateY(0); } /* 按钮图标 */ #decryptBtn::before { content: '🔓'; margin-right: 8px; font-size: 16px; } /* 解密结果区域 */ #decryptResult { margin-top: 15px; padding: 12px; background: #f5f5f5; border-radius: 6px; border: 1px dashed #bdbdbd; font-family: 'Consolas', 'Monaco', monospace; font-size: 13px; max-height: 200px; overflow-y: auto; white-space: pre-wrap; } /* 安全锁图标 */ .aes-icon { display: inline-block; margin-right: 8px; font-size: 18px; } /* 响应式设计 */ @media (max-width: 768px) { .aes-panel { padding: 12px; } #aesValue { min-width: 250px; font-size: 12px; padding: 6px 12px; } #decryptResult { font-size: 12px; } }

关键技术点

generateSecurityLog()
generateSecurityLog() - 生成安全日志 function generateSecurityLog() { // 创建日志数据 const logData = { timestamp: new Date().toISOString(), deckMD5: deckMD5, deckAES: deckAES, deckSize: deck.length, handSize: hand.length, discardSize: discardPile.length, reshuffleUsed: reshuffleUsed, securityEvents: securityLog }; // 转换为JSON字符串 const logString = JSON.stringify(logData, null, 2); // 创建Blob对象 const blob = new Blob([logString], { type: 'application/json' }); const url = URL.createObjectURL(blob); // 创建下载链接 const downloadLink = document.createElement('a'); downloadLink.href = url; downloadLink.download = `security_log_${new Date().getTime()}.json`; downloadLink.textContent = '下载安全日志'; downloadLink.className = 'security-download'; // 添加到日志容器 const logContainer = document.getElementById('securityLog'); logContainer.innerHTML = ''; logContainer.appendChild(downloadLink); // 添加日志预览 const preview = document.createElement('pre'); preview.className = 'log-preview'; preview.textContent = logString; logContainer.appendChild(preview); log("安全日志已生成,可下载查看"); // 添加安全日志记录 securityLog.push({ timestamp: new Date().toISOString(), action: 'generate_security_log', logSize: logString.length }); return logString; } // 安全日志查看器 function viewSecurityLog() { const logData = generateSecurityLog(); showModal('安全日志详情', `<pre>${logData}</pre>`); }
生成包含牌堆详细信息的JSON格式日志文件,支持下载和预览。
配套CSS
安全日志CSS /* 安全日志容器 */ #securityLog { background: #fff3e0; border: 1px solid #ff9800; border-radius: 8px; padding: 15px; margin: 15px 0; position: relative; overflow: hidden; } #securityLog::before { content: '安全日志'; position: absolute; top: -10px; left: 15px; background: #ff9800; color: white; padding: 2px 10px; border-radius: 4px; font-size: 12px; font-weight: bold; } /* 下载链接 */ .security-download { display: inline-block; background: #ff9800; color: white; padding: 10px 20px; border-radius: 4px; text-decoration: none; font-weight: bold; transition: all 0.3s ease; margin-bottom: 15px; box-shadow: 0 3px 6px rgba(0,0,0,0.1); } .security-download:hover { background: #f57c00; transform: translateY(-2px); box-shadow: 0 5px 10px rgba(0,0,0,0.15); } .security-download:active { transform: translateY(0); } /* 日志预览 */ .log-preview { background: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 6px; font-family: 'Consolas', 'Monaco', monospace; font-size: 13px; line-height: 1.5; max-height: 300px; overflow: auto; margin-top: 15px; border: 1px solid #555; } /* 日志高亮 */ .log-preview .timestamp { color: #f92672; } .log-preview .action { color: #a6e22e; } .log-preview .hash { color: #66d9ef; } /* 滚动条样式 */ .log-preview::-webkit-scrollbar { width: 8px; } .log-preview::-webkit-scrollbar-track { background: #3d3d3d; border-radius: 4px; } .log-preview::-webkit-scrollbar-thumb { background: #ff9800; border-radius: 4px; } .log-preview::-webkit-scrollbar-thumb:hover { background: #ffab40; } /* 安全日志按钮 */ #securityLogBtn { background: #ff9800; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; transition: all 0.3s ease; font-weight: bold; display: flex; align-items: center; margin: 10px 0; } #securityLogBtn:hover { background: #f57c00; transform: translateY(-2px); box-shadow: 0 5px 10px rgba(0,0,0,0.15); } #securityLogBtn:active { transform: translateY(0); } /* 按钮图标 */ #securityLogBtn::before { content: '🔒'; margin-right: 8px; font-size: 16px; } /* 响应式设计 */ @media (max-width: 768px) { #securityLog { padding: 12px; } .log-preview { font-size: 12px; padding: 10px; max-height: 200px; } .security-download { padding: 8px 16px; font-size: 14px; } }

关键技术点

四、测试与开发工具
testShuffleUniformity()
testShuffleUniformity() - 洗牌均匀性测试 function testShuffleUniformity() { // 创建测试面板 createTestPanel(); // 重置测试结果 testResults = { totalTests: 0, positionCounts: {}, cardDistribution: {} }; // 初始化统计数据 for (let i = 0; i < 160; i++) { testResults.positionCounts[i]=0; } // 设置测试卡牌 const testCard={ name: "测试卡" , suit: "♠" , point: 7, uid: "TEST_CARD_001" }; // 添加测试卡到牌堆 deck.push(testCard); // 开始测试 log("开始洗牌均匀性测试..."); startUniformityTest(); } // 创建测试面板 function createTestPanel() { // 检查是否已存在测试面板 if (document.getElementById('testPanel')) return; // 创建测试面板 const panel=document.createElement('div'); panel.id='testPanel' ; panel.className='test-panel' ; panel.innerHTML=`

洗牌均匀性测试

0%
`; // 添加到页面 document.body.appendChild(panel); // 绑定事件 document.getElementById('startTest').addEventListener('click', startUniformityTest); document.getElementById('stopTest').addEventListener('click', stopUniformityTest); } // 开始均匀性测试 function startUniformityTest() { if (isTesting) return; const testCount = parseInt(document.getElementById('testCount').value) || 1000; isTesting = true; testResults.totalTests = testCount; // 重置进度 const progressBar = document.getElementById('testProgress'); const progressText = document.getElementById('progressText'); progressBar.value = 0; progressText.textContent = '0%'; // 执行测试 let completed = 0; const testCard = deck.find(card => card.uid === "TEST_CARD_001"); const testInterval = setInterval(() => { if (!isTesting) { clearInterval(testInterval); return; } // 洗牌 shuffleDeck(); // 查找测试卡位置 const position = deck.findIndex(card => card.uid === testCard.uid); // 更新统计数据 testResults.positionCounts[position] = (testResults.positionCounts[position] || 0) + 1; // 更新进度 completed++; const percent = Math.round((completed / testCount) * 100); progressBar.value = percent; progressText.textContent = `${percent}%`; // 每10次更新一次结果 if (completed % 10 === 0) { updateTestResults(); } // 测试完成 if (completed >= testCount) { clearInterval(testInterval); isTesting = false; log(`洗牌均匀性测试完成,共测试 ${testCount} 次`); updateTestResults(true); } }, 10); } // 停止测试 function stopUniformityTest() { isTesting = false; log("测试已停止"); } // 更新测试结果 function updateTestResults(final = false) { const positionChart = document.getElementById('positionChart'); const statResults = document.getElementById('statResults'); // 清空容器 positionChart.innerHTML = ''; statResults.innerHTML = ''; // 计算统计数据 const totalTests = testResults.totalTests; let minPosition = Infinity; let maxPosition = -Infinity; let minCount = Infinity; let maxCount = -Infinity; // 找到最小和最大位置 for (let i = 0; i < 160; i++) { if (testResults.positionCounts[i]> 0) { if (i < minPosition) minPosition=i; if (i> maxPosition) maxPosition = i; } } // 创建图表 const chartHeight = 200; const barWidth = 3; const containerWidth = (maxPosition - minPosition + 1) * (barWidth + 1); positionChart.style.width = `${containerWidth}px`; // 创建条形图 for (let i = minPosition; i <= maxPosition; i++) { const count=testResults.positionCounts[i] || 0; const barHeight=count> 0 ? Math.max(1, (count / totalTests) * chartHeight * 10) : 0; const bar = document.createElement('div'); bar.className = 'chart-bar'; bar.style.height = `${barHeight}px`; bar.style.width = `${barWidth}px`; bar.style.left = `${(i - minPosition) * (barWidth + 1)}px`; bar.style.bottom = '0'; // 添加悬停提示 bar.title = `位置 ${i}: ${count} 次 (${((count / totalTests) * 100).toFixed(2)}%)`; positionChart.appendChild(bar); // 更新最小/最大值 if (count < minCount) minCount=count; if (count> maxCount) maxCount = count; } // 添加统计信息 const expected = totalTests / deck.length; const deviation = ((maxCount - minCount) / expected) * 100; statResults.innerHTML = `

统计结果

测试次数: ${totalTests}

牌堆大小: ${deck.length}

最小位置: ${minPosition} (${minCount}次)

最大位置: ${maxPosition} (${maxCount}次)

期望次数: ${expected.toFixed(2)}

最大偏差: ${deviation.toFixed(2)}%

标准差: ${calculateStandardDeviation().toFixed(2)}

`; // 最终结果添加动画 if (final) { positionChart.classList.add('final-results'); statResults.classList.add('final-results'); } } // 计算标准差 function calculateStandardDeviation() { const totalTests = testResults.totalTests; const deckSize = deck.length; const expected = totalTests / deckSize; let sumSquaredDiff = 0; for (let i = 0; i < deckSize; i++) { const count=testResults.positionCounts[i] || 0; const diff=count - expected; sumSquaredDiff +=diff * diff; } return Math.sqrt(sumSquaredDiff / deckSize); }
执行洗牌均匀性测试,统计特定卡牌在牌堆中不同位置出现的概率分布。
配套CSS
测试面板CSS /* 测试面板 */ .test-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 800px; background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); padding: 20px; z-index: 3000; max-height: 90vh; overflow-y: auto; } .test-panel h3 { color: #8e0e00; text-align: center; margin-top: 0; padding-bottom: 10px; border-bottom: 2px solid #f1c40f; } /* 测试控制区 */ .test-controls { display: flex; justify-content: center; gap: 15px; margin: 20px 0; flex-wrap: wrap; align-items: center; } .test-controls button { padding: 10px 20px; border: none; border-radius: 6px; font-weight: bold; cursor: pointer; transition: all 0.3s ease; } #startTest { background: #4CAF50; color: white; } #stopTest { background: #F44336; color: white; } .test-controls button:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.2); } .test-controls button:active { transform: translateY(0); } .test-controls input { width: 100px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; text-align: center; } .test-controls label { font-size: 14px; color: #666; } /* 进度条容器 */ .progress-container { position: relative; height: 30px; margin: 20px 0; background: #f5f5f5; border-radius: 15px; overflow: hidden; } #testProgress { width: 100%; height: 100%; appearance: none; border: none; } #testProgress::-webkit-progress-bar { background: #f5f5f5; } #testProgress::-webkit-progress-value { background: linear-gradient(to right, #4CAF50, #8BC34A); transition: width 0.3s ease; } #progressText { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-weight: bold; color: #333; pointer-events: none; } /* 结果容器 */ .results-container { display: flex; gap: 20px; margin-top: 20px; } .result-chart { flex: 1; height: 220px; background: #f9f9f9; border: 1px solid #eee; border-radius: 6px; position: relative; overflow: hidden; } .result-stats { width: 300px; background: #f5f5f5; padding: 15px; border-radius: 6px; font-size: 14px; } .result-stats h4 { margin-top: 0; color: #8e0e00; border-bottom: 1px solid #ddd; padding-bottom: 5px; } .result-stats p { margin: 8px 0; } /* 图表条形 */ .chart-bar { position: absolute; background: linear-gradient(to top, #4CAF50, #8BC34A); transition: height 0.5s ease; } .chart-bar:hover { background: linear-gradient(to top, #f44336, #ff9800); z-index: 10; } /* 最终结果动画 */ .final-results { animation: finalResultPulse 2s ease-in-out; } @keyframes finalResultPulse { 0% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.5); } 50% { box-shadow: 0 0 0 15px rgba(76, 175, 80, 0); } 100% { box-shadow: 0 0 0 0 rgba(76, 175, 80, 0); } } /* 响应式设计 */ @media (max-width: 768px) { .results-container { flex-direction: column; } .result-stats { width: 100%; } .test-panel { width: 95%; padding: 15px; } }

关键技术点

addSpecificCard(cardName)
addSpecificCard(cardName) - 添加指定卡牌 function addSpecificCard(cardName) { // 检查是否在开发者模式 if (!isDevMode) { log("开发者模式未启用,无法添加指定卡牌"); return; } // 在标准牌堆中查找匹配的卡牌 const cardTemplate = standardDeck.find(c => c.name === cardName); if (!cardTemplate) { log(`找不到卡牌: ${cardName}`); return; } // 创建新卡牌实例 const newCard = { name: cardTemplate.name, suit: cardTemplate.suits[0].match(/^([♠♥♣♦])/)[1], point: parseInt(cardTemplate.suits[0].match(/(\d+|[JQKA])$/)[0].replace('J', '11').replace('Q', '12').replace('K', '13').replace('A', '1')), uid: `DEV_${Date.now()}_${Math.floor(Math.random() * 1000)}` }; // 添加到手牌 hand.push(newCard); // 更新UI updateHandDisplay(); // 添加动画效果 animateCardAddition(newCard); log(`开发者: 已添加 ${cardName} 到手牌`); } // 卡牌添加动画 function animateCardAddition(card) { const animationContainer = document.getElementById('animation-container'); // 创建动画元素 const cardElement = document.createElement('div'); cardElement.className = 'card dev-card-addition'; cardElement.textContent = `${card.name} (${card.suit}${card.point})`; cardElement.style.position = 'fixed'; cardElement.style.zIndex = '2000'; cardElement.style.top = '20px'; cardElement.style.right = '20px'; cardElement.style.transform = 'scale(0.8)'; cardElement.style.opacity = '0'; animationContainer.appendChild(cardElement); // 获取目标位置(手牌区域) const handArea = document.getElementById('hand'); const targetRect = handArea.getBoundingClientRect(); // 执行动画 anime({ targets: cardElement, top: targetRect.top + 'px', left: targetRect.left + 'px', scale: [0.8, 1.2, 1], opacity: [0, 1, 1], duration: 1200, easing: 'easeInOutQuad', complete: () => { cardElement.remove(); } }); } // 切换开发者模式 function toggleDevMode() { isDevMode = !isDevMode; document.getElementById('devModeToggle').textContent = isDevMode ? '开发者模式: 开启' : '开发者模式: 关闭'; // 显示/隐藏开发者面板 const devPanel = document.getElementById('devPanel'); devPanel.style.display = isDevMode ? 'block' : 'none'; log(`开发者模式 ${isDevMode ? '已开启' : '已关闭'}`); }
在测试模式下添加指定卡牌到手牌中。
配套CSS
开发者工具CSS /* 开发者面板 */ .dev-panel { position: fixed; top: 20px; right: 20px; background: rgba(0, 0, 0, 0.8); color: #00ff00; border-radius: 8px; padding: 15px; z-index: 2000; font-family: 'Consolas', 'Monaco', monospace; box-shadow: 0 0 15px rgba(0, 255, 0, 0.5); border: 1px solid #00ff00; max-width: 300px; display: none; } .dev-panel h3 { margin-top: 0; color: #00ff00; border-bottom: 1px solid #00ff00; padding-bottom: 8px; font-size: 18px; } /* 开发者模式切换按钮 */ #devModeToggle { position: fixed; top: 20px; left: 20px; background: #333; color: #00ff00; border: 1px solid #00ff00; border-radius: 4px; padding: 8px 16px; font-family: 'Consolas', 'Monaco', monospace; cursor: pointer; z-index: 2000; transition: all 0.3s ease; } #devModeToggle:hover { background: #00ff00; color: #000; box-shadow: 0 0 15px rgba(0, 255, 0, 0.7); } /* 开发者控制项 */ .dev-control { margin: 10px 0; display: flex; align-items: center; } .dev-control label { flex: 1; font-size: 14px; } .dev-control input, .dev-control select { background: #222; color: #00ff00; border: 1px solid #00ff00; border-radius: 4px; padding: 6px; width: 150px; font-family: 'Consolas', 'Monaco', monospace; } .dev-control button { background: #333; color: #00ff00; border: 1px solid #00ff00; border-radius: 4px; padding: 6px 12px; cursor: pointer; transition: all 0.3s ease; font-family: 'Consolas', 'Monaco', monospace; } .dev-control button:hover { background: #00ff00; color: #000; } /* 开发者卡牌添加动画 */ .card.dev-card-addition { background: linear-gradient(45deg, #00ff00, #00cc00); color: black; font-weight: bold; border: 2px solid #00ff00; box-shadow: 0 0 20px rgba(0, 255, 0, 0.7); animation: devCardGlow 1.5s infinite alternate; } @keyframes devCardGlow { from { box-shadow: 0 0 10px rgba(0, 255, 0, 0.5); } to { box-shadow: 0 0 30px rgba(0, 255, 0, 0.9); } } /* 开发者模式下的特殊样式 */ .dev-mode .card { border: 1px dashed rgba(0, 255, 0, 0.5); } .dev-mode .card:hover { border: 1px solid #00ff00; box-shadow: 0 0 15px rgba(0, 255, 0, 0.5); } /* 控制台样式 */ .dev-console { background: #111; color: #00ff00; padding: 10px; border-radius: 4px; margin-top: 10px; max-height: 150px; overflow-y: auto; font-size: 12px; border: 1px solid #008800; } .dev-console::-webkit-scrollbar { width: 6px; } .dev-console::-webkit-scrollbar-thumb { background: #008800; border-radius: 3px; } /* 响应式设计 */ @media (max-width: 768px) { .dev-panel { top: 10px; right: 10px; left: 10px; max-width: none; } #devModeToggle { top: 10px; left: 10px; } }

关键技术点

五、用户界面系统
initializeButtons()
initializeButtons() - 初始化按钮事件 function initializeButtons() { // 核心游戏按钮 document.getElementById('shuffleBtn').addEventListener('click', shuffleDeck); document.getElementById('drawBtn').addEventListener('click', () => drawCards(3)); document.getElementById('endPhaseBtn').addEventListener('click', endPhase); // 特殊功能按钮 document.getElementById('reshuffleBtn').addEventListener('click', handleReshuffle); document.getElementById('securityLogBtn').addEventListener('click', generateSecurityLog); // 开发者模式按钮 document.getElementById('devModeToggle').addEventListener('click', toggleDevMode); // 卡牌操作按钮 const cardButtons = document.querySelectorAll('.card-action'); cardButtons.forEach(btn => { btn.addEventListener('click', function() { const cardId = this.dataset.cardId; const action = this.dataset.action; handleCardAction(cardId, action); }); }); // 阶段切换按钮 const phaseButtons = document.querySelectorAll('.phase-btn'); phaseButtons.forEach(btn => { btn.addEventListener('click', function() { const phase = this.dataset.phase; setPhase(phase); }); }); // 键盘快捷键 document.addEventListener('keydown', handleKeyDown); log("按钮事件初始化完成"); } // 键盘快捷键处理 function handleKeyDown(event) { // 防止在输入框中触发 if (event.target.tagName === 'INPUT') return; switch (event.key.toLowerCase()) { case 's': document.getElementById('shuffleBtn').click(); break; case 'd': document.getElementById('drawBtn').click(); break; case 'e': document.getElementById('endPhaseBtn').click(); break; case 'r': document.getElementById('reshuffleBtn').click(); break; case 'l': document.getElementById('securityLogBtn').click(); break; case 'f1': document.getElementById('helpBtn').click(); break; case 'escape': closeAllModals(); break; } } // 卡牌操作处理 function handleCardAction(cardId, action) { const card = hand.find(c => c.uid === cardId); if (!card) return; switch (action) { case 'use': useCard(card); break; case 'discard': discardCard(card); break; case 'equip': equipCard(card); break; } } // 关闭所有模态框 function closeAllModals() { document.querySelectorAll('.modal').forEach(modal => { modal.style.display = 'none'; }); }
绑定所有按钮的点击事件处理函数,并设置键盘快捷键。
配套CSS
导航按钮CSS /* 按钮容器 */ .button-container { display: flex; flex-wrap: wrap; gap: 12px; margin: 20px 0; justify-content: center; padding: 15px; background: #f8f9fa; border-radius: 8px; border: 1px solid #e0e0e0; } /* 核心按钮样式 */ .nav-button { padding: 12px 24px; background: linear-gradient(to bottom, #4CAF50, #2E7D32); color: white; border: none; border-radius: 6px; cursor: pointer; transition: all 0.3s ease; font-weight: bold; box-shadow: 0 4px 8px rgba(0,0,0,0.15); display: flex; align-items: center; justify-content: center; min-width: 120px; position: relative; overflow: hidden; } .nav-button:hover { transform: translateY(-3px); box-shadow: 0 6px 12px rgba(0,0,0,0.2); background: linear-gradient(to bottom, #66BB6A, #388E3C); } .nav-button:active { transform: translateY(1px); box-shadow: 0 2px 5px rgba(0,0,0,0.2); } /* 按钮波纹效果 */ .nav-button::after { content: ''; position: absolute; top: 50%; left: 50%; width: 0; height: 0; background: rgba(255, 255, 255, 0.3); border-radius: 50%; transform: translate(-50%, -50%); opacity: 0; } .nav-button:active::after { animation: ripple 0.6s linear; } @keyframes ripple { 0% { width: 0; height: 0; opacity: 1; } 100% { width: 300px; height: 300px; opacity: 0; } } /* 按钮图标 */ .nav-button::before { margin-right: 8px; font-size: 18px; } #shuffleBtn::before { content: '🔀'; } #drawBtn::before { content: '🃏'; } #endPhaseBtn::before { content: '⏭️'; } #reshuffleBtn::before { content: '🔄'; } #securityLogBtn::before { content: '📝'; } #helpBtn::before { content: '❓'; } /* 按钮状态 */ .nav-button.active { animation: buttonPulse 1.5s infinite; border: 2px solid #FFEB3B; } @keyframes buttonPulse { 0% { box-shadow: 0 0 0 0 rgba(255, 235, 59, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(255, 235, 59, 0); } 100% { box-shadow: 0 0 0 0 rgba(255, 235, 59, 0); } } .nav-button:disabled { opacity: 0.5; cursor: not-allowed; transform: none; box-shadow: none; } /* 卡牌操作按钮 */ .card-action { padding: 8px 12px; background: rgba(255, 255, 255, 0.8); border: 1px solid #ddd; border-radius: 4px; cursor: pointer; transition: all 0.2s ease; font-size: 12px; margin: 4px; } .card-action:hover { background: rgba(255, 255, 255, 1); transform: translateY(-2px); box-shadow: 0 2px 5px rgba(0,0,0,0.1); } /* 按钮组 */ .button-group { display: flex; gap: 8px; margin-top: 10px; } /* 响应式设计 */ @media (max-width: 768px) { .button-container { flex-direction: column; align-items: center; } .nav-button { width: 100%; max-width: 300px; margin-bottom: 10px; } .button-group { flex-direction: column; } }

关键技术点

updateTooltipAlignment()
updateTooltipAlignment() - 更新工具提示对齐 function updateTooltipAlignment() { // 获取所有工具提示元素 const tooltips = document.querySelectorAll('.css-tooltip'); // 遍历所有工具提示 tooltips.forEach(tooltip => { const parent = tooltip.parentElement; const parentRect = parent.getBoundingClientRect(); // 获取视口尺寸 const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; // 重置位置 tooltip.style.left = ''; tooltip.style.right = ''; tooltip.style.top = ''; tooltip.style.bottom = ''; // 水平位置调整 if (parentRect.left + tooltip.offsetWidth > viewportWidth - 20) { // 如果右边空间不足,显示在左侧 tooltip.style.right = '100%'; tooltip.style.left = 'auto'; } else { // 默认显示在右侧 tooltip.style.left = '0'; tooltip.style.right = 'auto'; } // 垂直位置调整 if (parentRect.top - tooltip.offsetHeight < 20) { // 如果上方空间不足,显示在下方 tooltip.style.top='100%' ; tooltip.style.bottom='auto' ; } else { // 默认显示在上方 tooltip.style.bottom='100%' ; tooltip.style.top='auto' ; } }); // 添加箭头指示器 addTooltipArrows(); } // 添加工具提示箭头 function addTooltipArrows() { // 移除现有箭头 document.querySelectorAll('.tooltip-arrow').forEach(arrow=> arrow.remove()); // 为每个工具提示添加箭头 document.querySelectorAll('.css-tooltip').forEach(tooltip => { const arrow = document.createElement('div'); arrow.className = 'tooltip-arrow'; // 根据位置设置箭头方向 if (tooltip.style.bottom === '100%') { // 上方提示框 arrow.style.bottom = '-5px'; arrow.style.borderTopColor = '#f8f9fa'; } else { // 下方提示框 arrow.style.top = '-5px'; arrow.style.borderBottomColor = '#f8f9fa'; } if (tooltip.style.left === '0') { // 右侧提示框 arrow.style.left = '15px'; } else { // 左侧提示框 arrow.style.right = '15px'; } tooltip.appendChild(arrow); }); } // 初始化响应式布局 function initResponsiveLayout() { // 初始调整 updateTooltipAlignment(); // 监听窗口大小变化 window.addEventListener('resize', updateTooltipAlignment); // 监听滚动事件 window.addEventListener('scroll', updateTooltipAlignment); log("响应式布局已初始化"); }
根据屏幕尺寸和位置动态调整工具提示的位置和方向。
配套CSS
响应式设计CSS /* 工具提示基础样式 */ .css-tooltip { position: absolute; z-index: 1000; box-shadow: 0 6px 16px rgba(0,0,0,0.15); border: 1px solid #dadce0; border-radius: 8px; padding: 15px; background: #f8f9fa; color: #202124; font-family: 'Consolas', 'Monaco', monospace; font-size: 13px; line-height: 1.5; white-space: pre-wrap; overflow: auto; max-height: 400px; display: none; transition: opacity 0.3s ease, transform 0.3s ease; } /* 工具提示箭头 */ .tooltip-arrow { position: absolute; width: 0; height: 0; border-style: solid; border-width: 5px; border-color: transparent transparent #f8f9fa transparent; } /* 响应式调整 */ @media (max-width: 1200px) { .css-tooltip { max-width: 500px; } } @media (max-width: 992px) { .css-tooltip { max-width: 400px; font-size: 12px; padding: 12px; } } @media (max-width: 768px) { .css-tooltip { max-width: 90vw; left: 5vw !important; right: 5vw !important; transform: none !important; } /* 移动设备上固定位置 */ .css-tooltip.mobile-position { position: fixed; top: 50% !important; left: 5% !important; right: 5% !important; bottom: auto !important; transform: translateY(-50%) !important; max-height: 70vh; } .tooltip-arrow { display: none; } } @media (max-width: 480px) { .css-tooltip { padding: 10px; font-size: 11px; } .css-tooltip code { padding: 10px; font-size: 10px; } } /* 工具提示显示动画 */ .css-tooltip.showing { animation: tooltipAppear 0.3s ease forwards; display: block; opacity: 0; transform: translateY(10px); } @keyframes tooltipAppear { to { opacity: 1; transform: translateY(0); } } /* 方向箭头 */ .tooltip-top .tooltip-arrow { bottom: -5px; left: 50%; transform: translateX(-50%); border-width: 5px 5px 0; border-color: #f8f9fa transparent transparent transparent; } .tooltip-bottom .tooltip-arrow { top: -5px; left: 50%; transform: translateX(-50%); border-width: 0 5px 5px; border-color: transparent transparent #f8f9fa transparent; } .tooltip-left .tooltip-arrow { right: -5px; top: 50%; transform: translateY(-50%); border-width: 5px 0 5px 5px; border-color: transparent transparent transparent #f8f9fa; } .tooltip-right .tooltip-arrow { left: -5px; top: 50%; transform: translateY(-50%); border-width: 5px 5px 5px 0; border-color: transparent #f8f9fa transparent transparent; } /* 触摸设备优化 */ @media (hover: none) { .css-name:hover .css-tooltip { display: none; } .css-tooltip.mobile-tap { display: block; animation: tooltipAppear 0.3s ease forwards; } }

关键技术点

highlightCard(cardElement)
highlightCard(cardElement) - 高亮卡牌 function highlightCard(cardElement) { // 移除现有高亮效果 cardElement.classList.remove('card-highlight'); // 触发重排以重置动画 void cardElement.offsetWidth; // 添加高亮类 cardElement.classList.add('card-highlight'); // 添加粒子效果 createHighlightParticles(cardElement); // 3秒后移除高亮效果 setTimeout(() => { cardElement.classList.remove('card-highlight'); }, 3000); } // 创建高亮粒子效果 function createHighlightParticles(cardElement) { const rect = cardElement.getBoundingClientRect(); const particleContainer = document.getElementById('particle-container'); // 创建粒子数量 const particleCount = 30; for (let i = 0; i < particleCount; i++) { const particle=document.createElement('div'); particle.className='highlight-particle' ; // 随机位置 const x=rect.left + Math.random() * rect.width; const y=rect.top + Math.random() * rect.height; particle.style.left=`${x}px`; particle.style.top=`${y}px`; // 随机颜色 const colors=['#ffeb3b', '#4caf50' , '#2196f3' , '#f44336' ]; const color=colors[Math.floor(Math.random() * colors.length)]; particle.style.backgroundColor=color; // 随机大小 const size=3 + Math.random() * 5; particle.style.width=`${size}px`; particle.style.height=`${size}px`; particleContainer.appendChild(particle); // 粒子动画 anime({ targets: particle, translateX: ()=> anime.random(-100, 100), translateY: () => anime.random(-100, 100), scale: [1, 0], opacity: [1, 0], duration: 1000 + Math.random() * 1000, easing: 'easeOutExpo', complete: () => { particle.remove(); } }); } }
为指定卡牌添加高亮效果,提供视觉反馈。
配套CSS
高亮动画CSS /* 卡牌高亮效果 */ .card-highlight { animation: cardHighlight 3s ease-in-out; z-index: 100; position: relative; } @keyframes cardHighlight { 0% { transform: scale(1); box-shadow: 0 0 0 rgba(255, 235, 59, 0); } 25% { transform: scale(1.1); box-shadow: 0 0 20px rgba(255, 235, 59, 0.8); } 50% { transform: scale(1.05); box-shadow: 0 0 30px rgba(255, 235, 59, 0.9); } 75% { transform: scale(1.08); box-shadow: 0 0 25px rgba(255, 235, 59, 0.7); } 100% { transform: scale(1); box-shadow: 0 0 0 rgba(255, 235, 59, 0); } } /* 高亮粒子 */ .highlight-particle { position: absolute; border-radius: 50%; pointer-events: none; z-index: 2000; } /* 粒子容器 */ #particle-container { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1500; overflow: hidden; } /* 特殊卡牌类型的高亮 */ .card[data-card-type="kill"].card-highlight { animation: killHighlight 3s ease-in-out; } @keyframes killHighlight { 0% { box-shadow: 0 0 0 rgba(244, 67, 54, 0); } 50% { box-shadow: 0 0 30px rgba(244, 67, 54, 0.8); } 100% { box-shadow: 0 0 0 rgba(244, 67, 54, 0); } } .card[data-card-type="dodge"].card-highlight { animation: dodgeHighlight 3s ease-in-out; } @keyframes dodgeHighlight { 0% { box-shadow: 0 0 0 rgba(33, 150, 243, 0); } 50% { box-shadow: 0 0 30px rgba(33, 150, 243, 0.8); } 100% { box-shadow: 0 0 0 rgba(33, 150, 243, 0); } } /* 悬停高亮效果 */ .card:hover:not(.disabled) { transform: translateY(-8px); box-shadow: 0 12px 20px rgba(0, 0, 0, 0.2); z-index: 50; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); } /* 减少运动偏好设置 */ @media (prefers-reduced-motion: reduce) { .card-highlight { animation: none !important; box-shadow: 0 0 10px rgba(255, 235, 59, 0.7) !important; } .highlight-particle { display: none; } .card:hover:not(.disabled) { transform: translateY(-4px); transition: none; } }

关键技术点

六、更新日志系统
renderVersionList(versions)
renderVersionList(versions) - 渲染版本列表 function renderVersionList(versions) { const container = document.getElementById('version-history'); container.innerHTML = ''; // 按日期降序排序 versions.sort((a, b) => new Date(b.date) - new Date(a.date)); // 创建时间线容器 const timeline = document.createElement('div'); timeline.className = 'version-timeline'; versions.forEach(version => { // 创建版本条目 const versionItem = document.createElement('div'); versionItem.className = 'version-item'; // 创建版本头 const header = document.createElement('div'); header.className = 'version-header'; const versionBadge = document.createElement('span'); versionBadge.className = 'version-badge'; versionBadge.textContent = version.version; const dateBadge = document.createElement('span'); dateBadge.className = 'date-badge'; dateBadge.textContent = new Date(version.date).toLocaleDateString(); header.appendChild(versionBadge); header.appendChild(dateBadge); // 创建更新内容 const content = document.createElement('div'); content.className = 'version-content'; if (version.changes && version.changes.length > 0) { const changeList = document.createElement('ul'); changeList.className = 'change-list'; version.changes.forEach(change => { const changeItem = document.createElement('li'); changeItem.className = `change-item change-${change.type || 'feature'}`; const changeIcon = document.createElement('span'); changeIcon.className = 'change-icon'; changeIcon.textContent = change.type === 'fix' ? '🐞' : change.type === 'improvement' ? '⚡' : '✨'; const changeText = document.createElement('span'); changeText.className = 'change-text'; changeText.textContent = change.description; changeItem.appendChild(changeIcon); changeItem.appendChild(changeText); changeList.appendChild(changeItem); }); content.appendChild(changeList); } // 添加版本条目 versionItem.appendChild(header); versionItem.appendChild(content); timeline.appendChild(versionItem); }); container.appendChild(timeline); }
将版本更新数据渲染为时间线样式的可视化列表。
配套CSS
日志页面CSS /* 版本历史容器 */ #version-history { max-width: 800px; margin: 0 auto; padding: 20px; } /* 时间线样式 */ .version-timeline { position: relative; padding-left: 30px; border-left: 3px solid #e0e0e0; margin-left: 15px; } .version-timeline::before { content: ''; position: absolute; top: 0; left: -8px; width: 16px; height: 16px; border-radius: 50%; background: #2196F3; z-index: 1; } .version-timeline::after { content: ''; position: absolute; bottom: 0; left: -8px; width: 16px; height: 16px; border-radius: 50%; background: #4CAF50; z-index: 1; } /* 版本条目 */ .version-item { position: relative; margin-bottom: 30px; padding: 20px; background: white; border-radius: 8px; box-shadow: 0 3px 10px rgba(0,0,0,0.08); transition: all 0.3s ease; } .version-item:hover { transform: translateY(-5px); box-shadow: 0 5px 15px rgba(0,0,0,0.1); } .version-item::before { content: ''; position: absolute; top: 24px; left: -33px; width: 12px; height: 12px; border-radius: 50%; background: #2196F3; border: 3px solid white; z-index: 2; } /* 版本头 */ .version-header { display: flex; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #f0f0f0; } .version-badge { background: #2196F3; color: white; padding: 5px 12px; border-radius: 20px; font-weight: bold; font-size: 16px; margin-right: 15px; } .date-badge { background: #f5f5f5; color: #666; padding: 4px 10px; border-radius: 4px; font-size: 14px; } /* 更新内容 */ .change-list { list-style: none; padding: 0; margin: 0; } .change-item { padding: 8px 0; display: flex; align-items: flex-start; } .change-icon { margin-right: 10px; font-size: 18px; min-width: 24px; text-align: center; } .change-text { flex: 1; line-height: 1.5; } /* 不同类型的更新 */ .change-feature .change-icon { color: #4CAF50; } .change-fix .change-icon { color: #F44336; } .change-improvement .change-icon { color: #FF9800; } /* 动画效果 */ .version-item { animation: fadeInUp 0.6s ease forwards; opacity: 0; transform: translateY(20px); } @keyframes fadeInUp { to { opacity: 1; transform: translateY(0); } } .version-item:nth-child(1) { animation-delay: 0.1s; } .version-item:nth-child(2) { animation-delay: 0.2s; } .version-item:nth-child(3) { animation-delay: 0.3s; } .version-item:nth-child(4) { animation-delay: 0.4s; } /* 响应式设计 */ @media (max-width: 768px) { .version-timeline { padding-left: 20px; margin-left: 10px; } .version-item::before { left: -28px; } .version-header { flex-direction: column; align-items: flex-start; } .date-badge { margin-top: 8px; } } @media (max-width: 480px) { .version-item { padding: 15px; } .version-badge { font-size: 14px; } .change-item { font-size: 14px; } }

关键技术点

七、辅助工具组件
showTooltip(element, content)
showTooltip(element, content) - 显示工具提示 function showTooltip(element, content) { // 检查是否已存在工具提示 const existingTooltip = document.getElementById('custom-tooltip'); if (existingTooltip) existingTooltip.remove(); // 创建工具提示元素 const tooltip = document.createElement('div'); tooltip.id = 'custom-tooltip'; tooltip.className = 'tooltip'; tooltip.innerHTML = content; // 添加到DOM document.body.appendChild(tooltip); // 获取元素位置 const rect = element.getBoundingClientRect(); const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; // 计算位置(避免超出视口) let top = rect.top + scrollTop - tooltip.offsetHeight - 10; let left = rect.left + scrollLeft + rect.width / 2 - tooltip.offsetWidth / 2; // 边界检查 if (top < 10) top=rect.bottom + scrollTop + 10; if (left < 10) left=10; if (left + tooltip.offsetWidth> window.innerWidth - 10) { left = window.innerWidth - tooltip.offsetWidth - 10; } // 设置位置 tooltip.style.top = `${top}px`; tooltip.style.left = `${left}px`; // 添加箭头 const arrow = document.createElement('div'); arrow.className = 'tooltip-arrow'; tooltip.appendChild(arrow); // 设置箭头方向 if (top > rect.top + scrollTop) { // 工具提示在下方 arrow.style.top = '-10px'; arrow.style.borderBottomColor = '#333'; } else { // 工具提示在上方 arrow.style.bottom = '-10px'; arrow.style.borderTopColor = '#333'; } // 添加显示动画 tooltip.style.opacity = '0'; tooltip.style.transform = 'translateY(10px)'; setTimeout(() => { tooltip.style.transition = 'opacity 0.3s, transform 0.3s'; tooltip.style.opacity = '1'; tooltip.style.transform = 'translateY(0)'; }, 10); // 返回工具提示元素以便后续操作 return tooltip; } // 隐藏工具提示 function hideTooltip() { const tooltip = document.getElementById('custom-tooltip'); if (tooltip) { tooltip.style.opacity = '0'; tooltip.style.transform = 'translateY(10px)'; setTimeout(() => { tooltip.remove(); }, 300); } } // 自动绑定工具提示 function bindTooltips() { const tooltipElements = document.querySelectorAll('[data-tooltip]'); tooltipElements.forEach(el => { const content = el.dataset.tooltip; el.addEventListener('mouseenter', () => { showTooltip(el, content); }); el.addEventListener('mouseleave', hideTooltip); el.addEventListener('click', hideTooltip); }); }
在指定元素附近显示自定义内容的工具提示。
配套CSS
工具提示CSS /* 工具提示基础样式 */ .tooltip { position: absolute; z-index: 1000; background: #333; color: white; padding: 12px 16px; border-radius: 6px; font-size: 14px; line-height: 1.5; max-width: 300px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); pointer-events: none; transform-origin: center bottom; } /* 工具提示箭头 */ .tooltip-arrow { position: absolute; width: 0; height: 0; border-style: solid; border-width: 10px; border-color: transparent; left: 50%; transform: translateX(-50%); } /* 工具提示内容 */ .tooltip-content { position: relative; z-index: 2; } /* 工具提示标题 */ .tooltip-title { font-weight: bold; margin-bottom: 8px; font-size: 16px; color: #ffd700; display: flex; align-items: center; } .tooltip-title::before { content: 'ℹ️'; margin-right: 8px; } /* 工具提示代码块 */ .tooltip code { background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 4px; font-family: 'Consolas', monospace; display: inline-block; margin: 4px 0; } /* 工具提示动画 */ .tooltip-enter { animation: tooltipFadeIn 0.3s ease forwards; } @keyframes tooltipFadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .tooltip-exit { animation: tooltipFadeOut 0.2s ease forwards; } @keyframes tooltipFadeOut { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(10px); } } /* 主题变体 */ .tooltip-info { background: #3498db; } .tooltip-warning { background: #f39c12; } .tooltip-error { background: #e74c3c; } .tooltip-success { background: #2ecc71; } /* 响应式调整 */ @media (max-width: 768px) { .tooltip { max-width: 250px; font-size: 13px; padding: 10px 14px; } .tooltip-title { font-size: 15px; } } /* 触摸设备优化 */ @media (hover: none) { .tooltip { display: none; } .mobile-tooltip { position: fixed; bottom: 20px; left: 20px; right: 20px; max-width: none; display: block !important; animation: mobileTooltipAppear 0.4s ease; } @keyframes mobileTooltipAppear { from { transform: translateY(100%); opacity: 0; } to { transform: translateY(0); opacity: 1; } } }

关键技术点

showModal(title, content)
showModal(title, content) - 显示模态对话框 function showModal(title, content) { // 关闭现有模态框 closeAllModals(); // 创建模态框容器 const modal = document.createElement('div'); modal.className = 'modal'; modal.id = 'custom-modal'; // 创建模态框内容 modal.innerHTML = ` `; // 添加到DOM document.body.appendChild(modal); // 添加事件监听器 modal.querySelector('.modal-backdrop').addEventListener('click', closeAllModals); modal.querySelector('.modal-close').addEventListener('click', closeAllModals); modal.querySelector('.modal-button.confirm').addEventListener('click', closeAllModals); modal.querySelector('.modal-button.cancel').addEventListener('click', closeAllModals); // 添加动画效果 setTimeout(() => { modal.classList.add('show'); }, 10); // 阻止背景滚动 document.body.style.overflow = 'hidden'; return modal; } // 关闭所有模态框 function closeAllModals() { const modals = document.querySelectorAll('.modal'); modals.forEach(modal => { modal.classList.remove('show'); // 添加关闭动画 setTimeout(() => { modal.remove(); }, 300); }); // 恢复背景滚动 document.body.style.overflow = ''; } // 显示确认对话框 function showConfirmModal(message, callback) { const modal = showModal('确认操作', `
⚠️

${message}

`); // 修改按钮行为 modal.querySelector('.modal-button.confirm').addEventListener('click', () => { callback(true); closeAllModals(); }); modal.querySelector('.modal-button.cancel').addEventListener('click', () => { callback(false); closeAllModals(); }); return modal; }
显示带有标题和内容的模态对话框,支持确认和取消操作。
配套CSS
模态框CSS /* 模态框基础样式 */ .modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 10000; display: flex; align-items: center; justify-content: center; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; } .modal.show { opacity: 1; visibility: visible; } /* 模态框背景 */ .modal-backdrop { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(4px); } /* 模态框容器 */ .modal-container { position: relative; background: white; border-radius: 12px; box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3); width: 90%; max-width: 600px; max-height: 90vh; display: flex; flex-direction: column; z-index: 2; transform: translateY(30px); opacity: 0; transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); } .modal.show .modal-container { transform: translateY(0); opacity: 1; } /* 模态框头部 */ .modal-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 25px; border-bottom: 1px solid #eee; } .modal-title { margin: 0; font-size: 22px; color: #333; } .modal-close { background: none; border: none; font-size: 28px; cursor: pointer; color: #999; transition: all 0.2s ease; line-height: 1; padding: 5px; } .modal-close:hover { color: #f44336; transform: scale(1.2); } /* 模态框内容 */ .modal-content { padding: 25px; overflow-y: auto; flex: 1; max-height: 60vh; } /* 模态框页脚 */ .modal-footer { display: flex; justify-content: flex-end; padding: 15px 25px; border-top: 1px solid #eee; gap: 15px; } .modal-button { padding: 10px 25px; border: none; border-radius: 6px; cursor: pointer; font-weight: bold; transition: all 0.2s ease; font-size: 16px; } .modal-button.confirm { background: #4CAF50; color: white; } .modal-button.cancel { background: #f5f5f5; color: #666; } .modal-button:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.1); } .modal-button.confirm:hover { background: #388E3C; } .modal-button.cancel:hover { background: #e0e0e0; } /* 确认对话框样式 */ .confirm-message { display: flex; align-items: center; gap: 20px; } .confirm-icon { font-size: 48px; flex-shrink: 0; } .confirm-message p { margin: 0; font-size: 18px; line-height: 1.6; } /* 动画效果 */ @keyframes modalIn { from { opacity: 0; transform: translateY(50px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } } @keyframes modalOut { from { opacity: 1; transform: translateY(0) scale(1); } to { opacity: 0; transform: translateY(50px) scale(0.95); } } /* 响应式设计 */ @media (max-width: 768px) { .modal-container { width: 95%; } .modal-header { padding: 15px 20px; } .modal-title { font-size: 20px; } .modal-content { padding: 20px; } .modal-footer { padding: 12px 20px; } .confirm-message { flex-direction: column; text-align: center; } } @media (max-width: 480px) { .modal-title { font-size: 18px; } .modal-content { padding: 15px; } .modal-button { padding: 8px 20px; font-size: 14px; } }

关键技术点

八、性能优化
optimizeCardRendering()
optimizeCardRendering() - 优化卡牌渲染 function optimizeCardRendering() { // 获取卡牌容器 const cardContainer = document.getElementById('card-container'); // 创建虚拟滚动容器 const virtualContainer = document.createElement('div'); virtualContainer.id = 'virtual-card-container'; virtualContainer.className = 'virtual-scroll-container'; // 计算可见区域高度 const containerHeight = cardContainer.clientHeight; const cardHeight = 150; // 卡牌高度(含间距) // 计算可见卡牌数量 const visibleCards = Math.ceil(containerHeight / cardHeight) + 2; // 设置占位容器高度 virtualContainer.style.height = `${allCards.length * cardHeight}px`; // 添加虚拟容器 cardContainer.appendChild(virtualContainer); // 初始渲染 renderVisibleCards(0); // 添加滚动事件监听 cardContainer.addEventListener('scroll', handleCardScroll); log("卡牌渲染优化已启用,使用虚拟滚动技术"); } // 渲染可见卡牌 function renderVisibleCards(scrollTop) { const container = document.getElementById('virtual-card-container'); const cardHeight = 150; const startIndex = Math.floor(scrollTop / cardHeight); const endIndex = startIndex + Math.ceil(container.clientHeight / cardHeight) + 2; // 清空容器 container.innerHTML = ''; // 渲染可见卡牌 for (let i = startIndex; i < Math.min(endIndex, allCards.length); i++) { const card=allCards[i]; const cardElement=createCardElement(card); // 设置位置 cardElement.style.position='absolute' ; cardElement.style.top=`${i * cardHeight}px`; container.appendChild(cardElement); } } // 滚动事件处理 function handleCardScroll() { const container=document.getElementById('card-container'); renderVisibleCards(container.scrollTop); } // 创建卡牌元素(优化版) function createCardElement(card) { // 使用文档片段减少重排 const fragment=document.createDocumentFragment(); // 创建卡牌容器 const cardElement=document.createElement('div'); cardElement.className='card virtual-card' ; cardElement.dataset.id=card.id; // 使用CSS变量存储卡牌数据 cardElement.style.setProperty('--card-color', card.color); cardElement.style.setProperty('--card-type', card.type); // 添加基础内容 cardElement.innerHTML=`
${card.name} ${card.cost}
${card.description}
`; // 使用will-change优化动画 cardElement.style.willChange = 'transform, opacity'; // 添加到文档片段 fragment.appendChild(cardElement); return fragment; } // 使用Intersection Observer延迟加载图片 function initLazyLoading() { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const card = entry.target; const cardId = card.dataset.id; const cardData = allCards.find(c => c.id === cardId); // 加载图片 const img = card.querySelector('.card-image'); img.style.backgroundImage = `url(${cardData.image})`; // 停止观察 observer.unobserve(card); } }); }, { threshold: 0.1 }); // 观察所有卡牌 document.querySelectorAll('.virtual-card').forEach(card => { observer.observe(card); }); }
使用虚拟滚动技术优化大量卡牌的渲染性能。
配套CSS
渲染优化CSS /* 虚拟滚动容器 */ .virtual-scroll-container { position: relative; width: 100%; overflow: hidden; } /* 优化后的卡牌样式 */ .virtual-card { position: absolute; width: 100%; height: 140px; margin-bottom: 10px; background: white; border-radius: 8px; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); transition: transform 0.3s ease, box-shadow 0.3s ease; will-change: transform, box-shadow; overflow: hidden; display: flex; flex-direction: column; border: 1px solid #eee; } /* 卡牌头部 */ .card-header { display: flex; justify-content: space-between; padding: 10px 15px; background: var(--card-color, #f5f5f5); border-bottom: 1px solid #e0e0e0; } .card-name { font-weight: bold; font-size: 16px; color: #333; } .card-cost { background: white; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } /* 卡牌图片 */ .card-image { height: 70px; background-color: #f9f9f9; background-size: cover; background-position: center; flex-shrink: 0; } /* 卡牌描述 */ .card-description { padding: 10px 15px; font-size: 13px; line-height: 1.4; color: #555; flex-grow: 1; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } /* 性能优化属性 */ .virtual-card { contain: strict; content-visibility: auto; } /* 卡牌加载状态 */ .card-image.loading { background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200% 100%; animation: loadingAnimation 1.5s infinite; } @keyframes loadingAnimation { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } /* 减少运动偏好设置 */ @media (prefers-reduced-motion: reduce) { .virtual-card { transition: none; } } /* 打印样式优化 */ @media print { .virtual-scroll-container { height: auto !important; } .virtual-card { position: static; page-break-inside: avoid; } }

关键技术点