`;
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.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 +=`
| ${formatAddress(players[i])} |
${scores[i]} |
${isWinner ? '赢家' : '输家'} |
`;
}
resultContent.innerHTML += `
`;
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=`
`;
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 = `
排行榜每小时更新一次,展示邀请人数最多的用户
排行榜奖励
- 🥇 第1名:额外获得平台收益的1%分红
- 🥈 第2名:额外获得平台收益的0.5%分红
- 🥉 第3名:额外获得平台收益的0.3%分红
- 🏆 前10名:获得专属排行榜徽章
`;
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}
首次试用: ${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 = `
${allContent.replace(avatarElement.outerHTML, '').replace(nameElement.outerHTML, '').replace(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 = `
${existingContent.replace(avatarElement.outerHTML, '').replace(nameElement.outerHTML, '').replace(badgeElement
? badgeElement.outerHTML : '', '').replace(/class="d-flex justify-content-between align-items-center
mb-1"[^>]*>[\s\S]*?<\ /div>/, '')}
`;
// 替换内容
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秒,进一步减少移动端闪动
});