bash高级使用_通过构建扫雷来磨练高级Bash技能

bash高级使用

我不是教授编程的专家,但是当我想在某些方面变得更好时,我试图找到一种玩转它的方法。 例如,当我想精通shell脚本时,我决定通过在Bash中编写Minesweeper游戏版本进行练习。

如果您是经验丰富的Bash程序员,并且想在玩耍时磨练自己的技能,请继续在终端中编写自己的Minesweeper版本。 完整的源代码可在此GitHub存储库中找到。

做好准备

在开始编写任何代码之前,我概述了创建游戏所需的要素:

  1. 打印雷区
  2. 创建游戏逻辑
  3. 创建逻辑以确定可用的雷区
  4. 保持可用和发现(提取)地雷的数量
  5. 创建残局逻辑

在Minesweeper中,游戏世界是2D隐藏的单元阵列(列和行)。 每个单元可能包含也可能不包含爆炸地雷。 玩家的目标是揭示不包含地雷的单元,并且永远不显示地雷。 游戏的Bash版本使用10x10矩阵,该矩阵使用简单的bash数组实现。

首先,我分配一些随机变量。 这些是可以在板上放置地雷的位置。 通过限制位置数量,可以很容易地在此基础上进行构建。 逻辑可能会更好,但我想让游戏看起来简单而又不成熟。 (我写这个很有趣,但是很高兴欢迎您的贡献,使它看起来更好。)

以下变量是一些默认变量,它们声明为随机调用以进行字段放置,例如变量ag,我们将使用它们来计算可提取的地雷:


   
   
# variables
score = 0 # will be used to store the score of the game
# variables below will be used to randomly get the extract-able cells/fields from our mine.
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"
#
# declarations
declare -a room   # declare an array room, it will represent each cell/field of our mine.

接下来,我用(0-9)列和(aj)行打印我的木板,形成一个10x10矩阵,用作游戏的雷区。 (M [10] [10]是一个带有索引0-99的100值数组。)如果您想了解有关Bash数组的更多信息,请阅读“ 您不知道Bash:Bash数组简介”

让我们称它为函数,首先我们打印标题:两条空白行,列标题和概述比赛场地顶部的一行:


   
   
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 # our counter
for row in $ ( seq 0 9 ) ; do
  printf '%d  ' " $row " # print the row numbers from 0-9

在我们从这里继续前进之前,让我们检查一下到目前为止所取得的成就。 我们首先水平打印序列[aj] ,然后在[0-9]范围内打印行号,我们将使用这两个范围作为用户输入坐标来定位要提取的地雷。  

下一个,   在每一行中,都有一个列交叉点,因此该打开一个新的for循环了。 这个负责管理每一列,因此它实质上是在运动场中生成每个单元。 我添加了一些帮助程序功能,您可以在源代码中看到的完整定义。 对于每个单元格,我们都需要一些东西来使该字段看起来像一个矿井,因此,我们使用一个名为is_null_field的自定义函数,用一个点(。)来初始化空的。 另外,我们需要一个数组变量来存储每个单元格的值,我们将使用预定义的全局数组变量room以及索引变量r 。 随着r的增加,我们遍历单元格,一路掉下地雷。


   
   
  for col in $ ( seq 0 9 ) ; do
    ( ( r+= 1 ) )   # increment the counter as we move forward in column sequence
    is_null_field $r   # assume a function which will check, if the field is empty, if so, initialize it with a dot(.)
    printf '%s \e[33m%s\e[0m ' "|" " ${room[$r]} " # finally print the separator, note that, the first value of ${room[$r]} will be '.', as it is just initialized.
  #close col loop
  done

最后,通过用一行将每行的底部括起来,然后关闭行循环,使电路板保持良好的定义:


   
   
printf '%s\n' "|"   # print the line end separator
printf '   %s\n' "-----------------------------------------"
# close row for loop
done
printf '\n\n'

完整的功能如下:


   
   
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 # we used index 'r' for array room already, let's call it 'e'
    if [ [ -z " ${room[$e]} " ] ] ; then
      room [ $r ] = "."   # this is where we put the dot(.) to initialize the cell/minefield
    fi
}

现在,我已经初始化了我的地雷中的所有单元,通过声明并随后调用如下所示的简单函数,我得到了所有可用地雷的计数:


   
   
