当前位置: 首页 > 科技观察

通过编写扫雷游戏来提高你的Bash技能

时间:2023-03-22 02:06:02 科技观察

那些怀旧的经典游戏是提高你的编程技能的好素材。今天就让我们详细探讨一下如何用Bash编写一个扫雷程序。我不是教授编程的专家,但当我想在某件事上做得更好时,我会尝试找到享受它的方法。例如,当我想更进一步我的shell编程时,我决定用Bash编写一个扫雷游戏来练习。如果您是一位经验丰富的Bash程序员,希望在提高技能的同时获得乐趣,请跟随我编写您自己的在终端中运行的扫雷游戏。完整代码可以在这个GitHub存储库中找到。准备好在编写任何代码之前,我列出了游戏所需的一些部分:显示雷区创建游戏逻辑创建逻辑以确定单元格是否可选跟踪可用和已识别(挖掘)的单元格创建的数量游戏结束逻辑显示雷区在扫雷游戏中,游戏界面是由二维数组(列和行)组成的不透明小方块。每个格子下都可能隐藏着地雷。玩家的任务是找到那些没有地雷的方块,并且在这个过程中不能点击任何地雷。这个Bash版本的扫雷器使用10x10矩阵,实际逻辑是用一个简单的Bash数组完成的。首先,我生成了一些随机数。这将是地雷在雷区中的位置。控制地雷的数量,在开始编码之前更容易做到这一点。实现它的逻辑可能会更好,但我这样做是为了保持游戏实现的整洁和改进的空间。(我写这个游戏是为了好玩,但如果你能把它做得更好,我会很高兴。)下面的变量在整个过程中都是不变的,并且被声明用于随机数生成。就像下面的a-g变量,它们将被用来计算可以排除的地雷值:#variablescore=0#将被用来存储游戏分数#下面的变量是用来随机生成实际值的可排除地雷a="110-10-1"b="-101"c="01"d="-101-2-3"e="122021100-10-20-23-2-1"f="12335302022100-10-20-25-30-35-3-2-1"g="14691015202530-30-24-11-10-9-8-7"##声明declare-aroom#声明一个房间数组,用来表示雷区中的每一个格子。接下来,我将在(0-9)列和(a-j)行中显示游戏界面,并使用10x10矩阵作为雷区。(M[10][10]是从0-99索引的100个值的数组。)要了解有关Bash中数组的更多信息,请阅读《关于Bash你不知道的事情:Bash数组简介》一书。创建一个名为plough的函数,我们首先显示标题:两个空行,列标题和一行-,表示游戏界面如下:printf'\n\n'printf'%s'"abcdefghij"printf'\n%s\n'"------------------------------------------"然后,我初始化了一个名为r的计数器变量,它将用于跟踪显示了多少行。请注意,稍后在游戏代码中,我们将使用相同的变量r作为我们的数组索引。在Bashfor循环中,使用seq命令从0递增到9。我使用数字(d%)来显示行号($row,definedbyseq):r=0#counterforrowin$(seq09);doprintf'%d'"$row"#displayLinenumbers0-9在我们继续之前,让我们看看到目前为止我们做了什么。我们先横向显示[a-j],然后显示行号[0-9]。我们将使用这两个范围来确定用户扫雷的确切位置。接下来,在每一行中插入列,因此是时候编写一个新的for循环了。这个循环管理每一列,也就是实际生成游戏界面的每一个格子。我添加了一些辅助函数,你可以在源代码中看到它的完整实现。对于每个方块,我们需要一些东西让它看起来像一个地雷,所以我们首先用一个点(.)初始化空间。为此,我们使用了一个名为is_null_field的自定义函数。同时,我们需要一个数组来存储每个单元格的具体值。这里我们将使用之前定义的全局数组room,并使用变量r作为索引。随着r的增加,遍历所有单元格并随机部署地雷。对于$(seq09)中的col;do((r+=1))#循环完一列的行数后,加一个is_null_field$r#假设这里有一个函数,它会检查单元格是否为空,如果为真,那么这个单元格的初始值为点(.)printf'%s\e[33m%s\e[0m'"|""${room[$r]}"#显示最后的分隔符,注意${room[$r]}的第一个值是'.',等于它的初始值。#Endcolloopdone最后,为了保持游戏界面的整洁美观,我会在每一行结束时加一个竖线,最后结束行循环:printf'%s\n'"|"#显示行分隔符printf'%s\n'"---------------------------------------"#endLineloopdoneprintf'\n\n'完整的犁代码如下:plough(){r=0printf'\n\n'printf'%s'"abcdefghij"printf'\n%s\n'"---------------------------------------"用于行$(序列09);为$(seq09)中的col做printf'%d'"$row";do((r+=1))is_null_field$rprintf'%s\e[33m%s\e[0m'"|""${room[$r]}"doneprintf'%s\n'"|"printf'%s\n'"----------------------------------------"doneprintf'\n\n'}我花了一段时间才弄清楚is_null_field到底做了什么。让我们看看它能做什么。一开始,我们需要游戏有一个固定的状态。您可以随意选择一个初始值,可以是数字也可以是任意字符。我最终决定所有单元格的初始值都是一个点(.),因为我认为这会让游戏界面更好看。下面是这个函数的完整代码:is_null_field(){locale=$1#在数组room中,我们已经使用了循环变量'r',这次我们使用'e'if[[-z"${room[$e]}"]];然后房间[$r]="."#这里,用点(.)来初始化每个cellfi}现在,我已经把所有的grid都初始化好了,现在很简单,用一个函数就可以得到当前游戏可以操作多少个cell:get_free_fields(){free_fields=0#在$(seq1${#room[@]})中为n初始化变量;如果[["${room[$n]}"="."]];then#检查当前单元格是否等于初始值(.),如果结果为真,则记录为空闲单元格。((free_fields+=1))fidone}这是显示的游戏界面,[a-j]为列,[0-9]为行。MinefieldCreatesPlayerLogic玩家动作背后的逻辑是先从stdin中读取数据作为坐标,然后找出对应位置实际包含的内容。这里使用bash的参数扩展来尝试获取行数和列数。然后将代表列号的字母传递给分支语句,得到对应的列号。为了更好的理解这个过程,可以看下面代码中变量o对应的值。例如,玩家输入c3,Bash将其拆分为两个字符:c和3。为了简单起见,我跳过了如何处理无效输入。colm=${opt:0:1}#获取第一个字符,一个字母ro=${opt:1:1}#获取第二个字符,一个整数$colmina)o=1;;#最后通过字母获取对应的列号。b)o=2;;c)o=3;;d)o=4;;e)o=5;;f)o=6;;g)o=7;;h)o=8;;一世)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-i0-5-n1)#调用自定义函数判断是否指向空/可选单元格。仔细观察这个计算过程,看看最后的结果i是如何计算出来的:i=$(((ro*10)+o))i=$(((3*10)+3))=$((30+3))=33最后的结果是33,在我们的游戏界面上显示玩家输入的坐标指向第33个格子,也就是第3行(从0开始,否则这里变成4),第3列.创建判断单元格是否可选的逻辑为了找到地雷,在转换坐标并找到实际位置后,程序将检查单元格是否可选。如果不可用,程序将显示一条警告消息并要求玩家重新输入坐标。在这段代码中,单元格是否可选取决于数组中对应的值是否为点(.)。如果可选,则重置与单元格对应的值,并更新分数。否则,由于其对应的值不是点,因此设置了变量not_allowed。为了简单起见,我将游戏中警告信息的源代码留给读者自行探索。is_free_field(){localf=$1localval=$2not_allowed=0if[["${room[$f]}"="."]];thenroom[$f]=$valscore=$((score+val))elsenot_allowed=1fi}提取地雷如果输入的坐标有效,对应的位置是地雷,如下图。玩家输入h6,游戏界面会出现一些随机生成的数值。这些值在找到地雷后添加到用户的分数中。提取地雷记住我们一开始定义的变量a-g,我会用它们来确定随机产生的地雷的具体值。因此,程序会根据玩家输入的坐标,根据(m)中随机生成的数字生成周围其他单元格的值(如上图所示)。然后将所有值加到初始输入坐标中,最后的结果放在i中(如上计算)。请注意下面代码中的X,这是我们唯一的游戏结束标记。我们将其添加到随机列表中。在shuf命令的神奇作用下,X可以在任何情况下出现,也有可能永远不会出现。m=$(shuf-eabcdefgX-n1)#将X加入随机列表,当m=X时,游戏结束if[["$m"!="X"]];然后#X将是我们的爆炸性地雷(游戏结束)触发标志以限制${!m};do#!m代表m变量的值field=$(shuf-i0-5-n1)#然后再取一个随机数index=$((i+limit))#将m和index中的每个值相加untiltheendofthelistis_free_field$index$fielddone我希望游戏界面中所有随机显示的单元格都靠近玩家选择的单元格。提取地雷记录了选定和可用单元格的数量。该程序需要记录游戏界面中哪些单元格是可选的。否则,程序将继续要求用户输入数据,即使已选择所有单元格也是如此。为此,我创建了一个名为free_fields的变量,其初始值为0。使用for循环记录游戏界面中可选单元格的数量。如果单元格对应的值是点(.),则将free_fields加一。get_free_fields(){free_fields=0fornin$(seq1${#room[@]});如果[["${room[$n]}"="."]];then((free_fields+=1))fidone}等等,如果free_fields=0怎么办?这意味着,玩家选择了所有单元格。如果你想更好地理解这部分,你可以在这里查看源代码。如果[[$free_fields-eq0]];then#这意味着你选择了所有字段printf'\n\n\t%s:%s%d\n\n'"YouWin""youscored""$score"exit0ficreatesgame-endinglogicForthe游戏结束的情况,我们在这里使用一些巧妙的技巧,将结果显示在屏幕中央。这部分留给读者朋友们自己去探索吧。如果[["$m"="X"]];theng=0#为了在参数扩展中使用它room[$i]=X#覆盖这个位置的原始值,并赋值给Xforjin{42..49};do#在游戏界面的中央,out="gameover"k=${out:$g:1}#在每个格子房间显示一个字母[$j]=${k^^}((g+=1))donefi最后展示玩家最关心的两行。如果[["$m"="X"]];然后printf'\n\n\t%s:%s%d\n'"GAMEOVER""youscored""$score"printf'\n\n\t%s\n\n'"Youwerejust$free_fields在地雷之外。”exit0fiMinecraftGameover文章到此结束,朋友们!如果您想了解更多信息,可以查看我的GitHub存储库,其中包含这款扫雷游戏的源代码,您还可以找到更多用Bash编写的游戏。我希望本文能激起您学习Bash的兴趣,并从中获得乐趣。