#!/bin/bash #Pargram tetris game #History Walker 2015-07-27 version:first APP_NAME="${0##*[\/]}" APP_VERSION="1.0" #颜色定义 iSumColor=7 #颜色总数 cRed=1 #红色 cGreen=2 cYellow=3 cBlue=4 cFuchSia=5 cCyan=6 cWhite=7 #位置定义 marginLeft=10 marginTop=10 ((mapLeft=marginLeft+2)) ((mapTop=marginTop+1)) mapWidth=10 mapHeight=15 #信号定义 sigRotate=25 sigLeft=26 sigRight=27 sigDown=28 sigAllDown=29 sigExit=30 #颜色设置 cBorder=$cGreen cScore=$cFuchsia cScoreValue=$cCyan #方块类型 box0_0=(0 0 0 1 1 0 1 1 0 4) box1_0=(0 1 1 1 2 1 3 1 0 3) box1_1=(1 0 1 1 1 2 1 3 -1 3) box2_0=(0 0 1 0 1 1 2 1 0 4) box2_1=(0 1 0 2 1 0 1 1 0 3) box3_0=(0 1 1 0 1 1 2 0 0 4) box3_1=(0 0 0 1 1 1 1 2 0 4) box4_0=(0 2 1 0 1 1 1 2 0 3) box4_1=(0 1 1 1 2 1 2 2 0 3) box4_2=(1 0 1 1 1 2 2 0 -1 3) box4_3=(0 0 0 1 1 1 2 1 0 4) box5_0=(0 0 1 0 1 1 1 2 0 3) box5_1=(0 1 0 2 1 1 2 1 0 3) box5_2=(1 0 1 1 1 2 2 2 -1 3) box5_3=(0 1 1 1 2 0 2 1 0 4) box6_0=(0 1 1 0 1 1 1 2 0 3) box6_1=(0 1 1 1 1 2 2 1 0 3) box6_2=(1 0 1 1 1 2 2 1 -1 3) box6_3=(0 1 1 0 1 1 2 1 0 4) iSumType=7 #方块总数 boxStyle=(1 2 2 2 4 4 4) #每种方块对应的变化数 iScoreEachLevel=50 #运行时数据 sig=0 iScore=0 iLevel=0 boxNext=() #下一个方块 iboxNextColor=0 iboxNextType=0 iboxNextStyle=0 boxCur=() iBoxCurColor=0 iBoxCurType=0 iBoxCurStyle=0 #显示进程PID pidDisplayer=0 #这两个是相对于边框的坐标 boxCurX=-1 boxCurY=-1 #背景数组,用颜色表示,没有方块则为-1 map=() for (( i = 0; i < mapHeight * mapWidth ; i++ )) do map[$i]=-1 done MyExitNoSub() { local y stty $sTTY #恢复终端 ((y = marginTop + mapHeight + 10 )) echo -e " 33[?25h 33[${y};0H" exit } MyExit() { kill -$sigExit $pidDisplayer #关闭显示进程 MyExitNoSub } #显示退出 ShowExit() { local y (( y = marginTop + mapHeight + 3 )) echo -e " 33[${y};1HGameOver! 33[0m" exit } #绘制方块函数 DrawCurBox() { local i x y bErase sBox bErase=$1 if (( ${bErase} == 0 )) #根据参数不同,选择擦除方块或绘制方块 then sBox=" 40 40" else sBox="[]" echo -ne " 33[1m 33[3${iBoxCurColor}m 33[4${iBoxCurColor}m" fi for (( i = 0; i < 8; i += 2)) do (( y = mapTop + 1 + ${boxCur[$i]} + boxCurY )) #方块刚出现时就会全部绘制在棋盘中 (( x = mapLeft + 1 + 2 * (boxCurX + ${boxCur[$i+1]}) )) echo -ne " 33[${y};${x}H${sBox}" done echo -ne " 33[0m" } #接收命令 RunAsKeyReceiver() { local Key aKey sig cESC sTTY pidDisplayer=$1 aKey=(0 0 0) cESC=`echo -ne " 33"` cSpace=`echo -ne " 40"` sTTY=`stty -g` #保存终端 trap "MyExit;" INT QUIT trap "MyExitNoSub;" $sigExit echo -ne " 33[?25l" while : do #始终在等待信号 read -s -n 1 Key aKey[0]=${aKey[1]} aKey[1]=${aKey[2]} aKey[2]=$Key sig=0 if [[ $Key == $cESC && {aKey[1]} == $cESC ]] then MyExit elif [[ ${aKey[0]} == $cESC && ${aKey[1]} == "[" ]] then if [[ $Key == "A" ]]; then sig=$sigRotate #判断上下左右输入 elif [[ $Key == "B" ]]; then sig=$sigDown elif [[ $Key == "C" ]]; then sig=$sigRight elif [[ $Key == "D" ]]; then sig=$sigLeft fi elif [[ $Key == "W" || $Key == "w" ]]; then sig=$sigRotate elif [[ $Key == "S" || $Key == "s" ]]; then sig=$sigDown elif [[ $Key == "A" || $Key == "a" ]]; then sig=$sigLeft elif [[ $Key == "D" || $Key == "d" ]]; then sig=$sigRight elif [[ [$Key] == "[]" ]]; then sig=$sigAllDown elif [[ $Key == "Q" || $Key == "q" ]] then MyExit fi if [[ $sig != 0 ]] then kill -$sig $pidDisplayer fi done } #绘制边界 DrawBorder() { clear local i y x1 x2 echo -ne " 33[1m 33[3${cBorder}m 33[4${cBorder}m" ((x1 = marginLeft + 1)) ((x2 = x1 + 2 + mapWidth * 2)) for (( i = 0; i < mapHeight; i++ )) do ((y = i+ marginTop + 2)) echo -ne " 33[${y};${x1}H||" echo -ne " 33[${y};${x2}H||" done ((x1 = marginTop + mapHeight + 2)) ((upBorder = marginTop +1)) for ((i =0 ;i < mapWidth + 2;i++)) do ((y = i * 2 + marginLeft + 1)) echo -ne " 33[${upBorder};${y}H==" echo -ne " 33[${x1};${y}H==" done echo -ne " 33[0m" echo -ne " 33[1m" ((y = marginLeft + mapWidth *2 + 7)) ((x1 = marginTop + 10)) echo -ne " 33[3${cScore}m 33[${x1};${y}HScore" ((x1 = marginTop + 11)) echo -ne " 33[3${cScoreValue}m 33[${x1};${y}H${iScore}" ((x1 = marginTop + 13)) echo -ne " 33[3${cScore}m 33[${x1};${y}HLevel" ((x1 = marginTop + 14)) echo -ne " 33[3${cScoreValue}m 33[${x1};${y}H${iLevel}" echo -ne " 33[0m" } #用于判断是否可以移动 BoxMove() { local i x y xPos yPos yPos=$1 xPos=$2 for (( i = 0 ; i < 8 ; i += 2 )) do (( y = yPos + ${boxCur[$i]} )) (( x = xPos + ${boxCur[$i+1]} )) if (( y < 0 || y >= mapHeight || x < 0 || x >= mapWidth )) then return 1 fi if (( ${map[y * mapWidth + x]} != -1 )) then return 1 fi done return 0 } #准备下一方块 PrepareNextBox() { local i x y #擦除已有的预显示方块 if (( ${#boxNext[@]} != 0 )); then for ((i = 0 ; i < 8 ;i += 2 )) do ((y = marginTop +1 + ${boxNext[$i]})) ((x = marginLeft + 2 * mapWidth + 7 + 2 * ${boxNext[$i +1]})) echo -ne " 33[${y};${x}H 40 40" done fi #随机生成下一方块 (( iBoxNextType = RANDOM % iSumType)) (( iBoxNextStyle = RANDOM % ${boxStyle[$iBoxNextType]} )) (( iBoxNextColor = RANDOM % ${iSumColor} + 1 )) boxNext=( `eval 'echo ${box'$iBoxNextType'_'$iBoxNextStyle'[@]}'` ) echo -ne " 33[1m 33[3${iBoxNextColor}m 33[4${iBoxNextColor}m" #绘制预显示方块 for (( i = 0; i < 8 ; i += 2 )) do (( y = marginTop + 1 + ${boxNext[$i]} )) (( x = marginLeft + 2 * mapWidth + 7 + 2 * ${boxNext[$i+1]} )) echo -ne " 33[${y};${x}H[]" done echo -ne " 33[0m" } #生成方块 CreateBox() { if (( ${#boxCur[@]} == 0 )) then (( iBoxCurType = RANDOM % iSumType)) (( iBoxCurStyle = RANDOM % ${boxStyle[$iBoxCurType]} )) (( iBoxCurColor = RANDOM % $iSumColor + 1 )) else iBoxCurType=$iBoxNextType iBoxCurStyle=$iBoxNextStyle iBoxCurColor=$iBoxNextColor fi boxCur=( `eval 'echo ${box'$iBoxCurType'_'$iBoxCurStyle'[@]}'` ) boxCurY=boxCur[8] boxCurX=boxCur[9] #创建后开始绘制 DrawCurBox 1 if ! BoxMove $boxCurY $boxCurX then # kill -$sigExit $PPID MyExit # ShowExit fi #同时开始准备下一方块 PrepareNextBox } #初始化 InitDraw() { clear DrawBorder CreateBox } #将方块写入背景当中 Box2Map() { local i j x y line #填充背景色 for ((i = 0 ; i < 8 ; i += 2)) do ((y = ${boxCur[$i]} + boxCurY )) ((x = ${boxCur[$i+1]} + boxCurX )) map[y*mapWidth+x]=$iBoxCurColor done line=0 #判断每一行 for (( i = 0 ; i < mapHeight ; i++)) do for (( j = 0; j < mapWidth; j++ )) do [[ ${map[i * mapWidth + j]} -eq -1 ]] && break done [ $j -lt $mapWidth ] && continue (( line++ )) #删除第i行,并将第0行到i-1行全部下移一行,移动行的下限(0)可以进一步简化 for (( j = i * mapWidth - 1; j >= 0; j-- )) do ((x = j + mapWidth)) map[$x]=${map[$j]} done #将第0行置空 for ((i = 0; i<mapWidth;i++)) do map[$i]=-1 done done #写入背景结束后,开始计算分数 [ $line -eq 0 ] && return (( x = marginLeft + mapWidth * 2 + 7)) (( y = marginTop + 11 )) (( iScore += line * 2 )) #显示新的分数 echo -ne " 33[1m 33[3${cScoreValue}m 33[${y};${x}H${iScore}" #显示速度等级 if ((iScore % iScoreEachLevel < line * 2 - 1)) then if ((iLevel < 20)) then (( iLevel++ )) (( y = marginTop + 14 )) echo -ne " 33[3${cScoreValue}m 33[${y};${x}H${iLevel}" fi fi echo -ne " 33[0m" #重新绘制界面 for (( i = 0; i < mapHeight ; i++)) do #棋盘相对于屏幕的坐标 ((y = i + mapTop + 1)) ((x = mapLeft + 1)) #移动光标 echo -ne " 33[${y};${x}H" for (( j = 0; j < mapWidth ; j++)) do ((tmp = i * mapWidth + j)) if ((${map[$tmp]} == -1)) #说明是空格 then echo -ne " " else echo -ne " 33[1m 33[3${map[$tmp]}m 33[4${map[$tmp]}m[] 33[0m" fi done done } #直接下落到底 BoxAllDown() { local y iDown iDown=0 (( y = boxCurY + 1 )) while BoxMove $y $boxCurX do (( y++ )) (( iDown++ )) done DrawCurBox 0 (( boxCurY += iDown )) DrawCurBox 1 Box2Map CreateBox } #上方向键,旋转 BoxRotate() { [ ${boxStyle[$iBoxCurType]} -eq 1 ] && return (( rotateStyle = (iBoxCurStyle +1) % ${boxStyle[$iBoxCurType]} )) boxTmp=( `eval 'echo ${boxCur[@]}'` ) boxCur=( `eval 'echo ${box'$iBoxCurType'_'$rotateStyle'[@]}'` ) if BoxMove $boxCurY $boxCurX then boxCur=( `eval 'echo ${boxTmp[@]}'` ) DrawCurBox 0 boxCur=( `eval 'echo ${box'$iBoxCurType'_'$rotateStyle'[@]}'` ) DrawCurBox 1 iBoxCurStyle=$rotateStyle else boxCur=( `eval 'echo ${boxTmp[@]}'` ) fi } BoxLeft() { local x ((x = boxCurX - 1)) if BoxMove $boxCurY $x then DrawCurBox 0 ((boxCurX = x)) DrawCurBox 1 fi } BoxRight() { local x ((x = boxCurX + 1)) if BoxMove $boxCurY $x then DrawCurBox 0 ((boxCurX = x)) DrawCurBox 1 fi } BoxDown() { local y (( y = boxCurY + 1 )) if BoxMove $y $boxCurX #如果可移动则移动,不能则写入背景当中 then DrawCurBox 0 (( boxCurY = y )) DrawCurBox 1 else Box2Map #写入背景当中,并创建下一方块 CreateBox fi } RunAsDisplayer() { #显示进程运行这一函数 local sigThis InitDraw #初始化操作 trap "sig=$sigRotate;" $sigRotate trap "sig=$sigLeft;" $sigLeft trap "sig=$sigRight;" $sigRight trap "sig=$sigDown;" $sigDown trap "sig=$sigAllDown;" $sigAllDown trap "ShowExit;" $sigExit while : #始终在循环等待 do #本循环用于接收信号,for循环中有个睡眠时间,for循环之后有个BoxDown函数 #for循环睡眠时间越长,自动下落延迟越长,所以for循环的次数决定了下落速度 for ((i = 0; i < 21 - iLevel; i++)) do sleep 0.02 sigThis=$sig sig=0 if (( sigThis == sigRotate )); then BoxRotate; elif (( sigThis == sigLeft )); then BoxLeft; elif (( sigThis == sigRight )); then BoxRight; elif (( sigThis == sigDown )); then BoxDown; elif (( sigThis == sigAllDown )); then BoxAllDown; fi done BoxDown done } #help usage() { echo "tetris.sh [option]" echo "option:" echo " --version:for version information" echo " --help:for help information" echo "no option to run game" } #游戏主程序,以上是函数定义 if [[ "$1" == "--version" ]]; then echo "$APP_NAME $APP_VERSION" elif [[ "$1" == "--help" || "$1" == "--h" ]];then usage elif [[ "$1" == "--show" || "$1" == "--v" ]]; then RunAsDisplayer #只运行显示进程 else bash $0 --show& #启动显示进程,并放入后台开始执行 RunAsKeyReceiver $! #获取最后一个后台进程 fi