在 Linux bash 中,之前文章介绍过挪动单个俄罗斯方块的 shell 脚本。
上面持续介绍能够旋转挪动俄罗斯方块所有形态的 shell 脚本。
执行成果
具体的执行成果如下:
- 旋转挪动的 Z 字形方块
- 旋转挪动的 L 字形方块
理论执行时,会随机显示某个类型的方块形态。
能够在边框外部,向下、向左、向右挪动 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.# 第三个小方块在第一个小方块的下一行,行数是 1. 它的列数是 2.# 第四个小方块和第三个小方块在同一行,行数是 1. 它的列数是 4.# 应用这些行列数加上方块的起始行列数,就能定位出每个小方块要# 显示在哪一行、哪一列.之后能够应用ANSI本义码设置光标的地位.# 后面 8 个数字对应横放的 Z 字形方块.# 前面 8 个数字对应竖放的 Z 字形方块.Z_BLOCKS=(\ 0 0 0 2 1 2 1 4\ 0 2 1 0 1 2 2 0\)# 上面的 REV_Z_BLOCKS 数组定义了反 Z 字形方块的所有形态# [][]# [][]REV_Z_BLOCKS=(\ 0 2 0 4 1 0 1 2\ 0 0 1 0 1 2 2 2\)# 上面的 FARM_BLOCKS 数组定义了田字形方块的所有形态# [][]# [][]FARM_BLOCKS=(0 0 0 2 1 0 1 2)# 上面的 LINE_BLOCKS 数组定义了一字形方块的所有形态# [][][][]LINE_BLOCKS=(\ 0 0 0 2 0 4 0 6\ 0 4 1 4 2 4 3 4\)# 上面的 L_BLOCKS 数组定义了 L 字形方块的所有形态# []# [][][]L_BLOCKS=(\ 0 0 1 0 1 2 1 4\ 0 0 0 2 1 0 2 0\ 0 0 0 2 0 4 1 4\ 0 2 1 2 2 0 2 2\)# 上面的 SEVEN_BLOCKS 数组定义了 7 字形方块的所有形态# []# [][][]SEVEN_BLOCKS=(\ 0 4 1 0 1 2 1 4\ 0 0 1 0 2 0 2 2\ 0 0 0 2 0 4 1 0\ 0 0 0 2 1 2 2 2\)# 上面的 DUST_BLOCKS 定义了土字形方块的所有形态# []# [][][]DUST_BLOCKS=(\ 0 2 1 0 1 2 1 4\ 0 0 1 0 1 2 2 0\ 0 0 0 2 0 4 1 2\ 0 2 1 0 1 2 2 2\)# 上面的常量定义根本形态的方块数目,共有 7 种.# Z字形、反Z字形、L字形、7字形、一字形、田字形、土字形BLOCKS_TYPE=7# 上面的 currentBlockType 变量保留以后的方块类型.# 例如以后显示 Z 字形方块,会保留 Z 字形方块的所有形态.# 如果以后显示土字形方块,会保留土字形方块的所有形态.# 默认值为空.前面初始化的时候会随机抉择一个方块类型.declare -a currentBlockType# 下面的 currentBlockType 变量以后方块的类型.# 上面的 displayBlockIndex 变量指向以后类型的某个方块.# 每个方块形态应用 8 个数字来示意.这个值要基于 8 进行递增.displayBlockIndex=0# 这个值加上对应方块形态数组外面的小方块行数,# 会指定每一个小方块要显示在哪一行.# 其初始值是边框上边行数的下一行.blockLine=$((FRAME_TOP + 1))# blockColumn 指定整个方块显示的起始列.# 这个值加上对应方块形态数组外面的小方块列数,# 会指定每一个小方块要显示在哪一列.# 其初始值是边框右边列数的下一列.blockColumn=$((FRAME_LEFT + 1))# 显示一个长方形边框,作为方块挪动的边界范畴function showFrame(){ # 设置边框字符的显示属性: 高亮反白显示,绿色文本,绿色背景 printf "\e[1;7;32;42m" local i # 上面应用 "\e[line;columnH" ANSI 本义码挪动 # 光标到指定的行和列,而后显示对应的边框边界字符. # 行数递增,列数不变,竖向显示边框的左右边界 for ((i = FRAME_TOP; i <= FRAME_BOTTOM; ++i)); do printf "\e[${i};${FRAME_LEFT}H|" printf "\e[${i};${FRAME_RIGHT}H|" done # 列数递增,行数不变,横向显示边框的高低边界 for ((i = FRAME_LEFT + 1; i < FRAME_RIGHT; ++i)); do printf "\e[${FRAME_TOP};${i}H=" printf "\e[${FRAME_BOTTOM};${i}H=" done # 显示边框之后,重置终端的字符属性为原来的状态 printf "\e[0m"}# 显示或者革除方块.由 displayBlockIndex 指定方块形态.# 传入的第一个参数为 1,会显示方块.# 传入的第一个参数为 0,会革除方块.function drawBlock(){ local i squareIndex # square 变量保留要显示的小方块内容. # 如果内容为 "[]",会显示具体的方块. # 如果内容为 " ",也就是两个空格,会革除方块 local square # line 变量指定某个小方块显示在哪一行 local line # column 变量指定某个小方块显示在哪一列 local column # 所给的第一个参数值为 1,示意要显示具体的方块 # 所给的第一个参数值为 0,示意要革除以后的方块 # 方块显示的地位由 blockLine 和 blockColumn 指定 if [ $1 -eq 1 ]; then square="[]" # 显示方块时,把方块的背景色设成红色 printf "\e[41m" else # 把原先显示的方块内容都替换为空格,显示为空 square=" " # 革除方块时,背景色要显示为原先的色彩 printf "\e[0m" fi for ((i = 0; i < 8; i += 2)); do # 基于 displayBlockIndex 获取到要显示的小方块index squareIndex=$((i + displayBlockIndex)) # 应用 blockLine 和以后方块形态数组指定的小方块行数 # 来获取每一个小方块要显示在哪一行. line=$((blockLine + ${currentBlockType[squareIndex]})) # 应用 blockLine 和以后方块形态数组指定的小方块列数 # 来获取每一个小方块要显示在哪一列. column=$((blockColumn + ${currentBlockType[squareIndex + 1]})) # 应用 "\e[line;columnH" 本义码挪动光标到指定的 # 行和列,而后开始显示对应的小方块. printf "\e[${line};${column}H${square}" done}# 该函数判断是否能够在指定的行和列搁置特定形态的方块.# 如果能够搁置,返回 0. 不能搁置,返回 1.function canPlaceBlock(){ # 所给的第一个参数指定要挪动到的起始行数 local nextBaseLine="$1" # 所给的第二个参数指定要挪动到的起始列数 local nextBaseColumn="$2" # 所给的第三个参数指定要搁置的方块形态index local blockIndex="$3" local i squareIndex nextLine nextColumn # blockIndex 变量指向以后显示的方块形态index. # 上面遍历以后方块的每一个小方块,获取它们 # 将被显示的行列数,查看是否超过了边框范畴. # 如果超过,则返回 1,示意不能挪动方块到指定的行或列. for ((i = 0; i < 8; i += 2)); do squareIndex=$((i + blockIndex)) nextLine=$((nextBaseLine + ${currentBlockType[squareIndex]})) nextColumn=$((nextBaseColumn + ${currentBlockType[squareIndex+1]})) # 上面两个 if 条件查看接下来要显示的行数、列数是否超过了 # 边框范畴.如果超过,则返回 1,不能搁置方块到新的行或列上. if ((nextLine<=FRAME_TOP || nextLine>=FRAME_BOTTOM)); then return 1 fi if ((nextColumn<=FRAME_LEFT || nextColumn>=FRAME_RIGHT)); then return 1 fi done # 遍历方块的所有小方块,发现都能够挪动,则返回 0 return 0}# 左移方块function leftMoveBlock(){ # 每次左移,要挪动一个小方块的间隔.每个小方块占据两列, # 所以左移后,新的起始列数是在后面第二列,上面要减 2. local newBaseColumn=$((blockColumn - 2)) # bash 的 if 语句能够对任意命令的返回值进行判断,并不是 # 只能判断 [、[[、(( 等命令的返回值. 上面直接判断 # canPlaceBlock 函数的返回值.如果返回 0,就是 true. if canPlaceBlock "$blockLine" "$newBaseColumn" "$displayBlockIndex"; then # 能够挪动方块. 先革除原先的方块 drawBlock 0 # 更新 blockColumn 的值,以便后续在新的列上显示方块 ((blockColumn -= 2)) drawBlock 1 fi}# 右移方块function rightMoveBlock(){ # 每次右移,要挪动一个小方块的间隔.每个小方块占据两列, # 所以右移后,新的起始列数是在前面第二列,上面要加 2. local newBaseColumn=$((blockColumn + 2)) # bash 的 if 语句能够对任意命令的返回值进行判断,并不是 # 只能判断 [、[[、(( 等命令的返回值. 上面直接判断 # canPlaceBlock 函数的返回值.如果返回 0,就是 true. if canPlaceBlock "$blockLine" "$newBaseColumn" "$displayBlockIndex"; then # 能够挪动方块. 先革除原先的方块 drawBlock 0 # 更新 blockColumn 的值,以便后续在新的列上显示方块 ((blockColumn += 2)) drawBlock 1 fi}# 下移方块function downMoveBlock(){ local newBaseLine=$((blockLine + 1)) if canPlaceBlock "$newBaseLine" "$blockColumn" "$displayBlockIndex"; then # 能够挪动方块. 先革除原先的方块 drawBlock 0 # 更新 blockLine 的值,以便后续在新的行上显示方块 ((blockLine += 1)) drawBlock 1 fi}# 基于 displayBlockIndex 的值,获取下一个要显示的# 方块index.须要查看新的index是否越界,越界则重置为 0.function getNextBlockIndex(){ local nextBlockIndex=$((displayBlockIndex + 8)) # ${#currentBlockType[@]} 获取 currentBlockType 数组的元素个数. # 当 nextBlockIndex 大于数组元素个数时,曾经越界, # 要重置为 0. 从头开始旋转方块. if [ $nextBlockIndex -ge ${#currentBlockType[@]} ]; then nextBlockIndex=0 fi # 这里要用 echo 命令输入新的index,以便里面应用 # $(getNextBlockIndex) 的形式获取这个值. # 如果用 return 命令返回,里面要用 $? 获取,不不便. echo "$nextBlockIndex"}# 旋转方块. 依照俄罗斯方块的规定,不能上移方块.function rotateBlock(){ local newBlockIndex="$(getNextBlockIndex)" # 旋转方块后,形态发生变化,要查看是否能够旋转 if canPlaceBlock "$blockLine" "$blockColumn" "$newBlockIndex"; then # 能够旋转成新的方块形态. 先革除原先的方块 drawBlock 0 # 更新 displayBlockIndex 的值,指向旋转后的下一个方块形态 displayBlockIndex="$(getNextBlockIndex)" # 显示新的方块 drawBlock 1 fi}# 重置终端的显示状态为原先的状态function resetDisplay(){ # 把光标显示到边框底部的下一行, # 以便终端提示符显示在边框之后,防止错乱 printf "\e[$((FRAME_BOTTOM + 1));0H" # 显示光标 printf "\e[?25h" # 重置终端的字符属性为原来的状态 printf "\e[0m"}# 随机设置以后要显示的方块类型.function randomSetCurrentBlockType(){ # RANDOM 是 bash 的全局变量,每次援用 # 都会获取到一个随机的整数值. local shape=$((RANDOM % BLOCKS_TYPE)) if [ $shape -eq 0 ]; then # 上面要用小括号把 "${Z_BLOCKS[@]}" 括起来, # 以便把 currentBlockType 赋值为数组.如果 # 不加小括号,会被赋值为字符串,而不是数组. currentBlockType=("${Z_BLOCKS[@]}") elif [ $shape -eq 1 ]; then currentBlockType=("${REV_Z_BLOCKS[@]}") elif [ $shape -eq 2 ]; then currentBlockType=("${DUST_BLOCKS[@]}") elif [ $shape -eq 3 ]; then currentBlockType=("${L_BLOCKS[@]}") elif [ $shape -eq 4 ]; then currentBlockType=("${SEVEN_BLOCKS[@]}") elif [ $shape -eq 5 ]; then currentBlockType=("${LINE_BLOCKS[@]}") else currentBlockType=("${FARM_BLOCKS[@]}") fi}# 随机设置新的方块类型,并随机抉择该类型的某个方块形态# 进行显示.默认显示在边框左上角.function displayNewBlockType(){ # 随机设置以后要显示的方块形态 randomSetCurrentBlockType # 重置 blockLine、blockColumnt 的值. # 新的方块形态要显示在边框左上方. blockLine=$((FRAME_TOP + 1)) blockColumn=$((FRAME_LEFT + 1)) # 获取以后类型的方块形态个数. 例如, # 土字形方块有 4 种形态. Z 字形方块有 2 种形态. local number=$((${#currentBlockType[@]} / 8)) # 上面随机获取以后类型的某个方块形态index displayBlockIndex=$(((RANDOM % number) * 8)) # 基于设置的方块形态,先显示一个默认的方块 drawBlock 1}# 初始化显示状态.例如显示边框,暗藏光标,等等function initDisplay(){ # 因为方块会显示在指定的行和列, # 为了防止已有内容的烦扰,先清屏. clear # 暗藏光标 printf "\e[?25l" # 显示提醒字符串 echo "Usage: k 键: 旋转方块. j/h/l 键: 下/左/右挪动方块."\ "r 键: 显示新的方块. q 键: 退出" # 显示边框 showFrame # 先显示一个初始的方块 displayNewBlockType}initDisplay# 循环读取用户按键,并进行相应解决while read -s -n 1 char; do case "$char" in # h 键要左移一列 "h") leftMoveBlock ;; # l 键要右移一列 "l") rightMoveBlock ;; # j 键要下移一行 "j") downMoveBlock ;; # k 键要旋转方块.俄罗斯方块不能上移方块 "k") rotateBlock ;; # r 键从新显示新的方块 "r") drawBlock 0 displayNewBlockType ;; # q 键退出 "q") break ;; esacdoneresetDisplayexit
代码关键点阐明
示意所有类型的方块形态
在俄罗斯方块中,有如下 7 种类型的方块:
田字形 土字形 Z 字形 反Z字形 L字形 反L字形 一字形[][] [] [][] [][] [] [] [][][][][][] [][][] [][] [][] [][][] [][][]
在 rotateblock_all.sh
脚本中,定义了 7 个数组来保留这 7 种类型的方块形态。
每个数组保留同一个类型的所有方块形态。
在显示方块的时候,应用 currentBlockType 数组要保留以后要显示的某种类型的所有方块形态。
应用 displayBlockIndex 变量来指向具体显示的某个方块形态。
即,以后的 rotateblock_all.sh
脚本显示所有方块的逻辑如下:
- 事后定义好 7 种类型的方块形态。
- 随机获取 0~6 之间的一个整数,把对应类型的方块形态赋值到 currentBlockType 数组。
- 用 displayBlockIndex 变量指向 currentBlockType 数组的某个方块形态。
- 显示displayBlockIndex 变量指向的方块形态。