根本原因: 每打完一輪都要人工決定誰上場、誰搭誰、誰負責記分,記分板也沒有「兩隊對換」和退回選先發球的操作,現場排場地或換人都得重開記分板。 影響: /記分 多一顆 🎲 隨機配對:勾選出席者(預設全選)後,依 tg_members.skill 與今天的 history 戰績排 2、3 號場地(休息最久優先、搭檔與對手不重複、實力平衡),預覽後一鍵推出各場地記分板;每場從休息者抽一位計分人(SCORER_EXCLUDE 不抽),只有他和管理員能按,休息人不夠則不鎖權限;開局可 🙋 換人計分、🔃 🅰️🅱️ 對換;計分鍵盤改為左欄 🅰️+1/🅱️+1,拿掉「重新開始」,0:0 時上一步可退回選先發球。 修法: - 新增 src/pairing.js:移植 badminton-match-hub V2 的配對規則,改成一次只排下一輪、以「休息輪數」取代平均場數;assignScorers 從休息者抽計分人 - history.js 新增 loadTodayGames 讀今日戰績;signup.js attendees() 帶 skill 與 user_id(權限靠 TG id 比對) - scoreboard.js 以 phase(pick → roster → paired)管理同一則訊息的選人/勾選/預覽流程,推出的記分板 ownerId 指向計分人;新增換人計分(ho / hoN / hoany / hocancel),候選排除場上、現任計分人與 SCORER_EXCLUDE - match.js 新增 swapTeams,setFirstServer 記入 history 讓 undo 可退回開局;render.js 重排鍵盤並新增勾選、預覽、換人面板與場地標題 - config 新增 SCORER_EXCLUDE;README、.env.example 與 37 個測試同步更新 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
84 lines
3.4 KiB
JavaScript
84 lines
3.4 KiB
JavaScript
import { test } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { addPoint, createMatch, setFirstServer, settleMatch } from '../src/match.js'
|
|
import { buildHistoryPayload, dayStartUnix, loadTodayGames, parseGameRow, saveHistory } from '../src/history.js'
|
|
|
|
test('buildHistoryPayload:欄位對齊 web 版 history(雙打)', () => {
|
|
const m = createMatch(['Gary', '蓉蓉'], ['柏威', '小念'])
|
|
setFirstServer(m, 0)
|
|
addPoint(m, 0)
|
|
addPoint(m, 1)
|
|
settleMatch(m)
|
|
const now = new Date('2026-08-17T20:30:00+08:00') // 週一
|
|
const p = buildHistoryPayload(m, now)
|
|
assert.deepEqual(p, {
|
|
time: Math.floor(now.getTime() / 1000),
|
|
dayOfWeek: 1,
|
|
score: [1, 1],
|
|
winScore: 21,
|
|
type: 0,
|
|
players: ['Gary', '蓉蓉', '柏威', '小念'],
|
|
team: [['Gary', '蓉蓉'], ['柏威', '小念']],
|
|
scoreList: [[0, 0, 0, 0], [1, 1, 0, 1]],
|
|
})
|
|
})
|
|
|
|
test('buildHistoryPayload:單打 players 以 [p1,p1,p3,p3] 讓 players[starter] 對得到人', () => {
|
|
const m = createMatch(['A'], ['B'], 15)
|
|
setFirstServer(m, 1)
|
|
addPoint(m, 1)
|
|
const p = buildHistoryPayload(m)
|
|
assert.equal(p.type, 1)
|
|
assert.equal(p.winScore, 15)
|
|
assert.deepEqual(p.players, ['A', 'A', 'B', 'B'])
|
|
assert.deepEqual(p.team, [['A'], ['B']])
|
|
assert.equal(p.players[p.scoreList[0][1]], 'B')
|
|
})
|
|
|
|
test('saveHistory:先 ensure table 再 INSERT,回傳 insertId', async () => {
|
|
const sqls = []
|
|
const query = async (sql, params) => {
|
|
sqls.push({ sql: sql.replace(/\s+/g, ' ').trim(), params })
|
|
return sql.includes('INSERT') ? { insertId: 42 } : []
|
|
}
|
|
const m = createMatch(['A', 'B'], ['C', 'D'])
|
|
setFirstServer(m, 0)
|
|
addPoint(m, 0)
|
|
const id = await saveHistory(m, { query, table: 'history_test' })
|
|
assert.equal(id, 42)
|
|
assert.match(sqls[0].sql, /CREATE TABLE IF NOT EXISTS `history_test`/)
|
|
assert.match(sqls[1].sql, /INSERT INTO `history_test` \(time, dayOfWeek, score, winScore, type, players, team, scoreList\)/)
|
|
assert.equal(sqls[1].params[2], '[1,0]')
|
|
assert.equal(sqls[1].params[5], '["A","B","C","D"]')
|
|
assert.equal(sqls[1].params[6], '[["A","B"],["C","D"]]')
|
|
assert.equal(sqls[1].params[7], '[[0,0,0,0]]')
|
|
})
|
|
|
|
test('loadTodayGames:只抓當天(台北時間)的 history,依時間排序並解析 team;壞掉的列略過', async () => {
|
|
assert.equal(dayStartUnix('20260824'), Math.floor(Date.parse('2026-08-24T00:00:00+08:00') / 1000))
|
|
assert.deepEqual(parseGameRow({ id: 1, time: '10', team: '[["A","B"],["C","D"]]' }), {
|
|
id: 1,
|
|
time: 10,
|
|
teams: [['A', 'B'], ['C', 'D']],
|
|
})
|
|
assert.deepEqual(parseGameRow({ id: 2, time: 10, team: '[["A"],["B"]]' }).teams, [['A'], ['B']], '單打')
|
|
assert.equal(parseGameRow({ id: 3, time: 10, team: 'oops' }), null)
|
|
assert.equal(parseGameRow({ id: 4, time: 10, team: '[["A","B"]]' }), null)
|
|
|
|
const sqls = []
|
|
const query = async (sql, params) => {
|
|
sqls.push({ sql: sql.replace(/\s+/g, ' ').trim(), params })
|
|
return [
|
|
{ id: 7, time: 100, team: '[["A","B"],["C","D"]]' },
|
|
{ id: 8, time: 200, team: 'bad' },
|
|
{ id: 9, time: 300, team: '[["E"],["F"]]' },
|
|
]
|
|
}
|
|
const games = await loadTodayGames({ query, table: 'history_test', date: '20260824' })
|
|
const start = dayStartUnix('20260824')
|
|
assert.match(sqls[0].sql, /SELECT id, time, team FROM `history_test` WHERE time >= \? AND time < \? ORDER BY time ASC, id ASC/)
|
|
assert.deepEqual(sqls[0].params, [start, start + 86400])
|
|
assert.deepEqual(games.map((g) => g.id), [7, 9])
|
|
assert.deepEqual(games[1].teams, [['E'], ['F']])
|
|
})
|