🎯 区块链游戏提示

本游戏运行在区块链上,每次操作(加入房间、下注、发牌等)都需要通过钱包确认交易。这是区块链的安全机制,确保您的资产安全。

const faceValue = (cardIndex % 13) + 1; if (faceValue === 11) cardValue = 'J'; else if (faceValue === 12) cardValue = 'Q'; else if (faceValue === 13 || faceValue === 0) cardValue = 'K'; else cardValue = '10'; // 以防万一 } else { cardValue = score.toString(); } // 使用原始卡牌索引的花色 cardSuit = getCardSuit(playerCardScore.card); const cardColor = (cardSuit === '♥' || cardSuit === '♦') ? 'text-danger' : 'text-dark'; seatHtml += `
${cardValue}${cardSuit}
点数: ${playerCardScore.score}
`; } // 添加统计信息 - 更紧凑的布局 seatHtml += `
总局数: ${playerStatus.totalGames} | 胜/负: ${playerStatus.totalWins}/${playerStatus.totalLosses}
净收益: ${formatTokenAmount(playerStatus.totalEarnings)}
`; seat.innerHTML = seatHtml; seatContainer.appendChild(seat); // 如果有牌,添加开牌动画 if (hasCard) { setTimeout(() => { const card = document.getElementById(`card-${playerAddr}`); if (card) { card.classList.add('card-animation'); // 移除动画类,以便下次可以再次触发 setTimeout(() => { card.classList.remove('card-animation'); }, 1500); } }, 100 * i); // 错开每个玩家的动画时间 } // 同时也添加到传统的玩家列表中 const col = document.createElement('div'); col.className = 'col-md-4 mb-3'; col.innerHTML = seatHtml; row.appendChild(col); } catch (error) { console.error(`获取玩家 ${playerAddr} 信息失败:`, error); } } playersContainer.appendChild(row); } // 下注 async function placeBet() { if (!currentAccount || currentRoomId === 0) { alert('请先加入房间'); return; } const betAmount = document.getElementById('betAmount').value; // 获取房间的最小下注额度 let roomMinBet = 10; // 默认值 try { // 尝试调用合约方法获取房间最小下注额度 let roomSpecificMinBet = await contract.methods.getRoomMinBetAmount(currentRoomId).call(); console.log(`当前房间 ${currentRoomId} 合约返回最小下注额度(wei):`, roomSpecificMinBet); if (roomSpecificMinBet && roomSpecificMinBet !== '0') { // 合约返回的是wei单位,转换为ether const minBetEther = web3.utils.fromWei(roomSpecificMinBet.toString(), 'ether'); const minBetValue = parseFloat(minBetEther); if (!isNaN(minBetValue) && minBetValue > 0) { roomMinBet = minBetValue; console.log(`当前房间 ${currentRoomId} 使用合约最小下注额度(ether):`, roomMinBet); } } else { // 获取全局默认值 const globalMinBet = await contract.methods.minBetAmount().call(); console.log(`当前房间 ${currentRoomId} 全局最小下注额度(wei):`, globalMinBet); if (globalMinBet && globalMinBet !== '0') { const globalEther = web3.utils.fromWei(globalMinBet.toString(), 'ether'); const globalValue = parseFloat(globalEther); if (!isNaN(globalValue) && globalValue > 0) { roomMinBet = globalValue; console.log(`当前房间 ${currentRoomId} 使用全局最小下注额度(ether):`, roomMinBet); } } } } catch (error) { console.error('获取房间最小下注额度失败:', error); roomMinBet = 10; } if (!betAmount || parseFloat(betAmount) < roomMinBet) { alert(`下注金额不能少于房间最小下注额度 ${roomMinBet} 代币`); return; } showLoading(true); try { // 检查代币余额 const balance=await tokenContract.balanceOf(currentAccount); const betAmountWei=ethers.utils.parseUnits(betAmount, tokenDecimals); if (balance.lt(betAmountWei)) { alert('代币余额不足'); showLoading(false); return; } // 检查授权 const allowance=await tokenContract.allowance(currentAccount, contractAddress); console.log("当前授权额度:", ethers.utils.formatUnits(allowance, tokenDecimals)); console.log("需要授权额度:", ethers.utils.formatUnits(betAmountWei, tokenDecimals)); if (allowance.lt(betAmountWei)) { // 显示授权提示 const confirmMessage=document.createElement('div'); confirmMessage.className='alert alert-warning position-fixed top-0 start-50 translate-middle-x mt-3' ; confirmMessage.style.zIndex="9999" ; confirmMessage.innerHTML=` 需要授权代币

游戏需要您授权合约使用您的代币。这是一次性操作,授权后可以多次下注。

