指令碼程式設計之骰子游戲

Dave Taylor發表於2018-02-20
作者: Dave Taylor 譯者: LCTT wenwensnow

| 2018-02-20 13:21   收藏: 1    

Bunco:一個使你的“快艇”遊戲看起來更復雜的擲骰子游戲。

我已經有段時間沒有編寫遊戲了,所以我覺得現在正是做一些這方面事情的時候。起初,我想“用指令碼編一個 Halo遊戲?”(LCTT 譯註:Halo,光暈系列遊戲),但我後來意識到這不太可能。來編一個叫 Bunco 的簡單骰子游戲吧。你也許沒有聽說過,不過你母親絕對知道 —— 當一群年輕女孩聚在當地的酒吧或者小酒館的時候,這是個很受歡迎的遊戲。

遊戲一共六輪,有三個骰子,規則很簡單。每次投三個骰子,投出的點數要和當前的輪數數字一致。如果三個骰子都和當前的輪數一致,(比如,在第三輪三個骰子都是 3),你這一輪的分數就是 21。 如果三個骰子點數都相同但和輪數數字不同,你會得到最低的 Bunco 分數,只有 5 分。如果你投出的點數兩者都不是,每一個和當前輪數相同的骰子得 1 分。

要想玩這個遊戲,它還涉及到團隊合作,每一隊(包括贏的那隊),每人付 5 美元現金,或贏家得到其他類似現金獎勵,並規定什麼樣的情況下才是贏家,例如“最多 Buncos” 或“最大點數”的情況下勝利。在這裡我會跳過這些,而只關注投骰子這一部分。

關於數學邏輯部分

在專注於程式設計這方面的事之前,我先簡單說說遊戲背後的數學邏輯。要是有一個適當重量的骰子投骰子會變得很容易,任意一個值出現機率都是 1/6。

完全隨機小提示:不確定你的骰子是否每個面都是一樣重量? 把它們扔進鹽水裡然後擲一下。YouTube 上有一些有趣的 D&D 世界的影片向你展示了怎麼來做這個測試。

所以三個骰子點數一樣的機率有多大? 第一個骰子 100% 會有一個值 (這兒沒什麼可說的),所以很簡單。第二個則有 16.66% 的機率和第一個骰子的值一樣,接下來第三個骰子也是一樣。 但當然,總機率是三個機率相乘的結果,所以最後,三個骰子值相等的機率是 2.7%。

接下來,每個骰子和當前輪數數字相同的機率都是 16.66%。從數學角度來說:0.166 * 0.166 * 0.166 = 0.00462 。

換句話說,你有 0.46% 的可能性投出 Bunco,比 200 次中出現一次的可能性還小一點。

實際上還可以更難。如果你有 5 個骰子,投出 Mini Bunco (也可以叫做 Yahtzee “快艇”) 的機率為 0.077%,如果你想所有的骰子的值都相同,假設都是 6,那機率就是 0.00012%,那就基本上沒什麼可能了。

開始程式設計吧

和所有遊戲一樣,最難的部分是有一個能生成真正的隨機數的隨機數發生器。這一部分在 shell 指令碼中還是很難實現的,所以我需要先回避這個問題,並假設 shell 內建的隨機數發生器就夠用了。

不過好在內建的隨機數發生器很好用。用 $RANDOM 就能得到一個 0MAXINT(32767) 之間的隨機值:

$ echo $RANDOM $RANDOM $RANDOM
10252 22142 14863

為了確保產生的值一定是 1 - 6 之中的某個值,使用取餘函式:

$ echo $(( $RANDOM % 6 ))
3
$ echo $(( $RANDOM % 6 ))
0

哦!我忘了要加 1,下面是另一次嘗試:

$ echo $(( ( $RANDOM % 6 ) + 1 ))
6

下面要實現投骰子這一功能。這個函式中你可以宣告一個區域性變數來儲存生成的隨機值:

rolldie()
{
   local result=$1
   rolled=$(( ( $RANDOM % 6 ) + 1 ))
   eval $result=$rolled
}

使用 eval 確保生成的隨機數被實際儲存在變數中。這一部分也很容易:

rolldie die1

這會為第一個骰子生成一個 1 - 6 之間的隨機值儲存到 die1 中。要擲 3 個骰子,很簡單:

rolldie die1 ; rolldie die2 ; rolldie die3

現在判斷下生成的值。首先,判斷是不是 Bunco(3 個骰子值相同),然後是不是和當前輪數值也相同:

if [ $die1 -eq $die2 ] && [ $die2 -eq $die3 ] ; then
  if [ $die1 -eq $round ] ; then
    echo "BUNCO!"
    score=25
  else
    echo "Mini Bunco!"
    score=5
  fi

