我用Bash編寫了一個掃雷遊戲

我在編程教學方面不是專家,但當我想更好掌握某同樣東西時,會試着找出讓本身樂在其中的方法。比方說,當我想在 shell 編程方面更進一步時,我決定用 Bash 編寫一個掃雷遊戲來加以練習。

我在編程教學方面不是專家,但當我想更好掌握某同樣東西時,會試着找出讓本身樂在其中的方法。比方說,當我想在 shell 編程方面更進一步時,我決定用 Bash 編寫一個掃雷遊戲來加以練習。linux

若是你是一個有經驗的 Bash 程序員,但願在提升技巧的同時樂在其中,那麼請跟着我編寫一個你的運行在終端中的掃雷遊戲。完整代碼能夠在這個 GitHub 存儲庫中找到。程序員

作好準備shell

在我編寫任何代碼以前,我列出了該遊戲所必須的幾個部分:編程

  1. 顯示雷區數組

  2. 建立遊戲邏輯ide

  3. 建立判斷單元格是否可選的邏輯函數

  4. 記錄可用和已查明(已排雷)單元格的個數學習

  5. 建立遊戲結束邏輯spa

顯示雷區code

在掃雷中,遊戲界面是一個由 2D 數組(列和行)組成的不透明小方格。每一格下都有可能藏有地雷。玩家的任務就是找到那些不含雷的方格,而且在這一過程當中,不能點到地雷。這個 Bash 版本的掃雷使用 10x10 的矩陣,實際邏輯則由一個簡單的 Bash 數組來完成。

首先,我先生成了一些隨機數字。這將是地雷在雷區裏的位置。控制地雷的數量,在開始編寫代碼以前,這麼作會容易一些。實現這一功能的邏輯能夠更好,但我這麼作,是爲了讓遊戲實現保持簡潔,並有改進空間。(我編寫這個遊戲純屬娛樂,但若是你能將它修改的更好,我也是很樂意的。)

下面這些變量在整個過程當中是不變的,聲明它們是爲了隨機生成數字。就像下面的 a - g 的變量,它們會被用來計算可排除的地雷的值:

# 變量
score=0 # 會用來存放遊戲分數
# 下面這些變量,用來隨機生成可排除地雷的實際值
a="1 10 -10 -1"
b="-1 0 1"
c="0 1"
d="-1 0 1 -2 -3"
e="1 2 20 21 10 0 -10 -20 -23 -2 -1"
f="1 2 3 35 30 20 22 10 0 -10 -20 -25 -30 -35 -3 -2 -1"
g="1 4 6 9 10 15 20 25 30 -30 -24 -11 -10 -9 -8 -7"
#
# 聲明
declare -a room # 聲明一個 room 數組,它用來表示雷區的每一格。

接下來,我會用列(0-9)和行(a-j)顯示出遊戲界面,而且使用一個 10x10 矩陣做爲雷區。(M[10][10] 是一個索引從 0-99,有 100 個值的數組。) 如想了解更多關於 Bash 數組的內容,請閱讀這本書那些關於 Bash 你所不瞭解的事: Bash 數組簡介。

建立一個叫 plough 的函數,咱們先將標題顯示出來:兩個空行、列頭,和一行 -,以示意往下是遊戲界面:

printf '\n\n'
printf '%s' "     a   b   c   d   e   f   g   h   i   j"
printf '\n   %s\n' "-----------------------------------------"

而後,我初始化一個計數器變量,叫 r,它會用來記錄已顯示多少橫行。注意,稍後在遊戲代碼中,咱們會用同一個變量 r,做爲咱們的數組索引。 在 Bash for 循環中,用 seq 命令從 0 增長到 9。我用數字(d%)佔位,來顯示行號($row,由 seq 定義):

r=0 # 計數器
for row in $(seq 0 9); do
printf '%d ' "$row" # 顯示 行數 0-9

在咱們接着往下作以前,讓咱們看看到如今都作了什麼。咱們先橫着顯示 [a-j] 而後再將 [0-9] 的行號顯示出來,咱們會用這兩個範圍,來肯定用戶排雷的確切位置。

接着,在每行中,插入列,因此是時候寫一個新的 for 循環了。這一循環管理着每一列,也就是說,其實是生成遊戲界面的每一格。我添加了一些輔助函數,你能在源碼中看到它的完整實現。 對每一格來講,咱們須要一些讓它看起來像地雷的東西,因此咱們先用一個點(.)來初始化空格。爲了實現這一想法,咱們用的是一個叫 is_null_field 的自定義函數。 同時,咱們須要一個存儲每一格具體值的數組,這兒會用到以前已定義的全局數組 room , 並用 變量 r做爲索引。隨着 r 的增長,遍歷全部單元格,並隨機部署地雷。

  for col in $(seq 0 9); do