`; document.body.appendChild(confirmMessage); try { console.log("开始授权..."); const approveTx = await tokenContract.approve(contractAddress, ethers.constants.MaxUint256); console.log("授权交易已提交:", approveTx.hash); // 等待交易确认 const receipt = await approveTx.wait(); console.log("授权交易已确认:", receipt); // 移除提示 document.body.removeChild(confirmMessage); // 显示成功消息 const successMessage = document.createElement('div'); successMessage.className = 'alert alert-success position-fixed top-0 start-50 translate-middle-x mt-3'; successMessage.style.zIndex = "9999"; successMessage.innerHTML = `授权成功! 现在您可以下注了。`; document.body.appendChild(successMessage); // 3秒后自动移除成功消息 setTimeout(() => { document.body.removeChild(successMessage); }, 3000); // 重新检查授权 const newAllowance = await tokenContract.allowance(currentAccount, contractAddress); console.log("新授权额度:", ethers.utils.formatUnits(newAllowance, tokenDecimals)); if (newAllowance.lt(betAmountWei)) { alert('授权失败,请重试'); showLoading(false); return; } } catch (error) { // 移除提示 document.body.removeChild(confirmMessage); console.error("授权失败:", error); alert('授权失败: ' + (error.message || JSON.stringify(error))); showLoading(false); return; } } // 获取当前gas价格 const gasPrice = await web3.eth.getGasPrice(); console.log("当前gas价格:", gasPrice); // 下注交易 try { const gasEstimate = await contract.methods.placeBet(currentRoomId, betAmountWei.toString()).estimateGas({ from: currentAccount }); console.log("估算gas用量:", gasEstimate); // 使用更高的gas限制 const tx = await contract.methods.placeBet(currentRoomId, betAmountWei.toString()).send({ from: currentAccount, gas: Math.floor(gasEstimate * 1.2), // 增加20%的gas限制 gasPrice: web3.utils.toHex(Math.floor(parseInt(gasPrice) * 1.1)) // 增加10%的gas价格 }); console.log("下注交易成功:", tx); playSound('betSound', 0.4); // 显示成功消息而不是alert const successMessage = document.createElement('div'); successMessage.className = 'alert alert-success position-fixed top-50 start-50 translate-middle'; successMessage.style.zIndex = "10000"; successMessage.style.transform = "translate(-50%, -50%) scale(0)"; successMessage.style.transition = "all 0.3s ease"; successMessage.innerHTML = `
下注成功!
`; document.body.appendChild(successMessage); // 动画显示 setTimeout(() => { successMessage.style.transform = "translate(-50%, -50%) scale(1)"; }, 10); // 3秒后移除 setTimeout(() => { successMessage.style.transform = "translate(-50%, -50%) scale(0)"; setTimeout(() => { if (document.body.contains(successMessage)) { document.body.removeChild(successMessage); } }, 300); }, 3000); // 强制刷新游戏信息 await forceRefreshGameInfo(); // 延迟再次刷新确保所有数据同步 setTimeout(async () => { await forceRefreshGameInfo(); }, 2000); } catch (error) { console.error("下注交易失败:", error); if (error.code === 4001) { alert('您拒绝了交易'); } else { alert('下注失败: ' + (error.message || JSON.stringify(error))); } } } catch (error) { console.error("下注过程中出错:", error); alert('下注失败: ' + (error.message || JSON.stringify(error))); } showLoading(false); } // 发牌/结算函数 async function distributeHands() { if (!currentAccount || !contract || !currentRoomId) { alert('请先连接钱包并进入房间'); return; } try { showLoading(true); console.log("开始发牌,房间ID:", currentRoomId); // 获取当前gas价格 const gasPrice = await web3.eth.getGasPrice(); console.log("当前gas价格:", gasPrice); // 估算gas用量 const gasEstimate = await contract.methods.distributeHandsAndSettle(currentRoomId).estimateGas({ from: currentAccount }); console.log("估算gas用量:", gasEstimate); // 发牌交易 const tx = await contract.methods.distributeHandsAndSettle(currentRoomId).send({ from: currentAccount, gas: Math.floor(gasEstimate * 1.2), // 增加20%的gas限制 gasPrice: web3.utils.toHex(Math.floor(parseInt(gasPrice) * 1.1)) // 增加10%的gas价格 }); console.log("发牌交易成功:", tx); playSound('dealSound', 0.6); // 显示成功消息 const successMessage = document.createElement('div'); successMessage.className = 'alert alert-success position-fixed top-50 start-50 translate-middle'; successMessage.style.zIndex = "10000"; successMessage.style.transform = "translate(-50%, -50%) scale(0)"; successMessage.style.transition = "all 0.3s ease"; successMessage.innerHTML = `
发牌结算成功!
`; document.body.appendChild(successMessage); // 动画显示 setTimeout(() => { successMessage.style.transform = "translate(-50%, -50%) scale(1)"; }, 10); // 3秒后移除 setTimeout(() => { successMessage.style.transform = "translate(-50%, -50%) scale(0)"; setTimeout(() => { if (document.body.contains(successMessage)) { document.body.removeChild(successMessage); } }, 300); }, 3000); // 强制刷新游戏信息 await forceRefreshGameInfo(); // 延迟再次刷新确保历史记录和所有数据同步 setTimeout(async () => { await forceRefreshGameInfo(); }, 3000); } catch (error) { console.error("发牌交易失败:", error); if (error.code === 4001) { alert('您拒绝了交易'); } else { alert('发牌失败: ' + (error.message || JSON.stringify(error))); } } finally { showLoading(false); } } // 检查所有玩家是否已下注 - 改进版 async function checkAllPlayersBet() { try { const players = await contract.methods.getRoomPlayers(currentRoomId).call(); if (players.length === 0) { console.log("房间中没有玩家"); return false; } console.log(`检查 ${players.length} 名玩家的下注状态`); for (const playerAddr of players) { try { const playerStatus = await contract.methods.getPlayerStatus(currentRoomId, playerAddr).call(); console.log(`玩家 ${formatAddress(playerAddr)} 下注状态: ${playerStatus.hasBet ? '已下注' : '未下注'}`); if (!playerStatus.hasBet) { return false; } } catch (error) { console.error(`获取玩家 ${playerAddr} 状态失败:`, error); return false; } } console.log("所有玩家都已下注"); return true; } catch (error) { console.error("检查玩家下注状态失败:", error); return false; } } // 获取房间奖池总额 - 修复超过1000显示为0的问题 async function getTotalPot(roomId) { try { let totalPot = 0; const players = await contract.methods.getRoomPlayers(roomId).call(); for (const playerAddr of players) { const playerStatus = await contract.methods.getPlayerStatus(roomId, playerAddr).call(); if (playerStatus.hasBet) { // 正确处理wei单位转换为ether const betAmountWei = playerStatus.bet.toString(); const betAmountEther = web3.utils.fromWei(betAmountWei, 'ether'); const betAmount = parseFloat(betAmountEther); if (!isNaN(betAmount)) { totalPot += betAmount; } } } console.log('计算得到的奖池总额(ether):', totalPot); return totalPot; } catch (error) { console.error("获取奖池总额失败:", error); return 0; } } // 续期房间 async function renewRoom() { if (!currentAccount || !isAdmin || currentRoomId === 0) { alert('您不是房间管理员'); return; } // 添加详细日志 console.log("续期房间 - 当前账户:", currentAccount.toLowerCase()); console.log("续期房间 - 房间ID:", currentRoomId); console.log("续期房间 - 是否管理员:", isAdmin); showLoading(true); try { // 获取当前gas价格 const gasPrice = await web3.eth.getGasPrice(); console.log("当前gas价格:", gasPrice); // 估算gas用量 const gasEstimate = await contract.methods.renewRoom(currentRoomId).estimateGas({ from: currentAccount }); console.log("估算gas用量:", gasEstimate); // 使用更高的gas限制 const result = await contract.methods.renewRoom(currentRoomId).send({ from: currentAccount, gas: Math.floor(gasEstimate * 1.5), // 增加50%的gas限制 gasPrice: web3.utils.toHex(Math.floor(parseInt(gasPrice) * 1.2)) // 增加20%的gas价格 }); console.log("续期房间交易结果:", result); alert('房间续期成功'); await loadGameInfo(); } catch (error) { console.error("续期房间失败 - 详细错误:", error); // 尝试提取更深层的错误信息 const errorMsg = error.message || JSON.stringify(error); const reason = errorMsg.includes("reason:") ? errorMsg.split("reason:")[1].trim() : errorMsg; alert('续期房间失败: ' + reason); } showLoading(false); } // 关闭房间 async function closeRoom() { if (!currentAccount || !isAdmin || currentRoomId === 0) { alert('您不是房间管理员'); return; } if (!confirm('确定要关闭房间吗?此操作不可逆。')) { return; } // 添加详细日志 console.log("关闭房间 - 当前账户:", currentAccount.toLowerCase()); console.log("关闭房间 - 房间ID:", currentRoomId); console.log("关闭房间 - 是否管理员:", isAdmin); showLoading(true); try { // 获取当前gas价格 const gasPrice = await web3.eth.getGasPrice(); console.log("当前gas价格:", gasPrice); // 估算gas用量 const gasEstimate = await contract.methods.closeRoom(currentRoomId).estimateGas({ from: currentAccount }); console.log("估算gas用量:", gasEstimate); // 使用更高的gas限制 const result = await contract.methods.closeRoom(currentRoomId).send({ from: currentAccount, gas: Math.floor(gasEstimate * 1.5), // 增加50%的gas限制 gasPrice: web3.utils.toHex(Math.floor(parseInt(gasPrice) * 1.2)) // 增加20%的gas价格 }); console.log("关闭房间交易结果:", result); alert('房间已关闭'); currentRoomId = 0; await checkPlayerRoom(); showRoomsPage(); } catch (error) { console.error("关闭房间失败 - 详细错误:", error); // 尝试提取更深层的错误信息 const errorMsg = error.message || JSON.stringify(error); const reason = errorMsg.includes("reason:") ? errorMsg.split("reason:")[1].trim() : errorMsg; alert('关闭房间失败: ' + reason); } showLoading(false); } // 转移管理权 async function transferAdmin() { if (!currentAccount || !isAdmin || currentRoomId === 0) { alert('您不是房间管理员'); return; } const newAdmin = document.getElementById('newAdminAddress').value; if (!newAdmin || !ethers.utils.isAddress(newAdmin)) { alert('请输入有效的以太坊钱包地址'); return; } // 添加详细日志 console.log("转移管理权 - 当前账户:", currentAccount.toLowerCase()); console.log("转移管理权 - 房间ID:", currentRoomId); console.log("转移管理权 - 是否管理员:", isAdmin); console.log("转移管理权 - 新管理员:", newAdmin.toLowerCase()); showLoading(true); try { // 获取当前gas价格 const gasPrice = await web3.eth.getGasPrice(); console.log("当前gas价格:", gasPrice); // 估算gas用量 const gasEstimate = await contract.methods.transferRoomAdmin(currentRoomId, newAdmin).estimateGas({ from: currentAccount }); console.log("估算gas用量:", gasEstimate); // 使用更高的gas限制 const result = await contract.methods.transferRoomAdmin(currentRoomId, newAdmin).send({ from: currentAccount, gas: Math.floor(gasEstimate * 1.5), // 增加50%的gas限制 gasPrice: web3.utils.toHex(Math.floor(parseInt(gasPrice) * 1.2)) // 增加20%的gas价格 }); console.log("转移管理权交易结果:", result); alert('管理权转移成功'); isAdmin = false; await loadGameInfo(); } catch (error) { console.error("转移管理权失败 - 详细错误:", error); // 尝试提取更深层的错误信息 const errorMsg = error.message || JSON.stringify(error); const reason = errorMsg.includes("reason:") ? errorMsg.split("reason:")[1].trim() : errorMsg; alert('转移管理权失败: ' + reason); } showLoading(false); } // 加载房间游戏历史 async function loadRoomGameHistory(roomId) { try { const historyLength = await contract.methods.getRoomGameHistoryLength(roomId).call(); const historyList = document.getElementById('roomHistoryList'); if (historyLength == 0) { historyList.innerHTML = '
  • 暂无游戏记录
  • '; return; } // 记录当前历史记录数量,用于检测新记录 const previousHistoryLength = historyList.dataset.historyLength || 0; historyList.dataset.historyLength = historyLength; historyList.innerHTML = ''; // 最多显示5条历史记录 const maxDisplay = Math.min(historyLength, 5); let isFirstItem = true; // 标记是否为最新的记录 for (let i = historyLength - 1; i >= historyLength - maxDisplay; i--) { try { const history = await contract.methods.getRoomGameHistory(roomId, i).call(); const date = new Date(history.timestamp * 1000); const item = document.createElement('li'); item.className = 'list-group-item history-item'; item.dataset.gameId = history.gameId; item.dataset.players = JSON.stringify(history.players); item.dataset.scores = JSON.stringify(history.scores); item.dataset.winner = history.winner; item.dataset.timestamp = history.timestamp; // 为最新记录添加特殊样式和展开的详情内容 const isLatest = isFirstItem; const expandedClass = isLatest ? 'expanded' : 'collapsed'; const expandedIcon = isLatest ? '▲' : '▼'; item.innerHTML = `
    游戏 #${history.gameId}
    ${date.toLocaleString()}
    ${isLatest ? '最新' : ''}
    赢家: ${formatAddress(history.winner)} ${expandedIcon}
    游戏流程:
    参与玩家:
      ${history.players.map((player, index) => `
    • ${formatAddress(player)} - 点数: ${history.scores[index]} ${player.toLowerCase() === history.winner.toLowerCase() ? ' 🏆' : ''}
    • ` ).join('')}
    游戏信息:
    游戏ID: ${history.gameId}
    时间: ${date.toLocaleString()}
    玩家数: ${history.players.length}
    最高点数: ${Math.max(...history.scores.map(s => parseInt(s)))}
    `; historyList.appendChild(item); isFirstItem = false; } catch (error) { console.error(`获取历史记录 ${i} 失败:`, error); } } // 如果有新的游戏记录生成,自动打开最新记录的完整结果 if (historyLength > previousHistoryLength && previousHistoryLength > 0) { console.log('检测到新的游戏记录,自动打开最新记录的完整结果'); // 获取最新记录的数据 const latestIndex = historyLength - 1; try { const latestHistory = await contract.methods.getRoomGameHistory(roomId, latestIndex).call(); const latestData = { gameId: latestHistory.gameId, players: JSON.stringify(latestHistory.players), scores: JSON.stringify(latestHistory.scores), winner: latestHistory.winner, timestamp: latestHistory.timestamp }; // 延迟一下再显示,让用户看到新记录已添加 setTimeout(() => { // 显示新记录提示 showNewGameNotification(); // 再延迟一下自动打开完整结果 setTimeout(() => { showGameResult(latestData); }, 1500); }, 500); } catch (error) { console.error('获取最新游戏记录失败:', error); // 如果获取失败,至少显示通知 showNewGameNotification(); } } } catch (error) { console.error("加载历史记录失败:", error); } } // 加载指定房间的历史记录 async function loadRoomHistory() { const roomId = document.getElementById('historyRoomId').value; if (!roomId || roomId <= 0) { alert('请输入有效的房间ID'); return; } showLoading(true); try { const historyLength=await contract.methods.getRoomGameHistoryLength(roomId).call(); const historyTableBody=document.getElementById('historyTableBody'); if (historyLength==0) { historyTableBody.innerHTML='该房间暂无游戏记录' ; showLoading(false); return; } historyTableBody.innerHTML='' ; for (let i=0; i < historyLength; i++) { try { const history=await contract.methods.getRoomGameHistory(roomId, i).call(); const date=new Date(history.timestamp * 1000); const row=document.createElement('tr'); row.innerHTML=` ${history.gameId} ${history.players.length} ${Math.max(...history.scores.map(s => parseInt(s)))} ${formatAddress(history.winner)} ${date.toLocaleString()} `; row.querySelector('.view-history-btn').addEventListener('click', () => { showGameResult({ gameId: history.gameId, players: JSON.stringify(history.players), scores: JSON.stringify(history.scores), winner: history.winner, timestamp: history.timestamp }); }); historyTableBody.appendChild(row); } catch (error) { console.error(`获取历史记录 ${i} 失败:`, error); } } } catch (error) { console.error("加载历史记录失败:", error); alert('加载历史记录失败: ' + error.message); } showLoading(false); } // 切换历史记录详情显示/隐藏 function toggleHistoryDetails(headerElement) { const historyItem = headerElement.closest('.history-item'); const detailsElement = historyItem.querySelector('.history-details'); const expandIcon = headerElement.querySelector('.expand-icon'); if (detailsElement.classList.contains('collapsed')) { // 展开 detailsElement.classList.remove('collapsed'); detailsElement.classList.add('expanded'); expandIcon.textContent = '▲'; } else { // 折叠 detailsElement.classList.remove('expanded'); detailsElement.classList.add('collapsed'); expandIcon.textContent = '▼'; } } // 显示新游戏记录通知 function showNewGameNotification() { const notification = document.createElement('div'); notification.className = 'position-fixed top-50 start-50 translate-middle'; notification.style.cssText = ` z-index: 10001; background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white; padding: 15px 25px; border-radius: 10px; font-family: 'Cinzel', serif; font-weight: 600; text-align: center; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); transform: translate(-50%, -50%) scale(0); transition: all 0.3s ease; box-shadow: 0 10px 30px rgba(40, 167, 69, 0.3); `; notification.innerHTML = `
    🎉 新游戏结果
    最新一局游戏已完成,即将自动打开完整结果
    `; document.body.appendChild(notification); // 动画显示 setTimeout(() => { notification.style.transform = 'translate(-50%, -50%) scale(1)'; }, 10); // 3秒后移除 setTimeout(() => { notification.style.transform = 'translate(-50%, -50%) scale(0)'; setTimeout(() => { if (document.body.contains(notification)) { document.body.removeChild(notification); } }, 300); }, 3000); } // 显示游戏结果 function showGameResult(data) { const gameId = data.gameId; const players = JSON.parse(data.players); const scores = JSON.parse(data.scores); const winner = data.winner; const timestamp = new Date(data.timestamp * 1000).toLocaleString(); const resultContent = document.getElementById('gameResultContent'); resultContent.innerHTML = `

    游戏 #${gameId} 结果

    时间: ${timestamp}

    `; for (let i = 0; i < players.length; i++) { const isWinner=players[i].toLowerCase()===winner.toLowerCase(); resultContent.innerHTML +=` `; } resultContent.innerHTML += `
    玩家 点数 结果
    ${formatAddress(players[i])} ${scores[i]} ${isWinner ? '赢家' : '输家'}
    `; const modal = new bootstrap.Modal(document.getElementById('gameResultModal')); modal.show(); // 在模态框显示后添加座位和牌 setTimeout(() => { const resultSeatContainer = document.getElementById('resultSeatContainer'); if (!resultSeatContainer) return; // 最多显示6个座位 const maxSeats = Math.min(players.length, 6); // 根据玩家数量设置座位容器的类名,用于自动调整布局 resultSeatContainer.className = `seat-container players-${maxSeats}`; // 计算座位位置 const positions = calculateSeatPositions(maxSeats); for (let i = 0; i < maxSeats; i++) { const playerAddr=players[i]; const score=parseInt(scores[i]); const isWinner=playerAddr.toLowerCase()===winner.toLowerCase(); // 创建座位元素 const seat=document.createElement('div'); seat.className=`player-seat seat-${i}`; // 设置座位位置 const position=positions[i]; seat.style.left=position.left; seat.style.top=position.top; seat.style.width=position.width; // 计算牌的值和花色 // 根据点数直接确定牌面值 let cardValue, cardSuit; // 游戏中,点数规则:A=1, J/Q/K=10 if (score===1) { cardValue='A' ; } else if (score===10) { // 对于10点,随机选择J/Q/K之一 const faceCards=['J', 'Q' , 'K' ]; cardValue=faceCards[i % 3]; } else { cardValue=score.toString(); } // 根据玩家索引选择花色,确保每个玩家有不同花色 const suits=['♠', '♥' , '♦' , '♣' ]; cardSuit=suits[i % 4]; const cardColor=(cardSuit==='♥' || cardSuit==='♦' ) ? 'text-danger' : 'text-dark' ; const playerClass=isWinner ? 'player-card winner' : 'player-card' ; // 根据玩家地址生成头像颜色 const avatarColor=generateColorFromAddress(playerAddr); const avatarText=formatUserAvatar(playerAddr); // 根据玩家数量设置头像大小类 const avatarSizeClass=`size-${maxSeats}`; seat.innerHTML=`
    ${avatarText}
    ${formatAddress(playerAddr)}
    ${isWinner ? '赢家' : ''}
    `; resultSeatContainer.appendChild(seat); // 添加翻牌动画,错开时间 setTimeout(() => { const card = document.getElementById(`result-card-${playerAddr}-${i}`); const scoreElement = document.getElementById(`result-score-${playerAddr}-${i}`); if (card && scoreElement) { // 播放翻牌音效 playSound('flipSound', 0.3, 0); // 翻牌动画 card.classList.add('flip-animation'); // 动画完成后显示牌面 setTimeout(() => { card.classList.remove('card-back'); card.classList.add(cardColor); card.innerHTML = `${cardValue}${cardSuit}`; scoreElement.textContent = score; // 如果是赢家,添加额外的强调效果 if (isWinner) { const isModernTheme = document.body.classList.contains('modern-theme'); if (isModernTheme) { card.style.boxShadow = '0 0 20px #00d4ff, 0 0 35px #ff00ff, 0 0 50px rgba(0, 255, 255, 0.5)'; card.style.animation = 'modern-winner-pulse 2s infinite'; } else { card.style.boxShadow = '0 0 20px gold, 0 0 35px yellow, 0 0 50px rgba(255, 215, 0, 0.5)'; card.style.animation = 'winner-pulse 2s infinite'; } card.style.transform = 'scale(1.2) rotateY(0deg)'; // 添加赢家光环效果 const halo = document.createElement('div'); const haloColor = isModernTheme ? 'radial-gradient(circle, rgba(0, 255, 255, 0.3) 0%, rgba(255, 0, 255, 0.2) 50%, transparent 70%)' : 'radial-gradient(circle, rgba(255, 215, 0, 0.3) 0%, transparent 70%)'; halo.style.cssText = ` position: absolute; top: -10px; left: -10px; right: -10px; bottom: -10px; border-radius: 15px; background: ${haloColor}; animation: halo-rotate 3s linear infinite; pointer-events: none; z-index: -1; `; card.style.position = 'relative'; card.appendChild(halo); // 播放赢家音效 playSound('winnerSound', 0.8, 200); } }, 500); } }, 400 * i); } // 在所有座位创建完成后初始化折叠状态 setTimeout(() => { initializePlayerSeatsCollapse(); }, 400 * maxSeats + 100); }, 300); } // 根据地址生成颜色 function generateColorFromAddress(address) { // 使用地址的前6个字符作为颜色 const colorCode = address.substring(2, 8); return `#${colorCode}`; } // 生成头像文本 - 显示钱包地址的后两位 function formatUserAvatar(address) { if (!address) return '??'; // 使用地址的后两位字符 const cleanAddress = address.replace('0x', '').toUpperCase(); return cleanAddress.substring(cleanAddress.length - 2); } // 计算座位位置 function calculateSeatPositions(playerCount) { const positions = []; const containerWidth = 100; // 百分比 const containerHeight = 100; // 百分比 // 根据玩家数量计算座位位置 for (let i = 0; i < playerCount; i++) { let left, top, width; if (playerCount===2) { // 2个玩家:左右对称 left=i===0 ? '20%' : '80%' ; top='50%' ; width='25%' ; } else if (playerCount===3) { // 3个玩家:三角形布局 if (i===0) { left='50%' ; top='20%' ; } else if (i===1) { left='20%' ; top='80%' ; } else { left='80%' ; top='80%' ; } width='22%' ; } else if (playerCount===4) { // 4个玩家:四角布局 if (i===0) { left='20%' ; top='20%' ; } else if (i===1) { left='80%' ; top='20%' ; } else if (i===2) { left='80%' ; top='80%' ; } else { left='20%' ; top='80%' ; } width='20%' ; } else if (playerCount===5) { // 5个玩家:五边形布局 const angle=(i * 2 * Math.PI) / 5 - Math.PI / 2; const radius=35; left=(50 + radius * Math.cos(angle)) + '%' ; top=(50 + radius * Math.sin(angle)) + '%' ; width='18%' ; } else if (playerCount===6) { // 6个玩家:六边形布局 const angle=(i * 2 * Math.PI) / 6 - Math.PI / 2; const radius=35; left=(50 + radius * Math.cos(angle)) + '%' ; top=(50 + radius * Math.sin(angle)) + '%' ; width='16%' ; } else { // 默认布局 left='50%' ; top='50%' ; width='20%' ; } positions.push({ left, top, width }); } return positions; } // 生成邀请链接 function generateInviteLink() { if (!currentAccount) { return window.location.origin + window.location.pathname; } return `${window.location.origin}${window.location.pathname}?inviter=${currentAccount}`; } // 更新邀请链接显示 function updateInviteLink() { const inviteLinkInput=document.getElementById('myInviteLink'); if (inviteLinkInput) { if (currentAccount) { inviteLinkInput.value=generateInviteLink(); inviteLinkInput.placeholder='点击复制按钮复制邀请链接' ; } else { inviteLinkInput.value='' ; inviteLinkInput.placeholder='连接钱包后生成邀请链接' ; } } } // 复制邀请链接 function copyInviteLink() { const inviteLinkInput=document.getElementById('myInviteLink'); if (inviteLinkInput && inviteLinkInput.value) { inviteLinkInput.select(); document.execCommand('copy'); // 显示复制成功提示 showInviteNotification('邀请链接已复制到剪贴板!', 'success' ); } else { showInviteNotification('请先连接钱包生成邀请链接', 'warning' ); } } // 分享邀请链接 function shareInviteLink() { if (!currentAccount) { showInviteNotification('请先连接钱包', 'warning' ); return; } const inviteLink=generateInviteLink(); const shareText=`🎮 邀请你来玩大吃小游戏! 💰 区块链技术保证公平透明 🎁 我能获得你输掉金额的2%分红 🚀 立即开始,一起赚钱! 点击链接立即开始: ${inviteLink}`; // 检查是否支持Web Share API(主要是移动端) if (navigator.share) { navigator.share({ title: '大吃小游戏邀请 - 一起来赚钱!' , text: shareText, url: inviteLink }).catch(err=> { console.log('分享失败:', err); fallbackShare(shareText); }); } else { fallbackShare(shareText); } } // 备用分享方法 function fallbackShare(shareText) { // 复制到剪贴板 if (navigator.clipboard) { navigator.clipboard.writeText(shareText).then(() => { showInviteNotification('分享内容已复制到剪贴板!', 'success'); }).catch(() => { showFallbackShareModal(shareText); }); } else { showFallbackShareModal(shareText); } } // 显示备用分享模态框 function showFallbackShareModal(shareText) { const modal = document.createElement('div'); modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); display: flex; justify-content: center; align-items: center; z-index: 10000; backdrop-filter: blur(10px); `; modal.innerHTML = `

    📤 分享邀请

    `; // 点击背景关闭 modal.addEventListener('click', function (e) { if (e.target === modal) { modal.remove(); } }); document.body.appendChild(modal); } // 复制分享文本 function copyShareText(text, button) { const textarea = document.createElement('textarea'); textarea.value = text; document.body.appendChild(textarea); textarea.select(); try { document.execCommand('copy'); button.innerHTML = '✓ 已复制'; button.style.background = 'linear-gradient(135deg, #28a745 0%, #20c997 100%)'; setTimeout(() => { button.innerHTML = '复制内容'; button.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'; }, 2000); } catch (error) { console.error('复制失败:', error); } document.body.removeChild(textarea); } // 初始化重要说明功能 function initImportantNotice() { const noticeCard = document.getElementById('importantNoticeCard'); const noticeCollapse = document.getElementById('importantNotice'); const noticeToggle = document.getElementById('noticeToggle'); const noticeIcon = document.getElementById('noticeIcon'); if (!noticeCard || !noticeCollapse || !noticeToggle || !noticeIcon) { console.error('重要说明元素未找到'); return; } // 检查是否是首次访问 const hasVisited = localStorage.getItem('hasVisitedImportantNotice'); if (!hasVisited) { // 首次访问,10秒后自动关闭 setTimeout(() => { const bsCollapse = new bootstrap.Collapse(noticeCollapse, { toggle: false }); bsCollapse.hide(); // 更新图标 noticeIcon.className = 'bi bi-chevron-right'; // 标记已访问 localStorage.setItem('hasVisitedImportantNotice', 'true'); console.log('重要说明已自动关闭'); }, 10000); // 10秒 } else { // 非首次访问,默认关闭 const bsCollapse = new bootstrap.Collapse(noticeCollapse, { toggle: false }); bsCollapse.hide(); noticeIcon.className = 'bi bi-chevron-right'; } // 监听折叠状态变化 noticeCollapse.addEventListener('show.bs.collapse', function () { noticeIcon.className = 'bi bi-chevron-down'; }); noticeCollapse.addEventListener('hide.bs.collapse', function () { noticeIcon.className = 'bi bi-chevron-right'; }); console.log('重要说明功能已初始化'); } // 显示邀请通知 function showInviteNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = 'position-fixed top-50 start-50 translate-middle'; let bgColor, icon; switch (type) { case 'success': bgColor = 'linear-gradient(135deg, #28a745 0%, #20c997 100%)'; icon = '✓'; break; case 'warning': bgColor = 'linear-gradient(135deg, #ffc107 0%, #fd7e14 100%)'; icon = '⚠'; break; case 'error': bgColor = 'linear-gradient(135deg, #dc3545 0%, #e83e8c 100%)'; icon = '✗'; break; default: bgColor = 'linear-gradient(135deg, #17a2b8 0%, #6f42c1 100%)'; icon = 'ℹ'; } notification.style.cssText = ` z-index: 10001; background: ${bgColor}; color: white; padding: 15px 25px; border-radius: 10px; font-family: 'Cinzel', serif; font-weight: 600; text-align: center; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); transform: translate(-50%, -50%) scale(0); transition: all 0.3s ease; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); max-width: 300px; `; notification.innerHTML = `
    ${icon}
    ${message}
    `; document.body.appendChild(notification); // 动画显示 setTimeout(() => { notification.style.transform = 'translate(-50%, -50%) scale(1)'; }, 10); // 3秒后移除 setTimeout(() => { notification.style.transform = 'translate(-50%, -50%) scale(0)'; setTimeout(() => { if (document.body.contains(notification)) { document.body.removeChild(notification); } }, 300); }, 3000); } // 降级分享方案 function fallbackShare(shareText) { // 尝试使用剪贴板API if (navigator.clipboard) { navigator.clipboard.writeText(shareText).then(() => { showInviteNotification('分享内容已复制到剪贴板!请粘贴发送给好友', 'success'); }).catch(() => { // 再次降级:显示分享内容让用户手动复制 showShareModal(shareText); }); } else { // 最终降级方案 showShareModal(shareText); } } // 显示分享模态框 function showShareModal(shareText) { const modal = document.createElement('div'); modal.className = 'modal fade'; modal.innerHTML = ` `; document.body.appendChild(modal); const bootstrapModal = new bootstrap.Modal(modal); bootstrapModal.show(); // 模态框关闭后移除元素 modal.addEventListener('hidden.bs.modal', () => { document.body.removeChild(modal); }); } // 复制分享文本 function copyShareText() { const shareTextArea = document.getElementById('shareTextArea'); if (shareTextArea) { shareTextArea.select(); document.execCommand('copy'); showInviteNotification('分享内容已复制!', 'success'); } } // 显示邀请相关通知 - 改进版本 function showInviteNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `alert alert-${type} position-fixed`; notification.style.cssText = ` top: 20px; right: 20px; z-index: 10000; max-width: 350px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); border: none; font-weight: 500; border-radius: 8px; animation: slideInRight 0.3s ease-out; `; const icons = { success: 'check-circle-fill', warning: 'exclamation-triangle-fill', error: 'x-circle-fill', info: 'info-circle-fill' }; const titles = { success: '成功', warning: '警告', error: '错误', info: '提示' }; notification.innerHTML = `
    ${titles[type]}
    ${message}
    `; document.body.appendChild(notification); // 自动移除 setTimeout(() => { if (document.body.contains(notification)) { notification.style.opacity = '0'; notification.style.transform = 'translateX(100%)'; setTimeout(() => { if (document.body.contains(notification)) { document.body.removeChild(notification); } }, 300); } }, 5000); } // 加载邀请统计数据 async function loadInviteStats() { try { const countEl = document.getElementById('inviteCount'); const earnEl = document.getElementById('inviteEarnings'); const gamesEl = document.getElementById('inviteGames'); if (!countEl || !earnEl || !gamesEl) return; if (!window.web3 || !window.contract || !currentAccount) { countEl.textContent = '0'; earnEl.textContent = '0'; gamesEl.textContent = '0'; return; } // 获取当前区块范围,避免全链扫描导致卡顿 const latestBlock = await web3.eth.getBlockNumber(); const fromBlock = Math.max(0, latestBlock - 100000); // 最近10万块,按需调整 // 统计邀请人数和参与次数:基于 PlayerJoined 事件,inviter 索引过滤 const joinedEvents = await contract.getPastEvents('PlayerJoined', { filter: { inviter: currentAccount }, fromBlock, toBlock: 'latest' }); const invitedAddresses = new Set(); let inviteGamesCount = 0; joinedEvents.forEach(ev => { if (ev.returnValues && ev.returnValues.player) { invitedAddresses.add(ev.returnValues.player.toLowerCase()); inviteGamesCount += 1; // 每次加入即算一次参与 } }); // 统计分红收益:基于 PlayerSettled 事件中的 inviterFee 汇总(该事件中 inviterFee 为wei) const settledEvents = await contract.getPastEvents('PlayerSettled', { // 无法按inviter索引过滤(未indexed),因此筛选同区块范围内并按日志主题解析或按玩家集合近似统计 fromBlock, toBlock: 'latest' }); let totalInviterFeeWei = web3.utils.toBN('0'); for (const ev of settledEvents) { const values = ev.returnValues || {}; // 近似策略:当结算发生时,如果赢家的邀请者是当前账户,则统计该场邀请者收益 // 需从链上读取赢家的全局邀请者;若ABI提供 getGlobalInviter(address) 则可调用 try { const playerAddr = values.player; if (!playerAddr) continue; const inviterAddr = await contract.methods.getGlobalInviter(playerAddr).call(); if (inviterAddr && inviterAddr.toLowerCase() === currentAccount.toLowerCase()) { const feeWei = web3.utils.toBN(values.inviterFee || '0'); totalInviterFeeWei = totalInviterFeeWei.add(feeWei); } } catch (e) { // 忽略单条失败,继续累积 } } // 如果在最近10万块内没有统计到任何数据,尝试扩大区块范围(回溯50万块)作为回退方案 if (invitedAddresses.size === 0 && inviteGamesCount === 0 && typeof totalInviterFeeWei.isZero === 'function' && totalInviterFeeWei.isZero()) { try { const latestBlock2 = await web3.eth.getBlockNumber(); const fromBlock2 = Math.max(0, latestBlock2 - 500000); const joinedEvents2 = await contract.getPastEvents('PlayerJoined', { filter: { inviter: currentAccount }, fromBlock: fromBlock2, toBlock: 'latest' }); invitedAddresses.clear(); inviteGamesCount = 0; joinedEvents2.forEach(ev => { if (ev.returnValues && ev.returnValues.player) { invitedAddresses.add(ev.returnValues.player.toLowerCase()); inviteGamesCount += 1; } }); const settledEvents2 = await contract.getPastEvents('PlayerSettled', { fromBlock: fromBlock2, toBlock: 'latest' }); totalInviterFeeWei = web3.utils.toBN('0'); for (const ev of settledEvents2) { const values = ev.returnValues || {}; try { const playerAddr = values.player; if (!playerAddr) continue; const inviterAddr = await contract.methods.getGlobalInviter(playerAddr).call(); if (inviterAddr && inviterAddr.toLowerCase() === currentAccount.toLowerCase()) { const feeWei = web3.utils.toBN(values.inviterFee || '0'); totalInviterFeeWei = totalInviterFeeWei.add(feeWei); } } catch (_) { } } } catch (fallbackErr) { console.warn('邀请统计回退查询失败:', fallbackErr); } } const totalInviterFee = parseFloat(ethers.utils.formatUnits(totalInviterFeeWei.toString(), tokenDecimals)); // 更新UI countEl.textContent = String(invitedAddresses.size); earnEl.textContent = totalInviterFee.toFixed(4); gamesEl.textContent = String(inviteGamesCount); } catch (error) { console.error('加载邀请统计失败:', error); } } // 营销合伙人投资功能 async function investAsMarketingPartner() { if (!checkWalletConnection()) { return; } const amount = document.getElementById('marketingInvestAmount').value; if (!amount || amount < 100) { alert('最低投资金额为100代币'); return; } try { showLoading(true); // 使用与下注相同的金额转换方式 const amountWei=ethers.utils.parseUnits(amount.toString(), tokenDecimals); // 检查代币余额 - 使用与下注相同的方式 const balance=await tokenContract.balanceOf(currentAccount); if (balance.lt(amountWei)) { alert('代币余额不足'); showLoading(false); return; } // 检查授权 - 使用与下注相同的方式 const allowance=await tokenContract.allowance(currentAccount, contractAddress); console.log("当前授权额度:", ethers.utils.formatUnits(allowance, tokenDecimals)); console.log("需要授权额度:", ethers.utils.formatUnits(amountWei, tokenDecimals)); if (allowance.lt(amountWei)) { // 显示授权提示 - 使用与下注相同的UI const confirmMessage=document.createElement('div'); confirmMessage.className='alert alert-warning position-fixed top-0 start-50 translate-middle-x mt-3' ; confirmMessage.style.zIndex="9999" ; confirmMessage.innerHTML=` 需要授权代币

    营销合伙人投资需要您授权合约使用您的代币。这是一次性操作,授权后可以多次投资。

    `; document.body.appendChild(confirmMessage); try { console.log("开始授权..."); const approveTx = await tokenContract.approve(contractAddress, ethers.constants.MaxUint256); console.log("授权交易已提交:", approveTx.hash); // 等待交易确认 const receipt = await approveTx.wait(); console.log("授权交易已确认:", receipt); // 移除提示 document.body.removeChild(confirmMessage); // 显示成功消息 const successMessage = document.createElement('div'); successMessage.className = 'alert alert-success position-fixed top-0 start-50 translate-middle-x mt-3'; successMessage.style.zIndex = "9999"; successMessage.innerHTML = `授权成功! 现在您可以投资了。`; document.body.appendChild(successMessage); // 3秒后自动移除成功消息 setTimeout(() => { document.body.removeChild(successMessage); }, 3000); // 重新检查授权 const newAllowance = await tokenContract.allowance(currentAccount, contractAddress); console.log("新授权额度:", ethers.utils.formatUnits(newAllowance, tokenDecimals)); if (newAllowance.lt(amountWei)) { alert('授权失败,请重试'); showLoading(false); return; } } catch (error) { // 移除提示 document.body.removeChild(confirmMessage); console.error("授权失败:", error); alert('授权失败: ' + (error.message || JSON.stringify(error))); showLoading(false); return; } } // 获取当前gas价格 - 使用与下注相同的方式 const gasPrice = await web3.eth.getGasPrice(); console.log("当前gas价格:", gasPrice); // 投资交易 - 使用与下注相同的方式 try { const gasEstimate = await contract.methods.investAsMarketingPartner(amountWei.toString()).estimateGas({ from: currentAccount }); console.log("估算gas用量:", gasEstimate); // 使用更高的gas限制 - 与下注相同 const tx = await contract.methods.investAsMarketingPartner(amountWei.toString()).send({ from: currentAccount, gas: Math.floor(gasEstimate * 1.2), // 增加20%的gas限制 gasPrice: web3.utils.toHex(Math.floor(parseInt(gasPrice) * 1.1)) // 增加10%的gas价格 }); console.log("投资交易成功:", tx); // 显示成功消息 - 使用与下注相同的样式 const successMessage = document.createElement('div'); successMessage.className = 'alert alert-success position-fixed top-50 start-50 translate-middle'; successMessage.style.zIndex = "10000"; successMessage.style.transform = "translate(-50%, -50%) scale(0)"; successMessage.style.transition = "all 0.3s ease"; successMessage.innerHTML = `
    投资成功!
    `; document.body.appendChild(successMessage); // 动画显示 setTimeout(() => { successMessage.style.transform = "translate(-50%, -50%) scale(1)"; }, 10); // 3秒后移除 setTimeout(() => { successMessage.style.transform = "translate(-50%, -50%) scale(0)"; setTimeout(() => { if (document.body.contains(successMessage)) { document.body.removeChild(successMessage); } }, 300); }, 3000); // 刷新营销合伙人数据 await loadMarketingPartnerStats(); // 清空输入框 document.getElementById('marketingInvestAmount').value = ''; } catch (error) { console.error("投资交易失败:", error); if (error.code === 4001) { alert('您拒绝了交易'); } else { alert('投资失败: ' + (error.message || JSON.stringify(error))); } } } catch (error) { console.error('投资过程中出错:', error); alert('投资失败: ' + (error.message || JSON.stringify(error))); } finally { showLoading(false); } } // 超级合伙人投资函数 async function investAsSuperPartner() { if (!checkWalletConnection()) { return; } const amount = document.getElementById('superPartnerAmount').value; if (!amount || amount < 10000) { alert('最低投资金额为10,000代币'); return; } try { showLoading(true); // 使用与下注相同的金额转换方式 const amountWei=ethers.utils.parseUnits(amount.toString(), tokenDecimals); // 检查代币余额 - 使用与下注相同的方式 const balance=await tokenContract.balanceOf(currentAccount); if (balance.lt(amountWei)) { alert('代币余额不足'); showLoading(false); return; } // 检查授权 - 使用与下注相同的方式 const allowance=await tokenContract.allowance(currentAccount, contractAddress); console.log("当前授权额度:", ethers.utils.formatUnits(allowance, tokenDecimals)); console.log("需要授权额度:", ethers.utils.formatUnits(amountWei, tokenDecimals)); if (allowance.lt(amountWei)) { // 显示授权提示 - 使用与下注相同的UI const confirmMessage=document.createElement('div'); confirmMessage.className='alert alert-warning position-fixed top-0 start-50 translate-middle-x mt-3' ; confirmMessage.style.zIndex="9999" ; confirmMessage.innerHTML=` 需要授权代币

    超级合伙人投资需要您授权合约使用您的代币。这是一次性操作,授权后可以多次投资。

    `; document.body.appendChild(confirmMessage); try { console.log("开始授权..."); const approveTx = await tokenContract.approve(contractAddress, ethers.constants.MaxUint256); console.log("授权交易已提交:", approveTx.hash); // 等待交易确认 const receipt = await approveTx.wait(); console.log("授权交易已确认:", receipt); // 移除提示 document.body.removeChild(confirmMessage); // 显示成功消息 const successMessage = document.createElement('div'); successMessage.className = 'alert alert-success position-fixed top-0 start-50 translate-middle-x mt-3'; successMessage.style.zIndex = "9999"; successMessage.innerHTML = `授权成功! 现在您可以投资了。`; document.body.appendChild(successMessage); // 3秒后自动移除成功消息 setTimeout(() => { document.body.removeChild(successMessage); }, 3000); // 重新检查授权 const newAllowance = await tokenContract.allowance(currentAccount, contractAddress); console.log("新授权额度:", ethers.utils.formatUnits(newAllowance, tokenDecimals)); if (newAllowance.lt(amountWei)) { alert('授权失败,请重试'); showLoading(false); return; } } catch (error) { // 移除提示 document.body.removeChild(confirmMessage); console.error("授权失败:", error); alert('授权失败: ' + (error.message || JSON.stringify(error))); showLoading(false); return; } } // 获取当前gas价格 - 使用与下注相同的方式 const gasPrice = await web3.eth.getGasPrice(); console.log("当前gas价格:", gasPrice); // 投资交易 - 使用与下注相同的方式 try { const gasEstimate = await contract.methods.investAsSuperPartner(amountWei.toString()).estimateGas({ from: currentAccount }); console.log("估算gas用量:", gasEstimate); // 使用更高的gas限制 - 与下注相同 const tx = await contract.methods.investAsSuperPartner(amountWei.toString()).send({ from: currentAccount, gas: Math.floor(gasEstimate * 1.2), // 增加20%的gas限制 gasPrice: web3.utils.toHex(Math.floor(parseInt(gasPrice) * 1.1)) // 增加10%的gas价格 }); console.log("超级合伙人投资交易成功:", tx); // 显示成功消息 - 使用与下注相同的样式 const successMessage = document.createElement('div'); successMessage.className = 'alert alert-success position-fixed top-50 start-50 translate-middle'; successMessage.style.zIndex = "10000"; successMessage.style.transform = "translate(-50%, -50%) scale(0)"; successMessage.style.transition = "all 0.3s ease"; successMessage.innerHTML = `
    超级合伙人投资成功!
    `; document.body.appendChild(successMessage); // 动画显示 setTimeout(() => { successMessage.style.transform = "translate(-50%, -50%) scale(1)"; }, 10); // 3秒后移除 setTimeout(() => { successMessage.style.transform = "translate(-50%, -50%) scale(0)"; setTimeout(() => { if (document.body.contains(successMessage)) { document.body.removeChild(successMessage); } }, 300); }, 3000); // 刷新超级合伙人数据(如果有相关函数的话) try { await loadSuperPartnerStats(); } catch (_) { } // 清空输入框 document.getElementById('superPartnerAmount').value = ''; } catch (error) { console.error("投资交易失败:", error); if (error.code === 4001) { alert('您拒绝了交易'); } else { alert('投资失败: ' + (error.message || JSON.stringify(error))); } } } catch (error) { console.error('投资过程中出错:', error); alert('投资失败: ' + (error.message || JSON.stringify(error))); } finally { showLoading(false); } } // 加载营销合伙人统计数据 async function loadMarketingPartnerStats() { try { const w3 = window.web3 || web3; const c = window.contract || contract; if (!w3 || !c || !currentAccount) { // 重置显示 document.getElementById('totalInvestment').textContent = '0'; document.getElementById('totalEarnings').textContent = '0'; document.getElementById('profitPercent').textContent = '0%'; document.getElementById('profitProgress').style.width = '0%'; document.getElementById('profitProgressText').textContent = '0% / 500%'; return; } // 获取营销合伙人信息 const partnerInfo = await c.methods.getMarketingPartnerInfo(currentAccount).call(); // 容错:当 tokenDecimals 尚未初始化时,先尝试读取一次代币精度 if ((typeof tokenDecimals !== 'number' || isNaN(tokenDecimals)) && typeof tokenContract?.decimals === 'function') { try { const dec = await tokenContract.decimals(); if (typeof dec === 'number' || typeof dec === 'bigint' || typeof dec === 'string') { tokenDecimals = Number(dec); } } catch (_) { } } const totalInvestment = parseFloat(ethers.utils.formatUnits(partnerInfo.totalInvestment.toString(), tokenDecimals)); const totalEarnings = parseFloat(ethers.utils.formatUnits(partnerInfo.totalEarnings.toString(), tokenDecimals)); const currentCycleInvestment = parseFloat(ethers.utils.formatUnits(partnerInfo.currentCycleInvestment.toString(), tokenDecimals)); const currentCycleEarnings = parseFloat(ethers.utils.formatUnits(partnerInfo.currentCycleEarnings.toString(), tokenDecimals)); // 计算当前周期收益率 let profitPercent = 0; if (currentCycleInvestment > 0) { profitPercent = (currentCycleEarnings / currentCycleInvestment) * 100; } // 更新UI // 防抖:若仍在初始化 tokenDecimals,延迟再试 if (typeof tokenDecimals !== 'number' || isNaN(tokenDecimals)) { setTimeout(loadMarketingPartnerStats, 800); return; } document.getElementById('totalInvestment').textContent = totalInvestment.toFixed(2); document.getElementById('totalEarnings').textContent = totalEarnings.toFixed(4); document.getElementById('profitPercent').textContent = profitPercent.toFixed(2) + '%'; // 更新进度条 const progressPercent = Math.min(profitPercent, 500); document.getElementById('profitProgress').style.width = (progressPercent / 500 * 100) + '%'; document.getElementById('profitProgressText').textContent = profitPercent.toFixed(2) + '% / 500%'; // 如果达到500%上限,改变进度条颜色 const progressBar = document.getElementById('profitProgress'); if (profitPercent >= 500) { progressBar.className = 'progress-bar bg-success'; document.getElementById('profitProgressText').textContent = '已达上限,可复投重置'; } else { progressBar.className = 'progress-bar bg-warning'; } } catch (error) { console.error('加载营销合伙人统计失败:', error); // 出错时也不要把 UI 清零,保留上一次正确值,避免“闪 0” // 同时设置一个稍后重试,缓解网络抖动 setTimeout(() => { try { loadMarketingPartnerStats(); } catch (_) { } }, 2000); } } // 超级合伙人即时详情统计 async function loadSuperPartnerStats() { try { const w3 = window.web3 || web3; const c = window.contract || contract; if (!w3 || !c || !currentAccount) { // 重置显示 document.getElementById('superPartnerTotalInvestment').textContent = '0 USDT'; document.getElementById('superPartnerTotalRewards').textContent = '0 USDT'; document.getElementById('superPartnerDownlineCount').textContent = '0'; document.getElementById('superPartnerDownlineTotalInvestment').textContent = '0 USDT'; document.getElementById('superPartnerLastUpdate').textContent = '--'; const pc = document.getElementById('superProgressContainer'); if (pc) pc.style.display = 'none'; return; } // 保障 tokenDecimals if ((typeof tokenDecimals !== 'number' || isNaN(tokenDecimals)) && typeof tokenContract?.decimals === 'function') { try { const dec = await tokenContract.decimals(); if (typeof dec === 'number' || typeof dec === 'bigint' || typeof dec === 'string') { tokenDecimals = Number(dec); } } catch (_) { } } // 读取超级合伙人信息 const sp = await c.methods.superPartners(currentAccount).call(); // 若仍在初始化 tokenDecimals,延迟再试 if (typeof tokenDecimals !== 'number' || isNaN(tokenDecimals)) { setTimeout(loadSuperPartnerStats, 800); return; } const totalInvestment = parseFloat(ethers.utils.formatUnits(sp.totalInvestment?.toString() || '0', tokenDecimals)); const totalEarnings = parseFloat(ethers.utils.formatUnits(sp.totalEarnings?.toString() || '0', tokenDecimals)); const currentCycleInvestment = parseFloat(ethers.utils.formatUnits(sp.currentCycleInvestment?.toString() || '0', tokenDecimals)); const currentCycleEarnings = parseFloat(ethers.utils.formatUnits(sp.currentCycleEarnings?.toString() || '0', tokenDecimals)); // 计算当前周期收益率(上限600%) let profitPercent = 0; if (currentCycleInvestment > 0) { profitPercent = (currentCycleEarnings / currentCycleInvestment) * 100; } // 尝试统计下线数量与下线总投资(遍历 public 数组 getter,直到越界报错) let downlineCount = 0; let downlineTotalInvestment = 0; try { // 防止死循环,最多遍历 500 个 for (let i = 0; i < 500; i++) { try { const addr=await c.methods.superPartnerDownlines(currentAccount, i).call(); if (!addr || addr==='0x0000000000000000000000000000000000000000' ) { break; } downlineCount++; // 读取该下线(也是超级合伙人)的投资额 try { const sp2=await c.methods.superPartners(addr).call(); const inv2=parseFloat(ethers.utils.formatUnits(sp2.totalInvestment?.toString() || '0' , tokenDecimals)); downlineTotalInvestment +=inv2; } catch (_) { } } catch (innerErr) { // 越界或出错即终止 break; } } } catch (_) { } // 更新UI document.getElementById('superPartnerTotalInvestment').textContent=totalInvestment.toFixed(2) + ' USDT' ; document.getElementById('superPartnerTotalRewards').textContent=totalEarnings.toFixed(4) + ' USDT' ; document.getElementById('superPartnerDownlineCount').textContent=String(downlineCount); document.getElementById('superPartnerDownlineTotalInvestment').textContent=downlineTotalInvestment.toFixed(2) + ' USDT' ; // 进度条(600%) const pc=document.getElementById('superProgressContainer'); const pb=document.getElementById('superProfitProgress'); const pbt=document.getElementById('superProfitProgressText'); if (pc && pb && pbt) { pc.style.display=sp.active ? 'block' : 'none' ; const progressPercent=Math.min(profitPercent, 600); pb.style.width=(progressPercent / 600 * 100) + '%' ; pbt.textContent=profitPercent.toFixed(2) + '% / 600%' ; if (profitPercent>= 600) { pb.className = 'progress-bar bg-success'; pbt.textContent = '已达上限,可复投重置'; } else { pb.className = 'progress-bar bg-danger'; } } // 更新时间 const now = new Date(); const ts = now.getHours().toString().padStart(2, '0') + ":" + now.getMinutes().toString().padStart(2, '0') + ":" + now.getSeconds().toString().padStart(2, '0'); document.getElementById('superPartnerLastUpdate').textContent = ts; } catch (error) { console.error('加载超级合伙人统计失败:', error); // 出错时稍后重试 setTimeout(() => { try { loadSuperPartnerStats(); } catch (_) { } }, 2000); } } // 启动邀请统计的实时监听(账户变化或新块触发) function startInviteRealtime() { stopInviteRealtime(); // 定时轮询:每30秒刷新一次 inviteRealtimeInterval = setInterval(() => { loadInviteStats(); }, 30000); // 订阅相关事件,触发即时刷新 try { if (contract && contract.events) { const sub1 = contract.events.PlayerJoined({ fromBlock: 'latest' }).on('data', (ev) => { if (ev.returnValues && ev.returnValues.inviter && currentAccount && ev.returnValues.inviter.toLowerCase() === currentAccount.toLowerCase()) { loadInviteStats(); } }); const sub2 = contract.events.PlayerSettled({ fromBlock: 'latest' }).on('data', async (ev) => { try { const playerAddr = ev.returnValues && ev.returnValues.player; if (playerAddr && currentAccount) { const inviterAddr = await contract.methods.getGlobalInviter(playerAddr).call(); if (inviterAddr && inviterAddr.toLowerCase() === currentAccount.toLowerCase()) { loadInviteStats(); } } } catch (_) { } }); inviteEventSubscriptions.push(sub1, sub2); } } catch (e) { console.warn('事件订阅失败,改用轮询:', e); } } function stopInviteRealtime() { if (inviteRealtimeInterval) { clearInterval(inviteRealtimeInterval); inviteRealtimeInterval = null; } // 取消事件订阅 if (inviteEventSubscriptions && inviteEventSubscriptions.length) { inviteEventSubscriptions.forEach(sub => { try { if (sub && typeof sub.unsubscribe === 'function') sub.unsubscribe(); } catch (_) { } }); inviteEventSubscriptions = []; } } // 显示二维码 function showQRCode() { if (!currentAccount) { showInviteNotification('请先连接钱包', 'warning'); return; } const qrCodeSection = document.getElementById('qrCodeSection'); const qrCodeContainer = document.getElementById('qrCodeContainer'); if (qrCodeSection.style.display === 'none') { // 显示二维码 const inviteLink = generateInviteLink(); // 清空容器 qrCodeContainer.innerHTML = ''; // 生成二维码 QRCode.toCanvas(qrCodeContainer, inviteLink, { width: 200, height: 200, colorDark: '#000000', colorLight: '#ffffff', margin: 2, errorCorrectionLevel: 'M' }, function (error) { if (error) { console.error('二维码生成失败:', error); qrCodeContainer.innerHTML = '
    二维码生成失败
    '; } else { console.log('二维码生成成功'); // 添加下载按钮 const downloadBtn = document.createElement('button'); downloadBtn.className = 'btn btn-sm btn-outline-primary mt-2'; downloadBtn.innerHTML = ' 下载二维码'; downloadBtn.onclick = downloadQRCode; qrCodeContainer.appendChild(downloadBtn); } }); qrCodeSection.style.display = 'block'; showInviteNotification('二维码已生成!', 'success'); } else { // 隐藏二维码 qrCodeSection.style.display = 'none'; } } // 下载二维码 function downloadQRCode() { const canvas = document.querySelector('#qrCodeContainer canvas'); if (canvas) { const link = document.createElement('a'); link.download = '邀请二维码.png'; link.href = canvas.toDataURL(); link.click(); showInviteNotification('二维码已下载!', 'success'); } } // 显示邀请排行榜 function showInviteRanking() { const modal = document.createElement('div'); modal.className = 'modal fade'; modal.innerHTML = ` `; document.body.appendChild(modal); const bootstrapModal = new bootstrap.Modal(modal); bootstrapModal.show(); // 加载排行榜数据 loadRankingData(); // 模态框关闭后移除元素 modal.addEventListener('hidden.bs.modal', () => { document.body.removeChild(modal); }); } // 加载排行榜数据 async function loadRankingData() { try { // 模拟排行榜数据(实际项目中需要从合约或后端API获取) const mockRankingData = [ { rank: 1, address: '0x1234...5678', invites: 156, earnings: '2.34', badge: '🥇' }, { rank: 2, address: '0x2345...6789', invites: 134, earnings: '2.01', badge: '🥈' }, { rank: 3, address: '0x3456...7890', invites: 98, earnings: '1.47', badge: '🥉' }, { rank: 4, address: '0x4567...8901', invites: 87, earnings: '1.31', badge: '🏆' }, { rank: 5, address: '0x5678...9012', invites: 76, earnings: '1.14', badge: '🏆' }, { rank: 6, address: '0x6789...0123', invites: 65, earnings: '0.98', badge: '🏆' }, { rank: 7, address: '0x7890...1234', invites: 54, earnings: '0.81', badge: '🏆' }, { rank: 8, address: '0x8901...2345', invites: 43, earnings: '0.65', badge: '🏆' }, { rank: 9, address: '0x9012...3456', invites: 32, earnings: '0.48', badge: '🏆' }, { rank: 10, address: '0x0123...4567', invites: 21, earnings: '0.32', badge: '🏆' } ]; const rankingTableBody = document.getElementById('rankingTableBody'); if (!rankingTableBody) return; // 清空表格 rankingTableBody.innerHTML = ''; // 填充排行榜数据 mockRankingData.forEach(item => { const row = document.createElement('tr'); const isCurrentUser = currentAccount && item.address.toLowerCase().includes(currentAccount.slice(-4).toLowerCase()); if (isCurrentUser) { row.classList.add('table-warning'); } row.innerHTML = ` ${item.rank} ${formatAddress(item.address)} ${isCurrentUser ? '' : ''} ${item.invites} ${item.earnings} ETH ${item.badge} `; rankingTableBody.appendChild(row); }); // 更新我的排名信息 if (currentAccount) { const myRank = mockRankingData.find(item => item.address.toLowerCase().includes(currentAccount.slice(-4).toLowerCase()) ); if (myRank) { document.getElementById('myRankPosition').textContent = myRank.rank; document.getElementById('myRankInvites').textContent = myRank.invites; document.getElementById('myRankEarnings').textContent = myRank.earnings + ' ETH'; } else { document.getElementById('myRankPosition').textContent = '未上榜'; document.getElementById('myRankInvites').textContent = '0'; document.getElementById('myRankEarnings').textContent = '0 ETH'; } } } catch (error) { console.error('加载排行榜数据失败:', error); const rankingTableBody = document.getElementById('rankingTableBody'); if (rankingTableBody) { rankingTableBody.innerHTML = ' 加载失败,请稍后重试 '; } } } // 刷新排行榜 function refreshRanking() { showInviteNotification('正在刷新排行榜...', 'info'); loadRankingData(); setTimeout(() => { showInviteNotification('排行榜已更新!', 'success'); }, 1000); } // 复制邀请链接(保留原有函数兼容性) function copyInviteLink() { const inviteLinkInput = document.getElementById('myInviteLink'); if (inviteLinkInput && inviteLinkInput.value) { inviteLinkInput.select(); document.execCommand('copy'); showInviteNotification('邀请链接已复制到剪贴板!', 'success'); } else { showInviteNotification('请先连接钱包生成邀请链接', 'warning'); } } // 格式化地址显示 function formatAddress(address) { if (!address) return '-'; return `...${address.substring(address.length - 2)}`; } // 格式化代币金额 - 正确处理wei和ether单位 function formatTokenAmount(amount) { if (!amount || amount === '0') return '0'; try { console.log('格式化金额输入:', amount, '类型:', typeof amount); let numValue; const amountStr = amount.toString(); // 判断是否是wei单位(通常是很大的数字) if (amountStr.length > 10 && !amountStr.includes('.')) { // 可能是wei单位,需要转换为ether try { const etherValue = web3.utils.fromWei(amountStr, 'ether'); numValue = parseFloat(etherValue); console.log('从wei转换为ether:', numValue); } catch (e) { // 如果wei转换失败,直接解析 numValue = parseFloat(amountStr); } } else { // 可能已经是ether单位或小数值 numValue = parseFloat(amountStr); } if (isNaN(numValue) || numValue < 0) { console.error('数值转换失败或为负数:', amount); return '0' ; } console.log('格式化后的数值:', numValue); // 格式化显示 if (numValue>= 1000000) { return (numValue / 1000000).toFixed(2) + 'M'; } else if (numValue >= 1000) { return (numValue / 1000).toFixed(2) + 'K'; } else if (numValue >= 1) { return numValue.toFixed(2); } else if (numValue > 0) { return numValue.toFixed(4); } else { return '0'; } } catch (error) { console.error('格式化金额失败:', error); return '0'; } } // 格式化时间显示 function formatTime(seconds) { if (seconds <= 0) return '0秒' ; const minutes=Math.floor(seconds / 60); const remainingSeconds=seconds % 60; if (minutes> 0) { return `${minutes}分${remainingSeconds}秒`; } else { return `${remainingSeconds}秒`; } } // 获取牌面值 function getCardValue(cardIndex) { const value = (cardIndex % 13) + 1; switch (value) { case 1: return 'A'; case 11: return 'J'; case 12: return 'Q'; case 13: return 'K'; default: return value.toString(); } } // 获取牌面花色 function getCardSuit(cardIndex) { const suit = Math.floor(cardIndex / 13); switch (suit) { case 0: return '♠'; // 黑桃 case 1: return '♥'; // 红心 case 2: return '♦'; // 方块 case 3: return '♣'; // 梅花 default: return ''; } } // 音效播放系统 function playSound(soundId, volume = 0.5, delay = 0) { setTimeout(() => { const sound = document.getElementById(soundId); if (sound) { sound.volume = volume; sound.currentTime = 0; const playPromise = sound.play(); if (playPromise !== undefined) { playPromise.catch(err => { console.error(`播放音效 ${soundId} 失败:`, err); // 尝试再次播放 setTimeout(() => { sound.play().catch(e => console.error(`二次尝试播放 ${soundId} 失败:`, e)); }, 100); }); } } }, delay); } // 播放背景音乐 function startBackgroundMusic() { const bgMusic = document.getElementById('backgroundMusic'); if (bgMusic) { bgMusic.volume = 0.05; bgMusic.play().catch(err => { console.log('背景音乐播放失败:', err); }); } } // 停止背景音乐 function stopBackgroundMusic() { const bgMusic = document.getElementById('backgroundMusic'); if (bgMusic) { bgMusic.pause(); bgMusic.currentTime = 0; } } // 风格切换系统 function switchTheme(theme) { const body = document.body; // 移除现有主题类 body.classList.remove('classic-theme', 'modern-theme'); // 添加新主题类 body.classList.add(theme + '-theme'); // 播放切换音效 playSound('flipSound', 0.2); // 添加切换动画 body.style.transition = 'all 0.5s ease'; console.log(`切换到${theme === 'classic' ? '经典奢华' : '现代科技'}风格`); } // 移动端环境检测 function detectMobileWalletEnvironment() { const userAgent = navigator.userAgent.toLowerCase(); const environment = { isMetaMaskApp: userAgent.includes('metamask') || (window.ethereum && window.ethereum.isMetaMask), isTrustWallet: userAgent.includes('trust') || (window.ethereum && window.ethereum.isTrust), isTokenPocket: userAgent.includes('tokenpocket') || (window.ethereum && window.ethereum.isTokenPocket), isImToken: userAgent.includes('imtoken') || (window.ethereum && window.ethereum.isImToken), isCoinbaseWallet: userAgent.includes('coinbasewallet') || (window.ethereum && window.ethereum.isCoinbaseWallet), hasEthereum: typeof window.ethereum !== 'undefined', isInAppBrowser: false }; // 检查是否在应用内浏览器 environment.isInAppBrowser = environment.isMetaMaskApp || environment.isTrustWallet || environment.isTokenPocket || environment.isImToken || environment.isCoinbaseWallet; console.log('移动端钱包环境检测:', environment); return environment; } // 移动端钱包连接函数 async function connectMobileWallet() { try { console.log('开始移动端钱包连接...'); // 环境检测 const walletEnv = detectMobileWalletEnvironment(); // 检查是否在钱包应用内浏览器中 const isInWalletBrowser = walletEnv.isInAppBrowser; if (!isInWalletBrowser) { // 不在钱包浏览器中,提供更友好的引导 console.log('不在钱包浏览器中,显示引导信息...'); showLoading(false); // 显示详细的引导信息 const guideModal = confirm( '🔗 移动端连接建议\n\n' + '为了获得最佳体验,建议您:\n\n' + '1. 打开 MetaMask 应用\n' + '2. 点击底部"浏览器"标签\n' + '3. 在地址栏输入当前网址\n\n' + '点击"确定"尝试打开MetaMask\n' + '点击"取消"继续当前连接' ); if (guideModal) { // 尝试打开MetaMask应用 const metamaskUrl = `https://metamask.app.link/dapp/${window.location.host}${window.location.pathname}`; window.open(metamaskUrl, '_blank'); return false; } else { // 用户选择继续,重新显示加载状态 showLoading(true); } } // 在钱包浏览器中或用户选择继续,尝试连接 if (!window.ethereum) { console.error('未检测到window.ethereum'); alert('未检测到钱包。请确保您在支持的钱包应用中打开此页面。\n\n建议:\n1. 使用MetaMask应用内浏览器\n2. 确保钱包应用已正确安装'); showLoading(false); return false; } // 移动端简化连接流程 console.log('开始移动端简化连接流程...'); // 首先尝试简单的连接方式 try { console.log('尝试简单连接方式...'); const simpleAccounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); if (simpleAccounts && simpleAccounts.length > 0) { console.log('简单连接成功:', simpleAccounts[0]); return await completeMobileConnection(simpleAccounts[0]); } } catch (simpleError) { console.log('简单连接失败,尝试高级连接:', simpleError); } console.log('尝试请求账户访问权限...'); // 移动端连接前的预检查 try { // 检查钱包是否响应 const chainId = await window.ethereum.request({ method: 'eth_chainId' }); console.log('钱包响应正常,当前网络:', chainId); } catch (preCheckError) { console.warn('钱包预检查失败:', preCheckError); throw new Error('钱包无响应,请确保钱包应用已正常启动'); } // 设置超时机制 - 移动端需要更长时间 const timeout = new Promise((_, reject) => { setTimeout(() => reject(new Error('连接超时')), 45000); // 45秒超时 }); // 添加重试机制 let accounts; let retryCount = 0; const maxRetries = 3; while (retryCount < maxRetries) { try { console.log(`第 ${retryCount + 1} 次尝试连接...`); const connectPromise=window.ethereum.request({ method: 'eth_requestAccounts' }); // 使用Promise.race来处理超时 accounts=await Promise.race([connectPromise, timeout]); if (accounts && accounts.length> 0) { console.log('高级连接成功,获取到账户:', accounts); return await completeMobileConnection(accounts[0]); } else { throw new Error('未获取到账户'); } } catch (connectError) { retryCount++; console.error(`第 ${retryCount} 次连接失败:`, connectError); if (retryCount >= maxRetries) { throw connectError; } // 等待2秒后重试 await new Promise(resolve => setTimeout(resolve, 2000)); } } } catch (error) { console.error('移动端钱包连接失败:', error); console.error('错误详情:', { message: error.message, code: error.code, stack: error.stack }); let errorMessage = '连接钱包失败'; let debugInfo = ''; if (error.message.includes('User rejected') || error.code === 4001) { errorMessage = '用户取消了连接请求'; } else if (error.message.includes('连接超时')) { errorMessage = '连接超时,请重试或切换到钱包应用内浏览器'; } else if (error.message.includes('未获取到账户')) { errorMessage = '未能获取钱包账户,请确保钱包已解锁'; } else if (error.code === -32002) { errorMessage = '钱包连接请求待处理,请在钱包应用中确认'; } else if (error.code === -32603) { errorMessage = '钱包内部错误,请重启钱包应用后重试'; } else { errorMessage = '连接失败,请尝试以下解决方案:\n\n1. 确保在钱包应用内浏览器中访问\n2. 检查钱包是否已解锁\n3. 重启钱包应用后重试'; debugInfo = `\n\n调试信息: ${error.message}`; } alert(errorMessage + debugInfo); showLoading(false); return false; } } // 完成移动端连接的函数 async function completeMobileConnection(account) { try { console.log('完成移动端连接,账户:', account); // 设置全局变量 window.userAccount = account; window.currentAccount = account; // 初始化Web3 window.web3 = new Web3(window.ethereum); // 初始化合约 if (typeof contractAbi !== 'undefined' && contractAddress) { window.contract = new window.web3.eth.Contract(contractAbi, contractAddress); console.log('移动端合约初始化成功'); } else { console.warn('移动端合约ABI或地址未定义'); } // 更新UI - 使用正确的元素ID const connectBtn = document.getElementById('connectWalletBtn'); const walletInfo = document.getElementById('walletInfo'); const walletAddress = document.getElementById('walletAddress'); const walletAlert = document.getElementById('walletAlert'); if (connectBtn) { connectBtn.style.display = 'none'; console.log('隐藏连接按钮'); } if (walletInfo) { walletInfo.style.display = 'block'; console.log('显示钱包信息'); } if (walletAddress) { walletAddress.textContent = `${account.substring(0, 6)}...${account.substring(38)}`; console.log('更新钱包地址显示'); } if (walletAlert) { walletAlert.style.display = 'none'; } // 检查网络 try { const chainId = await window.ethereum.request({ method: 'eth_chainId' }); console.log("移动端当前网络ID:", chainId); } catch (networkError) { console.log('移动端网络检查失败:', networkError); } // 加载游戏数据 - 使用与桌面端相同的函数 try { console.log('开始加载移动端游戏数据...'); // 调用完整的游戏信息加载函数 await loadGameInfo(); // 更新邀请链接和统计 updateInviteLink(); await loadInviteStats(); console.log('移动端游戏数据加载完成'); } catch (loadError) { console.error("移动端加载游戏信息失败:", loadError); // 如果loadGameInfo失败,尝试单独加载各个组件 try { console.log('尝试单独加载游戏组件...'); // 检查合约是否正确初始化 if (window.contract) { // 加载房间列表 if (typeof loadRooms === 'function') { await loadRooms(); console.log('房间列表加载成功'); } // 检查玩家房间状态 if (typeof checkPlayerRoom === 'function') { await checkPlayerRoom(); console.log('玩家房间状态检查完成'); } // 加载最小下注金额 if (typeof loadMinBetAmount === 'function') { await loadMinBetAmount(); console.log('最小下注金额加载完成'); } // 更新UI显示 updateMobileUI(); // 延迟再次刷新,确保数据完全加载 setTimeout(async () => { try { console.log('移动端延迟刷新开始...'); // 再次检查玩家房间状态 if (typeof checkPlayerRoom === 'function') { await checkPlayerRoom(); } // 再次加载房间列表 if (typeof loadRooms === 'function') { await loadRooms(); } else { // 如果loadRooms函数不存在,使用备用方法 await loadMobileRoomsList(); } // 最终UI更新 updateMobileUI(); console.log('移动端延迟刷新完成'); } catch (delayError) { console.error('移动端延迟刷新失败:', delayError); } }, 2000); } else { console.error('合约未正确初始化'); } } catch (fallbackError) { console.error('备用加载方案也失败:', fallbackError); } } showLoading(false); console.log('移动端钱包连接成功'); return true; } catch (error) { console.error('完成移动端连接失败:', error); showLoading(false); throw error; } } // 移动端UI更新函数 function updateMobileUI() { try { console.log('更新移动端UI显示...'); // 确保显示正确的页面标签 const roomsTab = document.getElementById('roomsTab'); const historyTab = document.getElementById('historyTab'); const adminTab = document.getElementById('adminTab'); // 激活房间标签 if (roomsTab) { roomsTab.classList.add('active'); roomsTab.setAttribute('aria-selected', 'true'); } if (historyTab) { historyTab.classList.remove('active'); historyTab.setAttribute('aria-selected', 'false'); } if (adminTab) { adminTab.classList.remove('active'); adminTab.setAttribute('aria-selected', 'false'); } // 显示房间页面 const roomsPage = document.getElementById('roomsPage'); const historyPage = document.getElementById('historyPage'); const adminPage = document.getElementById('adminPage'); if (roomsPage) { roomsPage.style.display = 'block'; roomsPage.classList.add('show', 'active'); } if (historyPage) { historyPage.style.display = 'none'; historyPage.classList.remove('show', 'active'); } if (adminPage) { adminPage.style.display = 'none'; adminPage.classList.remove('show', 'active'); } // 强制刷新房间列表显示 const roomsList = document.getElementById('roomsList'); if (roomsList) { // 触发重新渲染 roomsList.style.display = 'none'; setTimeout(() => { roomsList.style.display = 'block'; }, 100); } // 强制刷新当前房间信息显示 const currentRoomInfo = document.getElementById('currentRoomInfo'); if (currentRoomInfo) { currentRoomInfo.style.display = 'none'; setTimeout(() => { currentRoomInfo.style.display = 'block'; }, 100); } // 检查并显示玩家当前房间状态 checkAndDisplayPlayerRoom(); console.log('移动端UI更新完成'); } catch (uiError) { console.error('移动端UI更新失败:', uiError); } } // 检查并显示玩家房间状态(移动端专用) async function checkAndDisplayPlayerRoom() { try { console.log('移动端检查玩家房间状态...'); if (!window.contract || !window.userAccount) { console.log('合约或账户未初始化,跳过房间检查'); return; } // 获取玩家当前房间 const playerRoom = await window.contract.methods.playerRooms(window.userAccount).call(); console.log('移动端玩家房间信息:', playerRoom); if (playerRoom && playerRoom !== '0') { // 玩家在房间中,获取房间详细信息 const roomInfo = await window.contract.methods.rooms(playerRoom).call(); console.log('移动端房间详细信息:', roomInfo); // 显示当前房间信息 const currentRoomInfo = document.getElementById('currentRoomInfo'); const currentRoomId = document.getElementById('currentRoomId'); const currentRoomPlayers = document.getElementById('currentRoomPlayers'); const currentRoomBet = document.getElementById('currentRoomBet'); const currentRoomStatus = document.getElementById('currentRoomStatus'); if (currentRoomInfo) { currentRoomInfo.style.display = 'block'; } if (currentRoomId) { currentRoomId.textContent = playerRoom; } if (currentRoomPlayers) { currentRoomPlayers.textContent = `${roomInfo.playerCount}/${roomInfo.maxPlayers}`; } if (currentRoomBet) { const betAmount = window.web3.utils.fromWei(roomInfo.betAmount, 'ether'); currentRoomBet.textContent = `${betAmount} MATIC`; } if (currentRoomStatus) { const statusText = roomInfo.isActive ? '游戏中' : '等待中'; currentRoomStatus.textContent = statusText; currentRoomStatus.className = roomInfo.isActive ? 'badge bg-success' : 'badge bg-warning'; } console.log('移动端当前房间信息显示完成'); } else { // 玩家不在任何房间中 const currentRoomInfo = document.getElementById('currentRoomInfo'); if (currentRoomInfo) { currentRoomInfo.style.display = 'none'; } console.log('移动端玩家不在任何房间中'); } } catch (roomError) { console.error('移动端检查房间状态失败:', roomError); } } // 移动端专用房间列表加载 async function loadMobileRoomsList() { try { console.log('移动端加载房间列表...'); if (!window.contract) { console.error('合约未初始化,无法加载房间列表'); return; } // 获取房间总数 const roomCount = await window.contract.methods.roomCount().call(); console.log('移动端房间总数:', roomCount); const roomsList = document.getElementById('roomsList'); if (!roomsList) { console.error('房间列表元素不存在'); return; } // 清空现有列表 roomsList.innerHTML = ''; if (roomCount == 0) { roomsList.innerHTML = '
    暂无房间,请创建一个房间开始游戏
    '; return; } // 加载每个房间的信息 for (let i = 1; i <= roomCount; i++) { try { const room=await window.contract.methods.rooms(i).call(); console.log(`移动端房间 ${i} 信息:`, room); if (room && room.creator !=='0x0000000000000000000000000000000000000000' ) { const roomElement=createMobileRoomElement(i, room); roomsList.appendChild(roomElement); } } catch (roomError) { console.error(`移动端加载房间 ${i} 失败:`, roomError); } } console.log('移动端房间列表加载完成'); } catch (error) { console.error('移动端加载房间列表失败:', error); } } // 创建移动端房间元素 function createMobileRoomElement(roomId, roomData) { const roomDiv=document.createElement('div'); roomDiv.className='col-md-6 col-lg-4 mb-3' ; const betAmount=window.web3.utils.fromWei(roomData.betAmount, 'ether' ); const statusBadge=roomData.isActive ? '游戏中' : '等待中' ; roomDiv.innerHTML=`
    房间 #${roomId}
    ${statusBadge}

    创建者: ${roomData.creator.substring(0, 6)}...${roomData.creator.substring(38)}
    下注金额: ${betAmount} MATIC
    玩家: ${roomData.playerCount}/${roomData.maxPlayers}

    `; return roomDiv; } // 加入指定房间(移动端) async function joinSpecificRoom(roomId) { try { console.log('移动端加入房间:', roomId); // 设置房间ID输入框的值 const roomIdInput = document.getElementById('roomId'); if (roomIdInput) { roomIdInput.value = roomId; } // 调用加入房间函数 if (typeof joinRoom === 'function') { await joinRoom(); } else { console.error('joinRoom函数不存在'); alert('加入房间功能暂时不可用,请刷新页面重试'); } } catch (error) { console.error('移动端加入房间失败:', error); alert('加入房间失败: ' + error.message); } } // 自动连接钱包函数 async function autoConnectWallet() { try { // 检测设备类型 const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); // 检查是否有钱包 if (typeof window.ethereum !== 'undefined') { console.log('检测到钱包,尝试自动连接...'); // 检查必要的变量是否已定义 if (typeof contractAbi === 'undefined' || typeof contractAddress === 'undefined') { console.log('合约信息尚未加载完成,跳过自动连接'); return; } // 检查是否已经连接过 const accounts = await window.ethereum.request({ method: 'eth_accounts' }); if (accounts.length > 0) { // 已经连接过,直接使用 console.log('钱包已连接,账户:', accounts[0]); // 直接设置钱包状态,避免重复弹窗 window.userAccount = accounts[0]; // 更新UI显示 const connectBtn = document.getElementById('connectWallet'); const walletInfo = document.getElementById('walletInfo'); const accountAddress = document.getElementById('accountAddress'); if (connectBtn) connectBtn.style.display = 'none'; if (walletInfo) walletInfo.style.display = 'block'; if (accountAddress) accountAddress.textContent = formatAddress(accounts[0]); // 初始化Web3和合约 window.web3 = new Web3(window.ethereum); window.contract = new window.web3.eth.Contract(contractAbi, contractAddress); // 移动端需要额外检查网络 if (isMobile) { try { await checkNetwork(); } catch (networkError) { console.log('移动端网络检查失败:', networkError); } } // 加载游戏信息 await loadGameInfo(); // 更新邀请链接和统计 updateInviteLink(); await loadInviteStats(); console.log('自动连接成功,钱包地址:', accounts[0]); } else { // 钱包未授权,等待用户手动连接 console.log('钱包未授权,等待用户手动连接'); // 移动端显示友好提示 if (isMobile) { console.log('移动端用户,建议在钱包应用内浏览器中访问'); } } } else { console.log('未检测到钱包'); // 移动端显示安装提示 if (isMobile) { console.log('移动端未检测到钱包,建议安装MetaMask等钱包应用'); } } } catch (error) { console.log('自动连接钱包失败:', error); // 静默失败,不影响用户体验 } } // 显示/隐藏加载中 function showLoading(show) { document.getElementById('loading').style.display = show ? 'flex' : 'none'; } // 检查MetaMask是否安装并正确初始化 function checkMetaMaskInstalled() { // 检查ethereum对象是否存在 if (typeof window.ethereum === 'undefined') { console.log('MetaMask未安装'); // 显示安装指导 const message = '请安装MetaMask钱包插件以使用本应用。\n\n' + '您可以访问 https://metamask.io/download/ 下载安装,' + '安装完成后请刷新页面。'; alert(message); return false; } // 检查是否是MetaMask提供的以太坊对象 if (!window.ethereum.isMetaMask) { console.log('检测到非MetaMask的以太坊提供者'); alert('请使用MetaMask钱包插件,其他钱包可能不兼容。\n\n' + '如果您已安装MetaMask但仍看到此消息,请尝试刷新页面或重启浏览器。'); return false; } // 检查MetaMask是否已锁定 try { // 尝试获取MetaMask锁定状态(如果API支持) if (window.ethereum._metamask && typeof window.ethereum._metamask.isUnlocked === 'function') { window.ethereum._metamask.isUnlocked().then(unlocked => { if (!unlocked) { console.log('MetaMask已锁定'); // 提示用户解锁MetaMask alert('您的MetaMask钱包已锁定,请解锁后重试。\n\n' + '点击浏览器工具栏中的MetaMask图标,输入密码解锁。'); } }).catch(err => { console.error('检查MetaMask锁定状态失败:', err); }); } } catch (error) { console.error('检查MetaMask状态时出错:', error); // 不中断流程,继续尝试使用MetaMask } // 检查MetaMask是否正确初始化 try { // 尝试访问一些基本方法,确保MetaMask正常工作 if (!window.ethereum.request || typeof window.ethereum.request !== 'function') { console.error('MetaMask API不完整'); alert('MetaMask似乎未正确初始化。请刷新页面或重启浏览器后重试。'); return false; } } catch (error) { console.error('验证MetaMask功能时出错:', error); alert('验证MetaMask功能时出错。请刷新页面或重启浏览器后重试。'); return false; } console.log('MetaMask已安装且正常初始化'); return true; } // 生成设备指纹 async function generateDeviceFingerprint() { return new Promise((resolve) => { // 检查Fingerprint2是否已加载 if (typeof Fingerprint2 === 'undefined') { console.error('Fingerprint2库未加载'); // 返回一个默认值,避免错误 resolve('0x0000000000000000000000000000000000000000'); return; } // 确保在DOM加载完成后执行 const runFingerprint = () => { try { Fingerprint2.get(function (components) { const values = components.map(function (component) { return component.value }); const fingerprint = Fingerprint2.x64hash128(values.join(''), 31); resolve('0x' + fingerprint); }); } catch (error) { console.error('生成设备指纹失败:', error); resolve('0x0000000000000000000000000000000000000000'); } }; if (window.requestIdleCallback) { requestIdleCallback(runFingerprint); } else { setTimeout(runFingerprint, 500); } }); } // 计算IP哈希 async function calculateIpHash() { try { // 使用第三方服务获取IP地址 const response = await fetch('https://api.ipify.org?format=json'); const data = await response.json(); const ip = data.ip; // 将IP地址转换为数字 const ipParts = ip.split('.'); let ipNumber = 0; for (let i = 0; i < ipParts.length; i++) { ipNumber=ipNumber * 256 + parseInt(ipParts[i]); } return ipNumber; } catch (error) { console.error('获取IP地址失败:', error); return 0; // 失败时返回0 } } // 注册试用用户(带重试机制) async function registerTrialUser(retryCount=0) { // 确保MetaMask已安装 if (!checkMetaMaskInstalled()) { return; } // 确保已连接钱包和合约 if (!web3 || !contract) { alert('Web3或合约未初始化,请刷新页面重试'); return; } // 确保有当前账户 if (!currentAccount) { try { // 尝试重新连接钱包 const connected=await connectWallet(); if (!connected || !currentAccount) { alert('无法获取钱包地址,请确保MetaMask已解锁并授权本网站访问'); return; } } catch (error) { console.error('连接钱包失败:', error); alert('连接钱包失败: ' + (error.message || JSON.stringify(error))); return; } } try { showLoading(true); // 先检查当前账户的试用状态 console.log(' 检查当前账户试用状态...'); const currentStatus=await getTrialStatus(); console.log('当前试用状态:', currentStatus); // 如果已经是试用用户,提示用户 if (currentStatus && currentStatus.hasUsedTrial) { console.log('该账户已经注册过试用账户'); const firstTrialDate=new Date(currentStatus.firstTrialTime * 1000).toLocaleString(); alert('您的账户已经注册过试用账户,无需重复注册。\n\n' + '剩余创建房间次数: ' + currentStatus.remainingTrials + '\n' + '首次试用时间: ' + firstTrialDate); checkTrialStatus(); // 更新试用状态显示 showLoading(false); return; } // 获取设备指纹和IP哈希 console.log('生成设备指纹和IP哈希...'); const deviceFingerprint=await generateDeviceFingerprint(); const ipHash=await calculateIpHash(); console.log('设备指纹:', deviceFingerprint); console.log('IP哈希:', ipHash); // 检查设备指纹是否有效 if (deviceFingerprint==='0x0000000000000000000000000000000000000000' ) { throw new Error('无法生成有效的设备指纹,请刷新页面重试'); } // 检查该设备是否已经注册过 try { console.log('检查设备是否已注册...'); const deviceRegistered=await contract.methods.isDeviceRegistered(deviceFingerprint).call(); if (deviceRegistered) { console.log('该设备已经注册过试用账户'); alert('该设备已经注册过试用账户,每个设备只能注册一次。\n\n如果您认为这是错误,请联系管理员。'); showLoading(false); return; } } catch (checkError) { console.error('检查设备注册状态失败:', checkError); // 继续尝试注册,让合约处理可能的错误 } // 调用合约方法注册试用用户 console.log('估算gas用量...'); let gasEstimate; try { gasEstimate=await contract.methods.registerTrialUser(deviceFingerprint, ipHash).estimateGas({ from: currentAccount }); console.log('估算的gas用量:', gasEstimate); } catch (gasError) { console.error('估算gas失败:', gasError); // 检查是否是因为设备或IP已注册导致的错误 if (gasError.message && (gasError.message.includes('device already registered') || gasError.message.includes('IP already registered') || gasError.message.includes('user already registered'))) { alert('注册失败: 您的设备或IP地址已经注册过试用账户。\n\n每个设备和IP地址只能注册一次试用账户。'); showLoading(false); return; } // 使用默认gas限制 gasEstimate=300000; console.log('使用默认gas限制:', gasEstimate); } console.log('发送注册交易...'); // 获取当前gas价格 const gasPrice=await web3.eth.getGasPrice(); console.log("当前gas价格:", gasPrice); try { const tx=await contract.methods.registerTrialUser(deviceFingerprint, ipHash) .send({ from: currentAccount, gas: Math.floor(gasEstimate * 1.5), // 增加50%的gas限制 gasPrice: web3.utils.toHex(Math.floor(parseInt(gasPrice) * 1.2)) // 增加20%的gas价格 }); console.log('交易成功:', tx); alert('试用账户注册成功!'); checkTrialStatus(); // 更新试用状态显示 } catch (error) { console.error('注册试用用户失败:', error); // 提供更详细的错误信息 let errorMsg='注册试用用户失败: ' ; // 检查常见错误 if (error.message) { if (error.message.includes('device already registered') || error.message.includes('Device usage limit exceeded')) { errorMsg='注册失败: 您的设备已经注册过试用账户。\n\n每个设备只能注册一次试用账户。' ; } else if (error.message.includes('IP already registered') || error.message.includes('IP usage limit exceeded')) { errorMsg='注册失败: 您的IP地址已经注册过试用账户。\n\n每个IP地址只能注册一次试用账户。' ; } else if (error.message.includes('user already registered') || error.message.includes('Already registered')) { errorMsg='注册失败: 您的账户已经注册过试用账户。\n\n每个账户只能注册一次试用账户。' ; } else if (error.message.includes('User denied') || error.code===4001) { errorMsg='您取消了交易。如需注册试用账户,请在MetaMask中确认交易。' ; } else if (error.message.includes('Internal JSON-RPC error') || error.code===-32603) { if (retryCount < 2) { console.log(`网络错误,正在重试... (${retryCount + 1}/3)`); alert(`网络连接错误,正在重试... (${retryCount + 1}/3)\n\n请稍候...`); setTimeout(()=> registerTrialUser(retryCount + 1), 3000); return; } errorMsg = '网络连接错误,已重试3次仍然失败。\n\n请检查网络连接或稍后再试。'; } else if (error.message.includes('insufficient funds')) { errorMsg = '账户余额不足,无法支付交易费用。\n\n请确保账户有足够的MATIC用于支付gas费。'; } else { errorMsg += error.message || JSON.stringify(error); } } else { errorMsg += JSON.stringify(error); } alert(errorMsg); } } catch (error) { console.error('注册试用用户失败:', error); // 提供更详细的错误信息 let errorMsg = '注册试用用户失败: ' + (error.message || error); // 检查常见错误 if (error.message) { if (error.message.includes('device already registered')) { errorMsg = '注册失败: 您的设备已经注册过试用账户。\n\n每个设备只能注册一次试用账户。'; } else if (error.message.includes('IP already registered')) { errorMsg = '注册失败: 您的IP地址已经注册过试用账户。\n\n每个IP地址只能注册一次试用账户。'; } else if (error.message.includes('user already registered')) { errorMsg = '注册失败: 您的账户已经注册过试用账户。\n\n每个账户只能注册一次试用账户。'; } } alert(errorMsg); } finally { showLoading(false); } } // 检查用户是否在白名单中 async function isWhitelisted(address) { if (!web3 || !contract) return false; try { // 如果没有提供地址,则使用当前账户 const targetAddress = address || currentAccount; if (!targetAddress) return false; return await contract.methods.isWhitelisted(targetAddress).call(); } catch (error) { console.error('检查白名单状态失败:', error); return false; } } // 获取试用状态 async function getTrialStatus(address) { // 检查web3和合约是否已初始化 if (!web3) { console.error('Web3未初始化'); return null; } if (!contract) { console.error('合约未初始化'); return null; } try { // 如果没有提供地址,则使用当前账户 const targetAddress = address || currentAccount; if (!targetAddress) { console.error('未提供有效的以太坊地址'); return null; } // 验证地址是否为有效的以太坊地址 if (!web3.utils.isAddress(targetAddress)) { console.error('提供的地址不是有效的以太坊地址:', targetAddress); return null; } console.log('正在获取地址的试用状态:', targetAddress); // 调用合约方法获取试用状态 let trialStatus; try { trialStatus = await contract.methods.getTrialStatus(targetAddress).call(); console.log('合约返回的原始试用状态:', trialStatus); } catch (contractError) { console.error('调用合约getTrialStatus方法失败:', contractError); // 检查是否是因为用户未注册 if (contractError.message && contractError.message.includes('user not registered')) { console.log('用户未注册试用账户'); // 返回默认的未注册状态 return { remainingTrials: 3, firstTrialTime: 0, totalRoomsCreated: 0, hasUsedTrial: false, canCreateRoom: false, isRegistered: false }; } throw contractError; // 重新抛出其他错误 } // 验证返回的数据 if (!trialStatus) { console.error('获取到的试用状态为空'); return null; } // 解析返回的数据 const remainingTrials = parseInt(trialStatus[0]); const firstTrialTime = parseInt(trialStatus[1]); const totalRoomsCreated = parseInt(trialStatus[2]); // 检查数据有效性 if (isNaN(remainingTrials) || isNaN(firstTrialTime) || isNaN(totalRoomsCreated)) { console.error('解析试用状态数据失败,数据无效:', trialStatus); return null; } // 判断是否已使用试用账户 // 如果firstTrialTime > 0,说明用户已经注册过 const hasUsedTrial = firstTrialTime > 0; // 判断是否可以创建房间 const canCreateRoom = remainingTrials > 0; const result = { remainingTrials, firstTrialTime, totalRoomsCreated, hasUsedTrial, canCreateRoom, isRegistered: hasUsedTrial }; console.log('解析后的试用状态:', result); return result; } catch (error) { console.error('获取试用状态失败:', error); // 特殊错误处理 if (error.message && error.message.includes('open_metamask_install_page')) { console.error('MetaMask连接问题,可能需要重新安装或刷新页面'); // 尝试重新初始化MetaMask连接 if (typeof window.ethereum !== 'undefined') { try { await window.ethereum.request({ method: 'eth_requestAccounts' }); console.log('已重新请求MetaMask连接'); } catch (innerError) { console.error('重新连接MetaMask失败:', innerError); } } } // 检查是否是因为用户未注册 if (error.message && error.message.includes('user not registered')) { console.log('用户未注册试用账户'); // 返回默认的未注册状态 return { remainingTrials: 3, firstTrialTime: 0, totalRoomsCreated: 0, hasUsedTrial: false, canCreateRoom: false, isRegistered: false }; } return null; } } // 加载最小下注金额 async function loadMinBetAmount() { try { if (!contract) { console.log('合约未初始化,跳过最小下注金额加载'); return; } console.log('正在加载最小下注金额...'); const minBetAmount = await contract.methods.minBetAmount().call(); console.log('获取到最小下注金额:', minBetAmount); // 更新UI const betAmountInput = document.getElementById('betAmount'); const minBetText = document.getElementById('minBetText'); if (betAmountInput) { betAmountInput.min = minBetAmount; betAmountInput.placeholder = `输入下注金额 (最少${minBetAmount})`; } if (minBetText) { minBetText.textContent = `最小下注金额: ${minBetAmount} 代币`; } console.log('最小下注金额UI已更新'); } catch (error) { console.error('加载最小下注金额失败:', error); // 使用默认值 const minBetText = document.getElementById('minBetText'); if (minBetText) { minBetText.textContent = '最小下注金额: 1 代币 (默认)'; } } } // 白名单和试用机制已废除 - 以下试用相关函数已停用 /* async function checkTrialStatus() { console.log('开始检查试用状态...'); // 检查web3和合约是否已初始化 if (!web3 || !contract) { console.error('Web3或合约未初始化'); const errorMsg = '
    Web3或合约未初始化,请刷新页面重试
    '; const trialStatusContainer = document.getElementById('trialStatusContainer'); const trialStatusSummary = document.getElementById('trialStatusSummary'); if (trialStatusContainer) trialStatusContainer.innerHTML = errorMsg; if (trialStatusSummary) trialStatusSummary.innerHTML = errorMsg; return; } // 检查是否已连接钱包 if (!currentAccount) { console.log('钱包未连接'); const errorMsg = '
    请先连接钱包
    '; const trialStatusContainer = document.getElementById('trialStatusContainer'); const trialStatusSummary = document.getElementById('trialStatusSummary'); if (trialStatusContainer) trialStatusContainer.innerHTML = errorMsg; if (trialStatusSummary) { trialStatusSummary.innerHTML = errorMsg; // 添加连接钱包按钮 const connectBtn = ` `; trialStatusSummary.innerHTML += connectBtn; // 添加事件监听器 setTimeout(() => { const connectWalletBtnModal = document.getElementById('connectWalletBtnModal'); if (connectWalletBtnModal) { connectWalletBtnModal.addEventListener('click', connectWallet); } }, 100); } return; } try { showLoading(true); // 获取试用状态 console.log('调用getTrialStatus获取试用状态...'); const status = await getTrialStatus(); // 检查返回的状态是否有效 if (!status) { console.error('getTrialStatus返回null'); throw new Error('获取试用状态失败,返回值为空'); } console.log('试用状态:', status); // 解构状态数据 const { remainingTrials, firstTrialTime, totalRoomsCreated, hasUsedTrial, canCreateRoom, isRegistered } = status; // 格式化首次试用时间 let firstTrialTimeStr = '-'; if (firstTrialTime > 0) { firstTrialTimeStr = new Date(firstTrialTime * 1000).toLocaleString(); } // 更新模态框UI显示 let statusHtml = '
    '; statusHtml += '
    试用账户状态
    '; statusHtml += '
    '; statusHtml += '
      '; if (hasUsedTrial) { statusHtml += `
    • 已激活试用账户
    • `; statusHtml += `
    • 首次试用时间: ${firstTrialTimeStr}
    • `; statusHtml += `
    • 剩余创建房间次数: ${remainingTrials}
    • `; statusHtml += `
    • 已创建房间数: ${totalRoomsCreated}
    • `; statusHtml += `
    • 当前是否可创建房间: ${canCreateRoom ? '' : ''}
    • `; } else { statusHtml += '
    • 您尚未激活试用账户
    • '; statusHtml += '
    • 激活后可获得3次创建房间的机会
    • '; statusHtml += '
    • 试用期为30天
    • '; } statusHtml += '
    '; statusHtml += '
    '; statusHtml += '
    '; // 更新房间列表页面的试用状态摘要 let summaryHtml = ''; // 检查是否是白名单用户 console.log('检查白名单状态...'); const whitelistStatus = await isWhitelisted(); console.log('白名单状态:', whitelistStatus); if (whitelistStatus) { summaryHtml = `
    您是白名单用户,可以无限制创建房间
    `; } else if (hasUsedTrial) { const badgeClass = canCreateRoom ? 'bg-success' : 'bg-danger'; const badgeText = canCreateRoom ? '可用' : '不可用'; summaryHtml = `
    试用状态: 已激活 ${badgeText}
    ${remainingTrials}/3
    首次试用: ${firstTrialTimeStr}
    已创建房间: ${totalRoomsCreated}
    `; } else { summaryHtml = `
    您尚未激活试用账户
    `; } // 根据状态控制注册按钮 const registerBtn = document.getElementById('registerTrialBtn'); if (registerBtn) { if (hasUsedTrial) { console.log('用户已注册,禁用注册按钮'); registerBtn.disabled = true; registerBtn.textContent = '已注册'; } else { console.log('用户未注册,启用注册按钮'); registerBtn.disabled = false; registerBtn.textContent = '注册试用账户'; } } else { console.warn('未找到registerTrialBtn元素'); } // 更新UI const trialStatusContainer = document.getElementById('trialStatusContainer'); const trialStatusSummary = document.getElementById('trialStatusSummary'); if (trialStatusContainer) { trialStatusContainer.innerHTML = statusHtml; } else { console.warn('未找到trialStatusContainer元素'); } if (trialStatusSummary) { trialStatusSummary.innerHTML = summaryHtml; } else { console.warn('未找到trialStatusSummary元素'); } // 为新添加的按钮添加事件监听器 const openTrialModalBtn2 = document.getElementById('openTrialModalBtn2'); if (openTrialModalBtn2) { openTrialModalBtn2.addEventListener('click', () => { const trialModal = new bootstrap.Modal(document.getElementById('trialUserModal')); trialModal.show(); }); } console.log('试用状态检查完成'); } catch (error) { console.error('获取试用状态失败:', error); // 提供更详细的错误信息 let errorMsg = `
    获取试用状态失败: ${error.message || error}
    `; // 检查特定错误类型 if (error.message) { if (error.message.includes('user not registered')) { errorMsg = `
    您尚未注册试用账户,请点击"注册试用账户"按钮进行注册
    `; // 确保注册按钮可用 const registerBtn = document.getElementById('registerTrialBtn'); if (registerBtn) { registerBtn.disabled = false; registerBtn.textContent = '注册试用账户'; } } else if (error.message.includes('MetaMask') || error.message.includes('wallet')) { errorMsg = `
    钱包连接问题: ${error.message}。请刷新页面并确保MetaMask已解锁。
    `; } } const trialStatusContainer = document.getElementById('trialStatusContainer'); const trialStatusSummary = document.getElementById('trialStatusSummary'); if (trialStatusContainer) { trialStatusContainer.innerHTML = errorMsg; } if (trialStatusSummary) { trialStatusSummary.innerHTML = errorMsg; } } finally { showLoading(false); } } */ // 页面加载完成后初始化音效系统 document.addEventListener('DOMContentLoaded', function () { // 添加音效控制按钮 const soundToggle = document.createElement('button'); soundToggle.innerHTML = ''; soundToggle.className = 'btn btn-outline-light btn-sm position-fixed'; soundToggle.style.cssText = ` top: 20px; right: 20px; z-index: 1000; border-radius: 50%; width: 50px; height: 50px; background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); transition: all 0.3s ease; `; let soundEnabled = true; soundToggle.addEventListener('click', function () { soundEnabled = !soundEnabled; if (soundEnabled) { soundToggle.innerHTML = ''; startBackgroundMusic(); } else { soundToggle.innerHTML = ''; stopBackgroundMusic(); // 静音所有音效 document.querySelectorAll('audio').forEach(audio => { audio.muted = true; }); } }); document.body.appendChild(soundToggle); // 添加风格切换按钮 const styleToggle = document.createElement('button'); styleToggle.innerHTML = ''; styleToggle.className = 'btn btn-outline-light btn-sm position-fixed'; styleToggle.style.cssText = ` top: 80px; right: 20px; z-index: 1000; border-radius: 50%; width: 50px; height: 50px; background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); transition: all 0.3s ease; `; let currentStyle = 'classic'; // classic 或 modern styleToggle.addEventListener('click', function () { currentStyle = currentStyle === 'classic' ? 'modern' : 'classic'; switchTheme(currentStyle); // 更新按钮图标 if (currentStyle === 'modern') { styleToggle.innerHTML = ''; } else { styleToggle.innerHTML = ''; } }); document.body.appendChild(styleToggle); // 初始化默认主题 document.body.classList.add('classic-theme'); // 添加主题切换提示 function showThemeNotification(themeName) { const notification = document.createElement('div'); notification.className = 'position-fixed top-50 start-50 translate-middle'; notification.style.cssText = ` z-index: 10001; background: rgba(0, 0, 0, 0.8); color: white; padding: 15px 25px; border-radius: 10px; font-family: 'Cinzel', serif; font-weight: 600; text-align: center; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); transform: translate(-50%, -50%) scale(0); transition: all 0.3s ease; `; notification.innerHTML = `
    🎨 主题切换
    ${themeName}
    `; document.body.appendChild(notification); // 动画显示 setTimeout(() => { notification.style.transform = 'translate(-50%, -50%) scale(1)'; }, 10); // 2秒后移除 setTimeout(() => { notification.style.transform = 'translate(-50%, -50%) scale(0)'; setTimeout(() => { if (document.body.contains(notification)) { document.body.removeChild(notification); } }, 300); }, 2000); } // 更新主题切换函数 const originalSwitchTheme = window.switchTheme; window.switchTheme = function (theme) { originalSwitchTheme(theme); const themeName = theme === 'classic' ? '经典奢华风格' : '现代科技风格'; showThemeNotification(themeName); }; // 用户首次交互后启动背景音乐 let musicStarted = false; function startMusicOnInteraction() { if (!musicStarted && soundEnabled) { startBackgroundMusic(); musicStarted = true; document.removeEventListener('click', startMusicOnInteraction); document.removeEventListener('keydown', startMusicOnInteraction); } } document.addEventListener('click', startMusicOnInteraction); document.addEventListener('keydown', startMusicOnInteraction); // 添加页面加载动画 const loadingOverlay = document.createElement('div'); loadingOverlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(135deg, #0f0f23 0%, #1a1a3e 25%, #2d1b69 50%, #1a1a3e 75%, #0f0f23 100%); display: flex; justify-content: center; align-items: center; z-index: 9999; transition: opacity 1s ease; `; loadingOverlay.innerHTML = `
    ♠ ♥ ♦ ♣

    大吃小游戏

    正在加载豪华赌场...

    `; document.body.appendChild(loadingOverlay); // 2秒后移除加载动画 setTimeout(() => { loadingOverlay.style.opacity = '0'; setTimeout(() => { if (document.body.contains(loadingOverlay)) { document.body.removeChild(loadingOverlay); } }, 1000); }, 2000); // 移动端钱包引导 const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); if (isMobile) { addMobileWalletGuide(); } // 延迟自动连接钱包,确保所有变量都已初始化 setTimeout(() => { // 确保contractAbi已定义后再尝试自动连接 if (typeof contractAbi !== 'undefined') { autoConnectWallet(); } else { console.log('等待contractAbi初始化...'); // 如果contractAbi还未定义,继续等待 const checkAbi = setInterval(() => { if (typeof contractAbi !== 'undefined') { clearInterval(checkAbi); autoConnectWallet().then(() => { // 自动连接后也启动邀请统计 loadInviteStats(); startInviteRealtime(); }); } }, 500); } }, 3000); // 增加延迟时间,确保页面完全加载 // 初始化玩家座位折叠状态 initializePlayerSeatsCollapse(); // 监听玩家座位的动态创建 const observer = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { if (mutation.type === 'childList') { mutation.addedNodes.forEach(function (node) { if (node.nodeType === 1 && (node.classList.contains('player-seat') || node.querySelector('.player-seat'))) { // 延迟一点时间确保元素完全渲染 setTimeout(() => { initializePlayerSeatsCollapse(); }, 100); } }); } }); }); // 开始观察座位容器的变化 const seatContainer = document.getElementById('seatContainer'); if (seatContainer) { observer.observe(seatContainer, { childList: true, subtree: true }); } // 监听钱包账户变化 if (typeof window.ethereum !== 'undefined') { window.ethereum.on('accountsChanged', function (accounts) { console.log('钱包账户发生变化:', accounts); if (accounts.length > 0) { // 账户切换,自动更新 stopInviteRealtime(); autoConnectWallet().then(() => { loadInviteStats(); startInviteRealtime(); }); } else { // 账户断开连接,重置UI window.userAccount = null; const connectBtn = document.getElementById('connectWallet'); const walletInfo = document.getElementById('walletInfo'); if (connectBtn) connectBtn.style.display = 'block'; if (walletInfo) walletInfo.style.display = 'none'; // 重置邀请统计 stopInviteRealtime(); const countEl = document.getElementById('inviteCount'); const earnEl = document.getElementById('inviteEarnings'); const gamesEl = document.getElementById('inviteGames'); if (countEl) countEl.textContent = '0'; if (earnEl) earnEl.textContent = '0'; if (gamesEl) gamesEl.textContent = '0'; console.log('钱包已断开连接'); } }); window.ethereum.on('chainChanged', function (chainId) { console.log('网络发生变化:', chainId); // 网络变化时重新加载页面或重新连接 window.location.reload(); }); } // 添加加载动画CSS const style = document.createElement('style'); style.textContent = ` @keyframes loading-bar { 0% { transform: translateX(-100%); } 100% { transform: translateX(0); } } /* ==================== 现代科技风格主题 ==================== */ .modern-theme { background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 25%, #16213e 50%, #1a1a2e 75%, #0a0a0a 100%), radial-gradient(circle at 20% 80%, rgba(0, 255, 255, 0.1) 0%, transparent 50%), radial-gradient(circle at 80% 20%, rgba(255, 0, 255, 0.1) 0%, transparent 50%); } .modern-theme::before { background-image: radial-gradient(circle at 25% 25%, rgba(0, 255, 255, 0.2) 0%, transparent 50%), radial-gradient(circle at 75% 75%, rgba(255, 0, 255, 0.2) 0%, transparent 50%), linear-gradient(45deg, transparent 48%, rgba(0, 255, 255, 0.03) 49%, rgba(0, 255, 255, 0.03) 51%, transparent 52%); } /* 现代风格卡片 */ .modern-theme .card { background: rgba(0, 0, 0, 0.7); border: 1px solid rgba(0, 255, 255, 0.3); box-shadow: 0 8px 32px rgba(0, 255, 255, 0.2), 0 0 0 1px rgba(0, 255, 255, 0.1), inset 0 1px 0 rgba(0, 255, 255, 0.1); } .modern-theme .card:hover { border: 1px solid rgba(0, 255, 255, 0.6); box-shadow: 0 15px 40px rgba(0, 255, 255, 0.3), 0 0 0 1px rgba(0, 255, 255, 0.3), inset 0 1px 0 rgba(0, 255, 255, 0.2); } /* 现代风格卡片头部 */ .modern-theme .card-header { background: linear-gradient(135deg, #00d4ff 0%, #ff00ff 100%); font-family: 'Orbitron', 'Arial', sans-serif; text-transform: uppercase; letter-spacing: 2px; } .modern-theme .card-header::before { background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent); } /* 现代风格按钮 */ .modern-theme .btn-primary { background: linear-gradient(135deg, #00d4ff 0%, #ff00ff 100%); border: 1px solid rgba(0, 255, 255, 0.5); box-shadow: 0 4px 15px rgba(0, 212, 255, 0.4), 0 0 0 1px rgba(0, 255, 255, 0.2); font-family: 'Orbitron', 'Arial', sans-serif; text-transform: uppercase; letter-spacing: 1px; } .modern-theme .btn-primary:hover { background: linear-gradient(135deg, #ff00ff 0%, #00d4ff 100%); box-shadow: 0 8px 25px rgba(255, 0, 255, 0.6), 0 0 0 1px rgba(255, 0, 255, 0.3), 0 0 20px rgba(0, 255, 255, 0.3); } /* 现代风格牌桌 */ .modern-theme .game-table { background: radial-gradient(ellipse at center, #001122 0%, #002244 30%, #001122 100%), linear-gradient(45deg, #000011 0%, #002244 50%, #000011 100%); border: none; box-shadow: 0 20px 60px rgba(0, 255, 255, 0.3), 0 0 0 2px #00d4ff, 0 0 0 4px rgba(0, 212, 255, 0.3), inset 0 0 50px rgba(0, 255, 255, 0.1); } .modern-theme .game-table:hover { box-shadow: 0 25px 80px rgba(0, 255, 255, 0.4), 0 0 0 2px #ff00ff, 0 0 0 4px rgba(255, 0, 255, 0.3), inset 0 0 50px rgba(255, 0, 255, 0.1); } .modern-theme .game-table::before { background-image: radial-gradient(circle at 25% 25%, rgba(0, 255, 255, 0.1) 0%, transparent 50%), radial-gradient(circle at 75% 75%, rgba(255, 0, 255, 0.1) 0%, transparent 50%), url('data:image/svg+xml;utf8, '); } .modern-theme .game-table::after { content: '◆ ◇ ◆ ◇'; color: rgba(0, 255, 255, 0.3); text-shadow: 0 0 10px rgba(0, 255, 255, 0.5); animation: cyber-glow 2s ease-in-out infinite alternate; } @keyframes cyber-glow { 0% { opacity: 0.3; text-shadow: 0 0 10px rgba(0, 255, 255, 0.5); transform: translate(-50%, -50%) scale(1); } 100% { opacity: 0.6; text-shadow: 0 0 20px rgba(0, 255, 255, 0.8), 0 0 30px rgba(255, 0, 255, 0.4); transform: translate(-50%, -50%) scale(1.05); } } /* 现代风格扑克牌 */ .modern-theme .playing-card { background: linear-gradient(145deg, #1a1a2e 0%, #16213e 100%); border: 2px solid #00d4ff; box-shadow: 0 8px 25px rgba(0, 255, 255, 0.3), 0 0 0 1px rgba(0, 255, 255, 0.2), inset 0 1px 0 rgba(0, 255, 255, 0.1); color: #00d4ff; } .modern-theme .playing-card::before { background: linear-gradient(145deg, rgba(0, 255, 255, 0.1) 0%, rgba(255, 0, 255, 0.05) 100%); } .modern-theme .playing-card:hover { border: 2px solid #ff00ff; box-shadow: 0 15px 40px rgba(255, 0, 255, 0.4), 0 0 0 2px rgba(255, 0, 255, 0.3), inset 0 1px 0 rgba(255, 0, 255, 0.2), 0 0 20px rgba(0, 255, 255, 0.3); } .modern-theme .playing-card.card-back { background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 25%, #16213e 50%, #1a1a2e 75%, #0a0a0a 100%), radial-gradient(circle at 30% 30%, rgba(0, 255, 255, 0.2) 0%, transparent 50%); border: 2px solid #00d4ff; background-image: url('data:image/svg+xml;utf8, '); } .modern-theme .playing-card.card-back::after { background: linear-gradient(90deg, transparent, rgba(0, 255, 255, 0.4), transparent); } /* 现代风格玩家头像 */ .modern-theme .player-avatar { border: 2px solid rgba(0, 255, 255, 0.5); box-shadow: 0 0 10px rgba(0, 255, 255, 0.3); } /* 现代风格座位 */ .modern-theme .player-seat { background-color: rgba(0, 0, 0, 0.8); border: 1px solid rgba(0, 255, 255, 0.3); box-shadow: 0 5px 15px rgba(0, 255, 255, 0.2); } /* 现代风格赢家效果 */ .modern-theme .winner { background-color: rgba(0, 255, 255, 0.1); border: 2px solid #00d4ff; box-shadow: 0 0 20px rgba(0, 255, 255, 0.5); } @keyframes modern-winner-pulse { 0% { box-shadow: 0 0 20px #00d4ff, 0 0 35px #ff00ff, 0 0 50px rgba(0, 255, 255, 0.5); transform: scale(1.2) rotateY(0deg); } 50% { box-shadow: 0 0 30px #ff00ff, 0 0 50px #00d4ff, 0 0 70px rgba(255, 0, 255, 0.7); transform: scale(1.25) rotateY(0deg); } 100% { box-shadow: 0 0 20px #00d4ff, 0 0 35px #ff00ff, 0 0 50px rgba(0, 255, 255, 0.5); transform: scale(1.2) rotateY(0deg); } } /* 现代风格导航栏 */ .modern-theme .navbar { background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 100%) !important; border-bottom: 1px solid rgba(0, 255, 255, 0.3); box-shadow: 0 2px 20px rgba(0, 255, 255, 0.2); } .modern-theme .navbar-brand { font-family: 'Orbitron', sans-serif !important; font-weight: 700; color: #00d4ff !important; text-shadow: 0 0 10px rgba(0, 212, 255, 0.5); } .modern-theme .nav-link { font-family: 'Orbitron', sans-serif !important; color: rgba(0, 255, 255, 0.8) !important; transition: all 0.3s ease; } .modern-theme .nav-link:hover, .modern-theme .nav-link.active { color: #ff00ff !important; text-shadow: 0 0 5px rgba(255, 0, 255, 0.5); } /* 现代风格表格 */ .modern-theme .table { color: rgba(0, 255, 255, 0.9); } .modern-theme .table th { border-color: rgba(0, 255, 255, 0.3); color: #00d4ff; font-family: 'Orbitron', sans-serif; } .modern-theme .table td { border-color: rgba(0, 255, 255, 0.2); } .modern-theme .table-hover tbody tr:hover { background-color: rgba(0, 255, 255, 0.1); } /* 现代风格徽章 */ .modern-theme .badge { background: linear-gradient(135deg, #00d4ff 0%, #ff00ff 100%) !important; font-family: 'Orbitron', sans-serif; text-shadow: none; } /* 现代风格输入框 */ .modern-theme .form-control { background: rgba(0, 0, 0, 0.5); border: 1px solid rgba(0, 255, 255, 0.3); color: #00d4ff; } .modern-theme .form-control:focus { background: rgba(0, 0, 0, 0.7); border-color: #ff00ff; box-shadow: 0 0 10px rgba(255, 0, 255, 0.3); color: #ff00ff; } /* 现代风格警告框 */ .modern-theme .alert { background: rgba(0, 0, 0, 0.8); border: 1px solid rgba(0, 255, 255, 0.3); color: #00d4ff; } .modern-theme .alert-warning { border-color: rgba(255, 193, 7, 0.3); color: #ffc107; } .modern-theme .alert-success { border-color: rgba(0, 255, 255, 0.3); color: #00d4ff; } `; document.head.appendChild(style); }); // 移动端钱包引导函数 function addMobileWalletGuide() { // 添加移动端提示按钮 const mobileGuideBtn = document.createElement('button'); mobileGuideBtn.innerHTML = ''; mobileGuideBtn.className = 'btn btn-outline-light btn-sm position-fixed'; mobileGuideBtn.style.cssText = ` top: 140px; right: 20px; z-index: 1000; border-radius: 50%; width: 50px; height: 50px; background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); transition: all 0.3s ease; `; mobileGuideBtn.addEventListener('click', showMobileWalletGuide); document.body.appendChild(mobileGuideBtn); // 如果没有检测到钱包,自动显示引导 if (!window.ethereum) { setTimeout(() => { showMobileWalletGuide(); }, 5000); } } // 显示移动端钱包引导 function showMobileWalletGuide() { const modal = document.createElement('div'); modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); display: flex; justify-content: center; align-items: center; z-index: 10000; backdrop-filter: blur(10px); `; modal.innerHTML = `

    📱 移动端钱包连接指南

    🔗 推荐连接方式:

    1. MetaMask 应用内浏览器

    • 打开 MetaMask 应用

    • 点击底部"浏览器"标签

    • 输入网址访问游戏

    2. 其他钱包应用

    • Trust Wallet 内置浏览器

    • TokenPocket 浏览器

    • imToken 浏览器

    ⚠️ 常见问题解决:

    • 连接超时:切换到钱包应用内浏览器

    • 无法连接:确保钱包应用已解锁

    • 网络错误:检查是否连接到正确网络

    `; // 点击背景关闭 modal.addEventListener('click', function (e) { if (e.target === modal) { modal.remove(); } }); document.body.appendChild(modal); } // 在MetaMask中打开 function openInMetaMask() { const currentUrl = window.location.href; const metamaskUrl = `https://metamask.app.link/dapp/${window.location.host}${window.location.pathname}${window.location.search}`; try { window.location.href = metamaskUrl; } catch (error) { // 如果深度链接失败,提供手动指导 alert('请手动打开MetaMask应用,在浏览器中访问:\n' + currentUrl); } } // 检测是否为移动设备 function isMobileDevice() { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth <= 768; } // 初始化玩家座位的折叠状态 function initializePlayerSeatsCollapse() { const isMobile=isMobileDevice(); const playerSeats=document.querySelectorAll('.player-seat'); console.log(`初始化折叠状态,设备类型: ${isMobile ? '移动端' : '桌面端' },找到 ${playerSeats.length} 个座位`); playerSeats.forEach(seat=> { const contentElement = seat.querySelector('.player-info-content'); const indicator = seat.querySelector('.collapse-indicator'); // 检查是否已经初始化过,避免覆盖用户的手动操作 if (seat.hasAttribute('data-collapse-initialized')) { return; // 已经初始化过,跳过 } // 标记为已初始化 seat.setAttribute('data-collapse-initialized', 'true'); if (contentElement) { if (isMobile) { // 移动端默认折叠 contentElement.classList.remove('expanded'); contentElement.classList.add('collapsed'); if (indicator) indicator.classList.remove('expanded'); seat.classList.add('collapsed'); console.log('移动端座位设置为折叠状态'); } else { // 桌面端默认展开 contentElement.classList.remove('collapsed'); contentElement.classList.add('expanded'); if (indicator) indicator.classList.add('expanded'); seat.classList.remove('collapsed'); console.log('桌面端座位设置为展开状态'); } } }); } // 监听窗口大小变化,自动调整折叠状态 window.addEventListener('resize', function () { // 防抖处理 clearTimeout(window.resizeTimeout); window.resizeTimeout = setTimeout(function () { initializePlayerSeatsCollapse(); }, 250); }); // 切换玩家信息显示/隐藏 function togglePlayerInfo(headerElement) { // 防止事件冒泡和重复触发 if (window.event) { window.event.stopPropagation(); window.event.preventDefault(); } const contentElement = headerElement.nextElementSibling; const indicator = headerElement.querySelector('.collapse-indicator'); const seatElement = headerElement.closest('.player-seat'); // 添加防抖机制 if (seatElement.hasAttribute('data-toggling')) { return; // 正在切换中,忽略重复点击 } seatElement.setAttribute('data-toggling', 'true'); // 检查当前状态并切换 const isCurrentlyCollapsed = contentElement.classList.contains('collapsed'); console.log('点击前状态:', { collapsed: contentElement.classList.contains('collapsed'), expanded: contentElement.classList.contains('expanded'), height: contentElement.offsetHeight, opacity: window.getComputedStyle(contentElement).opacity, display: window.getComputedStyle(contentElement).display, maxHeight: window.getComputedStyle(contentElement).maxHeight, classList: Array.from(contentElement.classList), innerHTML: contentElement.innerHTML.substring(0, 100) + '...' }); if (isCurrentlyCollapsed) { // 当前是折叠状态,切换为展开 contentElement.classList.remove('collapsed'); contentElement.classList.add('expanded'); if (indicator) indicator.classList.add('expanded'); seatElement.classList.remove('collapsed'); // 添加临时边框帮助调试 contentElement.style.border = '2px solid green'; console.log('展开玩家信息'); } else { // 当前是展开状态,切换为折叠 contentElement.classList.remove('expanded'); contentElement.classList.add('collapsed'); if (indicator) indicator.classList.remove('expanded'); seatElement.classList.add('collapsed'); // 添加临时边框帮助调试 contentElement.style.border = '2px solid red'; console.log('折叠玩家信息'); } // 显示切换后状态 setTimeout(() => { console.log('点击后状态:', { collapsed: contentElement.classList.contains('collapsed'), expanded: contentElement.classList.contains('expanded'), height: contentElement.offsetHeight, opacity: window.getComputedStyle(contentElement).opacity, display: window.getComputedStyle(contentElement).display, maxHeight: window.getComputedStyle(contentElement).maxHeight, classList: Array.from(contentElement.classList), innerHTML: contentElement.innerHTML.substring(0, 100) + '...' }); }, 100); // 500ms后移除防抖标记 setTimeout(() => { seatElement.removeAttribute('data-toggling'); }, 500); } // 监听窗口大小变化,自动调整折叠状态 window.addEventListener('resize', function () { // 防抖处理 clearTimeout(window.resizeTimeout); window.resizeTimeout = setTimeout(function () { initializePlayerSeatsCollapse(); }, 250); }); // 转换现有玩家座位为可折叠结构 function convertExistingSeatsToCollapsible() { const playerSeats = document.querySelectorAll('.player-seat'); console.log(`转换座位:找到 ${playerSeats.length} 个座位`); playerSeats.forEach((seat, index) => { console.log(`检查座位 ${index}:`, seat); // 检查是否已经是可折叠结构 if (seat.querySelector('.player-info-header')) { console.log(`座位 ${index} 已经是可折叠结构,跳过`); return; // 已经是可折叠结构,跳过 } // 获取座位的所有内容 const seatHTML = seat.innerHTML; console.log(`座位 ${index} 的HTML内容:`, seatHTML); // 查找现有的玩家卡片 const playerCard = seat.querySelector('.player-card'); console.log(`座位 ${index} 的player-card:`, playerCard); if (!playerCard) { // 如果没有.player-card,尝试直接转换整个座位内容 console.log(`座位 ${index} 没有.player-card,尝试直接转换`); // 查找头像和名称元素 const avatarElement = seat.querySelector('.player-avatar'); const nameElement = seat.querySelector('h5'); const badgeElement = seat.querySelector('.badge'); console.log(`座位 ${index} 元素检查:`, { avatar: avatarElement, name: nameElement, badge: badgeElement }); if (avatarElement && nameElement) { // 获取其他内容(除了头部信息) const allContent = seat.innerHTML; // 创建新的可折叠结构 const newHTML = `
    ${avatarElement.outerHTML} ${nameElement.outerHTML}
    ${badgeElement ? badgeElement.outerHTML + ' ' : ''}
    `; // 替换内容 seat.innerHTML = newHTML; console.log(`座位 ${index} 转换完成`); } else { console.log(`座位 ${index} 缺少必要元素,跳过转换`); } return; } // 原有的.player-card转换逻辑 const existingContent = playerCard.innerHTML; const avatarElement = playerCard.querySelector('.player-avatar'); const nameElement = playerCard.querySelector('h5'); const badgeElement = playerCard.querySelector('.badge'); const cardElements = playerCard.querySelectorAll('.playing-card'); const otherContent = playerCard.querySelector('.d-flex.align-items-center:not(:first-child)'); if (!avatarElement || !nameElement) { console.log(`座位 ${index} 的.player-card结构不匹配,跳过`); return; } // 创建新的可折叠结构 const newHTML = `
    ${avatarElement.outerHTML} ${nameElement.outerHTML}
    ${badgeElement ? badgeElement.outerHTML + ' ' : ''}
    `; // 替换内容 seat.innerHTML = newHTML; console.log(`座位 ${index} (.player-card) 转换完成`); }); } // 页面加载完成后初始化 document.addEventListener('DOMContentLoaded', function () { // 转换现有座位为可折叠结构 setTimeout(() => { convertExistingSeatsToCollapsible(); initializePlayerSeatsCollapse(); }, 1000); // 监听玩家座位的动态创建 const observer = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { if (mutation.type === 'childList') { mutation.addedNodes.forEach(function (node) { if (node.nodeType === 1 && (node.classList.contains('player-seat') || node.querySelector('.player-seat'))) { // 延迟一点时间确保元素完全渲染 setTimeout(() => { convertExistingSeatsToCollapsible(); initializePlayerSeatsCollapse(); }, 100); } }); } }); }); // 开始观察座位容器的变化 const seatContainer = document.getElementById('seatContainer'); if (seatContainer) { observer.observe(seatContainer, { childList: true, subtree: true }); } // 观察结果模态框中的座位容器 const resultSeatContainer = document.getElementById('resultSeatContainer'); if (resultSeatContainer) { observer.observe(resultSeatContainer, { childList: true, subtree: true }); } // 定期检查并转换新的座位(优化移动端体验,降低刷新频率) setInterval(() => { convertExistingSeatsToCollapsible(); // 只对新座位初始化,不重复初始化已有座位 const uninitializedSeats = document.querySelectorAll('.player-seat:not([data-collapse-initialized])'); if (uninitializedSeats.length > 0) { initializePlayerSeatsCollapse(); } }, 60000); // 从15秒改为60秒,进一步减少移动端闪动 });