Sitemap

Vibe Coding 第二篇,解決《小熊諾樂GO!香港版》的隨機化問題

7 min readApr 12, 2025

遊戲只由一名香港人嘔心瀝血開發,目的在於讓香港人找回香港的感覺,並且可以讓新一代通過遊戲學習香港地理和各區特色。

Lorye Go! Hong Kong real in-game play. Video created by the author.

Vibe Coding 第一篇, 一個香港夢: 與 AI 共建小熊諾樂GO! | by Lorentz Yeung | Apr, 2025 | Medium

新挑戰:可預測的隨機性

作為《小熊諾樂GO!香港版》的開發者,我已經和 AI 合作了大約三週,將這個以火車為主題的棋盤遊戲帶入生活。但是我發現了一個新問題:遊戲內的問題和事件的隨機化不如預期那樣隨機。遊戲有 440 條 trivia 問題,黃色車站,和與季節性有關的隨機事件等,這些全都需要依靠隨機去選擇出來,因為我希望每場遊戲都能讓玩家感覺到新鮮感。更重要的是,透過隨機問題,希望年輕的玩家能從遊戲認識到香港的特色。然而,現在玩家卻不斷只看到相同的問題 — 如 QIDs 1、100、233、301 — 出現得太頻繁。事件的隨機性也感覺不對,雖然因為選項較少而較不明顯。因此我寫下了這篇日誌,記錄了我如何與 AI 一起解決這個問題。

罪魁禍首:PRNG 和可預測模式

問題源自 Python 的 random 模組,這是一個本質上確定性的偽隨機數生成器 (PRNG)。如果沒有多樣化的種子,它會生成相似的序列。在我的遊戲中,我首先在啟動時和每個遊戲會話開始時各洗牌問題列表一次,但選擇仍使用 question_count % len(all_questions)。如果每個遊戲開始時 question_count 重置為 0,我就總是從洗牌後列表的開頭挑選。如果由於 PRNG 種子相似,洗牌在會話之間變化不大,all_questions 的早期元素 — 如索引 0、1、2 — 會保持大致相同。因此,玩家反复看到相同的問題(例如 IDs 1、100、233、301)。黃色車站的事件使用 random.choice() 挑選,也面臨同樣的問題。

第一個解決方法:雙重洗牌

我最初的修復方法很簡單:洗牌兩次。代碼如下:

all_questions = [(cat, q) for cat in questions for q in questions[cat]]
random.shuffle(all_questions)
custom_print(f"已將 all_questions 洗牌一次...")

在會話開始時:

random.shuffle(all_questions)
custom_print(f"為新遊戲會話洗牌 {len(all_questions)} 個問題")

我認為更多洗牌等於更多混亂,對吧?錯了。相同的問題還是頻繁出現。PRNG 種子默認與系統時鐘綁定,變化不足,而我的選擇方法將我鎖定在列表的前端。

第二個解決方法:用納秒播種

接下來,我使用 time.time_ns() 提升種子的精確度到納秒級:

random.seed(time.time_ns())
random.shuffle(all_questions)
custom_print(f"使用種子 {time.time_ns()} 洗牌 {len(all_questions)} 個問題")

這有些幫助 — 種子更獨特 — 但選擇仍依賴 question_count。列表前端的項目仍占主導地位,440 個問題的龐大,但洗牌幅度不足以打破模式。

突破:隨機索引挑選

我的 vibe coding 夥伴 Grok 3 出場了。它指出真正的缺陷:如果我總是按可預測的方式循環,為什麼要洗牌?於是我改用 random.randint() 來挑選問題:

question_idx = random.randint(0, len(all_questions) - 1)
category, question_data = all_questions[question_idx]

砰 — 現在每個問題每次都有 1/440 的機會被選中,無論列表順序如何。納秒種子保持 PRNG 在會話間的新鮮度,重複現象大致消失了,但仍有跡可尋。

人性化的轉折:自定義種子風格

Grok 建議使用完整的 time.time_ns(),但我想按自己的方式來。我設計了這個:

random.seed((time.time_ns() % 10**9) / 1000)
random.shuffle(all_questions)
custom_print(f"使用種子 {(time.time_ns() % 10**9) / 1000} 洗牌 {len(all_questions)} 個問題")

只取納秒時間戳的最後 9 位(例如 823074000)並除以 1000(得到 823074.0),因為我發現最後嗰個位的前六個位的數字變化最大。於是這樣就給了我一個有趣的浮點種子,仍然適用於 random.seed()。感覺很動態,測試中也保持了不可預測性。

修復事件

黃色車站的事件也需要調整。原本是:

event_list = seasonal_events[current_season]
event_tuple = random.choice(event_list)

我將其與問題的修復對齊:

event_idx = random.randint(0, len(event_list) - 1)
event_tuple = event_list[event_idx]
custom_print(f"為 {current_season} 季節選中事件索引:{event_idx}")

使用相同的自定義種子,遊戲內的事件,大致上都隨機出現了,沒有特別偏向某一個事件。

This event came up too often, and this fix gives other events an equal chance to come up. Image created by Author.

最終配方

解決方案由三個要素組成:自定義種子 ((time.time_ns() % 10**9) / 1000)、random.randint() 用於挑選,以及日誌確認其有效。沒有了可預計重複性 — 只有純粹的、充滿活力的隨機性。

Vibe Coding 的實踐

我在玩遊戲時發現了可預計的隨機性 — AI 無法「感覺」到這一點。我調整了洗牌和種子變化,然後 Grok 帶著 random.randint() 介入,解決了技術問題。我們一起解決了隨機化問題。

為何人類依然主宰

AI 很出色,但它不能在沒有我玩遊戲的情況下發現遊戲「感覺不對」。由於我的數據科學背景,我之前就遇到過隨機化問題,所以我知道PRNG的成因和下一步該往哪個方向走。我推動了願景方向,Grok 完善了它。我相信這就是 vibe coding 的魔力 — 人類的經驗和願景與 AI 的力量相遇。

總結

修復《小熊諾樂GO!》的創造是一個難忘的旅程。今次這個問題,從雙重洗牌到自定義種子與索引挑選系統,現在每次玩這個遊戲都帶來滿足感。如果你正在編碼某樣東西,相信你的直覺,與 AI 一起 vibe,然後發布它!

遊戲只由一名香港人嘔心瀝血開發,目的在於讓香港人找回香港的感覺,並且可以讓新一代通過遊戲學習香港地理和各區特色。免費下載香港開發遊戲,請到下面的網址。

免費下載香港開發遊戲Lorye Go! 首頁
GitHub 儲存庫entzyeung/LoryeGo
Windows 或者 Mac版本安裝方法遊戲安裝方法

香港人,開心遊戲吧!快跳上車,一起感受這股熱血氛圍!

支持作者
如果覺得這篇文章有用,請考慮透過 PayPal小額贊助 捐款支持我!

如果你享受這段旅程,或只是想感謝我將香港的熱鬧混亂化為遊戲的一部分,歡迎透過PayPal小額贊助 成為GitHub贊助者。你的支持讓火車繼續奔馳、程式碼持續流動,也讓每位玩家的熱情氛圍永不止息。

謝謝你!

--

--

Lorentz Yeung
Lorentz Yeung

Written by Lorentz Yeung

Data Analyst in Microsoft, Founder of El Arte Design and Marketing, Certified Digital Marketer, MSc in Digital Marketing, London based.

No responses yet