三『資』小豬・誰是猜歌王是一套猜歌遊戲的計分與手機作答系統
主持人放歌,玩家用手機回答年份、歌手和歌名。收卷後系統自動批改、加分,投影的計分板同步更新
- 玩家手機(
/)— 輸入名字和組別代碼加入,作答後看對錯、得分和各組排名 - 投影計分板(
/scoreboard)— 全螢幕顯示各組總分,可以切換固定格或排名長條 - 後台控制台(
/dashboard)— 選歌、發題、收卷,查看作答狀況、改判、調整分數 - 主持人(
/host)— 用大字顯示解答,以及各組這題的得分和最高分的人
- 即時同步 — 每一頁都用 WebSocket 接收更新,不用重新整理
- 自動批改 — 年份精準 +3、差 3 年以內 +1;歌手、歌名各 +1,同音字和小錯字也算對;每組每項取組內最高分
- 比賽動畫 — 發題時倒數;收卷前倒數 5 秒,再揭曉對錯、名次換位,並自動產生戰況短評
- 四組或五組 — 一個設定就能切換,第五組是青色星形
- 免費方案就夠 — 一場約 40 人、70 首歌的活動,估算只用到 Workers 免費方案每日請求額度的三成多
用假資料打兩題:
- 發題後所有手機同時倒數,答案即時出現在後台。
- 收卷後計分板分數往上跳,名次跟著換位。
點圖片可以看完整影片(1 分 43 秒)
- Node 22 以上(執行 wrangler 和測試)
- Cloudflare 帳號(部署時需要)
git clone https://github.com/NTUST-OpenSource/guess-song.git
cd guess-song
npm ci # 安裝 wrangler 和 pinyin-pro
cp .dev.vars.example .dev.vars # 填入後台帳密和 AUTH_SECRET
npm test # 測試
npm run dev # 本機伺服器開啟 http://localhost:8787,後台從 /login 登入。
Note
專案沒有 build 步驟,public/ 裡的靜態檔直接上線;npm 只用來安裝 wrangler,以及比對同音字用的 pinyin-pro(部署時由 wrangler 打包進 Worker)
| 名稱 | 用途 |
|---|---|
USERNAME |
後台帳號,控制台和主持人共用 |
PASSWORD |
後台密碼 |
AUTH_SECRET |
簽 token 用的隨機字串(openssl rand -base64 32)。換掉會立刻登出所有人,包含玩家 |
FIVE_GROUPS |
選填。設成 1、true 或 True 時有第五組,每一頁和網站圖示都會變成五組;其他值或不設定就是四組 |
本機開發寫在 .dev.vars。正式環境四個都用 secret 設定:
npx wrangler secret put USERNAME
npx wrangler secret put PASSWORD
npx wrangler secret put AUTH_SECRET
npx wrangler secret put FIVE_GROUPS # 要五組時才設Important
FIVE_GROUPS 不是機密,但也要用 secret 設定。在 Cloudflare 後台設定的一般變數,會在下次部署時被清掉
main 合併後,Workers Builds 會自動部署到 https://song.ntust.org。網域和帳號設定在 wrangler.jsonc,要手動部署就執行 npm run deploy
- 賽前在控制台按齒輪,設定各組的組別代碼,並上傳歌單
- 選歌(或按「下一首」),主持人頁會顯示這首的解答
- 按「發題」,所有手機同時倒數並開始作答;按「收卷並計分」後,手機先倒數 5 秒(這段時間送出的答案照樣算分),結束後自動批改並加分
- 收卷後可以在「作答狀況」點分數改判,總分會自動加減差額
歌單含有解答,不要放進 git。照 songs.example.json 的格式寫成 songs.json(songs*.json 已加入 gitignore),再到控制台設定的「歌單與解答」上傳
[
{
"year": 2000,
"artist": ["範例歌手", "Example Artist"],
"title": "範例歌名",
"youtube": "https://www.youtube.com/watch?v=xxxxxxxxxxx"
},
{ "year": 2010, "artist": "另一位歌手", "title": ["另一首歌", "別名"] }
]artist和title可以是字串或陣列,陣列裡的每種寫法都算對youtube選填,只接受 http 和 https 網址- 有填:控制台會出現播放連結,收卷後玩家手機會顯示影片縮圖
- 沒填:連結改成到 YouTube 搜尋這首歌
- 歌依照順序編號。收過卷的歌只能原地修正解答,要加歌請加在最後面
- 解答只存在伺服器,玩家要等那一題收卷後才拿得到
- 年份精準 +3、差 3 年以內 +1;歌手、歌名答對各 +1。比對時忽略大小寫、全形半形、空白、標點和重音符號
- 中文同音字算對,簡體字、「妳」和「你」也算;zh/z、ch/c、sh/s、-ng/-n 視為同音。多一個字、少一個字或換成別的字都不算,常見的簡稱要自己加進歌單
- 英文和數字容許拼錯:不算空白和標點,4 個字元以內要完全一樣,5 到 8 個可以錯 1 個,9 個以上可以錯 2 個,相鄰兩個字母對調算錯 1 個;數字一定要對
- 只跟該題的解答比,比對規則在
src/match.js
- 每組每一項取組內最高分,所以一組一題最多 +5
- 收卷後改判或修正歌單,總分會自動加減差額
- 戰況短評的判斷條件和用詞在
src/notes.js
| 項目 | 選用 |
|---|---|
| 後端 | Cloudflare Workers;分數、歌單、作答都存在一個 Durable Object(Scores) |
| 即時同步 | WebSocket(Durable Object hibernation),每頁一條連線,不輪詢 |
| 前端 | 純 HTML、CSS、JavaScript,沒有框架也沒有 build |
| 靜態檔 | Workers Static Assets |
| 部署 | Workers Builds,main 自動部署 |
| 測試 | npm test(Node 內建的 test runner 和 assert) |
| CI | GitHub Actions 跑測試並試打包 Worker;Dependabot 每週更新 wrangler 和 Actions |
src/index.js Worker 與 Durable Object:API、WebSocket、批改與計分
src/match.js 比對歌手和歌名:正規化、同音字和拼錯
src/notes.js 收卷後的戰況短評
public/index.html 玩家手機
public/scoreboard.html 投影計分板
public/dashboard.html 控制台與主持人(同一頁的兩個畫面,/host 會轉到這裡)
public/login.html 後台登入
public/css/ base(共用)、play、admin、scoreboard
public/js/ 各頁的腳本;live.js 負責 WebSocket,scores.js 負責更新分數
public/five/ 五組時的網站圖示
test/ API 與計分的測試
.github/ CI、Dependabot 與 README 圖片
package.json wrangler 版本與 npm 指令
wrangler.jsonc Worker、網域、Durable Object 與靜態檔設定
端點一覽
POST 的參數都是 JSON,只有登入用表單
| 端點 | 參數 | 說明 |
|---|---|---|
GET /api/GetScore |
— | 回 {"1":0,...,"4":0},五組時多一個 "5" |
GET /api/groups.js |
— | 一行 JS,把組數標在 <html data-groups>;每一頁都在 <head> 先載入它 |
POST /api/login |
username、password |
回 {status, msg, token},token 12 小時後到期 |
POST /api/AddScore |
token、group、delta |
加減 delta 分(可以是負數,結果限制在 0–999) |
POST /api/SetScore |
token、group、score |
直接指定分數 |
POST /api/join |
name、code |
用組別代碼加入(不分大小寫),回傳 group 和玩家 token |
GET /api/ws |
token(query,選填) |
WebSocket。帶玩家 token 時推送自己的狀態,不帶時只推送總分;token 失效時第一則訊息會帶 rejoin: true |
POST /api/play/state |
token |
跟 WebSocket 推送的內容相同;收卷後多一個 result |
POST /api/play/history |
token |
收過卷的題目,最近的在最上面 |
POST /api/play/answer |
token、year、artist、title |
作答中可以重複送出,以最後一次為準 |
POST /api/admin/<action> |
token 等 |
後台管理。action 是 state、songs、select、open、close、judge、passwords、reset,參數見 src/index.js 的 handleAdmin |
group是 1–4 的整數,五組時是 1–5;score是 0–999 的整數- 認證失敗回 401,參數錯誤回 400
- 登出只會清掉瀏覽器裡的 token。token 是無狀態簽章,要強制撤銷就換
AUTH_SECRET
歡迎開 Issue 回報問題,或發 Pull Request
PR 送出前請確認
- UI 文案和文件一律用繁體中文;程式碼註解一律用英文
- commit 遵循 Conventional Commits
- 分支命名為
feat/your-feature或fix/your-fix npm test通過- 不使用 Emoji
Copyright (C) 2026 xinshoutw
本專案採用 GNU Affero General Public License v3.0 授權,完整條款見 LICENSE
本專案與國立臺灣科技大學沒有官方關聯