📦 附件
`;
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();
}
}
});