((r+=1)) # 循環完一列行數加一
is_null_field $r # 假設這裏有個函數,它會檢查單元格是否爲空,爲真,則此單元格初始值爲點(.)
printf '%s \e[33m%s\e[0m ' "|" "${room[$r]}" # 最後顯示分隔符,注意,${room[$r]} 的第一個值爲 '.',等於其初始值。
#結束 col 循環
done

最後,爲了保持遊戲界面整齊好看,我會在每行用一個豎線做爲結尾,並在最後結束行循環:

printf '%s\n' "|" # 顯示出行分隔符
printf ' %s\n' "-----------------------------------------"
# 結束行循環
done
printf '\n\n'

完整的 plough 代碼以下:

plough()
{
  r=0
  printf '\n\n'
  printf '%s' "     a   b   c   d   e   f   g   h   i   j"
  printf '\n   %s\n' "-----------------------------------------"
  for row in $(seq 0 9); do
    printf '%d  ' "$row"
    for col in $(seq 0 9); do
       ((r+=1))
       is_null_field $r
       printf '%s \e[33m%s\e[0m ' "|" "${room[$r]}"
    done
    printf '%s\n' "|"
    printf '   %s\n' "-----------------------------------------"
  done
  printf '\n\n'
}

我花了點時間來思考,is_null_field 的具體功能是什麼。讓咱們來看看,它到底能作些什麼。在最開始,咱們須要遊戲有一個固定的狀態。你能夠隨便選擇個初始值,能夠是一個數字或者任意字符。我最後決定,全部單元格的初始值爲一個點(.),由於我以爲,這樣會讓遊戲界面更好看。下面就是這一函數的完整代碼:

is_null_field()
{
local e=$1 # 在數組 room 中,咱們已經用過循環變量 'r' 了,此次咱們用 'e'
if [[ -z "${room[$e]}" ]];then
room[$r]="." #這裏用點(.)來初始化每個單元格
fi
}

如今,我已經初始化了全部的格子,如今只要用一個很簡單的函數就能得出當前遊戲中還有多少單元格能夠操做:

get_free_fields()
{
free_fields=0 # 初始化變量 
for n in $(seq 1 ${#room[@]}); do
if [[ "${room[$n]}" = "." ]]; then # 檢查當前單元格是否等於初始值(.),結果爲真,則記爲空餘格子。 
((free_fields+=1))
    fi
  done
}

這是顯示出來的遊戲界面,[a-j] 爲列,[0-9] 爲行。

如何用 Bash 編寫一個掃雷遊戲如何用 Bash 編寫一個掃雷遊戲

Minefield

建立玩家邏輯

玩家操做背後的邏輯在於,先從 stdin 中讀取數據做爲座標,而後再找出對應位置實際包含的值。這裏用到了 Bash 的參數擴展,來設法獲得行列數。而後將表明列數的字母傳給分支語句,從而獲得其對應的列數。爲了更好地理解這一過程,能夠看看下面這段代碼中,變量 o 所對應的值。 舉個例子,玩家輸入了 c3,這時 Bash 將其分紅兩個字符:c 和 3。爲了簡單起見,我跳過了如何處理無效輸入的部分。

colm=${opt:0:1} # 獲得第一個字符,一個字母
ro=${opt:1:1} # 獲得第二個字符,一個整數
case $colm in
a ) o=1;; # 最後,經過字母獲得對應列數。
b ) o=2;;
    c ) o=3;;
    d ) o=4;;
    e ) o=5;;
    f ) o=6;;
    g ) o=7;;
    h ) o=8;;
    i ) o=9;;
    j ) o=10;;
  esac

下面的代碼會計算用戶所選單元格實際對應的數字,而後將結果儲存在變量中。

這裏也用到了不少的 shuf 命令,shuf 是一個專門用來生成隨機序列的 Linux 命令。-i 選項後面須要提供須要打亂的數或者範圍,-n 選項則規定輸出結果最多須要返回幾個值。Bash 中,能夠在兩個圓括號內進行數學計算,這裏咱們會屢次用到。

仍是沿用以前的例子,玩家輸入了 c3。 接着,它被轉化成了 ro=3 和 o=3。 以後,經過上面的分支語句代碼, 將 c 轉化爲對應的整數,帶進公式,以獲得最終結果 i 的值。

i=$(((ro*10)+o)) # 遵循運算規則,算出最終值
is_free_field $i $(shuf -i 0-5 -n 1) # 調用自定義函數,判斷其指向空/可選擇單元格。

仔細觀察這個計算過程,看看最終結果 i 是如何計算出來的:

i=$(((ro*10)+o))
i=$(((3*10)+3))=$((30+3))=33

最後結果是 33。在咱們的遊戲界面顯示出來,玩家輸入座標指向了第 33 個單元格,也就是在第 3 行(從 0 開始,不然這裏變成 4),第 3 列。

