// 把比賽狀態轉成 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 }
}