271 lines
8.4 KiB
JavaScript
271 lines
8.4 KiB
JavaScript
// 全局状态管理
|
||
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); |