// 把比賽狀態轉成 Telegram 訊息文字(HTML)與 inline 鍵盤 import { getServing, getWinner, isDoubles } from './match.js' import { escapeHtml } from './util.js' const TEAM_ICON = ['🅰️', '🅱️'] const COURT_LABEL = { right: '右區', left: '左區' } // 隊員名單,依「畫面位置」排列(同 web 版:上排 1 2、下排 4 3),發球者前面加 🏸 // 上排 🅰️ 為鏡像:畫面左 = 自己的右發球區;下排 🅱️:畫面右 = 自己的右發球區 function renderPlayers(match, teamIndex, serving) { const team = match.teams[teamIndex] let ordered = team.players if (team.players.length === 2) { const rightPlayer = team.players[team.right] const leftPlayer = team.players[1 - team.right] ordered = teamIndex === 0 ? [rightPlayer, leftPlayer] : [leftPlayer, rightPlayer] } return ordered .map((name) => { const isServer = serving && serving.team === teamIndex && serving.server === name return `${isServer ? '🏸' : ''}${escapeHtml(name)}` }) .join(' / ') } export function renderText(match, { ended = false } = {}) { const serving = getServing(match) const winner = getWinner(match) const lines = [] lines.push(`🏸 羽球記分板 ${isDoubles(match) ? '雙打' : '單打'}・${match.target} 分制`) lines.push('') for (const teamIndex of [0, 1]) { const marker = winner === teamIndex ? ' 🏆' : '' lines.push( `${TEAM_ICON[teamIndex]} ${renderPlayers(match, teamIndex, serving)} ${match.score[teamIndex]}${marker}`, ) } lines.push('') const hasPoints = (match.scoreList ?? []).length > 0 if (ended) { if (match.historyId) lines.push(`⏹ 已結束 📝 已存入歷史戰績 #${match.historyId}`) else if (hasPoints && !match.finished) lines.push('⏹ 已結束(未結算,未存入戰績)') else lines.push('⏹ 已結束') } else if (match.confirmEnd) { if (match.finished) lines.push('❓ 確定要結束嗎?會把這場寫入歷史戰績') else if (hasPoints) lines.push('❓ 確定要結束嗎?比賽未結算,不會寫入戰績(要存請先按 ⏱ 結算)') else lines.push('❓ 確定要結束嗎?') } else if (match.settled) { const result = winner !== null ? `${TEAM_ICON[winner]} 獲勝` : '平手' lines.push(`⏱ 時間到結算 ${match.score[0]} : ${match.score[1]} ${result}`) } else if (winner !== null) { lines.push(`🎉 ${TEAM_ICON[winner]} 獲勝! ${match.score[0]} : ${match.score[1]}`) } else if (!serving) { lines.push('👉 請先選擇由哪一隊先發球') } else { lines.push( `發球:${TEAM_ICON[serving.team]} ${escapeHtml(serving.server)}(${COURT_LABEL[serving.court]})` + ` 接發:${escapeHtml(serving.receiver)}`, ) } return lines.join('\n') } // callback_data 格式:sc: const btn = (text, action) => ({ text, callback_data: `sc:${action}` }) export function renderKeyboard(match, { ended = false } = {}) { if (ended) return { inline_keyboard: [] } const rows = buildKeyboardRows(match) if (match.confirmEnd) { // 兩段式確認:把「結束」換成「確定結束 / 取消」 return { inline_keyboard: rows.map((row) => row.some((b) => b.callback_data === 'sc:end') ? [btn('✅ 確定結束', 'end'), btn('↩️ 取消', 'endcancel')] : row, ), } } return { inline_keyboard: rows } } function buildKeyboardRows(match) { if (match.finished) { return [ [btn('↩️ 上一步', 'undo'), btn('🔄 再一局', 'reset')], [btn('🏁 結束', 'end')], ] } if (match.server === null) { const rows = [[btn('🅰️ 先發球', 'srv0'), btn('🅱️ 先發球', 'srv1')]] if (isDoubles(match)) { rows.push([btn('🔀 🅰️ 換位', 'swap0'), btn('🔀 🅱️ 換位', 'swap1')]) } rows.push([btn('🏁 結束', 'end')]) return rows } return [ [btn('🅰️ +1', 'pt0'), btn('🅱️ +1', 'pt1')], [btn('↩️ 上一步', 'undo'), btn('🔄 重新開始', 'reset')], [btn('⏱ 結算', 'settle'), btn('🏁 結束', 'end')], ] } // ---------- 選人(/記分 無參數:從今日出席名單點選 1→4) ---------- const PICK_NUM = ['1️⃣', '2️⃣', '3️⃣', '4️⃣'] export function renderPickerText(picker) { const slot = (i) => (picker.picked[i] != null ? escapeHtml(picker.names[picker.picked[i]]) : '—') const lines = [ `🏸 選擇上場人員 依 1 → 4 順序點選(今日出席 ${picker.names.length} 人)`, '', `🅰️ 1️⃣ ${slot(0)} 2️⃣ ${slot(1)}`, `🅱️ 4️⃣ ${slot(3)} 3️⃣ ${slot(2)}`, '', ] if (picker.picked.length === 0) lines.push('👉 點名字加入;1、2 一隊,3、4 一隊(位置同 web 版:上 1 2/下 4 3);點滿 4 人自動開局') else if (picker.picked.length === 2) lines.push('👉 繼續點第 3、4 位,或按「單打開始」') else if (picker.picked.length < 4) lines.push(`👉 繼續點第 ${picker.picked.length + 1} 位`) return lines.join('\n') } export function renderPickerKeyboard(picker) { const rows = [] const perRow = picker.names.length > 8 ? 3 : 2 let row = [] picker.names.forEach((name, i) => { const order = picker.picked.indexOf(i) const label = order >= 0 ? `${PICK_NUM[order]} ${name}` : name row.push({ text: label, callback_data: `sc:pick${i}` }) if (row.length === perRow) { rows.push(row) row = [] } }) if (row.length) rows.push(row) const controls = [] if (picker.picked.length > 0) controls.push({ text: '↩️ 取消上一位', callback_data: 'sc:unpick' }) if (picker.picked.length === 2) controls.push({ text: '▶️ 單打開始', callback_data: 'sc:pickgo' }) controls.push({ text: '✖️ 取消', callback_data: 'sc:end' }) rows.push(controls) return { inline_keyboard: rows } }