get_free_fields ( )
{
  free_fields = 0     # initialize the variable
  for n in $ ( seq 1 ${#room[@]} ) ; do
    if [ [ " ${room[$n]} " = "." ] ] ; then   # check if the cells has initial value dot(.), then count it as a free field.
      ( ( free_fields+= 1 ) )
    fi
  done
}

这是打印的雷区,其中[ aj]是列,[ 0-9 ]是行。

Minefield

创建驱动播放器的逻辑

播放器逻辑从stdin读取一个选项作为地雷的坐标,并提取雷场上的精确区域。 它使用Bash的参数扩展提取列和行输入,然后将列馈入一个开关,该开关指向板上等效的整数符号,要了解这一点,请参阅在switch case语句中将值分配给变量' o'下面。 例如,玩家可能输入c3 ,Bash将其分为两个字符: c3 。 为了简单起见,我跳过了无效条目的处理方式。


   
   
  colm = ${opt:0:1}   # get the first char, the alphabet
  ro = ${opt:1:1}     # get the second char, the digit
  case $colm in
    a ) o = 1 ;;       # finally, convert the alphabet to its equivalent integer notation.
    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中进行数学评估 ,我们将在此处大量使用它们。

假设前面的示例通过stdin接收到c3 。 然后,从上述switch case语句中ro = 3o = 3c转换为其等效整数,并将其放入我们的公式中以计算最终索引' i'。


   
   
  i =$ ( ( ( ro * 10 ) +o ) )   # Follow BODMAS rule, to calculate final index.
  is_free_field $i $ ( shuf -i 0 - 5 -n 1 )   # call a custom function that checks if the final index value points to a an empty/free cell/field.

通过此数学运算来了解如何计算最终索引“ i ”:


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

最终索引值为33。在上面印刷的板上,最终索引指向第33个单元格,该索引应为第3行(从0开始,否则为第4行)和第3(C)列。

创建逻辑以确定可用的雷区

为了提取地雷,在对坐标进行解码并找到索引之后,程序将检查该字段是否可用。 如果不是,程序将显示警告,然后玩家选择另一个坐标。

在此代码中,如果单元格包含点( )字符,则该单元格可用。 假设可用,将重置单元格中的值并更新分数。 如果一个单元格由于不包含点而不可用,则设置一个变量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
}
Extracting mines

如果输入的坐标可用,则发现地雷,如下所示。 当提供h6作为输入时,一些值随机填充在我们的雷区中,这些值会在提取分钟后添加到用户分数中。

Extracting mines

现在记住我们在开始时声明的变量[ag],我现在将在这里使用它们来提取随机矿井,并使用Bash间接将其值分配给变量m 。 这样,根据输入的坐标,程序选取一个随机组附加号码(M)来计算附加的字段将被填充的(如上所示)通过将其添加到原始的输入坐标,来表示这里以i(如上计算

请注意,下面的代码片段中的字符X是我们唯一的GAME-OVER触发器,我们将其添加到我们的随机播放列表中以随机出现,带有shuf命令的妙处 ,它可以在任意多次出现之后出现,甚至可能不会出现给我们幸运的获奖用户。


   
   
m =$ ( shuf -e a b c d e f g X -n 1 )   # add an extra char X to the shuffle, when m=X, its GAMEOVER
  if [ [ " $m " ! = "X" ] ] ; then         # X will be our explosive mine(GAME-OVER) trigger
    for limit in ${!m} ; do           # !m represents the value of value of m
      field =$ ( shuf -i 0 - 5 -n 1 )     # again get a random number and
      index =$ ( ( i+limit ) )             # add values of m to our index and calculate a new index till m reaches its last element.
      is_free_field $index $field
    done

我希望所有显示的单元格都与玩家选择的单元格相邻。

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   # well that means you extracted all the mines.
      printf '\n\n\t%s: %s %d\n\n' "You Win" "you scored" " $score "
      exit 0
fi

创建Gameover的逻辑

对于Gameover情况,我们使用一些漂亮的逻辑将其打印到终端的中间,我将其留给读者来研究其工作原理。


   
   
if [ [ " $m " = "X" ] ] ; then
    g = 0                       # to use it in parameter expansion
    room [ $i ] =X               # override the index and print X
    for j in { 42 .. 49 } ; do     # in the middle of the minefields,
      out = "gameover"
      k = ${out:$g:1}           # print one alphabet in each cell
      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
Minecraft Gameover

就是这样,伙计们! 如果您想了解更多信息,请从我的GitHub存储库中访问此Minesweeper游戏和Bash中其他游戏的源代码。 我希望它能给您一些启发,让他们了解更多Bash并在此过程中获得乐趣。

翻译自: https://opensource.com/article/19/9/advanced-bash-building-minesweeper

bash高级使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值