本文共 13126 字,大约阅读时间需要 43 分钟。
bash高级使用
我不是教授编程的专家,但是当我想在某些方面变得更好时,我试图找到一种玩转它的方法。 例如,当我想精通shell脚本时,我决定通过在Bash中编写游戏版本进行练习。
如果您是经验丰富的Bash程序员,并且想在玩耍时磨练自己的技能,请继续在终端中编写自己的Minesweeper版本。 完整的源代码可在此找到。
在开始编写任何代码之前,我概述了创建游戏所需的要素:
在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数组的更多信息,请阅读“ 。
让我们称它为函数,首先,我们打印标题:两条空白行,列标题和概述比赛场地顶部的一行:
printf '\n\n' printf '%s' " a b c d e f g h i j" printf '\n %s\n' "-----------------------------------------"
接下来,我建立一个计数器变量r ,以跟踪填充了多少水平行。 请注意,稍后在游戏代码中,我们将使用与数组索引相同的计数器变量' r '。 在 ,使用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循环了。 这个负责管理每一列,因此它实质上是在运动场中生成每个单元。 我添加了一些帮助程序功能,您可以在源代码中看到的完整定义。 对于每个单元格,我们都需要一些东西来使该字段看起来像一个矿井,因此,我们使用一个名为的自定义函数,用一个点(。)来初始化空的。 另外,我们需要一个数组变量来存储每个单元格的值,我们将使用预定义的全局数组变量以及索引 。 随着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 ]是行。
播放器逻辑从读取一个选项作为地雷的坐标,并提取雷场上的精确区域。 它使用Bash的提取列和行输入,然后将列馈入一个开关,该开关指向板上等效的整数符号,要了解这一点,请参阅在switch case语句中将值分配给变量' o'下面。 例如,玩家可能输入c3 ,Bash将其分为两个字符: c和3 。 为了简单起见,我跳过了无效条目的处理方式。
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是一种旨在提供信息的随机排列,其中-i选项表示改编的索引或可能范围,而-n表示返回的最大数量或输出。 双括号允许在Bash中进行 ,我们将在此处大量使用它们。
假设前面的示例通过stdin接收到c3 。 然后,从上述switch case语句中ro = 3和o = 3将c转换为其等效整数,并将其放入我们的公式中以计算最终索引' 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 }
如果输入的坐标可用,则发现地雷,如下所示。 当提供h6作为输入时,一些值随机填充在我们的雷区中,这些值会在提取分钟后添加到用户分数中。
现在记住我们在开始时声明的变量[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
我希望所有显示的单元格都与玩家选择的单元格相邻。
该计划需要跟踪雷区中可用的牢房; 否则,即使在所有单元格都显示出来之后,它仍会继续询问玩家输入。 为了实现这一点,我创建了一个名为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情况,我们使用一些将其打印到终端的中间,我将其留给读者来研究其工作原理。
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
就是这样,伙计们! 如果您想了解更多信息,请从我的访问此Minesweeper游戏和Bash中其他游戏的源代码。 我希望它能给您一些启发,让他们了解更多Bash并在此过程中获得乐趣。
翻译自:
bash高级使用
转载地址:http://cvszd.baihongyu.com/