#!/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 "\033[?25h\033[${y};0H"
exit
}
MyExit()
{
kill -$sigExit $pidDisplayer #关闭显示进程
MyExitNoSub
}
#显示退出
ShowExit()
{
local y
(( y = marginTop + mapHeight + 3 ))
echo -e "\033[${y};1HGameOver!\033[0m"
exit
}
#绘制方块函数
DrawCurBox()
{
local i x y bErase sBox
bErase=$1
if (( ${bErase} == 0 )) #根据参数不同,选择擦除方块或绘制方块
then
sBox="\040\040"
else
sBox="[]"
echo -ne "\033[1m\033[3${iBoxCurColor}m\033[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 "\033[${y};${x}H${sBox}"
done
echo -ne "\033[0m"
}
#接收命令
RunAsKeyReceiver()
{
local Key aKey sig cESC sTTY
pidDisplayer=$1
aKey=(0 0 0)
cESC=`echo -ne "\033"`
cSpace=`echo -ne "\040"`
sTTY=`stty -g` #保存终端
trap "MyExit;" INT QUIT
trap "MyExitNoSub;" $sigExit
echo -ne "\033[?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 "\033[1m\033[3${cBorder}m\033[4${cBorder}m"
((x1 = marginLeft + 1))
((x2 = x1 + 2 + mapWidth * 2))
for (( i = 0; i < mapHeight; i++ ))
do
((y = i+ marginTop + 2))
echo -ne "\033[${y};${x1}H||"
echo -ne "\033[${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 "\033[${upBorder};${y}H=="
echo -ne "\033[${x1};${y}H=="
done
echo -ne "\033[0m"
echo -ne "\033[1m"
((y = marginLeft + mapWidth *2 + 7))
((x1 = marginTop + 10))
echo -ne "\033[3${cScore}m\033[${x1};${y}HScore"
((x1 = marginTop + 11))
echo -ne "\033[3${cScoreValue}m\033[${x1};${y}H${iScore}"
((x1 = marginTop + 13))
echo -ne "\033[3${cScore}m\033[${x1};${y}HLevel"
((x1 = marginTop + 14))
echo -ne "\033[3${cScoreValue}m\033[${x1};${y}H${iLevel}"
echo -ne "\033[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 "\033[${y};${x}H\040\040"
done
fi
#随机生成下一方块
(( iBoxNextType = RANDOM % iSumType))
(( iBoxNextStyle = RANDOM % ${boxStyle[$iBoxNextType]} ))
(( iBoxNextColor = RANDOM % ${iSumColor} + 1 ))
boxNext=( `eval 'echo ${box'$iBoxNextType'_'$iBoxNextStyle'[@]}'` )
echo -ne "\033[1m\033[3${iBoxNextColor}m\033[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 "\033[${y};${x}H[]"
done
echo -ne "\033[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 "\033[1m\033[3${cScoreValue}m\033[${y};${x}H${iScore}"
#显示速度等级
if ((iScore % iScoreEachLevel < line * 2 - 1))
then
if ((iLevel < 20))
then
(( iLevel++ ))
(( y = marginTop + 14 ))
echo -ne "\033[3${cScoreValue}m\033[${y};${x}H${iLevel}"
fi
fi
echo -ne "\033[0m"
#重新绘制界面
for (( i = 0; i < mapHeight ; i++))
do
#棋盘相对于屏幕的坐标
((y = i + mapTop + 1))
((x = mapLeft + 1))
#移动光标
echo -ne "\033[${y};${x}H"
for (( j = 0; j < mapWidth ; j++))
do
((tmp = i * mapWidth + j))
if ((${map[$tmp]} == -1)) #说明是空格
then
echo -ne " "
else
echo -ne "\033[1m\033[3${map[$tmp]}m\033[4${map[$tmp]}m[]\033[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
转载于:https://www.cnblogs.com/zclzqbx/p/4687029.html