You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
shell-project/games/tetris.sh

636 lines
18 KiB

2 years ago
#!/bin/bash
# filename: tetris.sh
#
# Author:
# Date: 2013.5.14
# Email:
#
# const value
#======================================固定值================================#
BXLINES=3; BXCOLNS=6; # 小方块的高和宽
MAPX=20; MAPY=10;
NAME=('I' 'S' 'Z' 'L' 'J' 'T' 'O');
FLAG=('2' '2' '2' '4' '4' '4' '1');
declare -A mapflag mapname;
mapflag=([I]=2 [S]=2 [Z]=2 [L]=4 [J]=4 [T]=4 [O]=1);
mapname=([I]=1 [S]=2 [Z]=3 [L]=4 [J]=5 [T]=6 [O]=7);
colorone=(31 32 33 34 35 36 37);
colortwo=(31 32 33 34 35 36 37);
Iax=( 0 0 0 0); Iay=(-1 0 1 2);
Ibx=(-1 0 1 2); Iby=( 0 0 0 0);
Sax=( 1 1 0 0); Say=(-1 0 0 1);
Sbx=(-1 0 0 1); Sby=( 0 0 1 1);
Zax=( 0 0 1 1); Zay=(-1 0 0 1);
Zbx=(-1 0 0 1); Zby=( 1 1 0 0);
Lax=(-1 0 1 1); Lay=( 0 0 0 -1);
Lbx=(-1 0 0 0); Lby=(-1 -1 0 1);
Lcx=(-1 -1 0 1);Lcy=( 1 0 0 0);
Ldx=( 0 0 0 1); Ldy=(-1 0 1 1);
Jax=(-1 0 1 1); Jay=( 0 0 0 1);
Jbx=( 1 0 0 0); Jby=(-1 -1 0 1);
Jcx=(-1 -1 0 1);Jcy=(-1 0 0 0);
Jdx=( 0 0 0 -1);Jdy=(-1 0 1 1);
Tax=(0 0 0 -1); Tay=(-1 0 1 0);
Tbx=(-1 0 1 0); Tby=( 0 0 0 1);
Tcx=( 0 0 0 1); Tcy=(-1 0 1 0);
Tdx=(-1 0 1 0); Tdy=(0 0 0 -1);
Oax=( 0 0 1 1); Oay=( 0 1 0 1);
good_game=(
' '
' G A M E O V E R ! '
' '
' Score: '
' '
' press Q to quit '
' press N to start a new game '
' press S to change the level '
' press R to replay your game '
' '
);
start_game=(
' '
' ~~~ T E T R I S ~~~ '
' '
' Author: LKJ '
' '
' press S to change the level '
' '
' C H O O S E L E V E L: '
' 1 '
' '
' Press <Enter> to start the game '
' '
);
blockarr=(); #记录name 和 flag
keyarray=(); #记录按键
#------------------------------------------------------------------------#
#========================================================================#
game_init() { # game_init
SCLINES=`tput lines`; # 屏幕的高
SCCOLNS=`tput cols`; # 屏幕的宽
#主框的属性
mainw=59; mainh=60; # 主框的宽和高
mainctx=0; maincty=4; # 主框中心打印点
upx=$((SCLINES-62)); dnx=$((SCLINES-1)); # 界面的上下 x
lty=$((SCCOLNS/2-50)); rty=$((lty+61)); # 界面的左右 y
#next的属性
nextw=40; nexth=16; # next框的高和宽
ntx=$((upx)); nty=$((rty+2)); # next框的位置
ntctx=$((ntx+5)); ntcty=$((nty+12)); # next框的中心打印位置
#score的属性
scorw=$nextw; scorh=5;
scx=$((ntx+20)); scy=$((nty));
scctx=$((scx+4)); sccty=$((scy+19));
#level的属性
levew=$nextw; leveh=5;
lvx=$((scx+9)); lvy=$((scy));
lvctx=$((lvx+4)); lvcty=$((lvy+20));
#help的属性
helpw=$nextw; helph=21;
hpx=$((lvx+10)); hpy=$((nty));
hpctx=$((hpx+4)); hpcty=$((hpy+10));
#map
MAP=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
); #所有的格子
#paint_gui
clear; paint_gui;
local x=$((RANDOM%7));
name=${NAME[$((x))]};
flag=$((RANDOM%FLAG[$x]+1));
nname='I'; nflag=2; #下一个方块
centerx=$mainctx; #每个图形的中心打印点
centery=$maincty;
#各个方块的相对坐标
ax="Iax"; ay="Iay";
}
#------------------------------------------------------------------------#
game_exit() {
tput rmcup;
tput cvvis;
stty echo;
(($#==1)) && echo "window is too small";
exit 0;
}
#============================三个原子函数paint_block, erase_block, paint_box=================
#打印一个小方块, 四个参数, 两个位置和两个颜色
paint_block() {
local x=$1 y=$2 crone=$3 crtwo=$4 i
x=$((upx+x*BXLINES+1));
y=$((lty+y*BXCOLNS+1));
echo -ne "\033[$((x));$((y))H\033[${crone}m+---+\033[0m";
echo -ne "\033[$((x+1));$((y))H\033[${crone}m|\033[${crtwo}m###\033[0m\033[${crone}m|\033[0m";
echo -ne "\033[$((x+2));$((y))H\033[${crone}m+---+\033[0m";
}
#删除一个小方块, 两个参数,即位置
erase_block() {
local x=$1 y=$2
x=$((upx+x*BXLINES+1));
y=$((lty+y*BXCOLNS+1));
echo -ne "\033[$(( x ));$((y))H ";
echo -ne "\033[$((x+1));$((y))H ";
echo -ne "\033[$((x+2));$((y))H ";
}
#画一个盒子$1 $2 $3 $4 $5
paint_box() {
local x=$1 y=$2 w=$3 h=$4 color=$5 i
echo -ne "\033[$x;$((y))H\033[${color}m+\033[0m";
echo -ne "\033[$x;$((y+w+1))H\033[${color}m+\033[0m";
for (( i = 1; i <= w; i++ )); do
echo -ne "\033[$x;$((y+i))H\033[${color}m-\033[0m";
echo -ne "\033[$((x+h+1));$((y+i))H\033[${color}m-\033[0m";
done
echo -ne "\033[$((x+h+1));$((y))H\033[${color}m+\033[0m";
echo -ne "\033[$((x+h+1));$((y+w+1))H\033[${color}m+\033[0m";
for (( i = 1; i <= h; i++ )); do
echo -ne "\033[$((x+i));$((y))H\033[${color}mI\033[0m";
echo -ne "\033[$((x+i));$((y+w+1))H\033[${color}mI\033[0m";
done
}
#---------------------------------------------------------------------------------------
#打印界面
paint_gui() {
((upx<=0 || lty<=0)) && game_exit 1;
paint_box $upx $lty $mainw $mainh 34; #画主框
paint_box $ntx $nty $nextw $nexth 33; #画next框
paint_box $lvx $lvy $levew $leveh 32; #画level框
paint_box $scx $scy $scorw $scorh 36; #画分数框
paint_box $hpx $hpy $helpw $helph 31; #画帮助框
#打印score, help 等提示字符
echo -ne "\033[$((ntx+2));$((nty+17))H\033[34mN E X T\033[0m";
echo -ne "\033[$((scx+2));$((scy+16))H\033[31mS C O R E\033[0m";
echo -ne "\033[$((scx+4));$((scy+20))H\033[31m0\033[0m";
echo -ne "\033[$((lvx+2));$((lvy+16))H\033[31mL E V E L\033[0m";
echo -ne "\033[$((lvctx));$((lvcty))H\033[31m1\033[0m";
echo -ne "\033[$((hpx+2));$((hpy+17))H\033[33mH E L P\033[0m";
echo -ne "\033[$((hpctx));$((hpcty))H\033[34mH --- Move Left\033[0m";
echo -ne "\033[$((hpctx+2));$((hpcty))H\033[34mL --- Move Right\033[0m";
echo -ne "\033[$((hpctx+4));$((hpcty))H\033[34mJ --- Soft Drop\033[0m";
echo -ne "\033[$((hpctx+6));$((hpcty))H\033[34mK --- Rotate\033[0m";
echo -ne "\033[$((hpctx+8));$((hpcty))H\033[34mSpace or Enter --- Hard Drop\033[0m";
echo -ne "\033[$((hpctx+11));$((hpcty))H\033[34mP --- Pause Game\033[0m";
echo -ne "\033[$((hpctx+13));$((hpcty))H\033[34mQ --- Quit Game\033[0m";
echo -ne "\033[$((hpctx+15));$((hpcty))H\033[34mE --- Exit Replay\033[0m";
}
#---------------------------------------------------------
#在next框中打印下一个方块图形
paint_next() {
(($#==0)) && mk_random;
local oflag=$flag oname=$name
((centerx=mainctx+2)); ((centery=maincty+9));
erase_x;
flag=$nflag; name=$nname;
paint_x;
flag=$oflag; name=$oname;
centerx=$mainctx; centery=$maincty;
}
# 打印分数和level
paint_score() {
level=0;
echo -ne "\033[$((scctx));$((sccty))H\033[31m$score\033[0m";
((score>2000 )) && ((level=1)); ((score>5000 )) && ((level=2));
((score>9000 )) && ((level=3)); ((score>14000)) && ((level=4));
((score>20000)) && ((level=5)); ((score>27000)) && ((level=6));
((score>35000)) && ((level=7)); ((score>44000)) && ((level=8));
((level=olevel+level));
((level>9)) && ((level=9));
((TIME=10-level));
echo -ne "\033[$((lvctx));$((lvcty))H\033[31m$level\033[0m";
}
#根据name选择要打印的方块
paint_x() {
local x=$centerx y=$centery i
local n=$((${mapname[$name]}-1));
find_array;
for (( i = 0; i < 4; i++ )); do
paint_block $((x+${ax}[$i])) $((y+${ay}[$i])) ${colorone[$n]} ${colortwo[$n]}
done
}
#根据name选择要删除的方块
erase_x() {
local x=$centerx y=$centery i
find_array;
for (( i = 0; i < 4; i++ )); do
erase_block $((x+${ax}[$i])) $((y+${ay}[$i]));
done
}
rotate_x() {
((flag+=1));
((flag>mapflag[$name])) && flag=1;
}
#------------------------------------------------------------------------#
#========================================================================#
update() { #update the map
local x=$1 n=0 i j
for (( i = 0; i < MAPY; i++ )); do
erase_block $x $i; #消掉一行
MAP[$((x*MAPY+i))]=0; #更新为0
done
#将上面的格子向下移动一行
for (( i = 0; i < MAPY; i++ )); do
for (( j = x; j > 0; j-- )); do
((n=MAP[$(((j-1)*MAPY+i))]));
if ((n!=0)); then
erase_block $((j-1)) $i;
paint_block $j $i ${colorone[$((n-1))]} ${colortwo[$((n-1))]};
fi
done
done
# 更新MAP的值
for (( i = 0; i < MAPY; i++ )); do
for (( j = x; j >0; j-- )); do
MAP[$((j*MAPY+i))]=$((MAP[$(((j-1)*MAPY+i))]));
done
done
}
# 检测是否可以消掉一行
have_score() {
local n=0 i j;
for (( i = 0; i < MAPX; i++ )); do
for (( j = 0; j < MAPY; j++ )); do
((MAP[$((10*i+j))]==0)) && break; #有空格就退出
done
((j==MAPY)) && ((n+=1)) && update $i; #可以消掉一行
done
case $n in
1) ((score+=100)); ;;
2) ((score+=200)); ;;
3) ((score+=400)); ;;
4) ((score+=800)); ;;
esac
}
#根据flag 和 name找到其的坐标数组
find_array() {
case $flag in
1) ax="${name}ax"; ay="${name}ay";
;;
2) ax="${name}bx"; ay="${name}by";
;;
3) ax="${name}cx"; ay="${name}cy";
;;
4) ax="${name}dx"; ay="${name}dy";
;;
esac
}
# 检测方块首次出现时,是否会越界,并作出矫正或者游戏结束
check_first() {
local x=$centerx y=$centery minx=0 i
find_array;
# 检测是否越界
for (( i = 0; i < 4; i++ )); do
(((x+${ax}[$i])<minx)) && ((minx=(x+${ax}[$i])));
done
((centerx-=minx));
paint_x; #开始打印方块
paint_score;
# 检测是否会结束游戏
for (( i = 0; i < 4; i++ )); do
((x=centerx+${ax}[$i])); ((y=centery+${ay}[$i]))
((MAP[$((x*10+y))]!=0)) && return 1; #游戏结束
done
return 0;
}
#检测是否可以固定方块
check_stop() {
local sx=$centerx sy=$centery
local x=0 y=0 n=0 i=0
find_array;
for (( i = 0; i < 4; i++ )); do
((x=(sx+${ax}[$i]))); ((y=(sy+${ay}[$i])));
((x+1>19)) && break; #到底
((MAP[$((10*(x+1)+y))] != 0)) && break; #有方块挡住
done
if ((i!=4)); then #不能在动了,则记录
for (( i = 0; i < 4; i++ )); do
((x=(sx+${ax}[$i]))); ((y=(sy+${ay}[$i])));
n=$((10*x+y)); MAP[$n]=${mapname[$name]};
done
have_score;
return 1;
fi
return 0;
}
#检测是否可以移到$1 $2这个格子
check_next() {
local sx=$1 sy=$2
local x=0 y=0 n=0 i=0
find_array;
for (( i = 0; i < 4; i++ )); do
((x=(sx+${ax}[$i]))); ((y=(sy+${ay}[$i])));
((x<0 || x>19 || y<0 || y>9)) && return 1;
((MAP[$((10*x+y))] != 0)) && return 1; #不能移到这个格子
done
return 0;
}
#------------------------------------------------------------------------#
#========================================================================#
go_left() { #向左移一个
check_next $centerx $((centery-1));
(($?==1)) && return 1;
erase_x; ((centery-=1));
return 0;
}
go_right() { #向右移一格
check_next $centerx $((centery+1));
(($?==1)) && return 1;
erase_x; ((centery+=1));
return 0;
}
go_down() { #加速向下
check_next $((centerx+1)) $centery
(($?==1)) && return 1;
erase_x; ((centerx+=1));
return 0;
}
go_rotate() { #旋转
local oflag=$flag #保存原来的flag值
rotate_x;
check_next $centerx $centery;
(($?==1)) && ((flag=oflag)) && return 1;
flag=$oflag;
erase_x; rotate_x;
return 0;
}
go_fast() { #快速固定
erase_x;
check_next $((centerx+1)) $centery
local res=$?;
while ((res==0)); do
((centerx+=1));
check_next $((centerx+1)) $centery
res=$?;
done
}
game_pause() {
echo -ne "\033[$((hpctx+17));$((hpcty+5))H\033[31mGame Paused\033[0m";
local pkey;
while true; do
read -n 1 pkey;
[[ $pkey = 'q' ]] || [[ $pkey == 'Q' ]] && game_exit;
[[ $pkey = 'p' ]] || [[ $pkey == 'P' ]] && break;
done
echo -ne "\033[$((hpctx+17));$((hpcty+5))H\033[31m \033[0m";
}
# 根据按键作出选择
keypress() {
local result=0;
case ${key:-space} in
H|h) go_left; result=$?; # 向左一个格子
;;
J|j) go_down; result=$?; # 向下, 加速向下一个格子
;;
K|k) go_rotate; result=$?; # 向上, 旋转90度
;;
L|l) go_right; result=$?; # 向右一个格子
;;
Q|q) game_exit; # 退出游戏
;;
P|p) game_pause;
;;
space)
go_fast; nextbk=1;
;;
esac
((result==0)) && paint_x;
}
#----------------------------------------------------------------#
#================================================================#
mk_random() { # 产生下一个随机方块
local x=$((RANDOM%7))
nname=${NAME[$x]};
nflag=$((RANDOM%FLAG[$x]+1));
}
#开始一个新游戏
new_game() {
local i gmover=0 nextbk=0;
game_init; #初始化游戏
while true; do
paint_next; #在next框中打印下一个方块
blockarr+=($name $flag $nname $nflag);
check_first; (($?==1)) && return; #检查是否游戏结束
while true; do
for (( i = 0; i < TIME; i++ )); do
read -n 1 -t 0.1 key; #等待按键
(($?==0)) && keypress;
(($?==0)) && keyarray+=(${key:-space});
(($?==0)) || keyarray+=("nothing");
((nextbk==1)) && !((nextbk=0)) && break;
done
check_stop; (($?==1)) && break;
erase_x; ((centerx+=1)); paint_x;
done
name=$nname; flag=$nflag;
((score+=10));
done
}
replay() {
score=0; level=$olevel;
local gmover=0 nextbk=0 i=0 j=0;
local blocklen=$((${#blockarr[@]})) keylen=${#keyarray[@]};
game_init;
for ((i=0; i<blocklen; i+=4)); do
name=${blockarr[i]}; flag=${blockarr[i+1]};
nname=${blockarr[i+2]}; nflag=${blockarr[i+3]};
paint_next -n;
check_first; (($?==1)) && return 0;
while true; do
local k=0 anykey;
while true; do
key=${keyarray[j++]}; [[ $key = [pP] ]] && continue;
keypress;
#((j+=1));
read -n 1 -t 0.1 anykey;
if (($?==0)); then
[[ $anykey = [pP] ]] && game_pause;
[[ $anykey = [qQ] ]] && game_exit;
[[ $anykey = [eE] ]] && level=1 && return 0;
fi
((nextbk==1)) && !((nextbk=0)) && break;
((k+=1)) && ((k==TIME)) && break;
done
check_stop; (($?==1)) && break;
erase_x; ((centerx+=1)); paint_x;
done
((score+=10));
done
score=0; level=1;
return 0;
}
paint_game_over() {
local xcent=$((`tput lines`/2)) ycent=$((`tput cols`/2))
local x=$((xcent-4)) y=$((ycent-25))
for (( i = 0; i < 10; i++ )); do
echo -ne "\033[$((x+i));${y}H\033[44m${good_game[$i]}\033[0m";
done
echo -ne "\033[$((x+3));$((ycent+1))H\033[44m${score}\033[0m";
}
game_over() {
paint_game_over;
level=1; local pkey;
while true; do
read -n 1 pkey;
[[ $pkey = 'q' ]] || [[ $pkey = 'Q' ]] && game_exit;
[[ $pkey = 'n' ]] || [[ $pkey = 'N' ]] && break;
[[ $pkey = 's' ]] || [[ $pkey = 'S' ]] && ((level=level%9+1));
[[ $pkey = 'r' ]] || [[ $pkey = 'R' ]] && replay && paint_game_over;
echo -ne "\033[$((lvctx));$((lvcty))H\033[31m$level\033[0m";
done
olevel=$level;
blockarr=();
keyarray=();
}
game_start() {
tput civis; stty -echo;
tput smcup; clear;
trap 'game_exit;' SIGINT SIGTERM
score=0; #总分数
level=1; #等级
TIME=9;
local xcent=$(tput lines) ycent=$(tput cols)
local n=$((xcent/BXLINES)) m=$((ycent/BXCOLNS)) i j;
for (( i = 3; i < n-2; i++ )); do
for (( j = 3; j < m-3; ++j)); do
paint_block $i $j $((RANDOM%7+31)) $((RANDOM%7+31))
done
done
local x=$((xcent/2-4)) y=$((ycent/2-25))
for (( i = 0; i < 12; i++ )); do
echo -ne "\033[$((x+i));${y}H\033[40m${start_game[$i]}\033[0m";
done
local pkey='x';
while true; do
read -n 1 pkey;
[[ ${pkey:-enter} = 'enter' ]] && break;
[[ $pkey = 'q' ]] || [[ $pkey = 'Q' ]] && game_exit;
[[ $pkey = 's' ]] || [[ $pkey = 'S' ]] && ((level=level%9+1));
echo -ne "\033[$((x+8));$((ycent/2-1))H\033[40m$level\033[0m";
done
olevel=$level;
}
game_main() {
game_start;
while true; do
new_game;
game_over;
done
}
#----------------------------------------------------------------------#
game_main;