2019年2月1日 星期五

春節摸彩程式展示

春節摸彩程式展示


Ching-Tang Tseng
Hamilton, New Zealand
2 February 2019







過年了,有個負責華人組織的朋友,請我設計一個亂數抽獎程式,在春節聯歡會上使用,今年的除夕是 2 月 4 日。我答應了,而且耗費了不少時間才落定程式。

本來,一個簡單到直接執行 『 100 rand . 』 就能解決的問題,何須如此大費周章地設計程式?

實踐才是檢驗真理的唯一標準,不要以為問題很簡單,就輕率地回答這個問題。身體力行做事情,才能真正體會出必須設計這個程式的道理。

在我個人現行使用的 FORTH 系統中,亂數產生程式是我自行建立的,建立的方法,以前在網文中已經談過許多次,這篇文章不擬再次討論,這一次,討論正確使用亂數的方法。

拿春節摸彩情況的實際要求,檢驗亂數產生程式的效果,很快就能發現,直接使用產生亂數的函數指令,不能解決抽出的獎號不得重複的問題。

假設參與抽獎的人數總共為 100 人,100 是個隨意假設的數字。在我自己設計的系統中,直接執行『 100 rand . 』,可以得到一個介於 0 到 99 之間隨機出現的數字。理論上,如果真夠隨機,那麼,執行 100 次這樣的指令,就能得出 100 個以混亂狀態出現的整套數字。在評鑑亂數產生程式的術語上,稱這種現象為『全週期』,也就是每個該出現的數字都出現過了。

我們在使用亂數產生函數 rand 時,必須認真看待它的性能,直接用,不會有全週期,亂用,甚至可能會遭遇到亂週期或根本沒有週期。產生這種現象的起因,在於亂數產生程式是根據系統單整數的處理範圍而設計的,基本上,它只在一個單整數的範圍內盡量的亂,然後才提供給別的場合使用。這麼一來,在 0 到 100 的範圍內以 rand 取得亂數時,所得亂數,算是次生亂數,不是原生亂數。

次生亂數的產生規則,有人採用只取原生亂數的最後幾位數字來獲得,單純地只用 mod 運算,就能達到目的。也有人採用只取原生亂數的前幾位數字來獲得,單純地使用 um* nip 運算,也能達到目的。這樣的產生規則,就造成了上述抽出獎號會重複的問題。舉取前兩位數字的例子來說,原生亂數在 64 位元的系統中為 18 位數,太多了,表示不便,我們改以只有五位數來等效說明,那麼,凡為 37xxx 的數字,如: 37125, 37998, 37452, ...等等等,總共有一千個,實際上是 18 位數,那就更多了。經過 100 rand 處理後,都會只得 37 。如此一來,原生夠亂的亂數,這樣使用,就不亂了。

我在實踐程式設計時,遭遇到這樣的問題,於是設計了解決問題的程式,其核心執行的部份,仍然使用 rand 亂數函數,這是一個活用亂數產生程式 rand 的實用範例。

最終落定的程式,使用了恰當的指令名稱,並標示指令執行前後,堆疊上數字的變化需求。因此,下列程式的執行內容,不需要解釋。總抽獎人數,就是那個可以被調整內容的 setting 變數,程式現成設定為 100 。



100 value setting

: LuckyArray
  create 1+ cells allot ( n -- )
  does>  swap cells  + ( n -- addr )
;

1000 LuckyArray table

0 value NewNumber
0 value LuckyAmount
0 value flag

: review ( -- )
  0 to flag    
  LuckyAmount 1+    1 
  do 
     NewNumber   I table @    =
     if -1 to flag   leave then 
  loop ;

: unique ( -- n )
  begin 
     setting rand 1+ to NewNumber    
     review    flag    0=
  until 
  1 +to LuckyAmount    
  NewNumber LuckyAmount table ! 
  NewNumber ;

: ListTable ( -- )
  cr 
  setting 1+ 1 
  do 
     i table @ 4 .R 
     i 10 mod 0=  
     if cr then 
  loop 
  cr ;

: reset ( -- )
  0 to NewNumber   0 to LuckyAmount   0 to flag
  setting 1+ 1 
  do 
     0 i table ! 
  loop
;

: >table ( n -- )
  1 +to LuckyAmount    LuckyAmount table !    
  ListTable 
;

: test  ( -- )
  reset 
  randomize
  ListTable cr cr 
  setting 1+ 1 
  do 
     unique drop 
  loop 
  ListTable cr cr
  reset
;

: main ( n --  ) 
  page    randomize 
  1+ 1 
  do 
     unique dup . (.) 
     400 ms 
     girlsay 
     key drop
  loop 
  ListTable
;

\ Usage:
\ 1. reset or test
\ 2. 3 main




上列程式完成測試後,我接受了新的設計要求,程式的執行畫面,必須在現場以投影機放大,顯示在螢幕上。抽出得獎的數字,應該同步以語音輸出。另外,需要配上前置簡短音樂,博取效果。

數字顯示時,同步以語音輸出,是我的系統原本就已擁有的性能,很容易完成。

顯示數字如果仍只採用慣常的顯示方式,字太小了,不夠清楚。於是,我花了點時間在網上搜索,安裝了一個簡易的大號字體顯示軟體,效果不是很好,但可以比不用時清楚。

每次抽出號碼前,都希望配上一些簡短的前置音樂,要尋找這樣的資源,比較耗時間。我在網上過濾了超過一千首以上的音樂,才勉強取得夠用的資源。後來,還試著自行擷取貝多芬第五交響曲中大約五秒鐘的震撼音樂,耗費了一些時間,完成設計。

FORTH 原本就是一種適合應用於即時控制的程式語言,文首的影音展示,告訴大家這樣的效果。但是,被控制的資源,不是 FORTH 的範疇,而且,網上擁有無數個能被這樣控制的資源,所以,它們都不在此處專門介紹。




沒有留言: