bash高级使用
我不是教授编程的专家,但是当我想在某些方面变得更好时,我试图找到一种玩转它的方法。 例如,当我想精通shell脚本时,我决定通过在Bash中编写Minesweeper游戏版本进行练习。
如果您是经验丰富的Bash程序员,并且想在玩耍时磨练自己的技能,请继续在终端中编写自己的Minesweeper版本。 完整的源代码可在此GitHub存储库中找到。
做好准备
在开始编写任何代码之前,我概述了创建游戏所需的要素:
- 打印雷区
- 创建游戏逻辑
- 创建逻辑以确定可用的雷区
- 保持可用和发现(提取)地雷的数量
- 创建残局逻辑
打印雷区
在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](https://i-blog.csdnimg.cn/blog_migrate/38582b62abce0505ee6574cd3eeaf0c8.png)
创建驱动播放器的逻辑
播放器逻辑从stdin读取一个选项作为地雷的坐标,并提取雷场上的精确区域。 它使用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是一种Linux实用程序,旨在提供信息的随机排列,其中-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
}
![开采地雷 Extracting mines](https://i-blog.csdnimg.cn/blog_migrate/68f022ca8c4891f7db9e784bd6256028.png)
如果输入的坐标可用,则发现地雷,如下所示。 当提供h6作为输入时,一些值随机填充在我们的雷区中,这些值会在提取分钟后添加到用户分数中。
![开采地雷 Extracting mines](https://i-blog.csdnimg.cn/blog_migrate/48ff7a1ccedba2a77fd079be23d09f74.png)
现在记住我们在开始时声明的变量[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](https://i-blog.csdnimg.cn/blog_migrate/cce27034d51de88012e57a07af9b1db2.png)
保持可用和已开采地雷的数量
该计划需要跟踪雷区中可用的牢房; 否则,即使在所有单元格都显示出来之后,它仍会继续询问玩家输入。 为了实现这一点,我创建了一个名为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
![我的世界Gameover Minecraft Gameover](https://i-blog.csdnimg.cn/blog_migrate/89806b941861732d819a3b1ffa372966.png)
就是这样,伙计们! 如果您想了解更多信息,请从我的GitHub存储库中访问此Minesweeper游戏和Bash中其他游戏的源代码。 我希望它能给您一些启发,让他们了解更多Bash并在此过程中获得乐趣。
翻译自: https://opensource.com/article/19/9/advanced-bash-building-minesweeper
bash高级使用