[設計案例] 生命遊戲#3, 時序的控制
摘要提示
- 回合制盲點: 同樣起始狀態與規則,因掃描順序不同會導致不同結果,暴露回合制的侷限。
- 即時制概念: 以倒數碼錶式的即時機制取代回合制,更貼近真實世界的並行演化。
- 時序規則: 每個細胞每隔約1000ms更新狀態,並加入±10%隨機誤差以製造非同步性。
- 多執行緒模型: 讓每個細胞各自擁有一條執行緒,獨立演進與睡眠,直到世代終止。
- Cell.WholeLife: 透過回圈與Thread.Sleep控制節奏,持續觸發狀態轉移事件。
- Game Host重構: Host負責產生細胞執行緒、週期性刷新地圖與等待執行緒結束。
- 螢幕刷新策略: 以固定頻率(例100ms)輪詢並重繪,不干擾細胞的內部運行。
- 非決定性結果: 加入時間抖動與並行後,系統演化更不可預測、更貼近現實。
- 成本與取捨: 不當自行實作context switch代價高,善用OS與排程器的thread能力更實際。
- 後續方向: 將引入繼承與多型,讓異質生命在同一世界共存,擴充規則複雜度。
全文重點
本文指出前一版生命遊戲的核心盲點:採回合制與固定掃描順序會讓結果受實作細節左右,導致同樣初始與規則卻可能出現不同終局,偏離真實世界的並行本質。作者以早期Final Fantasy從回合制走向即時制為比喻,提出將生命遊戲改為「每個個體獨立按自己的時鐘更新」的時序模型。新規則設定每個細胞約每1000毫秒進入下一狀態,並添加±10%時間誤差,使各細胞在時間上彼此錯開,形成非同步的即時演化。
在實作上,設計讓每個細胞擁有自己的執行緒,透過Cell.WholeLife方法在指定世代內反覆觸發狀態轉換並睡眠一小段時間,以達到節奏控制。Host端則為每個細胞建構並啟動一條Thread,並以固定刷新頻率輪詢並重繪整體世界狀態,讓畫面更新與細胞演化解耦。待所有細胞執行緒停止後,Host再Join以確保資源回收與完整關閉。
此改造使系統行為更接近現實的非決定性與不可預測性:即使同一初始條件,因獨立執行緒與時間抖動所引發的競合,演化軌跡不再整齊同步,畫面也由「一次一整步」轉變為連續閃動、各自跳動的生命節奏,更具生動性與可看性。然而作者也提醒,不當自行肩負context switch等職責代價高昂,應善用作業系統與執行緒排程器的能力來管理並行。文章最後預告下一步將在多執行緒時序解決方案之上,引入物件導向的繼承與多型,讓不同型態的生命於同一世界共存,持續豐富規則與行為互動。
段落重點
回合制的侷限與問題意識
作者首先指出前版生命遊戲的盲點:雖然起始狀態與規則一致,但結果會因程式掃描(SCAN)順序而異,反映回合制的本質缺陷。回合制如同「你下完換我」的輪替,先後順序直接影響局面。這與現實中眾多個體同時、持續演化的現象不符。透過FF從回合制走向即時制的例子,作者鋪陳出需要以時間軸與並行觀點重新設計生命遊戲的動機。
即時制與時序規則的重構
為貼近現實,本文引入即時制:每個細胞都有自己的「碼錶」,當倒數至零便觸發下一次狀態更新。具體規則是每隔約1000ms進入下一狀態,並加入±10%誤差(約950–1050ms),保證不同細胞在時間軸上錯開,形成非同步演化。其他生死規則維持不變。這種設計將「回合同步」拆解為「個體節奏」,讓系統具備更強的隨機性與逼真度,為之後做成線上或社交平台上的即時互動遊戲奠定基礎。
Cell.WholeLife:細胞自我節奏的實作
在Cell類別中新增WholeLife(object state)方法,接收世代數作為參數,透過for迴圈依序呼叫OnNextStateChange()觸發狀態轉換,然後Thread.Sleep隨機950–1050毫秒以控制節奏。此方法讓細胞自我驅動完成一生的演化週期,直到指定世代為止。實作重點簡潔:不更動原有狀態轉移邏輯,只用時間控制與循環包覆,確保與既有設計解耦且可重用。
Game Host:啟動、刷新與收斂
Host負責建立世界、為每個細胞生成一條Thread並啟動WholeLife。畫面更新策略改為固定時間片(例如每100ms)刷新:不斷輪詢世界並呼叫ShowMaps重繪,同時檢查所有執行緒是否停止(IsAllThreadStopped),直到全部完成後再逐一Join確保有序結束。此設計將「演化」與「顯示」分離:細胞各自演進,Host像人造衛星定時「拍照」,觀察而不干擾,使UI更新不阻塞核心邏輯,提升互動流暢度。
行為變化與非決定性、體驗提升
重構後的系統不再是整齊的「一刷一步」;由於獨立執行緒與時間抖動,細胞以不規則節奏變化,畫面呈現持續閃動與躍動,觀感更像卡通或即時模擬,更具生命力。非同步與隨機使結果難以預測,即便對「上帝視角」的主控者亦然,更符合複雜系統的非決定性特質。這也展示了時間建模對系統行為與可視化的重大影響。
並行代價與未來擴充方向
作者提醒,不當自行模擬context switch成本高昂,應依賴作業系統與排程器妥善管理執行緒,避免不必要的複雜度與效能損耗。本次改造以最小變動達成即時化並保留擴充彈性。預告下一篇將在時間並行解決後,導入繼承與多型,以支援多樣生命型態在同一世界共存,擴展互動規則與生態動力學,邁向更豐富的物件導向設計與系統行為研究。
資訊整理
知識架構圖
- 前置知識:
- 康威生命遊戲的基本規則與「回合制」更新機制
- C#/.NET 基礎語法與類別/方法
- 作業系統與多執行緒基本觀念(Thread、Sleep、Join、ThreadState)
- 隨機數與時間控制(模擬抖動/jitter)
- 核心概念:
- 回合制 vs 即時制:從同步「同一步」的回合更新,改為每個個體按自身節奏更新
- 多執行緒個體化:每個 Cell 擁有各自的執行緒與生命週期
- 時序抖動:以 Thread.Sleep(950~1050ms) 模擬非完全同步的現實
- 繪製與模擬解耦:Game Host 以固定頻率重繪,不干擾細胞狀態演進
- 非決定性結果:更新順序與時序導致相同起始狀態也可能產生不同演化
- 技術依賴:
- .NET Thread API:Thread, Thread.Start(state), Thread.Join(), ThreadState
- 計時與延遲:Thread.Sleep
- 隨機性:Random 產生時間抖動
- 模型物件:World、Cell、OnNextStateChange、WholeLife 方法
- 監控與收斂:IsAllThreadStopped 檢測所有執行緒是否結束
- 應用場景:
- 即時制遊戲與線上遊戲的行為模擬(非回合、個體節奏)
- 大量獨立代理(agent-based)系統的時序控制
- 視覺化模擬:渲染與邏輯分離,異步不阻塞畫面更新
- 教學示例:對比同步與非同步系統行為、決定性與非決定性
學習路徑建議
- 入門者路徑:
- 了解生命遊戲規則與傳統回合制實作
- 學習 C# 基礎、類別設計(World/Cell)
- 初探 Thread、Sleep、Join 的用法,能啟動/等待執行緒
- 進階者路徑:
- 將回合制轉為即時制:每 Cell 一個 Thread,加入時間抖動
- 練習渲染迴圈與模擬迴圈解耦(固定頻率刷新 UI)
- 探索非決定性與時序對結果的影響;觀察與記錄差異
- 進一步考慮資源成本與替代方案(ThreadPool、Timer、Task、Actor)
- 實戰路徑:
- 加入世代上限、收斂條件與結束監控(ThreadState/Join)
- 強化健壯性:例外處理、取消/停止(CancellationToken)、清理
- 優化隨機與時序:獨立 RNG、可配置抖動、統計分析結果
- 擴展規則:繼承與多型支援不同生命型態在同一世界共存
關鍵要點清單
- 回合制與即時制的差異: 回合制同步更新、即時制各自節奏更新會導致非決定性結果 (優先級: 高)
- 多執行緒 per-cell 模式: 每個 Cell 擁有 Thread,獨立推進生命週期 WholeLife (優先級: 高)
- 時序抖動設計: 使用 Thread.Sleep(950~1050ms) 模擬不完全同步的現實時序 (優先級: 高)
- 渲染與模擬解耦: Game Host 以固定頻率刷新畫面,不阻塞 Cell 的生命進行 (優先級: 高)
- 執行緒生命週期管理: 使用 Thread.Start/Join 與 ThreadState 檢測完成並統一收斂 (優先級: 高)
- 非決定性與可觀測性: 相同初始條件下結果不一致;需以視覺化或記錄觀察差異 (優先級: 中)
- 代價與可擴展性: 大量 Thread 造成 context switch 成本高,需慎選模型 (優先級: 高)
- 替代技術選型: ThreadPool/Task/Timer/Actor 模型可降低成本並提升可控性 (優先級: 中)
- 隨機數使用注意: 每 Cell 應使用獨立 RNG 或執行緒安全 RNG 避免競爭與相關性 (優先級: 中)
- 邏輯一致性風險: 即時制下鄰居狀態讀取是「當下值」,非傳統「下一代快照」,規則含義改變 (優先級: 高)
- 參數化與可配置: 世界大小、最大世代、刷新頻率與抖動範圍應可設定 (優先級: 中)
- 異常與取消: 執行緒中的例外處理、可中止條件與優雅關閉機制 (優先級: 中)
- 效能監控: 觀測 CPU 使用率、切換次數與刷新效能以調整設計 (優先級: 中)
- 視覺體驗提升: 更流暢刷新與動畫化呈現,提升可讀性與趣味性 (優先級: 低)
- 面向擴展的設計: 以繼承/多型加入不同生命型態,維持世界規約與相容性 (優先級: 中)