Files
2026-01-18 20:49:14 +08:00

271 lines
8.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 全局状态管理
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);