Bash - Jeux Tetris

J’ai trouvé 2 tetris en bash, les liens originaux se trouvent dans la section Documentation.

Scripts

Version color

Le script tétris en bash avec des couleurs :

#!/bin/bash

# Tetris Game

#APP declaration
APP_NAME="${0##*[\\/]}"
APP_VERSION="1.0"

cRed=1
cGreen=2
cYellow=3
cBlue=4
cFuchsia=5
cCyan=6
cWhite=7
colorTable=($cRed $cGreen $cYellow $cBlue $cFuchsia $cCyan $cWhite)

iLeft=3
iTop=2
((iTrayLeft = iLeft + 2))
((iTrayTop = iTop + 1))
((iTrayWidth = 10))
((iTrayHeight = 15))

cBorder=$cGreen
cScore=$cFuchsia
cScoreValue=$cCyan

sigRotate=25
sigLeft=26
sigRight=27
sigDown=28
sigAllDown=29
sigExit=30

box0=(0 0 0 1 1 0 1 1)
box1=(0 2 1 2 2 2 3 2 1 0 1 1 1 2 1 3)
box2=(0 0 0 1 1 1 1 2 0 1 1 0 1 1 2 0)
box3=(0 1 0 2 1 0 1 1 0 0 1 0 1 1 2 1)
box4=(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=(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=(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=(${box0[@]} ${box1[@]} ${box2[@]} ${box3[@]} ${box4[@]} ${box5[@]} ${box6[@]})
countBox=(1 2 2 2 4 4 4)
offsetBox=(0 1 3 5 7 11 15)

iScoreEachLevel=50 #be greater than 7

sig=0
iScore=0
iLevel=0
boxNew=()
cBoxNew=0
iBoxNewType=0
iBoxNewRotate=0
boxCur=()
cBoxCur=0
iBoxCurType=0
iBoxCurRotate=0
boxCurX=-1
boxCurY=-1
iMap=()

for ((i = 0; i < iTrayHeight * iTrayWidth; i++)); do iMap[$i]=-1; done


function RunAsKeyReceiver()
{
local pidDisplayer 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 TERM
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 == "D" ]]; then sig=$sigLeft
elif [[ $key == "C" ]]; then sig=$sigRight
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
}


function MyExitNoSub()
{
local y


stty $sTTY
((y = iTop + iTrayHeight + 4))


echo -e "\033[?25h\033[${y};0H"
exit
}


function MyExit()
{

kill -$sigExit $pidDisplayer

MyExitNoSub
}



function 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 ((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
#kill -$sigDown $$
BoxDown
done
}



function BoxMove()
{
local j i x y xTest yTest
yTest=$1
xTest=$2
for ((j = 0; j < 8; j += 2))
do
((i = j + 1))
((y = ${boxCur[$j]} + yTest))
((x = ${boxCur[$i]} + xTest))
if (( y < 0 || y >= iTrayHeight || x < 0 || x >= iTrayWidth))
then

return 1
fi
if ((${iMap[y * iTrayWidth + x]} != -1 ))
then

return 1
fi
done
return 0;
}




function Box2Map()
{
local j i x y xp yp line


for ((j = 0; j < 8; j += 2))
do
((i = j + 1))
((y = ${boxCur[$j]} + boxCurY))
((x = ${boxCur[$i]} + boxCurX))
((i = y * iTrayWidth + x))
iMap[$i]=$cBoxCur
done


line=0
for ((j = 0; j < iTrayWidth * iTrayHeight; j += iTrayWidth))
do
for ((i = j + iTrayWidth - 1; i >= j; i--))
do
if ((${iMap[$i]} == -1)); then break; fi
done
if ((i >= j)); then continue; fi

((line++))
for ((i = j - 1; i >= 0; i--))
do
((x = i + iTrayWidth))
iMap[$x]=${iMap[$i]}
done
for ((i = 0; i < iTrayWidth; i++))
do
iMap[$i]=-1
done
done

if ((line == 0)); then return; fi


((x = iLeft + iTrayWidth * 2 + 7))
((y = iTop + 11))
((iScore += line * 2 - 1))

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 = iTop + 14))

echo -ne "\033[3${cScoreValue}m\033[${y};${x}H${iLevel} "
fi
fi
echo -ne "\033[0m"



for ((y = 0; y < iTrayHeight; y++))
do
((yp = y + iTrayTop + 1))
((xp = iTrayLeft + 1))
((i = y * iTrayWidth))
echo -ne "\033[${yp};${xp}H"
for ((x = 0; x < iTrayWidth; x++))
do
((j = i + x))
if ((${iMap[$j]} == -1))
then
echo -ne " "
else
echo -ne "\033[1m\033[7m\033[3${iMap[$j]}m\033[4${iMap[$j]}m[]\033[0m"
fi
done
done
}



