将鼠标悬停在函数名,配套css上,会显示该函数和配套css的完整源代码。
每个函数下方添加了配套CSS部分,同样可以通过悬停查看CSS源代码。
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("牌堆初始化完成"); }
/* 卡牌基础样式 */
.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;
}
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);
}
/* 洗牌动画容器 */
.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;
}
}
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);
}
/* 抽牌动画容器 */
#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;
}
}
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');
}
}
/* 游戏区域开始动画 */
.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;
}
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();
}
/* 摸牌阶段按钮 */
#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);
}
}
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);
}
/* 卡牌使用状态 */
.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);
}
}
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("记牌器已更新");
}
/* 记牌器容器 */
#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;
}
}
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
});
}
/* 日志容器 */
#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;
}
}
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();
}
});
});
}
/* 脚气卡按钮 */
#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);
}
}
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验证面板 */
.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;
}
}
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加密面板 */
.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;
}
}
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>`);
}
/* 安全日志容器 */
#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;
}
}
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); }
/* 测试面板 */
.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;
}
}
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 ? '已开启' : '已关闭'}`);
}
/* 开发者面板 */
.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;
}
}
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';
});
}
/* 按钮容器 */
.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;
}
}
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-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;
}
}
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();
}
});
}
}
/* 卡牌高亮效果 */
.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;
}
}
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);
}
/* 版本历史容器 */
#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;
}
}
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);
});
}
/* 工具提示基础样式 */
.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;
}
}
}
function showModal(title, content) {
// 关闭现有模态框
closeAllModals();
// 创建模态框容器
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'custom-modal';
// 创建模态框内容
modal.innerHTML = `
${title}
`;
// 添加到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('确认操作', `
`);
// 修改按钮行为
modal.querySelector('.modal-button.confirm').addEventListener('click', () => {
callback(true);
closeAllModals();
});
modal.querySelector('.modal-button.cancel').addEventListener('click', () => {
callback(false);
closeAllModals();
});
return modal;
}
/* 模态框基础样式 */
.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;
}
}
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);
});
}
/* 虚拟滚动容器 */
.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;
}
}