🐹
1

万象卡域

经验: 0 / 400
💰 金币: 100 💎 宝石: 10
🎰 卡池(拖拽到下方卡槽)
📦 手牌(拖拽调整位置)
// ===== 音效系统 (Web Audio API) ===== let audioCtx = null; // 初始化音频上下文(需要用户交互后调用) function initAudio() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } // 确保 AudioContext 处于运行状态(某些浏览器创建后是 suspended) if (audioCtx.state === 'suspended') { audioCtx.resume(); } return audioCtx; } // 音效:点击按钮 function playClickSound() { try { const ctx = initAudio(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); osc.frequency.setValueAtTime(800, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(600, ctx.currentTime + 0.05); gain.gain.setValueAtTime(0.3, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.08); osc.start(ctx.currentTime); osc.stop(ctx.currentTime + 0.08); } catch (e) {} } // 音效:拖拽卡牌(短促的沙沙声) function playDragSound() { try { const ctx = initAudio(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.type = 'triangle'; osc.connect(gain); gain.connect(ctx.destination); osc.frequency.setValueAtTime(300, ctx.currentTime); osc.frequency.setValueAtTime(350, ctx.currentTime + 0.02); osc.frequency.setValueAtTime(300, ctx.currentTime + 0.04); gain.gain.setValueAtTime(0.15, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.06); osc.start(ctx.currentTime); osc.stop(ctx.currentTime + 0.06); } catch (e) {} } // 音效:抽卡(从卡池拿到手牌) function playDrawCardSound() { try { const ctx = initAudio(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.type = 'sine'; osc.connect(gain); gain.connect(ctx.destination); // 上升音调 osc.frequency.setValueAtTime(400, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(800, ctx.currentTime + 0.1); osc.frequency.exponentialRampToValueAtTime(1200, ctx.currentTime + 0.15); gain.gain.setValueAtTime(0.25, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.2); osc.start(ctx.currentTime); osc.stop(ctx.currentTime + 0.2); } catch (e) {} } // 音效:合成成功(清脆的成功音) function playComposeSuccessSound() { try { const ctx = initAudio(); // 第一声:上升 const osc1 = ctx.createOscillator(); const gain1 = ctx.createGain(); osc1.type = 'sine'; osc1.connect(gain1); gain1.connect(ctx.destination); osc1.frequency.setValueAtTime(523, ctx.currentTime); // C5 osc1.frequency.setValueAtTime(659, ctx.currentTime + 0.1); // E5 gain1.gain.setValueAtTime(0.3, ctx.currentTime); gain1.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.15); osc1.start(ctx.currentTime); osc1.stop(ctx.currentTime + 0.15); // 第二声:更高 const osc2 = ctx.createOscillator(); const gain2 = ctx.createGain(); osc2.type = 'sine'; osc2.connect(gain2); gain2.connect(ctx.destination); osc2.frequency.setValueAtTime(659, ctx.currentTime + 0.1); // E5 osc2.frequency.setValueAtTime(784, ctx.currentTime + 0.2); // G5 gain2.gain.setValueAtTime(0.3, ctx.currentTime + 0.1); gain2.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.3); osc2.start(ctx.currentTime + 0.1); osc2.stop(ctx.currentTime + 0.3); // 第三声:最高 const osc3 = ctx.createOscillator(); const gain3 = ctx.createGain(); osc3.type = 'sine'; osc3.connect(gain3); gain3.connect(ctx.destination); osc3.frequency.setValueAtTime(784, ctx.currentTime + 0.2); // G5 osc3.frequency.setValueAtTime(1047, ctx.currentTime + 0.3); // C6 gain3.gain.setValueAtTime(0.3, ctx.currentTime + 0.2); gain3.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.4); osc3.start(ctx.currentTime + 0.2); osc3.stop(ctx.currentTime + 0.4); } catch (e) {} } // 音效:合成失败(低沉的失败音) function playComposeFailSound() { try { const ctx = initAudio(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.type = 'sawtooth'; osc.connect(gain); gain.connect(ctx.destination); osc.frequency.setValueAtTime(200, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(100, ctx.currentTime + 0.2); gain.gain.setValueAtTime(0.2, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.25); osc.start(ctx.currentTime); osc.stop(ctx.currentTime + 0.25); } catch (e) {} } // 音效:存入保险箱 function playVaultInSound() { try { const ctx = initAudio(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.type = 'sine'; osc.connect(gain); gain.connect(ctx.destination); // 下降音调 osc.frequency.setValueAtTime(600, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(300, ctx.currentTime + 0.1); gain.gain.setValueAtTime(0.25, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.15); osc.start(ctx.currentTime); osc.stop(ctx.currentTime + 0.15); } catch (e) {} } // 音效:取出保险箱 function playVaultOutSound() { try { const ctx = initAudio(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.type = 'sine'; osc.connect(gain); gain.connect(ctx.destination); // 上升音调 osc.frequency.setValueAtTime(300, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(600, ctx.currentTime + 0.1); gain.gain.setValueAtTime(0.25, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.15); osc.start(ctx.currentTime); osc.stop(ctx.currentTime + 0.15); } catch (e) {} } // 音效:升级 function playLevelUpSound() { try { const ctx = initAudio(); // 播放一系列上升音阶 const notes = [523, 659, 784, 1047, 1319, 1568]; // C5, E5, G5, C6, E6, G6 notes.forEach((freq, i) => { const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.type = 'sine'; osc.connect(gain); gain.connect(ctx.destination); const startTime = ctx.currentTime + i * 0.1; osc.frequency.setValueAtTime(freq, startTime); gain.gain.setValueAtTime(0.25, startTime); gain.gain.exponentialRampToValueAtTime(0.01, startTime + 0.15); osc.start(startTime); osc.stop(startTime + 0.15); }); } catch (e) {} } // ===== 登录/注册系统 ===== // 有效邀请码列表(内测阶段) const VALID_INVITE_CODES = { }; // 默认新账号初始值 const DEFAULT_INITIAL_GOLD = 100; // 初始金币 const DEFAULT_INITIAL_GEM = 10; // 初始宝石 // 检查是否已登录 function checkLoginStatus() { const currentUser = localStorage.getItem('ccr_current_user'); if (currentUser) { // 加载用户数据 loadUserData(currentUser); // 隐藏登录界面 document.getElementById('loginOverlay').classList.add('hidden'); // 初始化游戏 initGame(); return true; } return false; } // 加载用户数据 function loadUserData(userKey) { const userData = localStorage.getItem('ccr_user_' + userKey); if (userData) { const data = JSON.parse(userData); // 恢复用户信息 currentEmail = data.email || userKey; currentPassword = data.password || ''; currentDisplayName = data.displayName || userKey.split('@')[0]; // 恢复游戏数据 playerLevel = data.level || 0; currentExp = data.exp || 0; gold = data.gold || DEFAULT_INITIAL_GOLD; gems = data.gems || DEFAULT_INITIAL_GEM; // 恢复卡牌数据 poolCards = data.poolCards || new Array(16).fill(null); handCards = data.handCards || new Array(32).fill(null); vaultCards = data.vaultCards || new Array(17).fill(null); completedDecks = data.completedDecks || []; // 恢复解锁槽位 unlockedPoolSlots = data.unlockedPoolSlots || 8; unlockedHandSlots = data.unlockedHandSlots || 8; unlockedVaultSlots = data.unlockedVaultSlots || 2; // 恢复邮箱 mails = data.mails || []; // 恢复拍卖行 auctionItems = data.auctionItems || []; myAuctionItems = data.myAuctionItems || []; // 恢复免费刷新次数 freeRefreshRemaining = data.freeRefreshRemaining || 1; freeRefreshCooldown = data.freeRefreshCooldown || 0; // 更新玩家ID显示 updatePlayerIdDisplay(currentDisplayName); console.log('用户数据加载成功:', userKey); } else { console.log('未找到用户数据,创建新账号'); } } // 保存用户数据 function saveUserData(userKey) { const data = { email: userKey, // 使用邮箱作为用户标识 password: currentPassword, // 保存密码 displayName: currentDisplayName, // 显示名称 level: playerLevel, exp: currentExp, gold: gold, gems: gems, poolCards: poolCards, handCards: handCards, vaultCards: vaultCards, completedDecks: completedDecks, unlockedPoolSlots: unlockedPoolSlots, unlockedHandSlots: unlockedHandSlots, unlockedVaultSlots: unlockedVaultSlots, mails: mails, auctionItems: auctionItems, myAuctionItems: myAuctionItems, freeRefreshRemaining: freeRefreshRemaining, freeRefreshCooldown: freeRefreshCooldown, lastSaveTime: Date.now() }; localStorage.setItem('ccr_user_' + userKey, JSON.stringify(data)); localStorage.setItem('ccr_current_user', userKey); console.log('用户数据保存成功:', userKey); } // 更新玩家ID显示 function updatePlayerIdDisplay(playerId) { const playerNameEl = document.querySelector('.player-stats h2'); if (playerNameEl) { playerNameEl.innerHTML = `
${playerId}
`; } } // 显示登录Tab function showLoginTab(tab) { const loginTab = document.querySelector('.login-tab:first-child'); const registerTab = document.querySelector('.login-tab:last-child'); const loginForm = document.getElementById('loginForm'); const registerForm = document.getElementById('registerForm'); const loginError = document.getElementById('loginError'); // 隐藏错误提示 loginError.style.display = 'none'; if (tab === 'login') { loginTab.classList.add('active'); registerTab.classList.remove('active'); loginForm.style.display = 'flex'; registerForm.style.display = 'none'; } else { loginTab.classList.remove('active'); registerTab.classList.add('active'); loginForm.style.display = 'none'; registerForm.style.display = 'flex'; } } // 显示错误提示 function showLoginError(message) { const loginError = document.getElementById('loginError'); loginError.textContent = message; loginError.style.display = 'block'; } // API 基础地址 const API_BASE = 'https://ccrgame.com/api'; // 执行登录 async function doLogin() { const email = document.getElementById('loginEmail').value.trim(); const password = document.getElementById('loginPassword').value.trim(); // 验证邮箱 if (!email) { showLoginError('请输入邮箱'); return; } // 验证密码 if (!password) { showLoginError('请输入密码'); return; } // 验证邮箱格式 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { showLoginError('请输入有效的邮箱地址'); return; } // 调用后端 API 登录 try { const response = await fetch(API_BASE + '/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); const data = await response.json(); if (data.success) { // 保存玩家数据到 localStorage(备用) localStorage.setItem('ccr_player_' + data.player.player_id, JSON.stringify(data.player)); currentEmail = email; currentPassword = password; currentDisplayName = data.player.player_id || email.split('@')[0]; loadPlayerDataFromServer(email); document.getElementById('loginOverlay').classList.add('hidden'); initGame(); doPoolRefresh(); } else { showLoginError(data.error || '登录失败'); } } catch (e) { // API 连接失败,回退到本地存储 console.error('API Error:', e); // 使用邮箱作为key查找本地数据 const userData = localStorage.getItem('ccr_user_' + email); if (!userData) { showLoginError('该邮箱未注册,请先注册'); return; } const user = JSON.parse(userData); // 验证密码 if (user.password !== password) { showLoginError('密码错误'); return; } currentEmail = email; currentPassword = password; currentDisplayName = user.displayName || email.split('@')[0]; loadPlayerDataFromServer(email); document.getElementById('loginOverlay').classList.add('hidden'); initGame(); doPoolRefresh(); } } // 执行注册 async function doRegister() { const email = document.getElementById('registerEmail').value.trim(); const inviteCode = document.getElementById('registerInviteCode').value.trim(); const password = document.getElementById('registerPassword').value.trim(); const passwordConfirm = document.getElementById('registerPasswordConfirm').value.trim(); // 验证邮箱 if (!email) { showLoginError('请输入邮箱'); return; } // 验证邮箱格式 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { showLoginError('请输入有效的邮箱地址'); return; } // 验证邀请码 if (!inviteCode) { showLoginError('请输入邀请码'); return; } if (!VALID_INVITE_CODES[inviteCode]) { showLoginError('邀请码无效,请使用正确的内测邀请码'); return; } // 验证密码 if (!password) { showLoginError('请输入密码'); return; } if (password.length < 6 || password.length > 20) { showLoginError('密码长度需为6-20个字符'); return; } if (password !== passwordConfirm) { showLoginError('两次输入的密码不一致'); return; } // 检查邮箱是否已存在 (本地检查 + API检查) if (localStorage.getItem('ccr_user_' + email)) { showLoginError('该邮箱已被注册'); return; } // 尝试通过API注册 try { const response = await fetch(API_BASE + '/register-email', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password, inviteCode }) }); const data = await response.json(); if (data.success) { // API注册成功 currentEmail = email; currentPassword = password; currentDisplayName = data.player.player_id || email.split('@')[0]; playerLevel = data.player.level; currentExp = data.player.exp; gold = data.player.gold; gems = data.player.gems; localStorage.setItem('ccr_player_' + currentDisplayName, JSON.stringify(data.player)); // 初始化空数据 poolCards = new Array(16).fill(null); handCards = new Array(32).fill(null); vaultCards = new Array(17).fill(null); completedDecks = []; unlockedPoolSlots = 8; unlockedHandSlots = 8; unlockedVaultSlots = 2; mails = []; auctionItems = []; myAuctionItems = []; freeRefreshRemaining = 1; freeRefreshCooldown = 0; // 保存用户数据 saveUserData(email); updatePlayerIdDisplay(currentDisplayName); document.getElementById('loginOverlay').classList.add('hidden'); initGame(); return; } } catch (e) { console.error('API注册失败:', e); } // API失败,回退到本地注册 // 创建新账号 - 初始状态 currentEmail = email; currentPassword = password; currentDisplayName = email.split('@')[0]; playerLevel = 1; // 等级:1(初始等级) currentExp = 0; // 经验:0 gold = DEFAULT_INITIAL_GOLD; // 初始金币 gems = DEFAULT_INITIAL_GEM; // 初始宝石 // 初始化空数据 poolCards = new Array(16).fill(null); handCards = new Array(32).fill(null); vaultCards = new Array(17).fill(null); completedDecks = []; // 初始解锁槽位 unlockedPoolSlots = 8; unlockedHandSlots = 8; unlockedVaultSlots = 2; // 空邮箱 mails = []; // 空的拍卖行 auctionItems = []; myAuctionItems = []; // 免费刷新 freeRefreshRemaining = 1; freeRefreshCooldown = 0; // 保存新账号数据(使用邮箱作为key) saveUserData(email); // 更新玩家ID显示(使用邮箱前缀作为显示名称) const displayName = email.split('@')[0]; updatePlayerIdDisplay(displayName); // 隐藏登录界面 document.getElementById('loginOverlay').classList.add('hidden'); // 初始化游戏 initGame(); console.log('新账号创建成功:', email, '初始金币:', gold, '初始宝石:', gems); } // 退出登录 function doLogout() { // 先保存当前用户数据 const currentUser = localStorage.getItem('ccr_current_user'); if (currentUser) { saveUserData(currentUser); } // 清除当前用户 localStorage.removeItem('ccr_current_user'); // 清除当前用户变量 currentEmail = ''; currentPassword = ''; currentDisplayName = ''; // 显示登录界面 document.getElementById('loginOverlay').classList.remove('hidden'); // 清空输入框 document.getElementById('loginEmail').value = ''; document.getElementById('loginPassword').value = ''; document.getElementById('registerEmail').value = ''; document.getElementById('registerInviteCode').value = ''; document.getElementById('registerPassword').value = ''; document.getElementById('registerPasswordConfirm').value = ''; document.getElementById('loginError').style.display = 'none'; console.log('已退出登录'); } // 自动保存(每30秒) setInterval(() => { const currentUser = localStorage.getItem('ccr_current_user'); if (currentUser) { saveUserData(currentUser); } }, 30000); // 页面关闭时保存 window.addEventListener('beforeunload', () => { const currentUser = localStorage.getItem('ccr_current_user'); if (currentUser) { saveUserData(currentUser); } }); // ===== 游戏初始化 ===== const SERIES = [ // 内测阶段只有太阳系卡组 { id: 'solar', name: '太阳系', cards: ['太阳', '水星', '金星', '地球', '火星', '木星', '土星', '天王星', '海王星', '月球'] } ]; // 服务器卡组系统(每天变化) const SERVER_DECKS = { // 固定3套:今天、昨天、前天 fixed: [ { day: 'today', cardName: '太阳', label: '今天' }, { day: 'yesterday', cardName: '月球', label: '昨天' }, { day: 'dayBeforeYesterday', cardName: '地球', label: '前天' } ], // 5套随机(从剩余星球中选择) random: [] }; // 初始化服务器卡组(随机5套不重复) function initServerDecks() { // 太阳系星球列表(排除太阳、月球、地球) const allPlanets = ['水星', '金星', '火星', '木星', '土星', '天王星', '海王星']; // 洗牌算法 const shuffled = [...allPlanets].sort(() => Math.random() - 0.5); // 取前5个作为随机卡组 SERVER_DECKS.random = shuffled.slice(0, 5).map((name, idx) => ({ cardName: name, label: `随机${idx + 1}` })); console.log('服务器卡组已初始化:', getVisibleServerDecks()); } // 根据玩家等级获取可见的服务器卡组 function getVisibleServerDecks() { const level = playerLevel; let visibleCount = 2; // 默认2套 if (level >= 2 && level <= 4) visibleCount = 3; else if (level >= 5 && level <= 9) visibleCount = 4; else if (level >= 10 && level <= 19) visibleCount = 5; else if (level >= 20 && level <= 29) visibleCount = 6; else if (level >= 30 && level <= 39) visibleCount = 7; else if (level >= 40) visibleCount = 8; // 合并固定卡组和随机卡组,取前visibleCount个 const allDecks = [...SERVER_DECKS.fixed, ...SERVER_DECKS.random]; return allDecks.slice(0, visibleCount); } // 获取玩家可见的卡组用于抽卡 function getPlayerVisibleCardNames() { const visibleDecks = getVisibleServerDecks(); return visibleDecks.map(d => d.cardName); } const COLORS = [ { name: '白卡', class: 'card-white', exp: 20 }, { name: '绿卡', class: 'card-green', exp: 40 }, { name: '蓝卡', class: 'card-blue', exp: 60 }, { name: '紫卡', class: 'card-purple', exp: 100 }, { name: '橙卡', class: 'card-orange', exp: 200 }, { name: '黑卡', class: 'card-black', exp: 400 }, { name: '红卡', class: 'card-red', exp: 1000 } // 终极红卡 ]; let poolCards = new Array(16).fill(null); let unlockedPoolSlots = 8; // 卡池解锁数量 let unlockedHandSlots = 8; // 手牌解锁数量 let handCards = new Array(32).fill(null); let completedDecks = []; let handPage = 0; // 默认显示第一页 let selectedHandCard = null; // 抽牌页面选中的手牌 let selectedVaultCard = null; // 保险箱页面选中的卡 let unlockedVaultSlots = 2; // 保险箱解锁数量 let vaultPage = 0; // 保险箱页码 let vaultCards = new Array(17).fill(null); // 保险箱卡牌 // 邮箱系统数据 let mails = []; // 邮件列表 let selectedMail = null; // 当前选中的邮件 // 拍卖行系统数据 let auctionItems = []; // 拍卖行物品列表 let myAuctionItems = []; // 我的拍卖 let selectedAuctionCard = null; // 选中的要上架的卡牌(手牌) let selectedAuctionDeck = null; // 选中的要上架的卡组 // selectedVaultCard 在全局变量区域已声明 let currentAuctionTab = 'browse'; // 当前拍卖行标签页 let auctionCardSource = 'hand'; // 卡牌来源:hand-手牌,vault-保险箱 // 拍卖行常量 const AUCTION_MAX_CARDS = 10; // 最多上架卡牌数量 const AUCTION_MAX_DECKS = 10; // 最多上架卡组数量 const AUCTION_FEE_RATE = 0.08; // 手续费率 const AUCTION_FEE_MIN = 1; // 最低手续费 // 拍卖时长选项(简化为2种) const AUCTION_DURATIONS = [ { value: 24, label: '24小时', shortLabel: '24小时', category: 'short', fee: { gold: 5, gem: 1 } }, { value: 48, label: '48小时(双倍上架费)', shortLabel: '48小时', category: 'long', fee: { gold: 10, gem: 2 } } ]; // 天梯榜系统数据 let ladderData = []; // 天梯榜数据 let selectedColors = ['red']; // 选中的颜色 // 国家代码映射 const COUNTRY_FLAGS = { 'CN': '🇨🇳', 'US': '🇺🇸', 'JP': '🇯🇵', 'KR': '🇰🇷', 'DE': '🇩🇪', 'FR': '🇫🇷', 'GB': '🇬🇧', 'RU': '🇷🇺', 'BR': '🇧🇷', 'IN': '🇮🇳' }; // 初始化天梯榜测试数据 function initLadder() { const seriesData = [ { id: 'city', name: '城市', cards: ['上海', '北京', '深圳', '广州', '杭州'] }, { id: 'food', name: '美食', cards: ['火锅', '烧烤', '串串', '小龙虾', '烤鸭'] }, { id: 'animal', name: '动物', cards: ['熊猫', '老虎', '狮子', '大象', '长颈鹿'] } ]; const colors = [ { name: '红', class: 'card-red', index: 6 }, { name: '黑', class: 'card-black', index: 5 }, { name: '橙', class: 'card-orange', index: 4 }, { name: '紫', class: 'card-purple', index: 3 }, { name: '蓝', class: 'card-blue', index: 2 }, { name: '绿', class: 'card-green', index: 1 }, { name: '白', class: 'card-white', index: 0 } ]; const playerNames = ['天梯王者', '卡牌大师', '合成专家', '收藏家', '新手玩家', '高级玩家', '传奇玩家', '至尊玩家', '无敌玩家', '神级玩家', '顶级玩家', '超级玩家', '精英玩家', '大师玩家', '王者玩家']; const countries = ['CN', 'US', 'JP', 'KR', 'DE', 'FR', 'GB', 'RU', 'BR', 'IN']; // 生成50条测试数据 ladderData = []; let id = 1; for (let i = 0; i < 50; i++) { const series = seriesData[Math.floor(Math.random() * seriesData.length)]; const cardName = series.cards[Math.floor(Math.random() * series.cards.length)]; const color = colors[Math.floor(Math.random() * colors.length)]; const playerName = playerNames[Math.floor(Math.random() * playerNames.length)]; const country = countries[Math.floor(Math.random() * countries.length)]; const deckCount = Math.floor(Math.random() * 50) + 1; ladderData.push({ id: id++, seriesId: series.id, seriesName: series.name, cardName: cardName, colorName: color.name, colorIndex: color.index, colorClass: color.class, playerId: `${playerName}${i + 1}`, country: country, deckCount: deckCount, rank: 0 // 排名将在排序后计算 }); } // 按套数降序排序 ladderData.sort((a, b) => b.deckCount - a.deckCount); // 分配排名 ladderData.forEach((item, index) => { item.rank = index + 1; }); console.log('天梯榜已初始化,共', ladderData.length, '条数据'); } // 切换颜色筛选 function toggleColorFilter(color) { const btn = document.querySelector(`[data-color="${color}"]`); if (selectedColors.includes(color)) { // 如果只有一个颜色,不允许取消 if (selectedColors.length > 1) { selectedColors = selectedColors.filter(c => c !== color); btn.classList.remove('active'); } } else { selectedColors.push(color); btn.classList.add('active'); } } // 搜索天梯榜 function searchLadder() { const seriesFilter = document.getElementById('ladderSeriesFilter').value; const cardFilter = document.getElementById('ladderCardFilter').value; // 过滤数据 let filtered = ladderData.filter(item => { // 系列筛选 if (seriesFilter !== 'all' && item.seriesId !== seriesFilter) return false; // 卡名筛选(编号1-5,这里用卡名来筛选) if (cardFilter !== 'all') { // 实际上这里是筛选编号,但我们的数据结构是卡名 // 简化处理:随机分配编号 const cardNum = parseInt(item.cardName.charCodeAt(0)) % 5 + 1; if (parseInt(cardFilter) !== cardNum) return false; } // 颜色筛选 const colorMap = { 'red': 6, 'black': 5, 'orange': 4, 'purple': 3, 'blue': 2, 'green': 1, 'white': 0 }; if (!selectedColors.includes(Object.keys(colorMap).find(key => colorMap[key] === item.colorIndex))) return false; return true; }); // 按套数降序排序 filtered.sort((a, b) => b.deckCount - a.deckCount); // 重新分配排名 filtered.forEach((item, index) => { item.rank = index + 1; }); renderLadderTable(filtered); } // 渲染天梯榜表格 function renderLadderTable(data) { const tbody = document.getElementById('ladderTableBody'); if (data.length === 0) { tbody.innerHTML = '没有找到匹配的记录'; return; } let html = ''; data.forEach(item => { const rankClass = item.rank <= 3 ? `ladder-rank-${item.rank}` : ''; const flag = COUNTRY_FLAGS[item.country] || '🏳️'; html += ` #${item.rank}
🏆
${item.seriesName}-${item.cardName}
${item.colorName}
${item.deckCount} 套 ${item.playerId} ${flag} `; }); tbody.innerHTML = html; } // 显示玩家卡组页面 function showPlayerDecks(playerId) { // 找到玩家数据 const playerData = ladderData.find(item => item.playerId === playerId); if (!playerData) return; // 渲染玩家信息 const header = document.getElementById('playerDecksHeader'); const flag = COUNTRY_FLAGS[playerData.country] || '🏳️'; const avatarEmojis = ['🐹', '🦊', '🐼', '🦁', '🐯', '🐨', '🐰', '🦊', '🐲', '🦄']; const avatar = avatarEmojis[playerId.charCodeAt(playerId.length - 1) % avatarEmojis.length]; header.innerHTML = `
${avatar}

