作者:xhchen

#!/bin/bash# Tetris Game
# 10.21.2003 xhchen<[email]xhchen&#64;winbond.com.tw[/email]>#APP declaration
APP_NAME&#61;"${0##*[\\/]}"
APP_VERSION&#61;"1.0"#颜色定义
cRed&#61;1
cGreen&#61;2
cYellow&#61;3
cBlue&#61;4
cFuchsia&#61;5
cCyan&#61;6
cWhite&#61;7
colorTable&#61;($cRed $cGreen $cYellow $cBlue $cFuchsia $cCyan $cWhite)#位置和大小
iLeft&#61;3
iTop&#61;2
((iTrayLeft &#61; iLeft &#43; 2))
((iTrayTop &#61; iTop &#43; 1))
((iTrayWidth &#61; 10))
((iTrayHeight &#61; 15))#颜色设置
cBorder&#61;$cGreen
cScore&#61;$cFuchsia
cScoreValue&#61;$cCyan#控制信号
#改游戏使用两个进程&#xff0c;一个用于接收输入&#xff0c;一个用于游戏流程和显示界面;
#当前者接收到上下左右等按键时&#xff0c;通过向后者发送signal的方式通知后者。
sigRotate&#61;25
sigLeft&#61;26
sigRight&#61;27
sigDown&#61;28
sigAllDown&#61;29
sigExit&#61;30#七中不同的方块的定义
#通过旋转&#xff0c;每种方块的显示的样式可能有几种
box0&#61;(0 0 0 1 1 0 1 1)
box1&#61;(0 2 1 2 2 2 3 2 1 0 1 1 1 2 1 3)
box2&#61;(0 0 0 1 1 1 1 2 0 1 1 0 1 1 2 0)
box3&#61;(0 1 0 2 1 0 1 1 0 0 1 0 1 1 2 1)
box4&#61;(0 1 0 2 1 1 2 1 1 0 1 1 1 2 2 2 0 1 1 1 2 0 2 1 0 0 1 0 1 1 1 2)
box5&#61;(0 1 1 1 2 1 2 2 1 0 1 1 1 2 2 0 0 0 0 1 1 1 2 1 0 2 1 0 1 1 1 2)
box6&#61;(0 1 1 1 1 2 2 1 1 0 1 1 1 2 2 1 0 1 1 0 1 1 2 1 0 1 1 0 1 1 1 2)
#所有其中方块的定义都放到box变量中
box&#61;(${box0[&#64;]} ${box1[&#64;]} ${box2[&#64;]} ${box3[&#64;]} ${box4[&#64;]} ${box5[&#64;]} ${box6[&#64;]})
#各种方块旋转后可能的样式数目
countBox&#61;(1 2 2 2 4 4 4)
#各种方块再box数组中的偏移
offsetBox&#61;(0 1 3 5 7 11 15)#每提高一个速度级需要积累的分数
iScoreEachLevel&#61;50        #be greater than 7#运行时数据
sig&#61;0                #接收到的signal
iScore&#61;0        #总分
iLevel&#61;0        #速度级
boxNew&#61;()        #新下落的方块的位置定义
cBoxNew&#61;0        #新下落的方块的颜色
iBoxNewType&#61;0        #新下落的方块的种类
iBoxNewRotate&#61;0        #新下落的方块的旋转角度
boxCur&#61;()        #当前方块的位置定义
cBoxCur&#61;0        #当前方块的颜色
iBoxCurType&#61;0        #当前方块的种类
iBoxCurRotate&#61;0        #当前方块的旋转角度
boxCurX&#61;-1        #当前方块的x坐标位置
boxCurY&#61;-1        #当前方块的y坐标位置
iMap&#61;()                #背景方块图表#初始化所有背景方块为-1, 表示没有方块
for ((i &#61; 0; i < iTrayHeight * iTrayWidth; i&#43;&#43;)); do iMap[$i]&#61;-1; done#接收输入的进程的主函数
function RunAsKeyReceiver()
{local pidDisplayer key aKey sig cESC sTTYpidDisplayer&#61;$1aKey&#61;(0 0 0)cESC&#61;&#96;echo -ne "\033"&#96;cSpace&#61;&#96;echo -ne "\040"&#96;#保存终端属性。在read -s读取终端键时&#xff0c;终端的属性会被暂时改变。#如果在read -s时程序被不幸杀掉&#xff0c;可能会导致终端混乱&#xff0c;#需要在程序退出时恢复终端属性。sTTY&#61;&#96;stty -g&#96;#捕捉退出信号trap "MyExit;" INT TERMtrap "MyExitNoSub;" $sigExit#隐藏光标echo -ne "\033[?25l"while :do#读取输入。注-s不回显&#xff0c;-n读到一个字符立即返回read -s -n 1 keyaKey[0]&#61;${aKey[1]}aKey[1]&#61;${aKey[2]}aKey[2]&#61;$keysig&#61;0#判断输入了何种键if [[ $key &#61;&#61; $cESC && ${aKey[1]} &#61;&#61; $cESC ]]then#ESC键MyExitelif [[ ${aKey[0]} &#61;&#61; $cESC && ${aKey[1]} &#61;&#61; "[" ]]thenif [[ $key &#61;&#61; "A" ]]; then sig&#61;$sigRotate        #<向上键>elif [[ $key &#61;&#61; "B" ]]; then sig&#61;$sigDown        #<向下键>elif [[ $key &#61;&#61; "D" ]]; then sig&#61;$sigLeft        #<向左键>elif [[ $key &#61;&#61; "C" ]]; then sig&#61;$sigRight        #<向右键>fielif [[ $key &#61;&#61; "W" || $key &#61;&#61; "w" ]]; then sig&#61;$sigRotate        #W, welif [[ $key &#61;&#61; "S" || $key &#61;&#61; "s" ]]; then sig&#61;$sigDown        #S, selif [[ $key &#61;&#61; "A" || $key &#61;&#61; "a" ]]; then sig&#61;$sigLeft        #A, aelif [[ $key &#61;&#61; "D" || $key &#61;&#61; "d" ]]; then sig&#61;$sigRight        #D, delif [[ "[$key]" &#61;&#61; "[]" ]]; then sig&#61;$sigAllDown        #空格键elif [[ $key &#61;&#61; "Q" || $key &#61;&#61; "q" ]]                        #Q, qthenMyExitfiif [[ $sig !&#61; 0 ]]then#向另一进程发送消息kill -$sig $pidDisplayerfidone
}#退出前的恢复
function MyExitNoSub()
{local y#恢复终端属性stty $sTTY((y &#61; iTop &#43; iTrayHeight &#43; 4))#显示光标echo -e "\033[?25h\033[${y};0H"exit
}function MyExit()
{#通知显示进程需要退出kill -$sigExit $pidDisplayerMyExitNoSub
}#处理显示和游戏流程的主函数
function RunAsDisplayer()
{local sigThisInitDraw#挂载各种信号的处理函数trap "sig&#61;$sigRotate;" $sigRotatetrap "sig&#61;$sigLeft;" $sigLefttrap "sig&#61;$sigRight;" $sigRighttrap "sig&#61;$sigDown;" $sigDowntrap "sig&#61;$sigAllDown;" $sigAllDowntrap "ShowExit;" $sigExitwhile :do#根据当前的速度级iLevel不同&#xff0c;设定相应的循环的次数for ((i &#61; 0; i < 21 - iLevel; i&#43;&#43;))dosleep 0.02sigThis&#61;$sigsig&#61;0#根据sig变量判断是否接受到相应的信号if ((sigThis &#61;&#61; sigRotate)); then BoxRotate;        #旋转elif ((sigThis &#61;&#61; sigLeft)); then BoxLeft;        #左移一列elif ((sigThis &#61;&#61; sigRight)); then BoxRight;        #右移一列elif ((sigThis &#61;&#61; sigDown)); then BoxDown;        #下落一行elif ((sigThis &#61;&#61; sigAllDown)); then BoxAllDown;        #下落到底fidone#kill -$sigDown $$BoxDown        #下落一行done
}#BoxMove(y, x), 测试是否可以把移动中的方块移到(x, y)的位置, 返回0则可以, 1不可以
function BoxMove()
{local j i x y xTest yTestyTest&#61;$1xTest&#61;$2for ((j &#61; 0; j < 8; j &#43;&#61; 2))do((i &#61; j &#43; 1))((y &#61; ${boxCur[$j]} &#43; yTest))((x &#61; ${boxCur[$i]} &#43; xTest))if (( y < 0 || y >&#61; iTrayHeight || x < 0 || x >&#61; iTrayWidth))then#撞到墙壁了return 1fiif ((${iMap[y * iTrayWidth &#43; x]} !&#61; -1 ))then#撞到其他已经存在的方块了return 1fidonereturn 0;
}#将当前移动中的方块放到背景方块中去,
#并计算新的分数和速度级。(即一次方块落到底部)
function Box2Map()
{local j i x y xp yp line#将当前移动中的方块放到背景方块中去for ((j &#61; 0; j < 8; j &#43;&#61; 2))do((i &#61; j &#43; 1))((y &#61; ${boxCur[$j]} &#43; boxCurY))((x &#61; ${boxCur[$i]} &#43; boxCurX))((i &#61; y * iTrayWidth &#43; x))iMap[$i]&#61;$cBoxCurdone#消去可被消去的行line&#61;0for ((j &#61; 0; j < iTrayWidth * iTrayHeight; j &#43;&#61; iTrayWidth))dofor ((i &#61; j &#43; iTrayWidth - 1; i >&#61; j; i--))doif ((${iMap[$i]} &#61;&#61; -1)); then break; fidoneif ((i >&#61; j)); then continue; fi((line&#43;&#43;))for ((i &#61; j - 1; i >&#61; 0; i--))do((x &#61; i &#43; iTrayWidth))iMap[$x]&#61;${iMap[$i]}donefor ((i &#61; 0; i < iTrayWidth; i&#43;&#43;))doiMap[$i]&#61;-1donedoneif ((line &#61;&#61; 0)); then return; fi#根据消去的行数line计算分数和速度级((x &#61; iLeft &#43; iTrayWidth * 2 &#43; 7))((y &#61; iTop &#43; 11))((iScore &#43;&#61; line * 2 - 1))#显示新的分数echo -ne "\033[1m\033[3${cScoreValue}m\033[${y};${x}H${iScore}         "if ((iScore % iScoreEachLevel < line * 2 - 1))thenif ((iLevel < 20))then((iLevel&#43;&#43;))((y &#61; iTop &#43; 14))#显示新的速度级echo -ne "\033[3${cScoreValue}m\033[${y};${x}H${iLevel}        "fifiecho -ne "\033[0m"#重新显示背景方块for ((y &#61; 0; y < iTrayHeight; y&#43;&#43;))do((yp &#61; y &#43; iTrayTop &#43; 1))((xp &#61; iTrayLeft &#43; 1))((i &#61; y * iTrayWidth))echo -ne "\033[${yp};${xp}H"for ((x &#61; 0; x < iTrayWidth; x&#43;&#43;))do((j &#61; i &#43; x))if ((${iMap[$j]} &#61;&#61; -1))thenecho -ne "  "elseecho -ne "\033[1m\033[7m\033[3${iMap[$j]}m\033[4${iMap[$j]}m[]\033[0m"fidonedone
}#下落一行
function BoxDown()
{local y s((y &#61; boxCurY &#43; 1))        #新的y坐标if BoxMove $y $boxCurX        #测试是否可以下落一行thens&#61;"&#96;DrawCurBox 0&#96;"        #将旧的方块抹去((boxCurY &#61; y))s&#61;"$s&#96;DrawCurBox 1&#96;"        #显示新的下落后方块echo -ne $selse#走到这儿, 如果不能下落了Box2Map                #将当前移动中的方块贴到背景方块中RandomBox        #产生新的方块fi
}#左移一列
function BoxLeft()
{local x s((x &#61; boxCurX - 1))if BoxMove $boxCurY $xthens&#61;&#96;DrawCurBox 0&#96;((boxCurX &#61; x))s&#61;$s&#96;DrawCurBox 1&#96;echo -ne $sfi
}#右移一列
function BoxRight()
{local x s((x &#61; boxCurX &#43; 1))if BoxMove $boxCurY $xthens&#61;&#96;DrawCurBox 0&#96;((boxCurX &#61; x))s&#61;$s&#96;DrawCurBox 1&#96;echo -ne $sfi
}#下落到底
function BoxAllDown()
{local k j i x y iDown siDown&#61;$iTrayHeight#计算一共需要下落多少行for ((j &#61; 0; j < 8; j &#43;&#61; 2))do((i &#61; j &#43; 1))((y &#61; ${boxCur[$j]} &#43; boxCurY))((x &#61; ${boxCur[$i]} &#43; boxCurX))for ((k &#61; y &#43; 1; k < iTrayHeight; k&#43;&#43;))do((i &#61; k * iTrayWidth &#43; x))if (( ${iMap[$i]} !&#61; -1)); then break; fidone((k -&#61; y &#43; 1))if (( $iDown > $k )); then iDown&#61;$k; fidones&#61;&#96;DrawCurBox 0&#96;        #将旧的方块抹去((boxCurY &#43;&#61; iDown))s&#61;$s&#96;DrawCurBox 1&#96;        #显示新的下落后的方块echo -ne $sBox2Map                #将当前移动中的方块贴到背景方块中RandomBox        #产生新的方块
}#旋转方块
function BoxRotate()
{local iCount iTestRotate boxTest j i siCount&#61;${countBox[$iBoxCurType]}        #当前的方块经旋转可以产生的样式的数目#计算旋转后的新的样式((iTestRotate &#61; iBoxCurRotate &#43; 1))if ((iTestRotate >&#61; iCount))then((iTestRotate &#61; 0))fi#更新到新的样式, 保存老的样式(但不显示)for ((j &#61; 0, i &#61; (${offsetBox[$iBoxCurType]} &#43; $iTestRotate) * 8; j < 8; j&#43;&#43;, i&#43;&#43;))doboxTest[$j]&#61;${boxCur[$j]}boxCur[$j]&#61;${box[$i]}doneif BoxMove $boxCurY $boxCurX        #测试旋转后是否有空间放的下then#抹去旧的方块for ((j &#61; 0; j < 8; j&#43;&#43;))doboxCur[$j]&#61;${boxTest[$j]}dones&#61;&#96;DrawCurBox 0&#96;#画上新的方块for ((j &#61; 0, i &#61; (${offsetBox[$iBoxCurType]} &#43; $iTestRotate) * 8; j < 8; j&#43;&#43;, i&#43;&#43;))doboxCur[$j]&#61;${box[$i]}dones&#61;$s&#96;DrawCurBox 1&#96;echo -ne $siBoxCurRotate&#61;$iTestRotateelse#不能旋转&#xff0c;还是继续使用老的样式for ((j &#61; 0; j < 8; j&#43;&#43;))doboxCur[$j]&#61;${boxTest[$j]}donefi
}#DrawCurBox(bDraw), 绘制当前移动中的方块, bDraw为1, 画上, bDraw为0, 抹去方块。
function DrawCurBox()
{local i j t bDraw sBox sbDraw&#61;$1s&#61;""if (( bDraw &#61;&#61; 0 ))thensBox&#61;"\040\040"elsesBox&#61;"[]"s&#61;$s"\033[1m\033[7m\033[3${cBoxCur}m\033[4${cBoxCur}m"fifor ((j &#61; 0; j < 8; j &#43;&#61; 2))do((i &#61; iTrayTop &#43; 1 &#43; ${boxCur[$j]} &#43; boxCurY))((t &#61; iTrayLeft &#43; 1 &#43; 2 * (boxCurX &#43; ${boxCur[$j &#43; 1]})))#\033[y;xH, 光标到(x, y)处s&#61;$s"\033[${i};${t}H${sBox}"dones&#61;$s"\033[0m"echo -n $s
}#更新新的方块
function RandomBox()
{local i j t#更新当前移动的方块iBoxCurType&#61;${iBoxNewType}iBoxCurRotate&#61;${iBoxNewRotate}cBoxCur&#61;${cBoxNew}for ((j &#61; 0; j < ${#boxNew[&#64;]}; j&#43;&#43;))doboxCur[$j]&#61;${boxNew[$j]}done#显示当前移动的方块if (( ${#boxCur[&#64;]} &#61;&#61; 8 ))then#计算当前方块该从顶端哪一行"冒"出来for ((j &#61; 0, t &#61; 4; j < 8; j &#43;&#61; 2))doif ((${boxCur[$j]} < t)); then t&#61;${boxCur[$j]}; fidone((boxCurY &#61; -t))for ((j &#61; 1, i &#61; -4, t &#61; 20; j < 8; j &#43;&#61; 2))doif ((${boxCur[$j]} > i)); then i&#61;${boxCur[$j]}; fiif ((${boxCur[$j]} < t)); then t&#61;${boxCur[$j]}; fidone((boxCurX &#61; (iTrayWidth - 1 - i - t) / 2))#显示当前移动的方块echo -ne &#96;DrawCurBox 1&#96;#如果方块一出来就没处放&#xff0c;Game over!if ! BoxMove $boxCurY $boxCurXthenkill -$sigExit ${PPID}ShowExitfifi#清除右边预显示的方块for ((j &#61; 0; j < 4; j&#43;&#43;))do((i &#61; iTop &#43; 1 &#43; j))((t &#61; iLeft &#43; 2 * iTrayWidth &#43; 7))echo -ne "\033[${i};${t}H        "done#随机产生新的方块((iBoxNewType &#61; RANDOM % ${#offsetBox[&#64;]}))((iBoxNewRotate &#61; RANDOM % ${countBox[$iBoxNewType]}))for ((j &#61; 0, i &#61; (${offsetBox[$iBoxNewType]} &#43; $iBoxNewRotate) * 8; j < 8; j&#43;&#43;, i&#43;&#43;))doboxNew[$j]&#61;${box[$i]};done((cBoxNew &#61; ${colorTable[RANDOM % ${#colorTable[&#64;]}]}))#显示右边预显示的方块echo -ne "\033[1m\033[7m\033[3${cBoxNew}m\033[4${cBoxNew}m"for ((j &#61; 0; j < 8; j &#43;&#61; 2))do((i &#61; iTop &#43; 1 &#43; ${boxNew[$j]}))((t &#61; iLeft &#43; 2 * iTrayWidth &#43; 7 &#43; 2 * ${boxNew[$j &#43; 1]}))echo -ne "\033[${i};${t}H[]"doneecho -ne "\033[0m"
}#初始绘制
function InitDraw()
{clearRandomBox        #随机产生方块&#xff0c;这时右边预显示窗口中有方快了RandomBox        #再随机产生方块&#xff0c;右边预显示窗口中的方块被更新&#xff0c;原先的方块将开始下落local i t1 t2 t3#显示边框echo -ne "\033[1m"echo -ne "\033[3${cBorder}m\033[4${cBorder}m"((t2 &#61; iLeft &#43; 1))((t3 &#61; iLeft &#43; iTrayWidth * 2 &#43; 3))for ((i &#61; 0; i < iTrayHeight; i&#43;&#43;))do((t1 &#61; i &#43; iTop &#43; 2))echo -ne "\033[${t1};${t2}H||"echo -ne "\033[${t1};${t3}H||"done((t2 &#61; iTop &#43; iTrayHeight &#43; 2))for ((i &#61; 0; i < iTrayWidth &#43; 2; i&#43;&#43;))do((t1 &#61; i * 2 &#43; iLeft &#43; 1))echo -ne "\033[${iTrayTop};${t1}H&#61;&#61;"echo -ne "\033[${t2};${t1}H&#61;&#61;"doneecho -ne "\033[0m"#显示"Score"和"Level"字样echo -ne "\033[1m"((t1 &#61; iLeft &#43; iTrayWidth * 2 &#43; 7))((t2 &#61; iTop &#43; 10))echo -ne "\033[3${cScore}m\033[${t2};${t1}HScore"((t2 &#61; iTop &#43; 11))echo -ne "\033[3${cScoreValue}m\033[${t2};${t1}H${iScore}"((t2 &#61; iTop &#43; 13))echo -ne "\033[3${cScore}m\033[${t2};${t1}HLevel"((t2 &#61; iTop &#43; 14))echo -ne "\033[3${cScoreValue}m\033[${t2};${t1}H${iLevel}"echo -ne "\033[0m"
}#退出时显示GameOVer!
function ShowExit()
{local y((y &#61; iTrayHeight &#43; iTrayTop &#43; 3))echo -e "\033[${y};0HGameOver!\033[0m"exit
}#显示用法.
function Usage
{cat << EOF
Usage: $APP_NAME
Start tetris game.-h, --help              display this help and exit--version           output version information and exit
EOF
}#游戏主程序在这儿开始.
if [[ "$1" &#61;&#61; "-h" || "$1" &#61;&#61; "--help" ]]; thenUsage
elif [[ "$1" &#61;&#61; "--version" ]]; thenecho "$APP_NAME $APP_VERSION"
elif [[ "$1" &#61;&#61; "--show" ]]; then#当发现具有参数--show时&#xff0c;运行显示函数RunAsDisplayer
elsebash $0 --show&        #以参数--show将本程序再运行一遍RunAsKeyReceiver $!        #以上一行产生的进程的进程号作为参数
fi