這個轉盤的運作順序是:
也就是說,動畫只是演出來的,結果在動畫開始前就決定了。
聽起來像作弊,其實相反 —— 這是比較公平的做法。
直覺上「隨機給一個力道讓它自由停下」比較公平,但實際上不是:
轉盤最後停在哪,取決於起始角度 + 隨機力道 + 減速曲線三者的組合。 這三個東西都不是均勻分布的:
easeOutQuart 之類)讓最後一段轉得很慢,落點會集中在某些區域三個偏差疊起來,結果就是某些格子比較容易停。而最糟的是: 這種偏差完全看不出來。畫面上轉盤在轉、看起來很隨機,但跑一萬次就會看到分布不平。
先抽再轉的做法把「隨機」壓縮成一行程式:
let r = Math.random() * 總權重;
for (…) { r -= 這個人的權重; if (r < 0) return 這個人; }
這一行可以直接被驗證,動畫怎麼寫都不會影響它。
這個工具的抽選函式有自動化測試,每次改版都會跑:
誤差必須小於 1%
防止「畫面指到 A、名單記的是 B」
最後一條特別重要:如果動畫算錯,現場會看到指針明明停在張三,跳出來的名字卻是李四。 那在尾牙舞台上是無法解釋的。
Math.random(),也就是瀏覽器內建的偽隨機數產生器。
⚠️ 誠實說明它的性質:這是偽隨機,不是密碼學等級的亂數。 對抽獎來說綽綽有餘(沒有人能預測或操縱它),但它不適合用在需要防止有心人破解的場合 (例如線上博弈)。尾牙抽獎用它是完全合理的。
動畫是非同步的,而瀏覽器會完全暫停背景分頁的動畫(實測:分頁隱藏時 1 秒 0 幀)。 主持人切去看投影片再切回來,中獎結果才會補寫入 —— 而那時候狀態可能已經變了。
所以結果寫進名單之前會重新確認兩件事:這個獎項還有名額、這個人還有資格。 不確認的話會出現「3 個名額卻列出 4 位中獎者」或「一人只中一次卻中了兩次」。 這兩個在畫面上都很明顯,但開發時測不到 —— 它們是實際踩到之後才補上的守衛。
現場操作的注意事項見尾牙現場的實務。
下載這一頁的 .md 原始檔 用編輯器打開它,就是一份現成的範例文件。