function BoxDown()
{
local y s
((y = boxCurY + 1))
if BoxMove $y $boxCurX
then
s="`DrawCurBox 0`"
((boxCurY = y))
s="$s`DrawCurBox 1`"
echo -ne $s
else

Box2Map
RandomBox
fi
}


function BoxLeft()
{
local x s
((x = boxCurX - 1))
if BoxMove $boxCurY $x
then
s=`DrawCurBox 0`
((boxCurX = x))
s=$s`DrawCurBox 1`
echo -ne $s
fi
}


function BoxRight()
{
local x s
((x = boxCurX + 1))
if BoxMove $boxCurY $x
then
s=`DrawCurBox 0`
((boxCurX = x))
s=$s`DrawCurBox 1`
echo -ne $s
fi
}



function BoxAllDown()
{
local k j i x y iDown s
iDown=$iTrayHeight


for ((j = 0; j < 8; j += 2))
do
((i = j + 1))
((y = ${boxCur[$j]} + boxCurY))
((x = ${boxCur[$i]} + boxCurX))
for ((k = y + 1; k < iTrayHeight; k++))
do
((i = k * iTrayWidth + x))
if (( ${iMap[$i]} != -1)); then break; fi
done
((k -= y + 1))
if (( $iDown > $k )); then iDown=$k; fi
done

s=`DrawCurBox 0`
((boxCurY += iDown))
s=$s`DrawCurBox 1`
echo -ne $s
Box2Map
RandomBox
}



function BoxRotate()
{
local iCount iTestRotate boxTest j i s
iCount=${countBox[$iBoxCurType]}


((iTestRotate = iBoxCurRotate + 1))
if ((iTestRotate >= iCount))
then
((iTestRotate = 0))
fi


for ((j = 0, i = (${offsetBox[$iBoxCurType]} + $iTestRotate) * 8; j < 8; j++, i++))
do
boxTest[$j]=${boxCur[$j]}
boxCur[$j]=${box[$i]}
done

if BoxMove $boxCurY $boxCurX
then

for ((j = 0; j < 8; j++))
do
boxCur[$j]=${boxTest[$j]}
done
s=`DrawCurBox 0`


for ((j = 0, i = (${offsetBox[$iBoxCurType]} + $iTestRotate) * 8; j < 8; j++, i++))
do
boxCur[$j]=${box[$i]}
done
s=$s`DrawCurBox 1`
echo -ne $s
iBoxCurRotate=$iTestRotate
else

for ((j = 0; j < 8; j++))
do
boxCur[$j]=${boxTest[$j]}
done
fi
}



function DrawCurBox()
{
local i j t bDraw sBox s
bDraw=$1

s=""
if (( bDraw == 0 ))
then
sBox="\040\040"
else
sBox="[]"
s=$s"\033[1m\033[7m\033[3${cBoxCur}m\033[4${cBoxCur}m"
fi

for ((j = 0; j < 8; j += 2))
do
((i = iTrayTop + 1 + ${boxCur[$j]} + boxCurY))
((t = iTrayLeft + 1 + 2 * (boxCurX + ${boxCur[$j + 1]})))

s=$s"\033[${i};${t}H${sBox}"
done
s=$s"\033[0m"
echo -n $s
}