${playerId}

${flag} 国家: ${getCountryName(playerData.country)} | 总卡组数: ${getPlayerTotalDecks(playerId)} 套
`; // 渲染玩家卡组列表 const deckGrid = document.getElementById('playerDeckGrid'); const playerDecks = ladderData.filter(item => item.playerId === playerId); if (playerDecks.length === 0) { deckGrid.innerHTML = '
该玩家暂无卡组
'; } else { // 按颜色排序(红->白) playerDecks.sort((a, b) => b.colorIndex - a.colorIndex); let html = ''; playerDecks.forEach(deck => { const colorStyle = getDeckColorStyle(deck.colorIndex); html += `
${deck.seriesName}
${deck.cardName}
${deck.deckCount} 套
`; }); deckGrid.innerHTML = html; } // 切换到玩家卡组页面 document.getElementById('ladderSection').style.display = 'none'; document.getElementById('playerDecksSection').style.display = 'block'; } // 获取玩家总卡组数 function getPlayerTotalDecks(playerId) { const playerDecks = ladderData.filter(item => item.playerId === playerId); return playerDecks.reduce((sum, item) => sum + item.deckCount, 0); } // 获取国家名称 function getCountryName(code) { const names = { 'CN': '中国', 'US': '美国', 'JP': '日本', 'KR': '韩国', 'DE': '德国', 'FR': '法国', 'GB': '英国', 'RU': '俄罗斯', 'BR': '巴西', 'IN': '印度' }; return names[code] || code; } // 获取随机渐变 function getRandomGradient() { const gradients = [ ' #ff6b6b, #feca57', ' #a55eea, #8854d0', ' #2ed573, #7bed9f', ' #70a1ff, #1e90ff', ' #ff4757, #c0392b', ' #ffa502, #ff7f50' ]; return gradients[Math.floor(Math.random() * gradients.length)]; } // 获取卡组颜色样式 function getDeckColorStyle(colorIndex) { const styles = [ 'background: linear-gradient(145deg, #ffffff, #e0e0e0); border: 2px solid #ccc; color: #333;', 'background: linear-gradient(145deg, #7bed9f, #2ed573); border: 2px solid #26de81; color: #333;', 'background: linear-gradient(145deg, #70a1ff, #1e90ff); border: 2px solid #1e90ff; color: #fff;', 'background: linear-gradient(145deg, #a55eea, #8854d0); border: 2px solid #8854d0; color: #fff;', 'background: linear-gradient(145deg, #ffa502, #ff7f50); border: 2px solid #ff6348; color: #fff;', 'background: linear-gradient(145deg, #2f3542, #1e272e); border: 2px solid #000; color: #fff;', 'background: linear-gradient(145deg, #ff4757, #c0392b); border: 2px solid #e74c3c; color: #fff;' ]; return styles[colorIndex] || styles[0]; } // 初始化拍卖行测试数据 function initAuction() { // 添加一些测试拍卖品(包含各种时长) auctionItems = [ { id: 1, type: 'card', card: { seriesId: 'city', seriesName: '城市', cardName: '上海', colorIndex: 1, number: 1 }, seller: '玩家A', currency: 'gold', bidPrice: 100, buyoutPrice: 500, timeLeft: 5, // 短 createdAt: Date.now() }, { id: 2, type: 'deck', deck: { color: '蓝', colorIndex: 2, seriesName: '城市', cardName: '上海', count: 1 }, seller: '玩家B', currency: 'gem', bidPrice: 10, buyoutPrice: 50, timeLeft: 10, // 中 createdAt: Date.now() }, { id: 3, type: 'card', card: { seriesId: 'city', seriesName: '城市', cardName: '北京', colorIndex: 2, number: 3 }, seller: '玩家C', currency: 'gold', bidPrice: 200, buyoutPrice: 800, timeLeft: 20, // 长 createdAt: Date.now() }, { id: 4, type: 'deck', deck: { color: '紫', colorIndex: 3, seriesName: '城市', cardName: '深圳', count: 1 }, seller: '玩家D', currency: 'gem', bidPrice: 20, buyoutPrice: 100, timeLeft: 35, // 较长 createdAt: Date.now() }, { id: 5, type: 'card', card: { seriesId: 'city', seriesName: '城市', cardName: '广州', colorIndex: 4, number: 5 }, seller: '玩家E', currency: 'gold', bidPrice: 500, buyoutPrice: 2000, timeLeft: 48, // 非常长 createdAt: Date.now() }, // 添加更多测试数据用于演示搜索功能 { id: 6, type: 'card', card: { seriesId: 'city', seriesName: '城市', cardName: '深圳', colorIndex: 0, number: 2 }, seller: '玩家F', currency: 'gold', bidPrice: 50, buyoutPrice: 200, timeLeft: 8, // 短 createdAt: Date.now() }, { id: 7, type: 'card', card: { seriesId: 'food', seriesName: '美食', cardName: '火锅', colorIndex: 3, number: 1 }, seller: '玩家G', currency: 'gem', bidPrice: 15, buyoutPrice: 60, timeLeft: 15, // 中 createdAt: Date.now() }, { id: 8, type: 'deck', deck: { color: '橙', colorIndex: 4, seriesName: '美食', cardName: '烧烤', count: 2 }, seller: '玩家H', currency: 'gold', bidPrice: 300, buyoutPrice: 1000, timeLeft: 36, // 较长 createdAt: Date.now() }, { id: 9, type: 'card', card: { seriesId: 'animal', seriesName: '动物', cardName: '熊猫', colorIndex: 5, number: 4 }, seller: '玩家I', currency: 'gem', bidPrice: 50, buyoutPrice: 200, timeLeft: 3, // 短 createdAt: Date.now() }, { id: 10, type: 'card', card: { seriesId: 'city', seriesName: '城市', cardName: '杭州', colorIndex: 1, number: 3 }, seller: '玩家J', currency: 'gold', bidPrice: 80, buyoutPrice: 300, timeLeft: 18, createdAt: Date.now() } ]; } // 格式化拍卖剩余时间 - 只显示文字分类 function formatAuctionTime(timeLeft) { if (timeLeft <= 0) return '已结束'; let category = ''; let categoryColor = '#2ed573'; // 绿色 - 短 if (timeLeft <= 12) { category = '短'; categoryColor = '#2ed573'; } else if (timeLeft <= 24) { category = '中'; categoryColor = '#ffa502'; } else if (timeLeft <= 30) { category = '长'; categoryColor = '#ff4757'; } else if (timeLeft <= 40) { category = '较长'; categoryColor = '#70a1ff'; } else { category = '非常长'; categoryColor = '#a55eea'; } return `${category}`; } // 拍卖行选中状态 let selectedAuctionItem = null; // 渲染拍卖行列表 - 魔兽世界表格风格 function renderAuctionList() { const container = document.getElementById('auctionTableContainer'); const typeFilter = document.getElementById('auctionTypeFilter').value; const currencyFilter = document.getElementById('auctionCurrencyFilter').value; const searchTerm = (document.getElementById('auctionSearchInput')?.value || '').toLowerCase().trim(); // 过滤物品 let filteredItems = auctionItems.filter(item => { if (typeFilter !== 'all' && item.type !== typeFilter) return false; if (currencyFilter !== 'all' && item.currency !== currencyFilter) return false; // 搜索过滤 if (searchTerm) { const isCard = item.type === 'card'; const name = isCard ? item.card.cardName : item.deck.cardName; if (!name.toLowerCase().includes(searchTerm)) return false; } return true; }); if (filteredItems.length === 0) { container.innerHTML = `
${searchTerm ? '没有找到匹配的拍卖品' : '暂无拍卖品'}
`; return; } // 构建表格 HTML let tableHtml = ` `; filteredItems.forEach(item => { const isCard = item.type === 'card'; const name = isCard ? item.card.cardName : item.deck.cardName; const seriesName = isCard ? item.card.seriesName : item.deck.seriesName; const detail = isCard ? `${COLORS[item.card.colorIndex].name} ${item.card.number}号` : `${item.deck.color}卡组`; const colorClass = isCard ? `card-${COLORS[item.card.colorIndex].class.replace('card-', '')}` : `card-${COLORS[item.deck.colorIndex].class.replace('card-', '')}`; const priceClass = item.currency === 'gold' ? 'gold' : 'gem'; const priceIcon = item.currency === 'gold' ? '💰' : '💎'; const buyoutDisplay = item.buyoutPrice ? `${priceIcon}${item.buyoutPrice}` : '-'; // 选中状态 const isSelected = selectedAuctionItem === item.id; const rowStyle = isSelected ? 'background: rgba(74, 74, 255, 0.3);' : ''; tableHtml += ` `; }); tableHtml += `
图标 名称 卖家 货币 竞标价 一口价 剩余时间
🃏
${name} ${detail} ${item.seller} ${priceIcon} ${item.bidPrice} ${buyoutDisplay} ${formatAuctionTime(item.timeLeft)}
`; // 添加底部操作区域 tableHtml += ` `; container.innerHTML = tableHtml; } // 选中拍卖行某一行 function selectAuctionRow(id) { const item = auctionItems.find(i => i.id === id); if (!item) return; selectedAuctionItem = id; renderAuctionList(); // 显示底部操作面板 const panel = document.getElementById('auctionBidPanel'); if (panel) { panel.style.display = 'block'; // 设置默认值 const input = document.getElementById('bidAmountInput'); if (input && item) { input.value = item.bidPrice + 1; } // 设置一口价按钮 const quickBuyBtn = document.getElementById('quickBuyBtn'); if (quickBuyBtn && item && item.buyoutPrice) { quickBuyBtn.style.display = 'block'; quickBuyBtn.textContent = `一口价购买 (${item.buyoutPrice} ${item.currency === 'gold' ? '💰' : '💎'})`; } else if (quickBuyBtn) { quickBuyBtn.style.display = 'none'; } } } // 提交竞标 function submitBid() { const item = auctionItems.find(i => i.id === selectedAuctionItem); if (!item) return; const bidAmount = parseInt(document.getElementById('bidAmountInput').value); if (!bidAmount || bidAmount <= item.bidPrice) { alert('出价必须高于当前竞标价'); return; } // 检查余额 if (item.currency === 'gold' && gold < bidAmount) { alert('您的金币不足'); return; } if (item.currency === 'gem' && gems < bidAmount) { alert('您的宝石不足'); return; } // 扣除费用 if (item.currency === 'gold') { gold -= bidAmount; } else { gems -= bidAmount; } // 计算手续费 const fee = Math.max(AUCTION_FEE_MIN, Math.ceil(bidAmount * AUCTION_FEE_RATE)); const sellerEarn = bidAmount - fee; // 添加物品到邮箱 const itemName = item.type === 'card' ? `${item.card.seriesName}-${item.card.cardName}` : `${item.deck.seriesName}-${item.deck.cardName}`; const attachment = item.type === 'card' ? { type: 'card', name: itemName, count: 1, card: item.card } : { type: 'deck', name: itemName, count: 1, deck: item.deck }; mails.unshift({ id: Date.now(), sender: '拍卖行', subject: '您竞拍成功的物品已送达', body: `您以 ${bidAmount} ${item.currency === 'gold' ? '金币' : '宝石'} 竞拍成功的 ${itemName} 已送达,请查收。`, time: new Date(), read: false, attachments: [attachment], expired: false }); // 给卖家发送邮件 mails.unshift({ id: Date.now() + 1, sender: '拍卖行', subject: '您的拍卖品已成交', body: `您的 ${itemName} 以 ${bidAmount} ${item.currency === 'gold' ? '金币' : '宝石'} 成功出售,获得 ${sellerEarn} ${item.currency === 'gold' ? '金币' : '宝石'}(扣除手续费 ${fee})`, time: new Date(), read: false, attachments: [], expired: false }); // 从拍卖行移除 auctionItems = auctionItems.filter(i => i.id !== item.id); myAuctionItems = myAuctionItems.filter(i => i.id !== item.id); // 隐藏操作面板 selectedAuctionItem = null; const panel = document.getElementById('auctionBidPanel'); if (panel) panel.style.display = 'none'; updatePlayerInfo(); renderAuctionList(); alert('竞标成功!物品已放入邮箱'); } // 一口价购买 function quickBuy() { const item = auctionItems.find(i => i.id === selectedAuctionItem); if (!item || !item.buyoutPrice) return; // 检查余额 if (item.currency === 'gold' && gold < item.buyoutPrice) { alert('您的金币/宝石不足'); return; } if (item.currency === 'gem' && gems < item.buyoutPrice) { alert('您的金币/宝石不足'); return; } // 扣除费用 if (item.currency === 'gold') { gold -= item.buyoutPrice; } else { gems -= item.buyoutPrice; } // 计算手续费 const fee = Math.max(AUCTION_FEE_MIN, Math.ceil(item.buyoutPrice * AUCTION_FEE_RATE)); const sellerEarn = item.buyoutPrice - fee; // 添加物品到邮箱 const itemName = item.type === 'card' ? `${item.card.seriesName}-${item.card.cardName}` : `${item.deck.seriesName}-${item.deck.cardName}`; const attachment = item.type === 'card' ? { type: 'card', name: itemName, count: 1, card: item.card } : { type: 'deck', name: itemName, count: 1, deck: item.deck }; mails.unshift({ id: Date.now(), sender: '拍卖行', subject: '您一口价购买的物品已送达', body: `您以一口价 ${item.buyoutPrice} ${item.currency === 'gold' ? '金币' : '宝石'} 购买的 ${itemName} 已送达,请查收。`, time: new Date(), read: false, attachments: [attachment], expired: false }); // 给卖家发送邮件 mails.unshift({ id: Date.now() + 1, sender: '拍卖行', subject: '您的拍卖品已成交(一口价)', body: `您的 ${itemName} 以一口价 ${item.buyoutPrice} ${item.currency === 'gold' ? '金币' : '宝石'} 成功出售,获得 ${sellerEarn} ${item.currency === 'gold' ? '金币' : '宝石'}(扣除手续费 ${fee})`, time: new Date(), read: false, attachments: [], expired: false }); // 从拍卖行移除 auctionItems = auctionItems.filter(i => i.id !== item.id); myAuctionItems = myAuctionItems.filter(i => i.id !== item.id); // 隐藏操作面板 selectedAuctionItem = null; const panel = document.getElementById('auctionBidPanel'); if (panel) panel.style.display = 'none'; updatePlayerInfo(); renderAuctionList(); alert('购买成功!物品已放入邮箱'); } // 搜索拍卖品 function searchAuction() { renderAuctionList(); } // 渲染上架库存 function renderAuctionInventory() { // 渲染卡牌库存(根据来源显示手牌或保险箱) const cardInv = document.getElementById('auctionCardInventory'); let cards = []; if (auctionCardSource === 'hand') { cards = handCards.filter(c => c !== null); } else { // 保险箱来源 cards = vaultCards.filter(c => c !== null); } if (cards.length === 0) { cardInv.innerHTML = '
暂无卡牌
'; } else { cardInv.innerHTML = cards.map((card, idx) => { const color = COLORS[card.colorIndex]; // 找到实际在数组中的索引 let actualIndex = -1; if (auctionCardSource === 'hand') { actualIndex = handCards.indexOf(card); } else { actualIndex = vaultCards.indexOf(card); } const isSelected = auctionCardSource === 'hand' ? selectedAuctionCard === actualIndex : selectedVaultCard === actualIndex; return `
${card.seriesName}
${card.cardName}
${card.number}号
`; }).join(''); } // 渲染卡组库存 const deckInv = document.getElementById('auctionDeckInventory'); if (completedDecks.length === 0) { deckInv.innerHTML = '
暂无卡组
'; } else { deckInv.innerHTML = completedDecks.map((deck, idx) => { const color = COLORS[deck.colorIndex]; const isSelected = selectedAuctionDeck === idx; return `
${deck.seriesName}
${deck.cardName}
x${deck.count}
`; }).join(''); } } // 渲染我的拍卖 function renderMyAuction() { const list = document.getElementById('myAuctionList'); if (myAuctionItems.length === 0) { list.innerHTML = '
暂无拍卖中的物品
'; return; } list.innerHTML = myAuctionItems.map(item => { const isCard = item.type === 'card'; const name = isCard ? `${item.card.seriesName}-${item.card.cardName}` : `${item.deck.seriesName}-${item.deck.cardName}`; const desc = isCard ? `${COLORS[item.card.colorIndex].name} ${item.card.number}号` : `${item.deck.color}卡组 x${item.deck.count}`; const priceClass = item.currency === 'gold' ? 'gold' : 'gem'; const priceIcon = item.currency === 'gold' ? '💰' : '💎'; return `
${isCard ? '卡牌' : '卡组'} ${formatAuctionTime(item.timeLeft)}
${name}
${desc}
${priceIcon} ${item.bidPrice} 禁止取消
`; }).join(''); } // 切换拍卖行标签页 function showAuctionTab(tab) { currentAuctionTab = tab; // 更新标签按钮状态 document.querySelectorAll('.auction-tab').forEach(btn => { btn.classList.remove('active'); if (btn.textContent.includes(tab === 'browse' ? '浏览' : tab === 'list' ? '上架' : '我的')) { btn.classList.add('active'); } }); // 显示/隐藏对应内容 document.getElementById('auctionBrowseTab').style.display = tab === 'browse' ? 'block' : 'none'; document.getElementById('auctionListTab').style.display = tab === 'list' ? 'block' : 'none'; document.getElementById('auctionMyTab').style.display = tab === 'my' ? 'block' : 'none'; if (tab === 'browse') { renderAuctionList(); } else if (tab === 'list') { // 重置来源选择 setAuctionCardSource('hand'); renderAuctionInventory(); } else if (tab === 'my') { renderMyAuction(); } } // 设置卡牌来源(手牌/保险箱) function setAuctionCardSource(source) { auctionCardSource = source; // 更新按钮状态 document.getElementById('auctionSourceHand').classList.toggle('active', source === 'hand'); document.getElementById('auctionSourceVault').classList.toggle('active', source === 'vault'); // 清除选择 selectedAuctionCard = null; selectedVaultCard = null; // 移除表单 const formContainer = document.getElementById('auctionFormContainer'); if (formContainer) formContainer.innerHTML = ''; renderAuctionInventory(); } // 选择要上架的卡牌 function selectAuctionCard(index) { // 根据来源设置对应的选中状态 if (auctionCardSource === 'hand') { selectedAuctionCard = selectedAuctionCard === index ? null : index; selectedVaultCard = null; } else { selectedVaultCard = selectedVaultCard === index ? null : index; selectedAuctionCard = null; } selectedAuctionDeck = null; renderAuctionInventory(); if (selectedAuctionCard !== null || selectedVaultCard !== null) { showListForm('card'); } } // 选择要上架的卡组 function selectAuctionDeck(index) { selectedAuctionDeck = selectedAuctionDeck === index ? null : index; selectedAuctionCard = null; renderAuctionInventory(); if (selectedAuctionDeck !== null) { showListForm('deck'); } } // 显示上架表单 function showListForm(type) { const listTab = document.getElementById('auctionListTab'); // 检查数量限制 const myCards = myAuctionItems.filter(i => i.type === 'card').length; const myDecks = myAuctionItems.filter(i => i.type === 'deck').length; if (type === 'card' && myCards >= AUCTION_MAX_CARDS) { alert(`最多只能上架 ${AUCTION_MAX_CARDS} 张卡牌`); return; } if (type === 'deck' && myDecks >= AUCTION_MAX_DECKS) { alert(`最多只能上架 ${AUCTION_MAX_DECKS} 套卡组`); return; } const isCard = type === 'card'; let item, name, sourceType; if (isCard) { // 获取卡牌来源 if (auctionCardSource === 'hand' && selectedAuctionCard !== null) { item = handCards[selectedAuctionCard]; sourceType = 'hand'; } else if (auctionCardSource === 'vault' && selectedVaultCard !== null) { item = vaultCards[selectedVaultCard]; sourceType = 'vault'; } name = item ? `${item.seriesName}-${item.cardName}` : ''; } else { item = completedDecks[selectedAuctionDeck]; sourceType = 'deck'; name = item ? `${item.seriesName}-${item.cardName}` : ''; } // 构建时长选项(简化为2种) let durationOptions = AUCTION_DURATIONS.map(d => { let categoryLabel = d.category === 'short' ? '短' : '长'; return ``; }).join(''); // 创建表单 const formHtml = `
📤 上架: ${name}
📋 费用说明:
• 上架费: 5 💰
• 手续费: 8%(成交后收取,向上取整,最低1 💰
`; // 添加表单到页面 let formContainer = document.getElementById('auctionFormContainer'); if (!formContainer) { formContainer = document.createElement('div'); formContainer.id = 'auctionFormContainer'; listTab.appendChild(formContainer); } formContainer.innerHTML = formHtml; updateAuctionFee(); } // 更新费用显示(根据时限和货币计算) function updateAuctionFee() { const currency = document.getElementById('auctionCurrency').value; const durationSelect = document.getElementById('auctionDuration'); const durationOption = durationSelect.options[durationSelect.selectedIndex]; // 从option中获取费用 const fee = currency === 'gold' ? parseInt(durationOption.dataset.feeGold) : parseInt(durationOption.dataset.feeGem); const icon = currency === 'gold' ? '💰' : '💎'; document.getElementById('listingFee').textContent = fee; document.getElementById('listingFeeIcon').textContent = icon; document.getElementById('transactionFeeIcon').textContent = icon; } // 提交拍卖 function submitAuction(type, sourceType) { const currency = document.getElementById('auctionCurrency').value; const duration = parseFloat(document.getElementById('auctionDuration').value); const bidPrice = parseInt(document.getElementById('auctionBidPrice').value); const buyoutPrice = document.getElementById('auctionBuyoutPrice').value ? parseInt(document.getElementById('auctionBuyoutPrice').value) : null; if (!bidPrice || bidPrice < 1) { alert('请输入有效的竞标价'); return; } // 获取当前时限的上架费 const durationSelect = document.getElementById('auctionDuration'); const durationOption = durationSelect.options[durationSelect.selectedIndex]; const listingFee = currency === 'gold' ? parseInt(durationOption.dataset.feeGold) : parseInt(durationOption.dataset.feeGem); // 检查余额 if (currency === 'gold' && gold < listingFee) { alert('金币不足,需要 ' + listingFee + ' 金币作为上架费'); return; } if (currency === 'gem' && gems < listingFee) { alert('宝石不足,需要 ' + listingFee + ' 宝石作为上架费'); return; } // 扣除上架费 if (currency === 'gold') { gold -= listingFee; } else { gems -= listingFee; } updatePlayerInfo(); // 创建拍卖品 const isCard = type === 'card'; let item; if (isCard) { // 根据来源获取卡牌 if (sourceType === 'hand') { item = handCards[selectedAuctionCard]; } else if (sourceType === 'vault') { item = vaultCards[selectedVaultCard]; } } else { item = completedDecks[selectedAuctionDeck]; } const newAuction = { id: Date.now(), type: type, sourceType: sourceType, // 记录来源:hand/vault/deck card: isCard ? { ...item } : null, deck: isCard ? null : { ...item }, seller: '我', currency: currency, bidPrice: bidPrice, buyoutPrice: buyoutPrice, timeLeft: duration, // 支持小数(0.5小时=30分钟) createdAt: Date.now() }; // 从库存中移除 if (isCard) { if (sourceType === 'hand' && selectedAuctionCard !== null) { handCards[selectedAuctionCard] = null; } else if (sourceType === 'vault' && selectedVaultCard !== null) { vaultCards[selectedVaultCard] = null; } } else { if (selectedAuctionDeck !== null) { completedDecks[selectedAuctionDeck].count--; if (completedDecks[selectedAuctionDeck].count <= 0) { completedDecks.splice(selectedAuctionDeck, 1); } } } // 添加到我的拍卖和拍卖行列表 myAuctionItems.push(newAuction); auctionItems.push(newAuction); // 重置选择 selectedAuctionCard = null; selectedAuctionDeck = null; selectedVaultCard = null; // 重新渲染 renderHand(); renderDecks(); renderVault(); renderAuctionInventory(); renderMyAuction(); // 移除表单 const formContainer = document.getElementById('auctionFormContainer'); if (formContainer) formContainer.innerHTML = ''; alert('上架成功!'); } // 模拟测试邮件 function initMails() { mails = [ { id: 1, sender: '系统', subject: '欢迎来到万象卡域', body: '恭喜您进入万象卡域!集齐同系列同颜色的5张卡牌即可合成一套卡组。更有稀有卡组等你来发现!', time: new Date(Date.now() - 86400000 * 2), read: true, attachments: [ { type: 'gold', name: '金币', count: 100 }, { type: 'gem', name: '宝石', count: 10 } ], expired: false }, { id: 2, sender: '拍卖行', subject: '您竞拍的卡牌已送达', body: '您在拍卖行成功竞拍的卡牌已进入邮箱,请查收。', time: new Date(Date.now() - 86400000), read: false, attachments: [ { type: 'card', name: '城市-上海-蓝', count: 1 } ], expired: false }, { id: 3, sender: '拍卖行', subject: '您的卡组流拍通知', body: '您寄售的卡组在拍卖行流拍,现已退回至邮箱。请在30天内提取,逾期将被销毁。', time: new Date(Date.now() - 86400000 * 5), read: true, attachments: [ { type: 'deck', name: '城市-北京-紫', count: 1 } ], expired: false, expireTime: Date.now() + 86400000 * 25 }, { id: 4, sender: '系统', subject: '红卡组生成通知', body: '恭喜!您已集齐城市-上海系列的全色卡组,成功生成红色卡组!', time: new Date(Date.now() - 3600000), read: false, attachments: [ { type: 'deck', name: '城市-上海-红', count: 1 } ], expired: false }, { id: 5, sender: '系统', subject: '每日签到奖励', body: '感谢您的每日签到,这是您今天的奖励!', time: new Date(Date.now() - 1800000), read: false, attachments: [ { type: 'gold', name: '金币', count: 50 }, { type: 'gem', name: '宝石', count: 5 } ], expired: false } ]; } // 渲染邮件列表 function renderMailList() { const mailList = document.getElementById('mailList'); const unreadCount = mails.filter(m => !m.read).length; // 更新未读数显示 const unreadBadge = document.getElementById('unreadMailCount'); if (unreadCount > 0) { unreadBadge.textContent = unreadCount; unreadBadge.style.display = 'inline'; } else { unreadBadge.style.display = 'none'; } if (mails.length === 0) { mailList.innerHTML = '
暂无邮件
'; return; } let html = ''; // 按时间排序,最新的在前面 const sortedMails = [...mails].sort((a, b) => b.time - a.time); sortedMails.forEach(mail => { const timeStr = formatMailTime(mail.time); const unreadClass = mail.read ? '' : 'unread'; const unreadDot = mail.read ? '' : ' '; html += `
${mail.sender} ${timeStr}
${unreadDot}${mail.subject}
${mail.body.substring(0, 30)}...
`; }); mailList.innerHTML = html; } // 格式化邮件时间 function formatMailTime(date) { const now = new Date(); const diff = now - date; const days = Math.floor(diff / 86400000); const hours = Math.floor(diff / 3600000); if (days > 0) return `${days}天前`; if (hours > 0) return `${hours}小时前`; return '刚刚'; } // 选择邮件 function selectMail(mailId) { const mail = mails.find(m => m.id === mailId); if (!mail) return; selectedMail = mail; // 标记为已读 if (!mail.read) { mail.read = true; renderMailList(); } renderMailContent(mail); } // 渲染邮件内容 function renderMailContent(mail) { const content = document.getElementById('mailContent'); const timeStr = formatMailTime(mail.time); let html = `
来自: ${mail.sender}
${mail.subject}
${timeStr}
${mail.body}
`; if (mail.attachments && mail.attachments.length > 0) { html += `
📦 附件
`; mail.attachments.forEach(att => { let icon = '📦'; if (att.type === 'card') icon = '🃏'; else if (att.type === 'deck') icon = '🏆'; else if (att.type === 'gold') icon = '💰'; else if (att.type === 'gem') icon = '💎'; html += `
${icon} ${att.name || att.type} x${att.count}
`; }); // 添加提取按钮 html += `
`; } else { html += `
`; } content.innerHTML = html; } // 提取附件 function takeAttachment(mailId) { const mailIndex = mails.findIndex(m => m.id === mailId); if (mailIndex === -1) return; const mail = mails[mailIndex]; if (!mail.attachments || mail.attachments.length === 0) return; let successMsg = ''; let errorMsg = ''; let hasCardFullError = false; // 处理每个附件 mail.attachments.forEach(att => { if (att.type === 'gold') { // 金币 gold += att.count; successMsg += `+${att.count}金币 `; } else if (att.type === 'gem') { // 宝石 gems += att.count; successMsg += `+${att.count}宝石 `; } else if (att.type === 'deck') { // 卡组 const result = addDeckToPlayer(att.name); if (result === 'new') { successMsg += `卡组[${att.name}] `; } else if (result === 'exists') { successMsg += `卡组[${att.name}]套数+1 `; } } else if (att.type === 'card') { // 单卡 const result = addCardToHand(att.name); if (result === 'ok') { successMsg += `卡牌[${att.name}] `; } else if (result === 'full') { errorMsg += `卡牌[${att.name}]手牌已满 `; hasCardFullError = true; // 标记有卡牌提取失败 } } }); // 更新界面 updateCurrencyDisplay(); renderHand(); renderDecks(); // 显示结果 if (errorMsg) { alert(errorMsg + ',请先清理手牌!'); } if (successMsg) { alert('提取成功!获得: ' + successMsg); } // 只有在没有卡牌提取失败的情况下才删除邮件 // 如果有卡牌提取失败,邮件保留,30天后自动销毁 if (!hasCardFullError) { mails.splice(mailIndex, 1); selectedMail = null; renderMailList(); document.getElementById('mailContent').innerHTML = '
选择一封邮件查看详情
'; } else { // 刷新邮件内容显示(更新附件状态) renderMailContent(mail); } } // 解析卡牌/卡组名称 // 格式: "城市-上海-蓝" -> {seriesName, cardName, color} function parseItemName(name) { const parts = name.split('-'); if (parts.length >= 3) { return { seriesName: parts[0], cardName: parts[1], color: parts[2] }; } return null; } // 获取颜色索引 function getColorIndex(colorName) { const colorMap = { '白': 0, '白卡': 0, '绿': 1, '绿卡': 1, '蓝': 2, '蓝卡': 2, '紫': 3, '紫卡': 3, '橙': 4, '橙卡': 4, '黑': 5, '黑卡': 5, '红': 6, '红卡': 6 }; return colorMap[colorName] || 0; } // 添加卡组到玩家卡组 function addDeckToPlayer(deckName) { const info = parseItemName(deckName); if (!info) return 'error'; const colorIndex = getColorIndex(info.color); const color = info.color; // 检查是否已有该卡组 const existingIndex = completedDecks.findIndex(d => d.colorIndex === colorIndex && d.seriesName === info.seriesName && d.cardName === info.cardName ); if (existingIndex >= 0) { // 已有该卡组,套数+1 completedDecks[existingIndex].count++; return 'exists'; } else { // 新建卡组 completedDecks.push({ color: color, colorIndex: colorIndex, seriesName: info.seriesName, cardName: info.cardName, count: 1 }); return 'new'; } } // 添加卡牌到玩家手牌 function addCardToHand(cardName) { // 找手牌空位 let emptyIndex = -1; for (let i = 0; i < unlockedHandSlots; i++) { if (handCards[i] === null) { emptyIndex = i; break; } } if (emptyIndex === -1) { return 'full'; // 手牌满 } const info = parseItemName(cardName); if (!info) { // 随机生成一个 const seriesData = SERIES[Math.floor(Math.random() * SERIES.length)]; const cardNames = seriesData.cards; handCards[emptyIndex] = { id: Math.random().toString(36).substr(2, 9), seriesId: seriesData.id, seriesName: seriesData.name, cardName: cardNames[Math.floor(Math.random() * cardNames.length)], number: Math.floor(Math.random() * 5) + 1, colorIndex: Math.floor(Math.random() * 6) // 不含红卡 }; } else { // 使用附件的名称 handCards[emptyIndex] = { id: Math.random().toString(36).substr(2, 9), seriesId: info.seriesName === '城市' ? 'city' : 'food', seriesName: info.seriesName, cardName: info.cardName, number: Math.floor(Math.random() * 5) + 1, colorIndex: getColorIndex(info.color) }; } return 'ok'; } // 删除邮件 function deleteMail(mailId) { const mailIndex = mails.findIndex(m => m.id === mailId); if (mailIndex === -1) return; mails.splice(mailIndex, 1); selectedMail = null; renderMailList(); document.getElementById('mailContent').innerHTML = '
选择一封邮件查看详情
'; } // 当前用户信息 let currentEmail = ''; // 当前用户邮箱 let currentPassword = ''; // 当前用户密码 let currentDisplayName = ''; // 当前用户显示名称 let gold = 100; // 初始金币 let gems = 10; // 初始宝石 let playerLevel = 1; let currentExp = 0; // 免费刷新系统 let freeRefreshRemaining = 1; // 免费刷新剩余次数 let freeRefreshCooldown = 0; // 免费刷新冷却时间(秒) const FREE_REFRESH_COOLDOWN = 60; // 免费刷新冷却时间(60秒) // 获取免费刷新次数(根据玩家等级) function getFreeRefreshMax() { // 1级:1次/分钟,每级+1次,最多40次(40级满级) return Math.min(playerLevel, 40); } // 免费刷新冷却计时器 function startFreeRefreshCooldown() { freeRefreshCooldown = FREE_REFRESH_COOLDOWN; updateFreeRefreshButton(); // 每秒更新一次 const cooldownInterval = setInterval(() => { freeRefreshCooldown--; if (freeRefreshCooldown <= 0) { // 冷却结束,恢复次数 freeRefreshRemaining = getFreeRefreshMax(); clearInterval(cooldownInterval); } updateFreeRefreshButton(); }, 1000); } // 更新免费刷新按钮状态 function updateFreeRefreshButton() { const btn = document.getElementById('freeRefreshBtn'); const remainingEl = document.getElementById('freeRefreshRemaining'); if (remainingEl) { remainingEl.textContent = freeRefreshRemaining; } if (btn) { if (freeRefreshCooldown > 0) { // 冷却中 btn.disabled = true; btn.classList.add('cooldown'); btn.innerHTML = `🆓 冷却中(${freeRefreshCooldown}秒)`; } else if (freeRefreshRemaining <= 0) { // 次数用完 btn.disabled = true; btn.classList.add('cooldown'); btn.innerHTML = `🆓 免费刷新(次数已用完)`; } else { // 可用 btn.disabled = false; btn.classList.remove('cooldown'); btn.innerHTML = `🆓 免费刷新(剩余${freeRefreshRemaining}次)`; } } } // 免费刷新 function freeRefreshPool() { if (freeRefreshCooldown > 0 || freeRefreshRemaining <= 0) { return; } // 扣除次数 freeRefreshRemaining--; // 刷新卡池 doPoolRefresh(); syncAllDataToServer(); // 开始冷却 startFreeRefreshCooldown(); console.log('免费刷新成功,剩余次数:', freeRefreshRemaining); syncAllDataToServer(); } // 宝石刷新 function gemRefreshPool() { const gemCost = 5; if (gems < gemCost) { alert('宝石不足!需要 ' + gemCost + ' 宝石'); return; } // 扣除宝石 gems -= gemCost; updatePlayerInfo(); updateCurrencyDisplay(); // 刷新卡池 doPoolRefresh(); console.log('宝石刷新成功,花费', gemCost, '宝石'); } // 金币刷新 function goldRefreshPool() { const goldRefreshCost = Math.max(1, Math.floor(gold * 0.01)); if (gold < goldRefreshCost) { alert('金币不足!需要至少 ' + goldRefreshCost + ' 金币'); return; } // 扣除金币 gold -= goldRefreshCost; updatePlayerInfo(); updateCurrencyDisplay(); // 刷新卡池 doPoolRefresh(); console.log('金币刷新成功,花费', goldRefreshCost, '金币'); } // 执行卡池刷新(核心逻辑) function doPoolRefresh() { // 刷新卡池:重新生成12张卡(已解锁的卡槽) poolCards = new Array(16).fill(null); for (let i = 0; i < unlockedPoolSlots; i++) { poolCards[i] = createRandomCard(); } renderPool(); updateServerDeckInfoDisplay(); } const levelUpExp = [ 0, 400, 900, 1400, 2100, 3000, 4100, 5400, 7000, 8900, 11100, 13600, 16400, 19500, 23000, 26900, 31200, 36000, 41300, 47200, 53700, 60800, 68600, 77100, 86400, 96500, 107500, 119400, 132300, 146300, 161500, 177900, 195600, 214700, 235300, 257500, 281500, 307500, 335700, 366300, 400000 ]; let dragSource = null; // 初始化测试账号的已合成卡组 function initCompletedDecks() { // 颜色配置:橙色8种,其他每种2种(红黑各1种,但允许有2套所以还是2种) const deckConfig = [ { color: '红', colorIndex: 6, count: 2 }, // 2种 { color: '黑', colorIndex: 5, count: 2 }, // 2种 { color: '橙', colorIndex: 4, count: 8 }, // 8种 { color: '紫', colorIndex: 3, count: 2 }, // 2种 { color: '蓝', colorIndex: 2, count: 2 }, // 2种 { color: '绿', colorIndex: 1, count: 2 }, // 2种 { color: '白', colorIndex: 0, count: 2 } // 2种 ]; // 卡名池 const cardNames = ['上海', '北京', '深圳', '火锅', '烧烤', '串串', '熊猫', '老虎', '狮子', '广州', '杭州', '成都']; const seriesNames = ['城市', '城市', '美食', '美食', '动物']; let nameIndex = 0; deckConfig.forEach(cfg => { for (let i = 0; i < cfg.count; i++) { completedDecks.push({ color: cfg.color, colorIndex: cfg.colorIndex, seriesName: seriesNames[nameIndex % seriesNames.length], cardName: cardNames[nameIndex % cardNames.length], count: Math.floor(Math.random() * 5) + 1 // 随机1-5套 }); nameIndex++; } }); } function initGame() { console.log('游戏初始化...'); // 内测阶段:只初始化太阳系卡组 // 不再使用initCompletedDecks()(测试数据) // 不再使用initMails()(测试数据) // 不再使用initAuction()(测试数据) // 不再使用initLadder()(测试数据) initServerDecks(); // 初始化服务器卡组系统(太阳系) // 初始化免费刷新次数:保持从保存数据恢复的次数 // 不需要额外处理,因为 loadUserData 已经正确恢复了使用后的剩余次数 updateFreeRefreshButton(); // 初始卡池刷新 doPoolRefresh(); renderHand(); updatePlayerInfo(); renderDecks(); renderMailList(); // 渲染邮件列表(空) renderLadderTable(ladderData); // 渲染天梯榜(空) console.log('游戏初始化完成,玩家等级:', playerLevel, '金币:', gold, '宝石:', gems); } // 更新刷新花费显示(保留兼容性) function updateRefreshCostDisplay() { const goldRefreshCost = Math.max(1, Math.floor(gold * 0.01)); const costElement = document.getElementById('goldRefreshCost'); if (costElement) { costElement.textContent = goldRefreshCost; } } function createRandomCard() { // 内测阶段:只生成太阳系卡组的卡牌 const visibleCardNames = getPlayerVisibleCardNames(); // 从太阳系系列抽取,使用玩家可见的卡组 const cardName = visibleCardNames[Math.floor(Math.random() * visibleCardNames.length)]; return { id: Math.random().toString(36).substr(2, 9), seriesId: 'solar', seriesName: '太阳系', cardName: cardName, number: getRandomNumber(), colorIndex: getRandomColor() }; } function getRandomNumber() { const r = Math.random() * 100; if (r < 30) return 1; if (r < 55) return 2; if (r < 75) return 3; if (r < 90) return 4; return 5; } function getRandomColor() { const r = Math.random() * 1000 / 10; if (r < 0.001) return 5; if (r < 0.1) return 4; if (r < 1) return 3; if (r < 11) return 2; if (r < 21) return 1; return 0; } function renderPool() { const grid = document.getElementById('poolGrid'); grid.innerHTML = ''; // 渲染全部16个槽位,包括锁定的 for (let i = 0; i < 16; i++) { if (poolCards[i]) { grid.appendChild(createCardElement(poolCards[i], 'pool', i)); } else { grid.appendChild(createEmptySlot('pool', i)); } } // 渲染服务器卡组信息 updateServerDeckInfoDisplay(); } // 更新服务器卡组信息显示 function updateServerDeckInfoDisplay() { const container = document.getElementById('serverDeckInfo'); if (!container) return; const visibleDecks = getVisibleServerDecks(); let html = '
'; html += '
🌟 服务器卡组(等级' + playerLevel + '可见' + visibleDecks.length + '套)
'; html += '
'; visibleDecks.forEach(deck => { const isFixed = deck.day !== undefined; html += `${deck.label}: ${deck.cardName}`; }); html += '
'; container.innerHTML = html; } function renderVault() { console.log('renderVault called, unlockedVaultSlots=' + unlockedVaultSlots); const grid = document.getElementById('vaultGrid'); if (!grid) { console.log('vaultGrid not found!'); return; } grid.innerHTML = ''; // 计算显示行数 let vaultRowsToShow = 2; if (unlockedVaultSlots > 8) { vaultRowsToShow = Math.ceil(unlockedVaultSlots / 8) + 1; if (vaultRowsToShow > 3) vaultRowsToShow = 3; } const slotsToShow = vaultRowsToShow * 8; for (let i = 0; i < slotsToShow; i++) { if (vaultCards[i]) { grid.appendChild(createCardElement(vaultCards[i], 'vault', i)); } else { // 锁定槽显示 if (i >= unlockedVaultSlots) { grid.appendChild(createLockedSlot()); } else { grid.appendChild(createEmptyVaultSlot(i)); } } } } function createEmptyVaultSlot(index) { const el = document.createElement('div'); el.className = 'empty-slot'; el.dataset.type = 'vault'; el.dataset.index = index; el.dataset.unlocked = 'true'; el.dataset.unlocked = 'true'; el.addEventListener('dragover', handleDragOver); el.addEventListener('dragleave', handleDragLeave); el.addEventListener('drop', handleDrop); return el; } function createLockedSlot() { const el = document.createElement('div'); el.className = 'locked-slot'; el.innerHTML = '🔒'; return el; } function renderHand() { const grid = document.getElementById('handGrid'); grid.innerHTML = ''; const startIdx = handPage * 16; // 渲染当前页全部16个槽位 for (let i = 0; i < 16; i++) { const idx = startIdx + i; // 超过解锁数量的显示锁定 if (idx >= unlockedHandSlots) { grid.appendChild(createEmptySlot('hand', idx)); continue; } if (handCards[idx]) { grid.appendChild(createCardElement(handCards[idx], 'hand', idx)); } else { grid.appendChild(createEmptySlot('hand', idx)); } } document.querySelectorAll('.page-btn').forEach((btn, i) => { btn.classList.toggle('active', i === handPage); }); } function createCardElement(card, type, index) { // vault: use unlockedVaultSlots, others use their own const isUnlocked = (type === 'pool' && index < unlockedPoolSlots) || (type === 'hand' && index < unlockedHandSlots) || (type === 'vault' && index < unlockedVaultSlots); if (!isUnlocked) return createEmptySlot(type, index); const color = COLORS[card.colorIndex]; let cardClass = color.class; // 选中状态 if ((type === 'hand' && index === selectedHandCard) || (type === 'vault' && index === selectedVaultCard)) { cardClass += ' selected'; } const el = document.createElement('div'); el.className = `card ${cardClass}`; el.dataset.type = type; el.dataset.index = index; el.dataset.unlocked = 'true'; el.draggable = true; el.innerHTML = `
${card.seriesName}-${card.cardName}
${card.number}
${color.name}
`; el.style.position = 'relative'; el.addEventListener('dragstart', handleDragStart); el.addEventListener('dragend', handleDragEnd); el.addEventListener('dragover', handleDragOver); el.addEventListener('dragleave', handleDragLeave); el.addEventListener('drop', handleDrop); // 双击移动到空槽 el.addEventListener('dblclick', () => handleDoubleClick(type, index)); // 点击选中(仅手牌) el.addEventListener('click', (e) => { e.stopPropagation(); if (type === 'hand') selectHandCard(index); }); // 保险箱卡牌支持拖拽和选中 if (type === 'vault') { el.addEventListener('dragstart', handleDragStart); el.addEventListener('dragend', handleDragEnd); el.addEventListener('dragover', handleDragOver); el.addEventListener('dragleave', handleDragLeave); el.addEventListener('drop', handleDrop); // 点击选中 el.addEventListener('click', (e) => { e.stopPropagation(); selectVaultCard(index); }); } return el; } function createEmptySlot(type, index) { const el = document.createElement('div'); const isUnlocked = (type === 'pool' && index < unlockedPoolSlots) || (type === 'hand' && index < unlockedHandSlots) || (type === 'vault' && index < unlockedVaultSlots); if (isUnlocked) { el.className = 'empty-slot'; } else { el.className = 'locked-slot'; el.innerHTML = '🔒'; } el.dataset.type = type; el.dataset.index = index; el.dataset.unlocked = isUnlocked; if (isUnlocked) { el.addEventListener('dragover', handleDragOver); el.addEventListener('dragleave', handleDragLeave); el.addEventListener('drop', handleDrop); } return el; } function handleDragStart(e) { playDragSound(); // 拖拽时播放音效 let target = e.target; console.log('dragstart target:', target, 'dataset:', target.dataset); // 如果拖的是卡牌里面的元素,找卡牌元素 if (!target.dataset || !target.dataset.type) { target = target.closest('.card') || target.closest('.empty-slot') || target.closest('.locked-slot'); console.log('closest target:', target); } if (target && target.dataset && target.dataset.type) { dragSource = { type: target.dataset.type, index: parseInt(target.dataset.index) }; target.classList.add('dragging'); console.log('开始拖拽:', dragSource); } else { console.log('无法获取拖拽源'); } } function handleDragEnd(e) { let target = e.target; if (!target.dataset.type) { target = target.closest('.card') || target.closest('.empty-slot'); } if (target) target.classList.remove('dragging'); document.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over')); } function handleDragOver(e) { e.preventDefault(); e.currentTarget.classList.add('drag-over'); } function handleDragLeave(e) { e.currentTarget.classList.remove('drag-over'); } function handleDrop(e) { e.preventDefault(); e.stopPropagation(); // 获取目标元素 - 可能是卡牌、空槽 let targetEl = e.target; // 如果是子元素,向上找 if (!targetEl.dataset.type) { targetEl = targetEl.closest('.card') || targetEl.closest('.empty-slot') || targetEl.closest('.locked-slot'); } if (!targetEl) { console.log('未找到目标元素', e.target); return; } targetEl.classList.remove('drag-over'); if (!dragSource) { console.log('没有拖拽源'); return; } // 获取目标槽的信息 const targetType = targetEl.dataset.type; const targetIndex = parseInt(targetEl.dataset.index); // 检查目标槽是否解锁 const isUnlocked = targetEl.dataset.unlocked === 'true'; if (!isUnlocked) { console.log('目标槽已锁定'); dragSource = null; return; } console.log('放置:', { source: dragSource, target: { type: targetType, index: targetIndex } }); // 获取拖拽源卡牌 let sourceCards; if (dragSource.type === 'pool') sourceCards = poolCards; else if (dragSource.type === 'hand') sourceCards = handCards; else if (dragSource.type === 'vault') sourceCards = vaultCards; const card = sourceCards ? sourceCards[dragSource.index] : null; if (!card) return; if (targetType === 'pool') { const targetCard = poolCards[targetIndex]; poolCards[targetIndex] = card; if (dragSource.type === 'pool') poolCards[dragSource.index] = targetCard; else if (dragSource.type === 'hand') handCards[dragSource.index] = targetCard; else if (dragSource.type === 'vault') vaultCards[dragSource.index] = targetCard; } else if (targetType === 'hand') { const targetCard = handCards[targetIndex]; handCards[targetIndex] = card; if (dragSource.type === 'pool') poolCards[dragSource.index] = targetCard; else if (dragSource.type === 'hand') handCards[dragSource.index] = targetCard; else if (dragSource.type === 'vault') vaultCards[dragSource.index] = targetCard; } else if (targetType === 'vault') { // 保险箱内部拖拽 const targetCard = vaultCards[targetIndex]; vaultCards[targetIndex] = card; if (dragSource.type === 'vault') { vaultCards[dragSource.index] = targetCard; if (currentPage === 'vault') renderVault(); } else { console.log('保险箱的卡不能移到其他区域'); } } dragSource = null; renderPool(); renderHand(); if (currentPage === 'vault') renderVault(); } function setHandPage(page) { handPage = page; renderHand(); } // 选中手牌 function selectHandCard(index) { selectedHandCard = index; renderHand(); updateActionButtons(); } // 取消选中 function deselectCard() { selectedHandCard = null; renderHand(); updateActionButtons(); } // 更新操作按钮状态 function updateActionButtons() { const composeBtn = document.getElementById('composeBtn'); const discardBtn = document.getElementById('discardBtn'); const vaultBtn = document.getElementById('vaultBtn'); if (selectedHandCard === null) { composeBtn.style.visibility = 'hidden'; discardBtn.style.visibility = 'hidden'; vaultBtn.style.visibility = 'hidden'; return; } const card = handCards[selectedHandCard]; if (!card) { selectedHandCard = null; updateActionButtons(); return; } // 检查是否可以合成 const canCompose = checkCanCompose(card); composeBtn.style.visibility = 'visible'; composeBtn.disabled = !canCompose; // 显示按钮 discardBtn.style.visibility = 'visible'; vaultBtn.style.visibility = 'visible'; // 检查保险箱是否有空位(只计算已解锁的槽位) let vaultEmptyCount = 0; for (let i = 0; i < unlockedVaultSlots; i++) { if (vaultCards[i] === null) vaultEmptyCount++; } const hasVaultSpace = vaultEmptyCount > 0; vaultBtn.disabled = !hasVaultSpace; } // 检查是否可以合成(同系列同颜色1-5号齐全) function checkCanCompose(card) { // 找到手牌中同系列同颜色同卡名的所有卡 const sameCards = []; for (let i = 0; i < handCards.length; i++) { if (handCards[i] && handCards[i].seriesId === card.seriesId && handCards[i].cardName === card.cardName && handCards[i].colorIndex === card.colorIndex) { sameCards.push(handCards[i].number); } } // 检查是否有1-5 return sameCards.includes(1) && sameCards.includes(2) && sameCards.includes(3) && sameCards.includes(4) && sameCards.includes(5); } // 丢弃选中的卡 function discardSelectedCard() { if (selectedHandCard === null) return; handCards[selectedHandCard] = null; selectedHandCard = null; renderHand(); updateActionButtons(); } // 存入保险箱 function moveToVault() { playVaultInSound(); // 存入保险箱音效 if (selectedHandCard === null) return; const card = handCards[selectedHandCard]; if (!card) return; // 找保险箱空位 for (let i = 0; i < vaultCards.length; i++) { if (vaultCards[i] === null) { vaultCards[i] = card; handCards[selectedHandCard] = null; selectedHandCard = null; renderHand(); updateActionButtons(); renderVault(); console.log('已存入保险箱'); syncAllDataToServer(); syncAllDataToServer(); return; } } console.log('保险箱已满'); } // 选中保险箱卡牌 function selectVaultCard(index) { selectedVaultCard = index; renderVault(); updateVaultActionButtons(); } // 取消保险箱选中 function deselectVaultCard() { selectedVaultCard = null; renderVault(); updateVaultActionButtons(); } // 更新保险箱操作按钮状态 function updateVaultActionButtons() { const composeBtn = document.getElementById('vaultComposeBtn'); if (selectedVaultCard === null) { composeBtn.style.visibility = 'hidden'; return; } const card = vaultCards[selectedVaultCard]; if (!card) { selectedVaultCard = null; updateVaultActionButtons(); return; } // 检查是否可以合成 const canCompose = checkVaultCanCompose(card); composeBtn.style.visibility = 'visible'; composeBtn.disabled = !canCompose; } // 检查保险箱内是否可以合成 function checkVaultCanCompose(card) { const sameCards = []; for (let i = 0; i < vaultCards.length; i++) { if (vaultCards[i] && vaultCards[i].seriesId === card.seriesId && vaultCards[i].cardName === card.cardName && vaultCards[i].colorIndex === card.colorIndex) { sameCards.push(vaultCards[i].number); } } return sameCards.includes(1) && sameCards.includes(2) && sameCards.includes(3) && sameCards.includes(4) && sameCards.includes(5); } // 保险箱合成 function composeVaultDeck() { if (selectedVaultCard === null) return; const card = vaultCards[selectedVaultCard]; if (!card) return; // 找齐5张卡 const cardsToCompose = []; for (let i = 0; i < vaultCards.length; i++) { if (vaultCards[i] && vaultCards[i].seriesId === card.seriesId && vaultCards[i].cardName === card.cardName && vaultCards[i].colorIndex === card.colorIndex) { cardsToCompose.push(vaultCards[i]); } } // 检查是否满足合成条件 const numbers = cardsToCompose.map(c => c.number).sort(); if (!numbers.includes(1) || !numbers.includes(2) || !numbers.includes(3) || !numbers.includes(4) || !numbers.includes(5)) { console.log('不满足合成条件'); return; } // 合成! const color = COLORS[card.colorIndex]; // 检查是否已有相同卡组 const existingIndex = completedDecks.findIndex(d => d.colorIndex === card.colorIndex && d.seriesName === card.seriesName && d.cardName === card.cardName ); if (existingIndex >= 0) { // 已有这种卡组,套数+1 completedDecks[existingIndex].count++; } else { // 新建卡组 completedDecks.push({ color: color.name.replace('卡', ''), colorIndex: card.colorIndex, seriesName: card.seriesName, cardName: card.cardName, count: 1 }); } // 移除这5张卡 cardsToCompose.forEach(c => { const idx = vaultCards.findIndex(v => v && v.id === c.id); if (idx !== -1) vaultCards[idx] = null; }); // 获得经验 const exp = COLORS[card.colorIndex].exp * 5; addExp(exp); selectedVaultCard = null; renderVault(); renderDecks(); updateVaultActionButtons(); console.log(`保险箱合成成功!获得经验 ${exp}`); } // 点击空白处取消选中 document.addEventListener('click', (e) => { if (!e.target.closest('.card') && !e.target.closest('.compose-btn') && !e.target.closest('.discard-btn') && !e.target.closest('.vault-btn') && !e.target.closest('.page-btn') && !e.target.closest('.section-header')) { deselectCard(); deselectVaultCard(); } }); // 双击卡牌移动到第一个空的手牌槽 function handleDoubleClick(type, index) { playDrawCardSound(); // 抽卡音效 if (type === 'pool') { // 双击卡池卡牌移动到第一个空的手牌槽 const card = poolCards[index]; if (!card) return; for (let i = 0; i < unlockedHandSlots; i++) { if (!handCards[i]) { handCards[i] = card; poolCards[index] = null; renderPool(); renderHand(); console.log('双击移动卡牌到的手牌槽:', i); return; } } console.log('手牌槽已满'); } else if (type === 'hand') { // 双击手牌卡牌移动到第一个空的卡池槽 const card = handCards[index]; if (!card) return; for (let i = 0; i < unlockedPoolSlots; i++) { if (!poolCards[i]) { poolCards[i] = card; handCards[index] = null; renderPool(); renderHand(); console.log('双击移动卡牌到卡池槽:', i); // 移动后取消选中 selectedHandCard = null; renderHand(); updateActionButtons(); return; } } console.log('卡池槽已满'); } } function composeDeck() { console.log('尝试合成...'); let composed = false; // 记录是否有成功合成 // 如果用户选中了某张卡,优先尝试合成该卡对应的卡组 if (selectedHandCard !== null && handCards[selectedHandCard]) { const selectedCard = handCards[selectedHandCard]; const key = `${selectedCard.series}|${selectedCard.colorIndex}`; // 找到所有同系列同颜色的卡 const sameGroupCards = handCards.filter(c => c !== null && `${c.series}|${c.colorIndex}` === key ); // 检查编号1-5是否齐全 const numbers = sameGroupCards.map(c => c.number).sort(); const hasAll = [1,2,3,4,5].every(n => numbers.includes(n)); if (hasAll && sameGroupCards.length >= 5) { composed = true; // 合成用户选中的卡组 const card = sameGroupCards[0]; const color = COLORS[card.colorIndex]; // 检查是否已有相同卡组 const existingIndex = completedDecks.findIndex(d => d.colorIndex === card.colorIndex && d.seriesName === card.seriesName && d.cardName === card.cardName ); if (existingIndex >= 0) { completedDecks[existingIndex].count++; } else { completedDecks.push({ color: color.name.replace('卡', ''), colorIndex: card.colorIndex, seriesName: card.seriesName, cardName: card.cardName, count: 1 }); } // 移除这5张卡 sameGroupCards.slice(0, 5).forEach(c => { const idx = handCards.findIndex(hc => hc && hc.id === c.id); if (idx !== -1) handCards[idx] = null; }); const exp = COLORS[card.colorIndex].exp * 5; addExp(exp); console.log(`合成成功: ${card.seriesName} ${card.cardName} ${color.name}, 经验 +${exp}`); selectedHandCard = null; renderHand(); renderDecks(); updateActionButtons(); } } // 如果用户没有选中卡牌,或选中的卡不能合成,则自动找第一个可合成的卡组 // 统计手牌:按 系列+名称+颜色 分组 const groups = {}; const cards = handCards.filter(c => c !== null); cards.forEach(card => { // key: series + name + color const key = `${card.series}|${card.colorIndex}`; if (!groups[key]) groups[key] = []; groups[key].push(card); }); // 检查是否有5张编号齐全的同组卡 for (const [key, cardList] of Object.entries(groups)) { // 检查编号1-5是否齐全 const numbers = cardList.map(c => c.number).sort(); const hasAll = [1,2,3,4,5].every(n => numbers.includes(n)); if (hasAll && cardList.length >= 5) { composed = true; // 合成! const card = cardList[0]; const color = COLORS[card.colorIndex]; // 检查是否已有相同卡组 const existingIndex = completedDecks.findIndex(d => d.colorIndex === card.colorIndex && d.seriesName === card.seriesName && d.cardName === card.cardName ); if (existingIndex >= 0) { // 已有这种卡组,套数+1 completedDecks[existingIndex].count++; } else { // 新建卡组 completedDecks.push({ color: color.name.replace('卡', ''), colorIndex: card.colorIndex, seriesName: card.seriesName, cardName: card.cardName, count: 1 }); } // 移除这5张卡 cardList.slice(0, 5).forEach(c => { const idx = handCards.findIndex(hc => hc && hc.id === c.id); if (idx !== -1) handCards[idx] = null; }); // 获得该颜色的完整经验 const exp = COLORS[card.colorIndex].exp * 5; addExp(exp); console.log(`合成成功: ${card.seriesName} ${card.cardName} ${color.name}, 经验 +${exp}`); } } // 播放音效 if (composed) { playComposeSuccessSound(); } else { // 如果有选中卡但不能合成,播放失败音效 if (selectedHandCard !== null) { playComposeFailSound(); } } renderHand(); if (composed) syncAllDataToServer(); renderDecks(); if (composed) syncAllDataToServer(); } function renderDecks() { const grid = document.getElementById('deckGrid'); if (completedDecks.length === 0) { grid.innerHTML = '
集齐5张同系列卡牌自动合成
'; return; } // 按颜色分组 const colorGroups = {}; completedDecks.forEach(deck => { if (!colorGroups[deck.colorIndex]) { colorGroups[deck.colorIndex] = []; } colorGroups[deck.colorIndex].push(deck); }); // 按颜色等级从高到低排序(红6 -> 白0) const sortedColors = Object.keys(colorGroups).sort((a, b) => b - a); let html = `
`; sortedColors.forEach(colorIndex => { const decks = colorGroups[colorIndex]; const colorName = decks[0].color; // 每行标题(颜色) html += `
`; html += `${colorName}`; html += `
`; // 每行最多6个,超过自动换行 let rowHtml = ''; decks.forEach((deck, idx) => { // 每6个换一行 if (idx > 0 && idx % 6 === 0) { html += `
${rowHtml}
`; rowHtml = ''; } const ds = getDeckColorClass(deck.colorIndex); // 最小单元:卡片 + 下方文字 rowHtml += `
`; // 卡组卡片(比卡牌大50%) rowHtml += `
`; rowHtml += `
${deck.seriesName}
`; rowHtml += `
${deck.cardName}
`; rowHtml += `
`; // 下方显示套数 rowHtml += `
已合成 ${deck.count} 套
`; rowHtml += `
`; }); // 输出最后一行 if (rowHtml) { html += `
${rowHtml}
`; } }); html += `
`; grid.innerHTML = html; } function getDeckColorClass(colorIndex) { const colors = [ 'background: linear-gradient(145deg, #ffffff, #e0e0e0); border: 2px solid #ccc;', 'background: linear-gradient(145deg, #7bed9f, #2ed573); border: 2px solid #26de81;', 'background: linear-gradient(145deg, #70a1ff, #1e90ff); border: 2px solid #1e90ff;', 'background: linear-gradient(145deg, #a55eea, #8854d0); border: 2px solid #8854d0; color: #fff;', 'background: linear-gradient(145deg, #ffa502, #ff7f50); border: 2px solid #ff6348; color: #fff;', 'background: linear-gradient(145deg, #2f3542, #1e272e); border: 2px solid #000; color: #fff;', 'background: linear-gradient(145deg, #ff4757, #c0392b); border: 2px solid #e74c3c; color: #fff;' ]; return colors[colorIndex] || colors[0]; } function addExp(exp) { const oldLevel = playerLevel; currentExp += exp; while (playerLevel < 40 && currentExp >= levelUpExp[playerLevel]) { playerLevel++; console.log(`升级到 ${playerLevel} 级!`); onLevelUp(); // 升级奖励金币和宝石 const goldReward = 50; const gemReward = 5; gold += goldReward; gems += gemReward; console.log(`奖励: +${goldReward}金币, +${gemReward}宝石`); } // 如果升级了,播放升级音效 if (playerLevel > oldLevel) { playLevelUpSound(); } updatePlayerInfo(); updateCurrencyDisplay(); } function updateCurrencyDisplay() { document.getElementById('goldAmount').textContent = gold; document.getElementById('gemAmount').textContent = gems; // 同时更新刷新花费显示 updateRefreshCostDisplay(); } // 升级时解锁卡槽 function getRewardTypeForLevel(level) { // V0.002 升级奖励顺序 if (level <= 16) { const cycle = (level - 1) % 4; if (cycle === 0) return 'hand'; if (cycle === 1) return 'pool'; if (cycle === 2) return 'hand'; if (cycle === 3) return 'vault'; } if (level === 17) return 'hand'; if (level === 18) return 'hand'; if (level === 19) return 'pool'; if (level === 20) return 'hand'; if (level === 21) return 'hand'; if (level === 22) return 'vault'; if (level === 23) return 'hand'; if (level === 24) return 'hand'; if (level === 25) return 'pool'; if (level === 26) return 'hand'; if (level === 27) return 'hand'; if (level === 28) return 'vault'; if (level === 29) return 'hand'; if (level === 30) return 'hand'; if (level === 31) return 'pool'; if (level === 32) return 'hand'; if (level === 33) return 'hand'; if (level === 34) return 'vault'; if (level === 35) return 'hand'; if (level === 36) return 'hand'; if (level === 37) return 'pool'; if (level === 38) return 'hand'; if (level === 39) return 'hand'; return null; } function onLevelUp() { // 升级时更新免费刷新次数 if (freeRefreshCooldown <= 0) { freeRefreshRemaining = getFreeRefreshMax(); updateFreeRefreshButton(); } // V0.002 升级解锁逻辑 const level = playerLevel; let extraHandSlots = 0; let extraPoolSlots = 0; let extraVaultSlots = 0; for (let i = 2; i <= level; i++) { const rewardType = getRewardTypeForLevel(i - 1); if (rewardType === 'hand') extraHandSlots++; else if (rewardType === 'pool') extraPoolSlots++; else if (rewardType === 'vault') extraVaultSlots++; } // 限制最大 extraPoolSlots = Math.min(extraPoolSlots, 8); extraHandSlots = Math.min(extraHandSlots, 24); extraVaultSlots = Math.min(extraVaultSlots, 7); unlockedPoolSlots = 8 + extraPoolSlots; unlockedHandSlots = 8 + extraHandSlots; unlockedVaultSlots = 2 + extraVaultSlots; // 保险箱显示逻辑:每页8个,显示到有解锁的下一行 // 至少显示8个,最多显示17个 let vaultRowsToShow = 2; // 默认显示2行 if (unlockedVaultSlots > 8) { vaultRowsToShow = Math.ceil(unlockedVaultSlots / 8) + 1; // 显示到下一行 if (vaultRowsToShow > 3) vaultRowsToShow = 3; // 最多3行 } console.log(`升级到 ${level} 级: 卡池${unlockedPoolSlots}, 手牌${unlockedHandSlots}, 保险箱${unlockedVaultSlots}`); renderPool(); renderHand(); // 如果在保险箱页面,刷新显示 if (currentPage === 'vault') { renderVault(); } } function updatePlayerInfo() { const levelBadge = document.querySelector('.level-badge'); const expFill = document.querySelector('.exp-fill'); const expText = document.querySelector('.player-stats .exp-bar + div'); levelBadge.textContent = playerLevel; const nextExp = levelUpExp[Math.min(playerLevel, 40)] || 400000; const prevExp = levelUpExp[playerLevel - 1] || 0; const progress = ((currentExp - prevExp) / (nextExp - prevExp)) * 100; expFill.style.width = `${Math.min(100, progress)}%`; expText.textContent = `经验: ${currentExp} / ${nextExp}`; } // 页面切换 let currentPage = 'draw'; function showPage(pageName) { currentPage = pageName; // 获取各区域 const poolSection = document.getElementById('poolSection'); const handSection = document.getElementById('handSection'); const vaultSection = document.getElementById('vaultSection'); const decksSection = document.getElementById('decksSection'); const mailSection = document.getElementById('mailSection'); const auctionSection = document.getElementById('auctionSection'); const ladderSection = document.getElementById('ladderSection'); const playerDecksSection = document.getElementById('playerDecksSection'); // 隐藏所有区域 if (poolSection) poolSection.style.display = 'none'; if (handSection) handSection.style.display = 'none'; if (vaultSection) vaultSection.style.display = 'none'; if (decksSection) decksSection.style.display = 'none'; if (mailSection) mailSection.style.display = 'none'; if (auctionSection) auctionSection.style.display = 'none'; if (ladderSection) ladderSection.style.display = 'none'; if (playerDecksSection) playerDecksSection.style.display = 'none'; // 显示选中页面 if (pageName === 'draw') { if (poolSection) poolSection.style.display = 'block'; if (handSection) handSection.style.display = 'block'; } else if (pageName === 'vault') { if (vaultSection) { vaultSection.style.display = 'block'; renderVault(); } } else if (pageName === 'decks') { if (decksSection) decksSection.style.display = 'block'; } else if (pageName === 'auction') { if (auctionSection) { auctionSection.style.display = 'block'; renderAuctionList(); renderAuctionInventory(); } } else if (pageName === 'mail') { if (mailSection) { mailSection.style.display = 'block'; renderMailList(); } } else if (pageName === 'ladder') { if (ladderSection) { ladderSection.style.display = 'block'; renderLadderTable(ladderData); // 重新渲染天梯榜 } } else if (pageName === 'playerDecks') { if (playerDecksSection) { playerDecksSection.style.display = 'block'; } } // 更新导航按钮状态 document.querySelectorAll('.nav-btn').forEach(btn => { btn.classList.remove('active'); if (btn.textContent.includes(getPageName(pageName))) { btn.classList.add('active'); } }); } function getPageName(name) { if (name === 'draw') return '抽牌'; if (name === 'vault') return '保险箱'; if (name === 'decks') return '卡组'; if (name === 'auction') return '拍卖行'; if (name === 'mail') return '邮箱'; if (name === 'ladder') return '天梯'; return ''; } // 页面加载时检查登录状态 // 如果已登录,直接初始化游戏 // 如果未登录,显示登录界面 if (!checkLoginStatus()) { console.log('未登录,显示登录界面'); // 保持登录界面显示,不初始化游戏 } // 全局点击音效 - 为所有按钮添加音效 document.addEventListener('click', function(e) { // 忽略登录/注册按钮(首次点击会初始化音频) if (e.target.closest('.login-btn')) { initAudio(); // 初始化音频上下文 playClickSound(); return; } // 其他按钮播放音效 if (e.target.tagName === 'BUTTON' || e.target.closest('button')) { const btn = e.target.tagName === 'BUTTON' ? e.target : e.target.closest('button'); // 忽略特定的按钮 const ignoreClasses = ['nav-btn']; const hasIgnoreClass = ignoreClasses.some(cls => btn.classList.contains(cls)); if (!hasIgnoreClass) { playClickSound(); } } });