建立判斷單元格是否可選的邏輯

爲了找到地雷,在將座標轉化,並找到實際位置以後,程序會檢查這一單元格是否可選。如不可選,程序會顯示一條警告信息,並要求玩家從新輸入座標。

在這段代碼中,單元格是否可選,是由數組裏對應的值是否爲點(.)決定的。若是可選,則重置單元格對應的值,並更新分數。反之,由於其對應值不爲點,則設置變量 not_allowed。爲簡單起見,遊戲中警告消息這部分源碼,我會留給讀者們本身去探索。

is_free_field()
{
  local f=$1
  local val=$2
  not_allowed=0
  if [[ "${room[$f]}" = "." ]]; then
    room[$f]=$val
    score=$((score+val))
  else
    not_allowed=1
  fi
}

如輸入座標有效,且對應位置爲地雷,以下圖所示。玩家輸入 h6,遊戲界面會出現一些隨機生成的值。在發現地雷後,這些值會被加入用戶得分。

如何用 Bash 編寫一個掃雷遊戲如何用 Bash 編寫一個掃雷遊戲

Extracting mines

還記得咱們開頭定義的變量,a - g 嗎,我會用它們來肯定隨機生成地雷的具體值。因此,根據玩家輸入座標,程序會根據(m)中隨機生成的數,來生成周圍其餘單元格的值(如上圖所示)。以後將全部值和初始輸入座標相加,最後結果放在 i(計算結果如上)中。

請注意下面代碼中的 X,它是咱們惟一的遊戲結束標誌。咱們將它添加到隨機列表中。在 shuf 命令的魔力下,X 能夠在任意狀況下出現,但若是你足夠幸運的話,也可能一直不會出現。

m=$(shuf -e a b c d e f g X -n 1) # 將 X 添加到隨機列表中,當 m=X,遊戲結束
if [[ "$m" != "X" ]]; then # X 將會是咱們爆炸地雷(遊戲結束)的觸發標誌
for limit in ${!m}; do # !m 表明 m 變量的值
field=$(shuf -i 0-5 -n 1) # 而後再次得到一個隨機數字
index=$((i+limit)) # 將 m 中的每個值和 index 加起來,直到列表結尾
is_free_field $index $field
    done

我想要遊戲界面中,全部隨機顯示出來的單元格,都靠近玩家選擇的單元格。

如何用 Bash 編寫一個掃雷遊戲如何用 Bash 編寫一個掃雷遊戲

Extracting mines

記錄已選擇和可用單元格的個數

這個程序須要記錄遊戲界面中哪些單元格是可選擇的。不然,程序會一直讓用戶輸入數據,即便全部單元格都被選中過。爲了實現這一功能,我建立了一個叫 free_fields 的變量,初始值爲 0。用一個 for 循環,記錄下遊戲界面中可選擇單元格的數量。 若是單元格所對應的值爲點(.),則 free_fields 加一。

get_free_fields()
{
  free_fields=0
  for n in $(seq 1 ${#room[@]}); do
    if [[ "${room[$n]}" = "." ]]; then
      ((free_fields+=1))
    fi
  done
}

等下,若是 free_fields=0 呢? 這意味着,玩家已選擇過全部單元格。若是想更好理解這一部分,能夠看看這裏的源代碼。

if [[ $free_fields -eq 0 ]]; then # 這意味着你已選擇過全部格子
printf '\n\n\t%s: %s %d\n\n' "You Win" "you scored" "$score"
      exit 0
fi

建立遊戲結束邏輯

對於遊戲結束這種狀況,咱們這裏使用了一些很巧妙的技巧,將結果在屏幕中央顯示出來。我把這部分留給讀者朋友們本身去探索。

if [[ "$m" = "X" ]]; then
g=0 # 爲了在參數擴展中使用它
room[$i]=X # 覆蓋此位置原有的值,並將其賦值爲X
for j in {42..49}; do # 在遊戲界面中央,
out="gameover"
k=${out:$g:1} # 在每一格中顯示一個字母
room[$j]=${k^^}
      ((g+=1))
    done
fi

最後,咱們顯示出玩家最關心的兩行。

if [[ "$m" = "X" ]]; then
      printf '\n\n\t%s: %s %d\n' "GAMEOVER" "you scored" "$score"
      printf '\n\n\t%s\n\n' "You were just $free_fields mines away."
      exit 0
fi

文章到這裏就結束了,朋友們!若是你想了解更多,具體能夠查看個人 GitHub 存儲庫,那兒有這個掃雷遊戲的源代碼,而且你還能找到更多用 Bash 編寫的遊戲。 我但願,這篇文章能激起你學習 Bash 的興趣,並樂在其中。

相關文章
相關標籤/搜索