function RandomBox()
{
local i j t


iBoxCurType=${iBoxNewType}
iBoxCurRotate=${iBoxNewRotate}
cBoxCur=${cBoxNew}
for ((j = 0; j < ${#boxNew[@]}; j++))
do
boxCur[$j]=${boxNew[$j]}
done



if (( ${#boxCur[@]} == 8 ))
then

for ((j = 0, t = 4; j < 8; j += 2))
do
if ((${boxCur[$j]} < t)); then t=${boxCur[$j]}; fi
done
((boxCurY = -t))
for ((j = 1, i = -4, t = 20; j < 8; j += 2))
do
if ((${boxCur[$j]} > i)); then i=${boxCur[$j]}; fi
if ((${boxCur[$j]} < t)); then t=${boxCur[$j]}; fi
done
((boxCurX = (iTrayWidth - 1 - i - t) / 2))


echo -ne `DrawCurBox 1`


if ! BoxMove $boxCurY $boxCurX
then
kill -$sigExit ${PPID}
ShowExit
fi
fi




for ((j = 0; j < 4; j++))
do
((i = iTop + 1 + j))
((t = iLeft + 2 * iTrayWidth + 7))
echo -ne "\033[${i};${t}H "
done


((iBoxNewType = RANDOM % ${#offsetBox[@]}))
((iBoxNewRotate = RANDOM % ${countBox[$iBoxNewType]}))
for ((j = 0, i = (${offsetBox[$iBoxNewType]} + $iBoxNewRotate) * 8; j < 8; j++, i++))
do
boxNew[$j]=${box[$i]};
done

((cBoxNew = ${colorTable[RANDOM % ${#colorTable[@]}]}))


echo -ne "\033[1m\033[7m\033[3${cBoxNew}m\033[4${cBoxNew}m"
for ((j = 0; j < 8; j += 2))
do
((i = iTop + 1 + ${boxNew[$j]}))
((t = iLeft + 2 * iTrayWidth + 7 + 2 * ${boxNew[$j + 1]}))
echo -ne "\033[${i};${t}H[]"
done
echo -ne "\033[0m"
}



function InitDraw()
{
clear
RandomBox
RandomBox
local i t1 t2 t3


echo -ne "\033[1m Tetris by LinuxScriptsHub"
echo -ne "\033[1m"
echo -ne "\033[3${cBorder}m\033[4${cBorder}m"

((t2 = iLeft + 1))
((t3 = iLeft + iTrayWidth * 2 + 3))
for ((i = 0; i < iTrayHeight; i++))
do
((t1 = i + iTop + 2))
echo -ne "\033[${t1};${t2}H||"
echo -ne "\033[${t1};${t3}H||"
done

((t2 = iTop + iTrayHeight + 2))
for ((i = 0; i < iTrayWidth + 2; i++))
do
((t1 = i * 2 + iLeft + 1))
echo -ne "\033[${iTrayTop};${t1}H=="
echo -ne "\033[${t2};${t1}H=="
done
echo -ne "\033[0m"



echo -ne "\033[1m"
((t1 = iLeft + iTrayWidth * 2 + 7))
((t2 = iTop + 10))
echo -ne "\033[3${cScore}m\033[${t2};${t1}HScore"
((t2 = iTop + 11))
echo -ne "\033[3${cScoreValue}m\033[${t2};${t1}H${iScore}"
((t2 = iTop + 13))
echo -ne "\033[3${cScore}m\033[${t2};${t1}HLevel"
((t2 = iTop + 14))
echo -ne "\033[3${cScoreValue}m\033[${t2};${t1}H${iLevel}"
echo -ne "\033[0m"
}



function ShowExit()
{
local y
((y = iTrayHeight + iTrayTop + 3))
echo -e "\033[${y};0HGameOver!\033[0m"
exit
}



function Usage
{
cat << EOF
Usage: $APP_NAME
Start tetris game. (linuxscriptshub.com)

-h, --help display this help and exit
--version output version information and exit
EOF
}



if [[ "$1" == "-h" || "$1" == "--help" ]]; then
Usage
elif [[ "$1" == "--version" ]]; then
echo "$APP_NAME $APP_VERSION"
elif [[ "$1" == "--show" ]]; then

RunAsDisplayer
else
bash $0 --show&
RunAsKeyReceiver $!
fi

Version commentée

Le script tétris en bash avec des commentaires :

#!/bin/bash

# Tetris game written in pure bash
#
# I tried to mimic as close as possible original tetris game
# which was implemented on old soviet DVK computers (PDP-11 clones)
#
# Videos of this tetris can be found here:
#
# http://www.youtube.com/watch?v=O0gAgQQHFcQ
# http://www.youtube.com/watch?v=iIQc1F3UuV4
#
# This script was created on ubuntu 13.04 x64 and bash 4.2.45(1)-release.
# It was not tested on other unix like operating systems.
#
# Enjoy :-)!
#
# Author: Kirill Timofeev <[email protected]>

set -u # non initialized variable is an error

# 2 signals are used: SIGUSR1 to decrease delay after level up and SIGUSR2 to quit
# they are sent to all instances of this script
# because of that we should process them in each instance
# in this instance we are ignoring both signals
trap '' SIGUSR1 SIGUSR2

# Those are commands sent to controller by key press processing code
# In controller they are used as index to retrieve actual functuon from array
QUIT=0
RIGHT=1
LEFT=2
ROTATE=3
DOWN=4
DROP=5
TOGGLE_HELP=6
TOGGLE_NEXT=7
TOGGLE_COLOR=8

DELAY=1 # initial delay between piece movements
DELAY_FACTOR=0.8 # this value controld delay decrease for each level up

# color codes
RED=1
GREEN=2
YELLOW=3
BLUE=4
FUCHSIA=5
CYAN=6
WHITE=7

# Location and size of playfield, color of border
PLAYFIELD_W=10
PLAYFIELD_H=20
PLAYFIELD_X=30
PLAYFIELD_Y=1
BORDER_COLOR=$YELLOW

# Location and color of score information
SCORE_X=1
SCORE_Y=2
SCORE_COLOR=$GREEN

# Location and color of help information
HELP_X=58
HELP_Y=1
HELP_COLOR=$CYAN

# Next piece location
NEXT_X=14
NEXT_Y=11

# Location of "game over" in the end of the game
GAMEOVER_X=1
GAMEOVER_Y=$((PLAYFIELD_H + 3))

# Intervals after which game level (and game speed) is increased
LEVEL_UP=20

colors=($RED $GREEN $YELLOW $BLUE $FUCHSIA $CYAN $WHITE)

no_color=true # do we use color or not
showtime=true # controller runs while this flag is true
empty_cell=" ." # how we draw empty cell
filled_cell="[]" # how we draw filled cell

score=0 # score variable initialization
level=1 # level variable initialization
lines_completed=0 # completed lines counter initialization

# screen_buffer is variable, that accumulates all screen changes
# this variable is printed in controller once per game cycle
puts() {
screen_buffer+=${1}
}

# move cursor to (x,y) and print string
# (1,1) is upper left corner of the screen
xyprint() {
puts "\033[${2};${1}H${3}"
}

show_cursor() {
echo -ne "\033[?25h"
}

hide_cursor() {
echo -ne "\033[?25l"
}

# foreground color
set_fg() {
$no_color && return
puts "\033[3${1}m"
}

# background color
set_bg() {
$no_color && return
puts "\033[4${1}m"
}

reset_colors() {
puts "\033[0m"
}

set_bold() {
puts "\033[1m"
}

# playfield is 1-dimensional array, data is stored as follows:
# [ a11, a21, ... aX1, a12, a22, ... aX2, ... a1Y, a2Y, ... aXY]
# |< 1st line >| |< 2nd line >| ... |< last line >|
# X is PLAYFIELD_W, Y is PLAYFIELD_H
# each array element contains cell color value or -1 if cell is empty
redraw_playfield() {
local j i x y xp yp

((xp = PLAYFIELD_X))
for ((y = 0; y < PLAYFIELD_H; y++)) {
((yp = y + PLAYFIELD_Y))
((i = y * PLAYFIELD_W))
xyprint $xp $yp ""
for ((x = 0; x < PLAYFIELD_W; x++)) {
((j = i + x))
if ((${play_field[$j]} == -1)) ; then
puts "$empty_cell"
else
set_fg ${play_field[$j]}
set_bg ${play_field[$j]}
puts "$filled_cell"
reset_colors
fi
}
}
}

update_score() {
# Arguments: 1 - number of completed lines
((lines_completed += $1))
# Unfortunately I don't know scoring algorithm of original tetris
# Here score is incremented with squared number of lines completed
# this seems reasonable since it takes more efforts to complete several lines at once
((score += ($1 * $1)))
if (( score > LEVEL_UP * level)) ; then # if level should be increased
((level++)) # increment level
pkill -SIGUSR1 -f "/bin/bash $0" # and send SIGUSR1 signal to all instances of this script (please see ticker for more details)
fi
set_bold
set_fg $SCORE_COLOR
xyprint $SCORE_X $SCORE_Y "Lines completed: $lines_completed"
xyprint $SCORE_X $((SCORE_Y + 1)) "Level: $level"
xyprint $SCORE_X $((SCORE_Y + 2)) "Score: $score"
reset_colors
}

help=(
" Use cursor keys"
" or"
" s: up"
"a: left, d: right"
" space: drop"
" q: quit"
" c: toggle color"
"n: toggle show next"
"h: toggle this help"
)

help_on=-1 # if this flag is 1 help is shown

toggle_help() {
local i s

set_bold
set_fg $HELP_COLOR
for ((i = 0; i < ${#help[@]}; i++ )) {
# ternary assignment: if help_on is 1 use string as is, otherwise substitute all characters with spaces
((help_on == 1)) && s="${help[i]}" || s="${help[i]//?/ }"
xyprint $HELP_X $((HELP_Y + i)) "$s"
}
((help_on = -help_on))
reset_colors
}

# this array holds all possible pieces that can be used in the game
# each piece consists of 4 cells
# each string is sequence of relative xy coordinates for different orientations
# depending on piece symmetry there can be 1, 2 or 4 orientations
piece=(
"00011011" # square piece
"0212223210111213" # line piece
"0001111201101120" # S piece
"0102101100101121" # Z piece
"01021121101112220111202100101112" # L piece
"01112122101112200001112102101112" # inverted L piece
"01111221101112210110112101101112" # T piece
)

draw_piece() {
# Arguments:
# 1 - x, 2 - y, 3 - type, 4 - rotation, 5 - cell content
local i x y

# loop through piece cells: 4 cells, each has 2 coordinates
for ((i = 0; i < 8; i += 2)) {
# relative coordinates are retrieved based on orientation and added to absolute coordinates
((x = $1 + ${piece[$3]:$((i + $4 * 8 + 1)):1} * 2))
((y = $2 + ${piece[$3]:$((i + $4 * 8)):1}))
xyprint $x $y "$5"
}
}

next_piece=0
next_piece_rotation=0
next_piece_color=0

next_on=1 # if this flag is 1 next piece is shown

draw_next() {
# Arguments: 1 - string to draw single cell
((next_on == -1)) && return
draw_piece $NEXT_X $NEXT_Y $next_piece $next_piece_rotation "$1"
}

clear_next() {
draw_next "${filled_cell//?/ }"
}

show_next() {
set_fg $next_piece_color
set_bg $next_piece_color
draw_next "${filled_cell}"
reset_colors
}

toggle_next() {
case $next_on in
1) clear_next; next_on=-1 ;;
-1) next_on=1; show_next ;;
esac
}

draw_current() {
# Arguments: 1 - string to draw single cell
# factor 2 for x because each cell is 2 characters wide
draw_piece $((current_piece_x * 2 + PLAYFIELD_X)) $((current_piece_y + PLAYFIELD_Y)) $current_piece $current_piece_rotation "$1"
}

show_current() {
set_fg $current_piece_color
set_bg $current_piece_color
draw_current "${filled_cell}"
reset_colors
}

clear_current() {
draw_current "${empty_cell}"
}

new_piece_location_ok() {
# Arguments: 1 - new x coordinate of the piece, 2 - new y coordinate of the piece
# test if piece can be moved to new location
local j i x y x_test=$1 y_test=$2

for ((j = 0, i = 1; j < 8; j += 2, i = j + 1)) {
((y = ${piece[$current_piece]:$((j + current_piece_rotation * 8)):1} + y_test)) # new y coordinate of piece cell
((x = ${piece[$current_piece]:$((i + current_piece_rotation * 8)):1} + x_test)) # new x coordinate of piece cell
((y < 0 || y >= PLAYFIELD_H || x < 0 || x >= PLAYFIELD_W )) && return 1 # check if we are out of the play field
((${play_field[y * PLAYFIELD_W + x]} != -1 )) && return 1 # check if location is already ocupied
}
return 0
}

get_random_next() {
# next piece becomes current
current_piece=$next_piece
current_piece_rotation=$next_piece_rotation
current_piece_color=$next_piece_color
# place current at the top of play field, approximately at the center
((current_piece_x = (PLAYFIELD_W - 4) / 2))
((current_piece_y = 0))
# check if piece can be placed at this location, if not - game over
new_piece_location_ok $current_piece_x $current_piece_y || cmd_quit
show_current

clear_next
# now let's get next piece
((next_piece = RANDOM % ${#piece[@]}))
((next_piece_rotation = RANDOM % (${#piece[$next_piece]} / 8)))
((next_piece_color = RANDOM % ${#colors[@]}))
show_next
}

draw_border() {
local i x1 x2 y

set_bold
set_fg $BORDER_COLOR
((x1 = PLAYFIELD_X - 2)) # 2 here is because border is 2 characters thick
((x2 = PLAYFIELD_X + PLAYFIELD_W * 2)) # 2 here is because each cell on play field is 2 characters wide
for ((i = 0; i < PLAYFIELD_H + 1; i++)) {
((y = i + PLAYFIELD_Y))
xyprint $x1 $y "<|"
xyprint $x2 $y "|>"
}

((y = PLAYFIELD_Y + PLAYFIELD_H))
for ((i = 0; i < PLAYFIELD_W; i++)) {
((x1 = i * 2 + PLAYFIELD_X)) # 2 here is because each cell on play field is 2 characters wide
xyprint $x1 $y '=='
xyprint $x1 $((y + 1)) "\/"
}
reset_colors
}

toggle_color() {
$no_color && no_color=false || no_color=true
show_next
update_score 0
toggle_help
toggle_help
draw_border
redraw_playfield
show_current
}

init() {
local i x1 x2 y

# playfield is initialized with -1s (empty cells)
for ((i = 0; i < PLAYFIELD_H * PLAYFIELD_W; i++)) {
play_field[$i]=-1
}

clear
hide_cursor
get_random_next
get_random_next
toggle_color
}

# this function runs in separate process
# it sends DOWN commands to controller with appropriate delay
ticker() {
# on SIGUSR2 this process should exit
trap exit SIGUSR2
# on SIGUSR1 delay should be decreased, this happens during level ups
trap 'DELAY=$(awk "BEGIN {print $DELAY * $DELAY_FACTOR}")' SIGUSR1

while true ; do echo -n $DOWN; sleep $DELAY; done
}

# this function processes keyboard input
reader() {
trap exit SIGUSR2 # this process exits on SIGUSR2
trap '' SIGUSR1 # SIGUSR1 is ignored
local -u key a='' b='' cmd esc_ch=$'\x1b'
# commands is associative array, which maps pressed keys to commands, sent to controller
declare -A commands=([A]=$ROTATE [C]=$RIGHT [D]=$LEFT
[_S]=$ROTATE [_A]=$LEFT [_D]=$RIGHT
[_]=$DROP [_Q]=$QUIT [_H]=$TOGGLE_HELP [_N]=$TOGGLE_NEXT [_C]=$TOGGLE_COLOR)

while read -s -n 1 key ; do
case "$a$b$key" in
"${esc_ch}["[ACD]) cmd=${commands[$key]} ;; # cursor key
*${esc_ch}${esc_ch}) cmd=$QUIT ;; # exit on 2 escapes
*) cmd=${commands[_$key]:-} ;; # regular key. If space was pressed $key is empty
esac
a=$b # preserve previous keys
b=$key
[ -n "$cmd" ] && echo -n "$cmd"
done
}

# this function updates occupied cells in play_field array after piece is dropped
flatten_playfield() {
local i j k x y
for ((i = 0, j = 1; i < 8; i += 2, j += 2)) {
((y = ${piece[$current_piece]:$((i + current_piece_rotation * 8)):1} + current_piece_y))
((x = ${piece[$current_piece]:$((j + current_piece_rotation * 8)):1} + current_piece_x))
((k = y * PLAYFIELD_W + x))
play_field[$k]=$current_piece_color
}
}

# this function goes through play_field array and eliminates lines without empty sells
process_complete_lines() {
local j i complete_lines
((complete_lines = 0))
for ((j = 0; j < PLAYFIELD_W * PLAYFIELD_H; j += PLAYFIELD_W)) {
for ((i = j + PLAYFIELD_W - 1; i >= j; i--)) {
((${play_field[$i]} == -1)) && break # empty cell found
}
((i >= j)) && continue # previous loop was interrupted because empty cell was found
((complete_lines++))
# move lines down
for ((i = j - 1; i >= 0; i--)) {
play_field[$((i + PLAYFIELD_W))]=${play_field[$i]}
}
# mark cells as free
for ((i = 0; i < PLAYFIELD_W; i++)) {
play_field[$i]=-1
}
}
return $complete_lines
}

process_fallen_piece() {
flatten_playfield
process_complete_lines && return
update_score $?
redraw_playfield
}

move_piece() {
# arguments: 1 - new x coordinate, 2 - new y coordinate
# moves the piece to the new location if possible
if new_piece_location_ok $1 $2 ; then # if new location is ok
clear_current # let's wipe out piece current location
current_piece_x=$1 # update x ...
current_piece_y=$2 # ... and y of new location
show_current # and draw piece in new location
return 0 # nothing more to do here
fi # if we could not move piece to new location
(($2 == current_piece_y)) && return 0 # and this was not horizontal move
process_fallen_piece # let's finalize this piece
get_random_next # and start the new one
return 1
}

cmd_right() {
move_piece $((current_piece_x + 1)) $current_piece_y
}

cmd_left() {
move_piece $((current_piece_x - 1)) $current_piece_y
}

cmd_rotate() {
local available_rotations old_rotation new_rotation

available_rotations=$((${#piece[$current_piece]} / 8)) # number of orientations for this piece
old_rotation=$current_piece_rotation # preserve current orientation
new_rotation=$(((old_rotation + 1) % available_rotations)) # calculate new orientation
current_piece_rotation=$new_rotation # set orientation to new
if new_piece_location_ok $current_piece_x $current_piece_y ; then # check if new orientation is ok
current_piece_rotation=$old_rotation # if yes - restore old orientation
clear_current # clear piece image
current_piece_rotation=$new_rotation # set new orientation
show_current # draw piece with new orientation
else # if new orientation is not ok
current_piece_rotation=$old_rotation # restore old orientation
fi
}

cmd_down() {
move_piece $current_piece_x $((current_piece_y + 1))
}

cmd_drop() {
# move piece all way down
# this is example of do..while loop in bash
# loop body is empty
# loop condition is done at least once
# loop runs until loop condition would return non zero exit code
while move_piece $current_piece_x $((current_piece_y + 1)) ; do : ; done
}

cmd_quit() {
showtime=false # let's stop controller ...
pkill -SIGUSR2 -f "/bin/bash $0" # ... send SIGUSR2 to all script instances to stop forked processes ...
xyprint $GAMEOVER_X $GAMEOVER_Y "Game over!"
echo -e "$screen_buffer" # ... and print final message
}

controller() {
# SIGUSR1 and SIGUSR2 are ignored
trap '' SIGUSR1 SIGUSR2
local cmd commands

# initialization of commands array with appropriate functions
commands[$QUIT]=cmd_quit
commands[$RIGHT]=cmd_right
commands[$LEFT]=cmd_left
commands[$ROTATE]=cmd_rotate
commands[$DOWN]=cmd_down
commands[$DROP]=cmd_drop
commands[$TOGGLE_HELP]=toggle_help
commands[$TOGGLE_NEXT]=toggle_next
commands[$TOGGLE_COLOR]=toggle_color

init

while $showtime; do # run while showtime variable is true, it is changed to false in cmd_quit function
echo -ne "$screen_buffer" # output screen buffer ...
screen_buffer="" # ... and reset it
read -s -n 1 cmd # read next command from stdout
${commands[$cmd]} # run command
done
}

stty_g=`stty -g` # let's save terminal state

# output of ticker and reader is joined and piped into controller
(
ticker & # ticker runs as separate process
reader
)|(
controller
)

show_cursor
stty $stty_g # let's restore terminal state

Documentation

https://linuxscriptshub.com/create-a-tetris-games-on-linux-with-shell-scripts-scripts-ready/
https://www.youtube.com/watch?v=rj88ggt-YqQ

https://github.com/dkorolev/bash-tetris/blob/master/tetris.sh

> Partager <