当前位置: 首页 > Linux

Bash小技巧:一个可以旋转和移动所有形状的俄罗斯方块的Shell脚本

时间:2023-04-07 03:17:18 Linux

在Linuxbash中,上一篇介绍了一个移动单个俄罗斯方块的shell脚本。让我们转到可以旋转和移动所有俄罗斯方块形状的shell脚本。执行效果具体执行效果如下:在实际执行旋转移动Z形块时,会随机显示某类块形状。之字形方块可以在边界内向下、向左和向右移动,但不能向上移动。俄罗斯方块不允许向上移动。您可以按k键在水平和垂直之间来回旋转块。按r键可以随机重新显示新块。支持所有形状的俄罗斯方块。脚本代码假设有一个rotateblock_all.sh脚本,具体代码内容如下。在这段代码中,几乎每一行代码都提供了详细的注释,方便阅读。本文后面也会对一些关键点进行说明,以帮助理解。#!/bin/bash#实现一个可以左右移动和旋转的盒子,#盒子的移动和旋转范围限制在指定的边框内。#移动和旋转所有类型和形状的俄罗斯方块。#下面的常量指定矩形边框的上、下、左、右边界#指定边框左侧的列数FRAME_LEFT=3#指定边框右侧的列数FRAME_RIGHT=26#指定边框顶部的行数FRAME_TOP=2#指定边框底部的行数FRAME_BOTTOM=18#下面的Z_BLOCKS数组定义了Z形块的所有形状。#给定的初始值对应的是水平放置的Z字形方块,具体形状为:#[][]#[][]#这里用行数和列数来坐标点的方式来表示每个小方块的位置。#第一个小方块的起始行号和列号均为0,作为整个方块的原点。#第二个小方块和第一个小方块在同一行,行数也为0。每个小方块#显示两个字符,所以第二个小方块的起始列号为2。#第三个小方块square在第一个小方块的下一行,行数为1。它的列数是2。#第四个小方块和第三个小方块在同一行,它的行数是1。它的列数是4。#用这些行数和列数加上开始方块的行和列,你可以定位每个小方块#显示在哪一行和哪一列。然后就可以使用ANSI转义码来设置光标的位置了。#前8个数字对应水平的Z形方块。#后8个数字对应垂直Z_BLOCKS=(\00021214\02101220\)#下面的REV_Z_BLOCKS数组定义了所有反向之字形块的形状#[][]#[][]REV_Z_BLOCKS=(\02041012\00101222\)#下面的FARM_BLOCKS数组定义了亚光方块的所有形状#[][]#[][]FARM_BLOCKS=(00021012)#下面的LINE_BLOCKS数组定义了一个字形块的所有形状#[][][][]LINE_BLOCKS=(\00020406\04142434\)#下面的L_BLOCKS数组定义了L形块的所有形状#[]#[][][]L_BLOCKS=(\00101214\00021020\00020414\02122022\)#下面的SEVEN_BLOCKS数组定义了7-异型块#[]#[][][]SEVEN_BLOCKS=(\04101214\00102022\00020410\00021222\)#下面的DUST_BLOCKS定义了土方块的所有形状#[]#[][][]DUST_BLOCKS=(\02101214\00101220\00020412\02101222\)#下面的常量定义了基本形状的方块个数,一共有7种#Zigzag,reversezigzag,L-shape,7-shape,one-shape,field-shape,soilFontBLOCKS_TYPE=7#下面的currentBlockType变量保存了当前的方块类型。#例如当前显示的是一个Z形块,则保存Z形块的所有形状。#如果当前显示的是土形方块,则保存土形方块的所有形状。#默认值为空。稍后初始化时会随机选择一个块类型。declare-acurrentBlockType#上面的currentBlockType变量就是当前块的类型。#下面的displayBlockIndex变量指向当前类型的块。#每个方块形状用8个数字来表示。这个值要在8的基础上递增。displayBlockIndex=0#这个值加上对应的方形数组中小方块的行数,#就会指定每个小方块应该显示在哪一行。#初始值为边框上方行数的下一行。blockLine=$((FRAME_TOP+1))#blockColumn指定整个block显示的起始列。#这个值加上对应的方形数组中小方块的列数,#就会指定每个小方块应该显示在哪一列。#初始值为左边列数的下一列框架。blockColumn=$((FRAME_LEFT+1))#显示一个矩形框作为块移动函数的边界范围showFrame(){#设置边框字符的显示属性:高亮和反显、绿色文本、绿色背景printf"\e[1;7;32;42m"locali#下面代码使用"\e[line;columnH"ANSI转义movement#将光标移动到指定的行和列,然后显示相应的帧边界字符。#行数增加,列数不变,框的左右边界垂直显示for((i=FRAME_TOP;i<=FRAME_BOTTOM;++i));做printf"\e[${i};${FRAME_LEFT}H|"printf"\e[${i};${FRAME_RIGHT}H|"done#列数增加,行数不变,水平显示框上下边界for((i=FRAME_LEFT+1;i=FRAME_BOTTOM));然后返回1fiif((nextColumn<=FRAME_LEFT||nextColumn>=FRAME_RIGHT));thenreturn1fidone#遍历方块的所有小方块,发现可以移动,则return0return0}#将方块向左移动functionleftMoveBlock(){#每次向左移动,你需要移动一小块的距离。一个小方块占两列,#所以向左移动后,新的起始列号就是前面的第二列,后面要减2。localnewBaseColumn=$((blockColumn-2))#bash的ifstatement可以对任何命令的返回值进行判断,不是#只能判断[,[[,((等命令的返回值,下面直接判断#canPlaceBlock函数的返回值,如果是返回0,是真的。如果canPlaceBlock"$blockLine""$newBaseColumn""$displayBlockIndex";然后#你可以移动方块。先清空原块drawBlock0#更新blockColumn的值,使块可以显示在新的列上((blockColumn-=2))drawBlock1fi}#将块向右移动functionrightMoveBlock(){#Every当你向右移动时,你需要移动一个小方块的距离。每个小块占两列,#所以向右移动后,新的起始列数在后面的第二列,下面要加2。localnewBaseColumn=$((blockColumn+2))#bash的if语句可以判断任何命令的返回值,不是#只能判断[,[[,((等命令的返回值。下面直接判断#canPlaceBlock函数的返回值。如果返回0,则为true。ifcanPlaceBlock"$blockLine""$newBaseColumn""$displayBlockIndex";then#可以移动方块了。先清空原BlockdrawBlock0#更新blockColumn的值,以便块将显示在新列上((blockColumn+=2))drawBlock1fi}#将块向下移动functiondownMoveBlock(){localnewBaseLine=$((blockLine+1))ifcanPlaceBlock"$newBaseLine""$blockColumn""$displayBlockIndex";then#方块可以移动,先清空原方块drawBlock0#更新blockLine的值,使方块可以换行显示((blockLine+=1))drawBlock1fi}#根据displayBlockIndex的值,得到下一个#要显示的块索引。需要检查新的索引是否越界,如果越界则重置为0functiongetNextBlockIndex(){localnextBlockIndex=$((displayBlockIndex+8))#${#currentBlockType[@]}获取currentBlockType数组中的元素数。#当nextBlockIndex大于数组元素个数时,越界,#重置为0。从头开始旋转方块。如果[$nextBlockIndex-ge${#currentBlockType[@]}];thennextBlockIndex=0fi#这里需要使用echo命令输出新的索引,这样我们就可以在外面使用#$(getNextBlockIndex)来获取这个值。#如果我们使用return命令返回,需要用$outside?收购,不方便。echo"$nextBlockIndex"}#旋转方块。根据俄罗斯方块的规则,方块不能向上移动。functionrotateBlock(){localnewBlockIndex="$(getNextBlockIndex)"#旋转方块后,形状发生变化,检查是否可以旋转ifcanPlaceBlock"$blockLine""$blockColumn""$newBlockIndex";then#可以旋转成新的方块形状。先清除原块drawBlock0#更新displayBlockIndex的值指向旋转后的下一个块ShapedisplayBlockIndex="$(getNextBlockIndex)"#显示一个新的块drawBlock1fi}#将终端的显示状态重置为原来的statefunctionresetDisplay(){#显示光标到边框底部的下一行,#使终端提示显示在边框后面,避免混淆printf"\e[$((FRAME_BOTTOM+1));0H"#显示光标printf"\e[?25h"#重置终端的字符属性回到原来的状态printf"\e[0m"}#随机设置当前要显示的块类型。functionrandomSetCurrentBlockType(){#RANDOM是bash的全局变量,每次引用#都会得到一个随机整数值。局部形状=$((RANDOM%BLOCKS_TYPE))if[$shape-eq0];then#使用圆括号括起“${Z_BLOCKS[@]}”,#将currentBlockType指定为数组。如果#不加括号,它会被分配一个字符串,而不是一个数组。currentBlockType=("${Z_BLOCKS[@]}")elif[$shape-eq1];然后currentBlockType=("${REV_Z_BLOCKS[@]}")elif[$shape-eq2];然后currentBlockType=("${DUST_BLOCKS[@]}")elif[$shape-eq3];然后currentBlockType=("${L_BLOCKS[@]}")elif[$shape-eq4];然后currentBlockType=("${SEVEN_BLOCKS[@]}")elif[$shape-eq5];thencurrentBlockType=("${LINE_BLOCKS[@]}")elsecurrentBlockType=("${FARM_BLOCKS[@]}")fi}#随机设置一个新的方块类型,并随机选择一个该类型的方块形状#来显示.默认显示在边框的左上角。函数displayNewBlockType(){#随机设置当前要显示的块形状randomSetCurrentBlockType#重置blockLine和blockColumnt的值。#新的块形状应该显示在边框的左上角。blockLine=$((FRAME_TOP+1))blockColumn=$((FRAME_LEFT+1))#获取当前类型的块形状数。例如,#Earth块有4个形状。Z形块有2种形状。localnumber=$((${#currentBlockType[@]}/8))#在索引下方随机获取当前类型的块形状displayBlockIndex=$(((RANDOM%number)*8))#根据设置的块shape,首先显示一个默认的块drawBlock1}#初始化显示状态。例如显示边框,隐藏光标等。functioninitDisplay(){#由于方块会显示在指定的行和列,#为了避免现有内容的干扰,先清屏。clear#隐藏光标printf"\e[?25l"#显示提示字符串echo"Usage:kkey:rotateblock.j/h/lkey:moveblockdown/left/right."\"rkey:shownewblock.qkey:exit"#显示帧showFrame#首先显示一个初始块displayNewBlockType}initDisplay#循环读取用户密钥并相应地处理它们whileread-s-n1char;docase"$char"in#h键需要向左移动一列"h")leftMoveBlock;;#l键向右移动一列"l")rightMoveBlock;;#j键向下移动一行"j")downMoveBlock;;#k键旋转方块。俄罗斯方块不能向上移动方块"k")rotateBlock;;#r键重新显示一个新方块"r")drawBlock0displayNewBlockType;;#q键退出"q")break;;以下7种方块:天形地形Z-形反Z形L形反L形单字[][][][][][][][][][][][][][][][][][][][][][][][][][][][]在rotateblock_all.sh脚本中,定义了7个数组来保存这7种方块形状,每个数组保存相同的所有方块形状type.显示方块时,使用currentBlockType数组保存当前要显示的某种类型的所有方块形状,使用displayBlockIndex变量指向具体的显示方块形状,即当前rotateblock_all.sh脚本的逻辑为显示所有方块如下:预先定义了7种方块形状,随机获取一个0到6之间的整数,将对应类型的方块形状赋值给currentBlockType数组,使用displayBlockIndex变量指向currentBlockType中的一个方块数组的形状。显示displayBlockIndex变量指向的块形状。