這可能是所有判斷語句中最難的部分了,注意第一個條件語句中這種不常用的寫法 : [ cond1 ] && [ cond2 ]。如果你想寫成 cond1 -a cond2 ,這樣也可以。在 shell 程式設計中,解決問題的方法往往不止一種。

程式碼剩下的部分很直白,你只需要判斷每個骰子的值是不是和本輪數字相同:

if [ $die1 -eq $round ] ; then
  score=1
fi
if [ $die2 -eq $round ] ; then
  score=$(( $score + 1 ))
fi
if [ $die3 -eq $round ] ; then
  score=$(( $score + 1 ))
fi

唯一要注意的是當出現 Bunco/Mini Bunco 就不需要再統計本輪分數了。所以整個第二部分的判斷語句都要寫在第一個條件語句的 else 中(為了判斷 3 個骰子值是否都相同)。

把所有的綜合起來,然後在命令列中輸入輪數,下面是現在的指令碼執行後的結果:

$ sh bunco.sh 5
You rolled: 1 1 5
score = 1
$ sh bunco.sh 2
You rolled: 6 4 3
score = 0
$ sh bunco.sh 1
You rolled: 1 1 1
BUNCO!
score = 25

竟然這麼快就出現 Bunco 了? 好吧,就像我說的,shell 內建的隨機數發生器在隨機數產生這方面可能有些問題。

你可以再寫個指令碼測試一下,去執行上述指令碼幾百次,然後看看 Bunco/Mini Bunco 出現次數所佔的百分比。但是我想把這部分作為練習,留給親愛的讀者你們。不過,也許我下次會抽時間完成剩下的部分。

讓我們完成這一指令碼吧,還有分數統計和一次性執行 6 次投骰子(這次不用再在命令列中手動輸入當前輪數了)這兩個功能。這也很容易,因為只是將上面的內容整個巢狀在裡面,換句話說,就是將一個複雜的條件巢狀結構全部寫在了一個函式中:

BuncoRound()
{
   # roll, display, and score a round of bunco!
   # round is specified when invoked, score added to totalscore

   local score=0 ; local round=$1 ; local hidescore=0

   rolldie die1 ; rolldie die2 ; rolldie die3
   echo Round $round. You rolled: $die1 $die2 $die3

   if [ $die1 -eq $die2 ] && [ $die2 -eq $die3 ] ; then
     if [ $die1 -eq $round ] ; then
       echo "  BUNCO!"
       score=25
       hidescore=1
     else
       echo "  Mini Bunco!"
       score=5
       hidescore=1
     fi
   else
     if [ $die1 -eq $round ] ; then
       score=1
     fi
     if [ $die2 -eq $round ] ; then
       score=$(( $score + 1 ))
     fi
     if [ $die3 -eq $round ] ; then
       score=$(( $score + 1 ))
     fi
   fi

   if [ $hidescore -eq 0 ] ; then
     echo "  score this round: $score"
   fi

   totalscore=$(( $totalscore + $score ))
}

我承認,我忍不住自己做了一點改進,包括判斷當前是 Bunco、Mini Bunco 還是其他需要計算分數的情況這一部分 (這就是 $hidescore 這一變數的作用)。

實現這個簡直是小菜一碟,只要一個迴圈就好了:

for round in {1..6} ; do
  BuncoRound $round
done

這就是現在所寫的整個程式。讓我們執行一下看看結果:


$ sh bunco.sh 1
Round 1\. You rolled: 2 3 3
  score this round: 0
Round 2\. You rolled: 2 6 6
  score this round: 1
Round 3\. You rolled: 1 2 4
  score this round: 0
Round 4\. You rolled: 2 1 4
  score this round: 1
Round 5\. You rolled: 5 5 6
  score this round: 2
Round 6\. You rolled: 2 1 3
  score this round: 0
Game over. Your total score was 4

嗯。並不是很令人滿意,可能是因為它只是遊戲的一次完整執行。不過,你可以將指令碼執行幾百幾千次,記下“Game over”出現的位置,然後用一些快速分析工具來看看你在每 6 輪中有幾次得分超過 3 分。(要讓 3 個骰子值相同,這個機率大概在 50% 左右)。

無論怎麼說,這都不是一個複雜的遊戲,但是它是一個很有意思的小程式專案。現在,如果有一個 20 面的骰子,每一輪遊戲有好幾十輪,每輪都擲同一個骰子,情況又會發生什麼變化呢?


via: http://www.linuxjournal.com/content/shell-scripting-bunco-game

作者:Dave Taylor 譯者:wenwensnow 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出

指令碼程式設計之骰子游戲

相關文章