737 lines
25 KiB
HTML
737 lines
25 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>麻将直播叠加系统</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Microsoft YaHei', Arial, sans-serif;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* 覆盖层样式 */
|
|
#overlay {
|
|
position: relative;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
background: #000;
|
|
}
|
|
|
|
#video-container {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #666;
|
|
font-size: 24px;
|
|
}
|
|
|
|
#video-stream {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.player-panel {
|
|
position: absolute;
|
|
backdrop-filter: blur(10px);
|
|
border-radius: 12px;
|
|
padding: 16px 24px;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
min-width: 200px;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.player-panel.east {
|
|
top: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
background: rgba(239, 68, 68, 0.8);
|
|
}
|
|
|
|
.player-panel.south {
|
|
right: 20px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
background: rgba(34, 197, 94, 0.8);
|
|
}
|
|
|
|
.player-panel.west {
|
|
bottom: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
background: rgba(59, 130, 246, 0.8);
|
|
}
|
|
|
|
.player-panel.north {
|
|
left: 20px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
background: rgba(234, 179, 8, 0.8);
|
|
}
|
|
|
|
.player-info {
|
|
color: white;
|
|
}
|
|
|
|
.player-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 4px;
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.position-badge {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.riichi-icon {
|
|
width: 16px;
|
|
height: 16px;
|
|
color: #fef08a;
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
.player-score {
|
|
font-size: 28px;
|
|
font-weight: bold;
|
|
color: white;
|
|
}
|
|
|
|
.game-info {
|
|
position: absolute;
|
|
top: 20px;
|
|
right: 20px;
|
|
background: rgba(17, 24, 39, 0.8);
|
|
backdrop-filter: blur(10px);
|
|
border-radius: 8px;
|
|
padding: 12px 16px;
|
|
color: white;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.win-animation {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
backdrop-filter: blur(8px);
|
|
animation: fadeIn 0.5s;
|
|
}
|
|
|
|
.win-animation.active {
|
|
display: flex;
|
|
}
|
|
|
|
.win-card {
|
|
background: linear-gradient(135deg, #f59e0b 0%, #ea580c 100%);
|
|
border-radius: 24px;
|
|
padding: 48px 64px;
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
|
border: 4px solid #fde047;
|
|
text-align: center;
|
|
animation: zoomIn 0.3s;
|
|
}
|
|
|
|
.win-icon {
|
|
width: 64px;
|
|
height: 64px;
|
|
margin: 0 auto 16px;
|
|
color: white;
|
|
}
|
|
|
|
.win-player {
|
|
color: white;
|
|
font-size: 36px;
|
|
font-weight: bold;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.win-points {
|
|
color: white;
|
|
font-size: 56px;
|
|
font-weight: bold;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.win-total {
|
|
color: #fef3c7;
|
|
font-size: 32px;
|
|
}
|
|
|
|
/* 控制面板样式 */
|
|
#control-panel {
|
|
display: none;
|
|
width: 100vw;
|
|
min-height: 100vh;
|
|
background: #111827;
|
|
color: white;
|
|
padding: 24px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
#control-panel.active {
|
|
display: block;
|
|
}
|
|
|
|
.control-header {
|
|
max-width: 1200px;
|
|
margin: 0 auto 24px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.control-header h1 {
|
|
font-size: 28px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.btn {
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: #3b82f6;
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: #2563eb;
|
|
}
|
|
|
|
.btn-success {
|
|
background: #10b981;
|
|
color: white;
|
|
}
|
|
|
|
.btn-success:hover {
|
|
background: #059669;
|
|
}
|
|
|
|
.control-grid {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 24px;
|
|
}
|
|
|
|
.control-section {
|
|
background: #1f2937;
|
|
border-radius: 12px;
|
|
padding: 24px;
|
|
}
|
|
|
|
.control-section h2 {
|
|
font-size: 20px;
|
|
margin-bottom: 16px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.player-control {
|
|
background: #374151;
|
|
border-radius: 8px;
|
|
padding: 16px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.player-control-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.player-control-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.form-group label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.form-group input,
|
|
.form-group select {
|
|
width: 100%;
|
|
padding: 8px 12px;
|
|
background: #4b5563;
|
|
border: none;
|
|
border-radius: 6px;
|
|
color: white;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.form-group input[type="checkbox"] {
|
|
width: auto;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.form-row {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 16px;
|
|
}
|
|
|
|
.btn-riichi {
|
|
padding: 6px 12px;
|
|
background: #4b5563;
|
|
border: none;
|
|
border-radius: 6px;
|
|
color: white;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.btn-riichi.active {
|
|
background: #ca8a04;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
|
|
@keyframes zoomIn {
|
|
from { transform: scale(0.8); }
|
|
to { transform: scale(1); }
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
}
|
|
|
|
/* SVG 图标 */
|
|
.icon {
|
|
width: 24px;
|
|
height: 24px;
|
|
display: inline-block;
|
|
vertical-align: middle;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- 覆盖层视图 -->
|
|
<div id="overlay">
|
|
<div id="video-container">
|
|
<div id="video-placeholder">视频流占位区域</div>
|
|
<video id="video-stream" style="display: none;"></video>
|
|
</div>
|
|
|
|
<!-- 玩家面板 -->
|
|
<div id="player-panels"></div>
|
|
|
|
<!-- 场况信息 -->
|
|
<div id="game-info" class="game-info">
|
|
本场: <span id="honba-display">0</span> | 立直棒: <span id="riichi-sticks-display">0</span>
|
|
</div>
|
|
|
|
<!-- 胡牌动画 -->
|
|
<div id="win-animation" class="win-animation">
|
|
<div class="win-card">
|
|
<svg class="win-icon" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z"/>
|
|
</svg>
|
|
<div class="win-player" id="win-player-name"></div>
|
|
<div class="win-points" id="win-points-text"></div>
|
|
<div class="win-total" id="win-total-text"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 控制面板视图 -->
|
|
<div id="control-panel">
|
|
<div class="control-header">
|
|
<h1>
|
|
<svg class="icon" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/>
|
|
</svg>
|
|
麻将直播控制面板
|
|
</h1>
|
|
<button class="btn btn-primary" onclick="switchToOverlay()">切换到覆盖层</button>
|
|
</div>
|
|
|
|
<div class="control-grid">
|
|
<!-- 玩家管理 -->
|
|
<div class="control-section">
|
|
<h2>
|
|
<svg class="icon" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/>
|
|
</svg>
|
|
玩家管理
|
|
</h2>
|
|
<div id="player-controls"></div>
|
|
</div>
|
|
|
|
<!-- 右侧控制 -->
|
|
<div>
|
|
<!-- 胡牌设置 -->
|
|
<div class="control-section" style="margin-bottom: 24px;">
|
|
<h2>胡牌设置</h2>
|
|
|
|
<div class="form-group">
|
|
<label>胜者</label>
|
|
<select id="winner-select"></select>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label>番数</label>
|
|
<input type="number" id="han-input" value="1" min="1" max="13">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>符数</label>
|
|
<input type="number" id="fu-input" value="30" min="20" max="110" step="10">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>
|
|
<input type="checkbox" id="tsumo-checkbox">
|
|
自摸
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-group" id="loser-group">
|
|
<label>放铳者</label>
|
|
<select id="loser-select"></select>
|
|
</div>
|
|
|
|
<button class="btn btn-success" style="width: 100%; padding: 12px; font-size: 16px;" onclick="triggerWin()">
|
|
确认胡牌
|
|
</button>
|
|
</div>
|
|
|
|
<!-- 场况控制 -->
|
|
<div class="control-section">
|
|
<h2>场况控制</h2>
|
|
|
|
<div class="form-group">
|
|
<label>本场数</label>
|
|
<input type="number" id="honba-input" value="0" min="0" onchange="updateGameState()">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>立直棒</label>
|
|
<input type="number" id="riichi-sticks-input" value="0" min="0" onchange="updateGameState()">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>透明度 (<span id="opacity-value">80</span>%)</label>
|
|
<input type="range" id="opacity-slider" value="80" min="0" max="100" oninput="updateOpacity(this.value)">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 游戏状态
|
|
const gameState = {
|
|
players: [
|
|
{ id: 'EAST', name: '玩家一', score: 25000, position: 'EAST', riichi: false, color: 'east', label: '东' },
|
|
{ id: 'SOUTH', name: '玩家二', score: 25000, position: 'SOUTH', riichi: false, color: 'south', label: '南' },
|
|
{ id: 'WEST', name: '玩家三', score: 25000, position: 'WEST', riichi: false, color: 'west', label: '西' },
|
|
{ id: 'NORTH', name: '玩家四', score: 25000, position: 'NORTH', riichi: false, color: 'north', label: '北' }
|
|
],
|
|
honba: 0,
|
|
riichiSticks: 0,
|
|
opacity: 80
|
|
};
|
|
|
|
// 初始化
|
|
function init() {
|
|
renderPlayerPanels();
|
|
renderPlayerControls();
|
|
updateSelects();
|
|
updateGameState();
|
|
|
|
// 自摸复选框事件
|
|
document.getElementById('tsumo-checkbox').addEventListener('change', function() {
|
|
document.getElementById('loser-group').style.display = this.checked ? 'none' : 'block';
|
|
});
|
|
}
|
|
|
|
// 渲染玩家面板(覆盖层)
|
|
function renderPlayerPanels() {
|
|
const container = document.getElementById('player-panels');
|
|
container.innerHTML = gameState.players.map(player => `
|
|
<div class="player-panel ${player.color}" style="opacity: ${gameState.opacity / 100}">
|
|
<div class="player-info">
|
|
<div class="player-header">
|
|
<span class="position-badge">${player.label}</span>
|
|
<span>${player.name}</span>
|
|
${player.riichi ? '<svg class="riichi-icon" fill="currentColor" viewBox="0 0 24 24"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg>' : ''}
|
|
</div>
|
|
<div class="player-score">${player.score.toLocaleString()}</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
// 渲染玩家控制(控制面板)
|
|
function renderPlayerControls() {
|
|
const container = document.getElementById('player-controls');
|
|
container.innerHTML = gameState.players.map(player => `
|
|
<div class="player-control">
|
|
<div class="player-control-header">
|
|
<div class="player-control-info">
|
|
<span class="position-badge" style="background: rgba(255,255,255,0.2); padding: 4px 8px; border-radius: 4px;">${player.label}</span>
|
|
<input type="text" value="${player.name}" onchange="updatePlayerName('${player.id}', this.value)" style="width: 150px;">
|
|
</div>
|
|
<button class="btn-riichi ${player.riichi ? 'active' : ''}" onclick="toggleRiichi('${player.id}')">
|
|
${player.riichi ? '👁' : '👁🗨'}
|
|
</button>
|
|
</div>
|
|
<div style="display: flex; align-items: center; gap: 8px;">
|
|
<span style="font-size: 24px; font-weight: bold;">${player.score.toLocaleString()}</span>
|
|
<input type="number" value="${player.score}" onchange="updatePlayerScore('${player.id}', this.value)" style="width: 120px;">
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
// 更新选择框
|
|
function updateSelects() {
|
|
const winnerSelect = document.getElementById('winner-select');
|
|
const loserSelect = document.getElementById('loser-select');
|
|
|
|
winnerSelect.innerHTML = gameState.players.map(p =>
|
|
`<option value="${p.id}">${p.name}</option>`
|
|
).join('');
|
|
|
|
updateLoserSelect();
|
|
}
|
|
|
|
// 更新放铳者选择框
|
|
function updateLoserSelect() {
|
|
const winnerId = document.getElementById('winner-select').value;
|
|
const loserSelect = document.getElementById('loser-select');
|
|
|
|
loserSelect.innerHTML = gameState.players
|
|
.filter(p => p.id !== winnerId)
|
|
.map(p => `<option value="${p.id}">${p.name}</option>`)
|
|
.join('');
|
|
}
|
|
|
|
// 监听胜者变化
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
document.getElementById('winner-select')?.addEventListener('change', updateLoserSelect);
|
|
});
|
|
|
|
// 切换视图
|
|
function switchToOverlay() {
|
|
document.getElementById('overlay').style.display = 'block';
|
|
document.getElementById('control-panel').classList.remove('active');
|
|
}
|
|
|
|
function switchToControl() {
|
|
document.getElementById('overlay').style.display = 'none';
|
|
document.getElementById('control-panel').classList.add('active');
|
|
}
|
|
|
|
// 更新玩家信息
|
|
function updatePlayerName(id, name) {
|
|
const player = gameState.players.find(p => p.id === id);
|
|
if (player) {
|
|
player.name = name;
|
|
renderPlayerPanels();
|
|
updateSelects();
|
|
}
|
|
}
|
|
|
|
function updatePlayerScore(id, score) {
|
|
const player = gameState.players.find(p => p.id === id);
|
|
if (player) {
|
|
player.score = parseInt(score) || 0;
|
|
renderPlayerPanels();
|
|
}
|
|
}
|
|
|
|
function toggleRiichi(id) {
|
|
const player = gameState.players.find(p => p.id === id);
|
|
if (player) {
|
|
player.riichi = !player.riichi;
|
|
if (player.riichi) {
|
|
gameState.riichiSticks++;
|
|
} else {
|
|
gameState.riichiSticks = Math.max(0, gameState.riichiSticks - 1);
|
|
}
|
|
renderPlayerPanels();
|
|
renderPlayerControls();
|
|
updateGameState();
|
|
}
|
|
}
|
|
|
|
// 计算得点
|
|
function calculatePoints(han, fu, isDealer, isTsumo) {
|
|
let basePoints = fu * Math.pow(2, 2 + han);
|
|
|
|
if (han >= 5) basePoints = 2000 * han;
|
|
if (han >= 6) basePoints = 3000 * han;
|
|
if (han >= 8) basePoints = 4000 * han;
|
|
if (han >= 11) basePoints = 6000 * han;
|
|
if (han >= 13) basePoints = 8000 * han;
|
|
|
|
basePoints = Math.ceil(basePoints / 100) * 100;
|
|
|
|
if (isTsumo) {
|
|
const dealerPay = isDealer ? Math.ceil(basePoints * 2 / 100) * 100 : Math.ceil(basePoints / 100) * 100;
|
|
const nonDealerPay = Math.ceil(basePoints / 100) * 100;
|
|
return { dealer: dealerPay, nonDealer: nonDealerPay };
|
|
} else {
|
|
return isDealer ? basePoints * 6 : basePoints * 4;
|
|
}
|
|
}
|
|
|
|
// 触发胡牌
|
|
function triggerWin() {
|
|
const winnerId = document.getElementById('winner-select').value;
|
|
const loserId = document.getElementById('loser-select').value;
|
|
const han = parseInt(document.getElementById('han-input').value) || 1;
|
|
const fu = parseInt(document.getElementById('fu-input').value) || 30;
|
|
const isTsumo = document.getElementById('tsumo-checkbox').checked;
|
|
|
|
const winner = gameState.players.find(p => p.id === winnerId);
|
|
const isDealer = winner.position === 'EAST';
|
|
const points = calculatePoints(han, fu, isDealer, isTsumo);
|
|
|
|
const riichiBonus = gameState.riichiSticks * 1000;
|
|
const honbaBonus = gameState.honba * 300;
|
|
|
|
let totalWin = 0;
|
|
|
|
if (isTsumo) {
|
|
// 自摸
|
|
gameState.players.forEach(p => {
|
|
if (p.id === winnerId) {
|
|
const win = (points.dealer * (isDealer ? 0 : 1) + points.nonDealer * (isDealer ? 3 : 2)) + riichiBonus + honbaBonus;
|
|
p.score += win;
|
|
totalWin = win;
|
|
} else {
|
|
const loss = p.position === 'EAST' && !isDealer ? points.dealer : points.nonDealer;
|
|
p.score -= (loss + Math.floor(honbaBonus / 3));
|
|
}
|
|
});
|
|
} else {
|
|
// 荣和
|
|
const winPoints = (typeof points === 'number' ? points : points.dealer) + riichiBonus + honbaBonus;
|
|
totalWin = winPoints;
|
|
|
|
gameState.players.forEach(p => {
|
|
if (p.id === winnerId) {
|
|
p.score += winPoints;
|
|
} else if (p.id === loserId) {
|
|
p.score -= winPoints;
|
|
}
|
|
});
|
|
}
|
|
|
|
gameState.riichiSticks = 0;
|
|
|
|
// 显示动画
|
|
showWinAnimation(winner.name, han, fu, totalWin);
|
|
renderPlayerPanels();
|
|
renderPlayerControls();
|
|
updateGameState();
|
|
}
|
|
|
|
// 显示胡牌动画
|
|
function showWinAnimation(playerName, han, fu, points) {
|
|
document.getElementById('win-player-name').textContent = `${playerName} 胡牌!`;
|
|
document.getElementById('win-points-text').textContent = `${han}番 ${fu}符`;
|
|
document.getElementById('win-total-text').textContent = `+${points.toLocaleString()} 点`;
|
|
|
|
const animation = document.getElementById('win-animation');
|
|
animation.classList.add('active');
|
|
|
|
setTimeout(() => {
|
|
animation.classList.remove('active');
|
|
}, 5000);
|
|
}
|
|
|
|
// 更新场况
|
|
function updateGameState() {
|
|
gameState.honba = parseInt(document.getElementById('honba-input')?.value || 0);
|
|
gameState.riichiSticks = parseInt(document.getElementById('riichi-sticks-input')?.value || 0);
|
|
|
|
document.getElementById('honba-display').textContent = gameState.honba;
|
|
document.getElementById('riichi-sticks-display').textContent = gameState.riichiSticks;
|
|
|
|
if (document.getElementById('honba-input')) {
|
|
document.getElementById('honba-input').value = gameState.honba;
|
|
}
|
|
if (document.getElementById('riichi-sticks-input')) {
|
|
document.getElementById('riichi-sticks-input').value = gameState.riichiSticks;
|
|
}
|
|
}
|
|
|
|
// 更新透明度
|
|
function updateOpacity(value) {
|
|
gameState.opacity = parseInt(value);
|
|
document.getElementById('opacity-value').textContent = value;
|
|
|
|
const panels = document.querySelectorAll('.player-panel');
|
|
panels.forEach(panel => {
|
|
panel.style.opacity = gameState.opacity / 100;
|
|
});
|
|
|
|
const gameInfo = document.getElementById('game-info');
|
|
if (gameInfo) {
|
|
gameInfo.style.opacity = gameState.opacity / 100;
|
|
}
|
|
}
|
|
|
|
// 页面加载时初始化
|
|
window.addEventListener('DOMContentLoaded', function() {
|
|
init();
|
|
// 默认显示控制面板
|
|
switchToControl();
|
|
});
|
|
|
|
// 键盘快捷键(可选)
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'F1') {
|
|
e.preventDefault();
|
|
switchToOverlay();
|
|
} else if (e.key === 'F2') {
|
|
e.preventDefault();
|
|
switchToControl();
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |