// 全局状态管理 const state = { players: [ { id: 0, name: '北家', score: 25000, color: 'rgba(255, 100, 100, 0.6)', is_tenpai: false }, { id: 1, name: '东家', score: 25000, color: 'rgba(100, 255, 100, 0.6)', is_tenpai: false }, { id: 2, name: '南家', score: 25000, color: 'rgba(100, 100, 255, 0.6)', is_tenpai: false }, { id: 3, name: '西家', score: 25000, color: 'rgba(255, 255, 100, 0.6)', is_tenpai: false } ], honba: 0, riichi_sticks: 0 }; // DOM元素 const elements = { playerPanels: document.querySelectorAll('.player-panel'), honbaCount: document.getElementById('honba-count'), riichiCount: document.getElementById('riichi-count'), winAnimation: document.getElementById('win-animation'), winText: document.querySelector('.win-text'), winDetails: document.querySelector('.win-details'), winScore: document.querySelector('.win-score'), scoreChangeIndicators: document.getElementById('score-change-indicators'), backgroundVideo: document.getElementById('backgroundVideo') }; // 初始化 function init() { // 设置事件监听 setupEventListeners(); // 初始化显示 updatePlayerDisplays(); updateGameInfo(); } // 设置事件监听 function setupEventListeners() { // 监听来自控制面板的消息 window.addEventListener('message', handleMessage); // 支持本地WebSocket(用于本地测试) if (window.location.hash === '#ws') { setupWebSocket(); } } // WebSocket设置(可选) function setupWebSocket() { try { const ws = new WebSocket('ws://localhost:8080'); ws.onopen = () => console.log('WebSocket连接已建立'); ws.onmessage = (event) => { try { const data = JSON.parse(event.data); handleEvent(data); } catch (e) { console.error('解析WebSocket消息失败:', e); } }; ws.onerror = (error) => console.error('WebSocket错误:', error); ws.onclose = () => console.log('WebSocket连接已关闭'); } catch (e) { console.warn('WebSocket连接失败,将使用postMessage:', e); } } // 处理消息 function handleMessage(event) { try { const data = event.data; if (typeof data === 'string') { handleEvent(JSON.parse(data)); } else if (typeof data === 'object') { handleEvent(data); } } catch (e) { console.error('处理消息失败:', e); } } // 处理各种事件 function handleEvent(event) { console.log('收到事件:', event); switch (event.event) { case 'update_player': handleUpdatePlayer(event); break; case 'win': handleWin(event); break; case 'adjust_score': handleAdjustScore(event); break; case 'set_tenpai': handleSetTenpai(event); break; case 'set_honba_riichi': handleSetHonbaRiichi(event); break; case 'set_style': handleSetStyle(event); break; case 'set_video_source': handleSetVideoSource(event); break; default: console.warn('未知事件类型:', event.event); } } // 更新玩家信息 function handleUpdatePlayer(event) { const player = state.players[event.player_index]; if (player) { if (event.name !== undefined) player.name = event.name; if (event.score !== undefined) { const oldScore = player.score; player.score = event.score; showScoreChange(event.player_index, player.score - oldScore); } if (event.color !== undefined) player.color = event.color; if (event.is_tenpai !== undefined) player.is_tenpai = event.is_tenpai; updatePlayerDisplay(event.player_index); } } // 处理胡牌事件 function handleWin(event) { const winner = state.players[event.winner]; // 显示胡牌动画 elements.winText.textContent = `${winner.name} 胡牌!`; elements.winDetails.textContent = `${event.han}番 ${event.fu}符${event.is_tsumo ? ' 自摸' : ''}`; elements.winScore.textContent = `${event.text || ''} +${event.points}点`; elements.winAnimation.style.display = 'block'; // 计算分数分配 const losersCount = event.losers?.length || 0; const pointsPerLoser = losersCount > 0 ? Math.floor(event.points / losersCount) : 0; // 更新分数 state.players[event.winner].score += event.points; showScoreChange(event.winner, event.points); event.losers?.forEach(loserIndex => { state.players[loserIndex].score -= pointsPerLoser; showScoreChange(loserIndex, -pointsPerLoser); }); // 更新显示 updatePlayerDisplays(); // 3秒后隐藏胡牌动画 setTimeout(() => { elements.winAnimation.style.display = 'none'; }, 3000); } // 调整分数 function handleAdjustScore(event) { const player = state.players[event.player_index]; if (player) { player.score += event.delta; showScoreChange(event.player_index, event.delta); updatePlayerDisplay(event.player_index); } } // 设置听牌状态 function handleSetTenpai(event) { const player = state.players[event.player_index]; if (player) { player.is_tenpai = event.is_tenpai; updatePlayerDisplay(event.player_index); } } // 设置本场和立直棒 function handleSetHonbaRiichi(event) { if (event.honba !== undefined) state.honba = event.honba; if (event.riichi_sticks !== undefined) state.riichi_sticks = event.riichi_sticks; updateGameInfo(); } // 设置样式 function handleSetStyle(event) { const panel = elements.playerPanels[event.player_index]; if (panel) { if (event.opacity !== undefined) { const bgColor = state.players[event.player_index].color; const rgbaMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); if (rgbaMatch) { panel.style.backgroundColor = `rgba(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]}, ${event.opacity})`; } } if (event.font_size !== undefined) { panel.querySelector('.player-name').style.fontSize = `${event.font_size}px`; panel.querySelector('.player-score').style.fontSize = `${event.font_size * 1.3}px`; } } } // 设置视频源 function handleSetVideoSource(event) { if (event.src) { elements.backgroundVideo.src = event.src; elements.backgroundVideo.load(); } } // 更新所有玩家显示 function updatePlayerDisplays() { state.players.forEach((player, index) => { updatePlayerDisplay(index); }); } // 更新单个玩家显示 function updatePlayerDisplay(index) { const panel = elements.playerPanels[index]; const player = state.players[index]; if (panel && player) { panel.querySelector('.player-name').textContent = player.name; panel.querySelector('.player-score').textContent = player.score; panel.querySelector('.tenpai-indicator').style.display = player.is_tenpai ? 'block' : 'none'; panel.style.backgroundColor = player.color; } } // 更新游戏信息显示 function updateGameInfo() { elements.honbaCount.textContent = state.honba; elements.riichiCount.textContent = state.riichi_sticks; } // 显示分数变动动画 function showScoreChange(playerIndex, delta) { if (delta === 0) return; const panel = elements.playerPanels[playerIndex]; const rect = panel.getBoundingClientRect(); const indicator = document.createElement('div'); indicator.className = 'score-change'; indicator.textContent = delta > 0 ? `+${delta}` : `${delta}`; indicator.style.color = delta > 0 ? '#4CAF50' : '#f44336'; indicator.style.left = `${rect.left + rect.width / 2}px`; indicator.style.top = `${rect.top + rect.height / 2}px`; indicator.style.transform = 'translate(-50%, -50%)'; elements.scoreChangeIndicators.appendChild(indicator); // 动画结束后移除 setTimeout(() => { indicator.remove(); }, 1500); } // 测试函数(用于手动触发事件) window.testEvent = function(event) { handleEvent(event); }; // 初始化应用 window.addEventListener('DOMContentLoaded', init);