555 lines
24 KiB
HTML
555 lines
24 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>麻将直播控制面板</title>
|
||
<style>
|
||
body {
|
||
font-family: 'Arial', sans-serif;
|
||
background: linear-gradient(135deg, #228B22, #556B2F);
|
||
color: #fff;
|
||
padding: 20px;
|
||
margin: 0;
|
||
min-height: 100vh;
|
||
}
|
||
.container { max-width: 800px; margin: 0 auto; }
|
||
h1 { text-align: center; color: #FFD700; text-shadow: 0 2px 4px rgba(0,0,0,0.5); }
|
||
.card {
|
||
background: rgba(255,255,255,0.1);
|
||
border-radius: 15px;
|
||
padding: 20px;
|
||
margin: 15px 0;
|
||
backdrop-filter: blur(10px);
|
||
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
|
||
}
|
||
.card h3 { color: #FFD700; margin-top: 0; border-bottom: 2px solid #FFD700; padding-bottom: 10px; }
|
||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; }
|
||
button {
|
||
background: linear-gradient(45deg, #FF6B35, #F7931E);
|
||
color: white;
|
||
border: none;
|
||
padding: 12px 20px;
|
||
border-radius: 25px;
|
||
cursor: pointer;
|
||
font-weight: bold;
|
||
transition: all 0.3s;
|
||
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
||
}
|
||
button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,0.3); }
|
||
input, select {
|
||
padding: 8px;
|
||
border-radius: 5px;
|
||
border: 1px solid #FFD700;
|
||
background: rgba(255,255,255,0.9);
|
||
margin: 5px;
|
||
width: 60px;
|
||
}
|
||
.hu-type { display: flex; gap: 10px; align-items: center; }
|
||
.hu-type input[type="radio"] { width: auto; }
|
||
label { font-size: 14px; }
|
||
.rotate-btns { display: flex; justify-content: space-around; }
|
||
#log { background: rgba(0,0,0,0.5); padding: 10px; border-radius: 5px; font-size: 12px; max-height: 100px; overflow-y: auto; }
|
||
.yaku-list { max-height: 200px; overflow-y: auto; }
|
||
.yaku-item { margin: 5px 0; }
|
||
.yaku-checkbox { margin-right: 10px; }
|
||
details { cursor: pointer; }
|
||
summary { font-weight: bold; color: #FFD700; }
|
||
#yaku-select { max-height: 150px; overflow-y: auto; border: 1px solid #FFD700; padding: 10px; border-radius: 5px; background: rgba(255,255,255,0.1); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>🀄 麻将直播控制面板</h1>
|
||
|
||
<!-- 全局设置 -->
|
||
<div class="card">
|
||
<h3>🌟 全局设置</h3>
|
||
<div class="grid">
|
||
<label>初始分数: <input id="initial-score" type="number" value="25000"></label>
|
||
<button onclick="sendResetScores()">重置所有分数</button>
|
||
<br />
|
||
<!-- 新增:直播链接输入和按钮 -->
|
||
<label>直播链接: <input id="video-src" type="text" value="https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/720/Big_Buck_Bunny_720_10s_1MB.mp4" style="width: 300px;"></label>
|
||
<button onclick="sendUpdateVideo()">更新视频</button>
|
||
<!-- 结束新增 -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 局况 -->
|
||
<div class="card">
|
||
<h3>📅 局况设置</h3>
|
||
<div class="grid">
|
||
<label>当前局: <select id="round-select" onchange="sendUpdateRound()">
|
||
<option value="东1局">东1局</option>
|
||
<option value="东2局">东2局</option>
|
||
<option value="东3局">东3局</option>
|
||
<option value="东4局">东4局</option>
|
||
<option value="南1局">南1局</option>
|
||
<option value="南2局">南2局</option>
|
||
<option value="南3局">南3局</option>
|
||
<option value="南4局">南4局</option>
|
||
<option value="西1局">西1局</option>
|
||
<option value="西2局">西2局</option>
|
||
<option value="西3局">西3局</option>
|
||
<option value="西4局">西4局</option>
|
||
</select></label>
|
||
<label>宝牌: <input id="dora-input" type="text" value="?" onchange="sendUpdateDora()"></label>
|
||
<button onclick="advanceRound()">下一局 ➡️</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 玩家信息 -->
|
||
<div class="card">
|
||
<h3>👥 玩家信息 (东庄)</h3>
|
||
<div class="grid">
|
||
<div>
|
||
<label>东: <input id="name-east" value="玩家1" onchange="sendUpdateName('east')"></label>
|
||
<input id="score-east" type="number" value="25000" onchange="sendUpdateScore('east')">
|
||
</div>
|
||
<div>
|
||
<label>南: <input id="name-south" value="玩家2" onchange="sendUpdateName('south')"></label>
|
||
<input id="score-south" type="number" value="25000" onchange="sendUpdateScore('south')">
|
||
</div>
|
||
<div>
|
||
<label>西: <input id="name-west" value="玩家3" onchange="sendUpdateName('west')"></label>
|
||
<input id="score-west" type="number" value="25000" onchange="sendUpdateScore('west')">
|
||
</div>
|
||
<div>
|
||
<label>北: <input id="name-north" value="玩家4" onchange="sendUpdateName('north')"></label>
|
||
<input id="score-north" type="number" value="25000" onchange="sendUpdateScore('north')">
|
||
</div>
|
||
</div>
|
||
<div class="rotate-btns">
|
||
<button onclick="rotatePlayers('prev')">⬅️ 上一局</button>
|
||
<button onclick="rotatePlayers('next')">下一局 ➡️</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!--
|
||
<div class="card">
|
||
<h3>🀄 番种参考 (日本麻将 37 种役)</h3>
|
||
<details>
|
||
<summary>点击展开所有番种</summary>
|
||
<div class="yaku-list">
|
||
<div class="yaku-item"><strong>1番役:</strong>立直、一发、门前清自摸和、平和、役牌 (自风/场风/三元牌)、断幺九 (副露)、一气通贯 (副露)、对倒、混一色 (副露)、三色同顺 (副露)、七对子、赤宝牌</div>
|
||
<div class="yaku-item"><strong>2番役:</strong>断幺九 (门清)、一气通贯 (门清)、混一色 (门清)、三色同顺 (门清)、对对和、混全带幺、三暗刻 (副露)、三暗刻 (门清,无显杠)</div>
|
||
<div class="yaku-item"><strong>3番役:</strong>平胡 (门清)、一发 (门清? 配役)、混全带 (门清)、三暗刻 (门清,有显杠)</div>
|
||
<div class="yaku-item"><strong>役满:</strong>国士无双、大三元、小四喜、四暗刻单骑、大四喜、清老头、九莲宝灯、绿一色、清一色、四槓子、字一色、天和、地和</div>
|
||
</div>
|
||
</details>
|
||
<p><em>选役后自动累加番数(门清/副露已考虑减番逻辑简化,实际手动调整总番)。</em></p>
|
||
</div>番种参考 -->
|
||
|
||
<!-- 胡牌 -->
|
||
<div class="card">
|
||
<h3>🎉 触发胡牌</h3>
|
||
<div class="grid">
|
||
<div>
|
||
<label>赢家: <select id="winner-select">
|
||
<option value="east">东</option><option value="south">南</option><option value="west">西</option><option value="north">北</option>
|
||
</select></label>
|
||
<label>类型: <div class="hu-type">
|
||
<label><input type="radio" name="hu-type" value="ron" checked> 荣和</label>
|
||
<label><input type="radio" name="hu-type" value="tsumo"> 自摸</label>
|
||
</div></label>
|
||
<label id="loser-label" style="display:block;">放铳: <select id="loser-select">
|
||
<option value="south">南</option><option value="west">西</option><option value="north">北</option><option value="east">东</option>
|
||
</select></label>
|
||
<label>役牌: <div id="yaku-select">
|
||
<label class="yaku-checkbox"><input type="checkbox" value="场风东" data-han="1">东</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="双东" data-han="2">双东</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="场凤南" data-han="1">南</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="双南" data-han="2">双南</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="场风西" data-han="1">西</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="双西" data-han="2">双西</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="场风北" data-han="1">北</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="役牌白" data-han="1">白</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="役牌发" data-han="1">发</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="役牌中" data-han="1">中</label><br />
|
||
|
||
</div></label>
|
||
<label>1番: <div id="yaku-select">
|
||
<label class="yaku-checkbox"><input type="checkbox" value="立直" data-han="1">立直</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="一发" data-han="1">一发</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="门前清自摸和" data-han="1">门前清自摸和</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="平和" data-han="1">平和</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="断幺九" data-han="1">断幺九</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="一盃口" data-han="1">一盃口</label><br />
|
||
|
||
|
||
</div></label>
|
||
<label>2番: <div id="yaku-select">
|
||
<label class="yaku-checkbox"><input type="checkbox" value="一气通贯" data-han="2">一气通贯</label><label class="yaku-checkbox"><input type="checkbox" value="一气通贯" data-han="1">非门清</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="混一色" data-han="3">混一色</label><label class="yaku-checkbox"><input type="checkbox" value="混一色" data-han="2">非门清</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="三色同顺" data-han="2">三色同顺</label><label class="yaku-checkbox"><input type="checkbox" value="三色同顺" data-han="1">非门清</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="三色同刻" data-han="2">三色同刻</label><br />
|
||
|
||
<label class="yaku-checkbox"><input type="checkbox" value="七对子" data-han="2" data-fu="25">七对子</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="对对和" data-han="2">对对和</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="三暗刻" data-han="2">三暗刻</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="混全带幺九" data-han="2">混全</label><label class="yaku-checkbox"><input type="checkbox" value="混全带幺九" data-han="1">非门清</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="纯全带幺九" data-han="3">纯全</label><label class="yaku-checkbox"><input type="checkbox" value="纯全带幺九" data-han="2">非门清</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="清一色" data-han="6">清一色</label><br />
|
||
|
||
</div></label>
|
||
<label>役满: <div id="yaku-select">
|
||
<label class="yaku-checkbox"><input type="checkbox" value="国士无双" data-han="13">国士无双</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="大三元" data-han="13">大三元</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="小四喜" data-han="13">小四喜</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="大四喜" data-han="13">大四喜</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="四暗刻" data-han="13">四暗刻</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="绿一色" data-han="13">绿一色</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="天和" data-han="13">天和</label><br />
|
||
<label class="yaku-checkbox"><input type="checkbox" value="地和" data-han="13">地和</label><br />
|
||
|
||
</div></label>
|
||
<label>总番: <input id="fan-input" type="number" value="0" readonly></label>
|
||
<label>符: <input id="fu-input" type="number" value="30"></label>
|
||
<button onclick="sendHuPai()">执行胡牌</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 场况 -->
|
||
<div class="card">
|
||
<h3>📊 场况</h3>
|
||
<div class="grid">
|
||
<label>本场: <input id="honba-input" type="number" value="0"></label>
|
||
<label>立直棒: <input id="riichi-input" type="number" value="0"></label>
|
||
<button onclick="sendUpdateField()">更新</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 听牌 & 样式 -->
|
||
<div class="card">
|
||
<h3>🎯 听牌提示</h3>
|
||
<div class="grid">
|
||
<label>玩家: <select id="tenpai-player">
|
||
<option value="east">东</option><option value="south">南</option><option value="west">西</option><option value="north">北</option>
|
||
</select></label>
|
||
<button onclick="sendTenpai(true)">置入听牌</button>
|
||
<button onclick="sendTenpai(false)">取消</button>
|
||
</div>
|
||
<h3>🎨 调整样式</h3>
|
||
<div class="grid">
|
||
<label>玩家: <select id="style-player">
|
||
<option value="east">东</option><option value="south">南</option><option value="west">西</option><option value="north">北</option>
|
||
</select></label>
|
||
<label>颜色: <input id="color-input" type="color" value="#ff0000"></label>
|
||
<label>透明度: <input id="opacity-input" type="number" min="0" max="1" step="0.1" value="0.8"></label>
|
||
<button onclick="sendUpdateStyle()">更新</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="log" class="card">日志: 准备就绪...</div>
|
||
</div>
|
||
|
||
<script>
|
||
const channel = 'mahjong-channel';
|
||
let bc = null;
|
||
let pos_names = {east: '玩家1', south: '玩家2', west: '玩家3', north: '玩家4'};
|
||
let pos_scores = {east: 25000, south: 25000, west: 25000, north: 25000};
|
||
const rounds = ['东1局', '东2局', '东3局', '东4局', '南1局', '南2局', '南3局', '南4局'];
|
||
let currentRoundIndex = 0;
|
||
|
||
try {
|
||
bc = new BroadcastChannel(channel);
|
||
log('BroadcastChannel 支持,通信就绪');
|
||
} catch (e) {
|
||
log('使用 localStorage 备用通信');
|
||
function postMessage(msg) { localStorage.setItem(channel, JSON.stringify(msg)); }
|
||
window.postMessage = postMessage;
|
||
}
|
||
|
||
function sendEvent(type, data) {
|
||
const msg = { type, data };
|
||
try {
|
||
if (bc) {
|
||
bc.postMessage(JSON.stringify(msg));
|
||
} else {
|
||
localStorage.setItem(channel, JSON.stringify(msg));
|
||
}
|
||
log(`发送: ${type} ${JSON.stringify(data)}`);
|
||
} catch (e) {
|
||
log(`发送失败: ${e.message}`);
|
||
}
|
||
}
|
||
|
||
function log(text) {
|
||
const logEl = document.getElementById('log');
|
||
logEl.innerHTML += `<br>${new Date().toLocaleTimeString()}: ${text}`;
|
||
logEl.scrollTop = logEl.scrollHeight;
|
||
}
|
||
|
||
function sendResetScores() {
|
||
try {
|
||
const initial = parseInt(document.getElementById('initial-score').value);
|
||
['east','south','west','north'].forEach(p => {
|
||
pos_scores[p] = initial;
|
||
document.getElementById(`score-${p}`).value = initial;
|
||
sendUpdateScore(p);
|
||
});
|
||
sendEvent('reset_scores', {initial});
|
||
log('重置分数到 ' + initial);
|
||
} catch (e) {
|
||
log('重置失败: ' + e.message);
|
||
}
|
||
}
|
||
|
||
// 新增:发送视频源更新事件
|
||
function sendUpdateVideo() {
|
||
try {
|
||
const src = document.getElementById('video-src').value.trim();
|
||
if (!src) {
|
||
log('错误:直播链接不能为空');
|
||
return;
|
||
}
|
||
sendEvent('update_video_src', {src});
|
||
log(`更新视频源: ${src}`);
|
||
} catch (e) {
|
||
log('视频源更新失败: ' + e.message);
|
||
}
|
||
}
|
||
// 结束新增
|
||
|
||
function sendUpdateName(player) {
|
||
const name = document.getElementById(`name-${player}`).value;
|
||
pos_names[player] = name;
|
||
sendEvent('update_name', {player, name});
|
||
log(`更新 ${player} 名字: ${name}`);
|
||
}
|
||
|
||
function sendUpdateScore(player) {
|
||
const score = parseInt(document.getElementById(`score-${player}`).value);
|
||
pos_scores[player] = score;
|
||
sendEvent('update_score', {player, score});
|
||
log(`更新 ${player} 分数: ${score}`);
|
||
}
|
||
|
||
function rotatePlayers(direction) {
|
||
try {
|
||
let old_e_name = pos_names.east, old_e_score = pos_scores.east;
|
||
let old_s_name = pos_names.south, old_s_score = pos_scores.south;
|
||
let old_w_name = pos_names.west, old_w_score = pos_scores.west;
|
||
let old_n_name = pos_names.north, old_n_score = pos_scores.north;
|
||
|
||
if (direction === 'next') {
|
||
pos_names.east = old_s_name; pos_scores.east = old_s_score;
|
||
pos_names.south = old_w_name; pos_scores.south = old_w_score;
|
||
pos_names.west = old_n_name; pos_scores.west = old_n_score;
|
||
pos_names.north = old_e_name; pos_scores.north = old_e_score;
|
||
} else {
|
||
pos_names.east = old_n_name; pos_scores.east = old_n_score;
|
||
pos_names.north = old_w_name; pos_scores.north = old_w_score;
|
||
pos_names.west = old_s_name; pos_scores.west = old_s_score;
|
||
pos_names.south = old_e_name; pos_scores.south = old_e_score;
|
||
}
|
||
|
||
['east','south','west','north'].forEach(p => {
|
||
document.getElementById(`name-${p}`).value = pos_names[p];
|
||
document.getElementById(`score-${p}`).value = pos_scores[p];
|
||
sendUpdateName(p);
|
||
sendUpdateScore(p);
|
||
});
|
||
log(`${direction === 'next' ? '下一局' : '上一局'} 轮换完成`);
|
||
} catch (e) {
|
||
log('轮换失败: ' + e.message);
|
||
}
|
||
}
|
||
|
||
function advanceRound() {
|
||
currentRoundIndex = (currentRoundIndex + 1) % rounds.length;
|
||
document.getElementById('round-select').value = rounds[currentRoundIndex];
|
||
sendUpdateRound();
|
||
// 如果进入南场
|
||
if (rounds[currentRoundIndex].startsWith('南')) {
|
||
sendUpdateField();
|
||
log('进入南场');
|
||
}
|
||
}
|
||
|
||
function sendUpdateRound() {
|
||
const round = document.getElementById('round-select').value;
|
||
sendEvent('update_round', {round});
|
||
log(`更新局况: ${round}`);
|
||
}
|
||
|
||
function sendUpdateDora() {
|
||
const dora = document.getElementById('dora-input').value || '?';
|
||
sendEvent('update_dora', {dora});
|
||
log(`更新宝牌: ${dora}`);
|
||
}
|
||
|
||
function calculateBasic(han, fu) {
|
||
let bp;
|
||
if (han >= 13) {
|
||
bp = 8000;
|
||
} else if (han >= 11) {
|
||
bp = 6000;
|
||
} else if (han >= 8) {
|
||
bp = 4000;
|
||
} else if (han >= 6) {
|
||
bp = 3000;
|
||
} else if (han >= 5) {
|
||
bp = 2000;
|
||
} else {
|
||
bp = fu * Math.pow(2, han + 2);
|
||
if (bp > 2000) bp = 2000;
|
||
}
|
||
return bp;
|
||
}
|
||
|
||
function calculatePayment(bp, mult) {
|
||
let pmt = bp * mult;
|
||
return Math.ceil(pmt / 100) * 100;
|
||
}
|
||
|
||
function sendHuPai() {
|
||
try {
|
||
const winner = document.getElementById('winner-select').value;
|
||
const type = document.querySelector('input[name="hu-type"]:checked').value;
|
||
const fu = parseInt(document.getElementById('fu-input').value) || 30;
|
||
let honba = parseInt(document.getElementById('honba-input').value) || 0;
|
||
let riichi_sticks = parseInt(document.getElementById('riichi-input').value) || 0;
|
||
const loser = type === 'ron' ? document.getElementById('loser-select').value : null;
|
||
const isDealer = winner === 'east';
|
||
|
||
if (type === 'ron' && winner === loser) {
|
||
log('错误:赢家和放铳者不能相同');
|
||
return;
|
||
}
|
||
|
||
// --- 1. 计算番种 ---
|
||
const selectedYaku = [];
|
||
let totalHan = 0;
|
||
document.querySelectorAll('#yaku-select input:checked').forEach(cb => {
|
||
selectedYaku.push(cb.value);
|
||
totalHan += parseInt(cb.dataset.han);
|
||
});
|
||
document.getElementById('fan-input').value = totalHan;
|
||
|
||
// --- 2. 按当前本场数计算分数 ---
|
||
const bp = calculateBasic(totalHan, fu);
|
||
const riichiExtra = riichi_sticks * 1000;
|
||
const honbaPer = honba * 100;
|
||
const honbaRon = honba * 300;
|
||
let totalGain = riichiExtra;
|
||
|
||
if (type === 'ron') {
|
||
const mult = isDealer ? 6 : 4;
|
||
const payment = calculatePayment(bp, mult);
|
||
const totalPay = payment + honbaRon;
|
||
pos_scores[loser] -= totalPay;
|
||
document.getElementById(`score-${loser}`).value = pos_scores[loser];
|
||
sendUpdateScore(loser);
|
||
totalGain += totalPay;
|
||
log(`放铳者 ${pos_names[loser]} 扣除 ${totalPay} 点`);
|
||
} else { // tsumo
|
||
if (isDealer) {
|
||
['south','west','north'].forEach(p => {
|
||
const pmt = calculatePayment(bp, 2);
|
||
const totalPmt = pmt + honbaPer;
|
||
pos_scores[p] -= totalPmt;
|
||
document.getElementById(`score-${p}`).value = pos_scores[p];
|
||
sendUpdateScore(p);
|
||
totalGain += totalPmt;
|
||
});
|
||
} else {
|
||
const dealerPmt = calculatePayment(bp, 2);
|
||
const totalDealerPmt = dealerPmt + honbaPer;
|
||
pos_scores.east -= totalDealerPmt;
|
||
document.getElementById('score-east').value = pos_scores.east;
|
||
sendUpdateScore('east');
|
||
totalGain += totalDealerPmt;
|
||
const others = ['south','west','north'].filter(p => p !== winner);
|
||
others.forEach(p => {
|
||
const pmt = calculatePayment(bp, 1);
|
||
const totalPmt = pmt + honbaPer;
|
||
pos_scores[p] -= totalPmt;
|
||
document.getElementById(`score-${p}`).value = pos_scores[p];
|
||
sendUpdateScore(p);
|
||
totalGain += totalPmt;
|
||
});
|
||
}
|
||
}
|
||
|
||
// --- 3. 赢家加分 ---
|
||
pos_scores[winner] += totalGain;
|
||
document.getElementById(`score-${winner}`).value = pos_scores[winner];
|
||
sendUpdateScore(winner);
|
||
|
||
// --- 4. 胡牌后才更新本场和立直棒 ---
|
||
if (isDealer) {
|
||
honba += 1; // 庄家连庄
|
||
riichi_sticks = 0;
|
||
} else {
|
||
honba = 0; // 非庄家胡,重置本场
|
||
riichi_sticks = 0;
|
||
}
|
||
document.getElementById('honba-input').value = honba;
|
||
document.getElementById('riichi-input').value = riichi_sticks;
|
||
sendUpdateField();
|
||
|
||
// --- 5. 输出事件日志 ---
|
||
sendEvent('hu_pai', {type, winner, loser, yaku: selectedYaku, fan: totalHan, fu, honba, riichi_sticks});
|
||
log(`胡牌执行: ${type} ${pos_names[winner]} ${selectedYaku.join(', ')} ${totalHan}番 ${fu}符,总获 ${totalGain}点${type === 'ron' ? `,放铳者 ${pos_names[loser]}` : ''}${isDealer ? ' (庄家连庄)' : ' (重置本场/立直棒)'}`);
|
||
} catch (e) {
|
||
log('胡牌执行失败: ' + e.message);
|
||
}
|
||
}
|
||
|
||
function sendUpdateField() {
|
||
try {
|
||
const honba = parseInt(document.getElementById('honba-input').value);
|
||
const riichi_sticks = parseInt(document.getElementById('riichi-input').value);
|
||
sendEvent('update_field', {honba, riichi_sticks});
|
||
log('场况更新');
|
||
} catch (e) {
|
||
log('场况更新失败: ' + e.message);
|
||
}
|
||
}
|
||
|
||
function sendTenpai(status) {
|
||
try {
|
||
const player = document.getElementById('tenpai-player').value;
|
||
sendEvent('tenpai', {player, status});
|
||
log(`${status ? '置入' : '取消'} ${player} 听牌`);
|
||
} catch (e) {
|
||
log('听牌更新失败: ' + e.message);
|
||
}
|
||
}
|
||
|
||
function sendUpdateStyle() {
|
||
try {
|
||
const player = document.getElementById('style-player').value;
|
||
const color = document.getElementById('color-input').value;
|
||
const opacity = parseFloat(document.getElementById('opacity-input').value);
|
||
sendEvent('update_style', {player, color, opacity});
|
||
log(`样式更新: ${player} ${color} ${opacity}`);
|
||
} catch (e) {
|
||
log('样式更新失败: ' + e.message);
|
||
}
|
||
}
|
||
|
||
// 事件监听
|
||
document.querySelectorAll('input[name="hu-type"]').forEach(r => {
|
||
r.addEventListener('change', (e) => {
|
||
document.getElementById('loser-label').style.display = e.target.value === 'ron' ? 'block' : 'none';
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('#yaku-select input[type="checkbox"]').forEach(cb => {
|
||
cb.addEventListener('change', () => {
|
||
let total = 0;
|
||
document.querySelectorAll('#yaku-select input:checked').forEach(checked => total += parseInt(checked.dataset.han));
|
||
document.getElementById('fan-input').value = total;
|
||
});
|
||
});
|
||
|
||
// 初始化当前局
|
||
document.getElementById('round-select').value = rounds[currentRoundIndex];
|
||
sendUpdateRound();
|
||
sendUpdateDora();
|
||
</script>
|
||
</body>
|
||
</html> |