Играючи BASH’им дальше

в 17:33, , рубрики: bash, game, scroller, КодоБред

Первая статья И. BASH'им в начало

image
Вдохновившись отзывами на первую статью я продолжил разработку piu-piu. В игре появилось интроменю, реализовано посимвольное появление объектов, изменилось цветовое решение. Палитра теперь определяется по времени года, правда, из-за недостатка цветов пришлось ограничиться 3-мя вариантами: зима — начало весны, весна — лето и осень. Изменения можно оценить, скачав игру тут. Далее немного букв как это все получилось.

Гифка с новым геймплеем
image

Я начал с реализации посимвольного вывода объектов. Что может быть проще? Просто сделай срез элемента спрайта, но…
image

Управляющие коды (цвет) срежутся, и вместо цветного символа получится каша. Необходимо пересобрать элемент спрайта посимвольно, вставляя нужные управляющие коды перед каждым символом. Поприсядав изрядное количество времени со срезами я, наконец, разработал похожий алгоритм. Тестовый сценарий:

#!/bin/bash

# подключаю свою табличку с красками
. ~/SCR/color

# тестовый спрайт
sprite=(
	'/¯¯¯¯¯'
	'|  0  |'
	'_____/'
)

# расцветка тестового спрайта
sprite_color=(
	"$RED"
	"$GRN"
	"$BLU"
)

# уже знакомые функции выхода и определения размеров
function bye () {
	stty echo
	printf "${CON}${DEF}"
	clear
	ls --color=auto
	exit
}

function get_dimensions {
	size=($(stty size))
	endx=${size[1]}
	endy=${size[0]}
}

# инициализация
get_dimensions
X=$endx
Y=$[$endy/2]
sn=${#sprite[@]} # количество элементов спрайта
sl=${#sprite[0]} # ширина спрайта, считаетсяя по наибольшему элементу

trap bye INT; stty -echo; printf "${COF}"; clear

# цикл
while true; do sleep 0.1; get_dimensions

	for (( p=0; p<${sn}; p++ )); do # цикл по элементам спрайта

		end=$[1-(${X}-${endx})]
		if [ $X -gt 0 ]; then
			bgn=0
			XY ${X} $(($Y + $p)) 
			#      цвет         |   срез спрайта
			"${sprite_color[$p]}${sprite[$p]:${bgn}:${end}} ${DEF}"
		else
			bgn=$[1-$X]
			XY 1 $(($Y + $p)) 
			"${sprite_color[$p]}${sprite[$p]:${bgn}:${end}} ${DEF}"
		fi

	done

	((X--)); [ $X -lt -${sl} ] && X=$endx

done

Получилось так:
image

Но этот алгоритм позволяет «красить» только строки целиком, это не было пределом мечтаний и не соответствовало существующей раскраске спрайтов. Пришлось поприседать еще. Таблица цветов расширилась, для каждого символа определен свой цвет. Каждый элемент массива цветов преобразуется в отдельный массив, индексы которого совпадают с индексами элемента спрайта. Затем элемент цвета и элемент спрайта рисуются в нужном месте и в нужное время.

#!/bin/bash

. ~/SCR/color

# новый тестовый спрайт
sprite=(
	'     /¯¯¯¯¯ '
	'-----|12345|-------- '
	'   --|12345|--- '
	' ----|12345|---- '
	'     _____/ '
)

# расцветка тестового спрайта, свой цвет для каждого символа
C01=${UND}${BLU}; C02=${UND}${BLD}${BLU} C03=${DEF}${MGN}
sprite_color=(
"$DEF $DEF $DEF $DEF $DEF $DEF $DEF"
"$DEF $RED $GRN $C01 $C03 $YLW $DEF $RED $DEF $DEF $DEF $BLU $DEF"
"$DEF $RED $GRN $C02 $C03 $YLW $DEF $DEF $GRN $DEF $DEF $DEF $DEF"
"$DEF $RED $GRN $C01 $C03 $YLW $DEF $DEF $DEF $BLU $DEF $DEF $DEF"
"$DEF $DEF $DEF $DEF $DEF $DEF $DEF"
)

function bye () {
	stty echo
	printf "${CON}${DEF}"
	clear
	ls --color=auto
	exit
}

function get_dimensions {
	size=($(stty size))
	endx=${size[1]}
	endy=${size[0]}
}

get_dimensions
X=$endx
Y=$[$endy/2]
L=1
sn=${#sprite[@]}
sl=${#sprite[1]}

trap bye INT; stty -echo; printf "${COF}"; clear

while true; do sleep 0.1; get_dimensions

	((L++)); ((X--)); [ $X -lt -${sl} ] && { X=$endx; L=1; }

	for (( p=0; p<${sn}; p++ )); do

		color=(${sprite_color[p]}); YY=$[$Y+${p}]

		# вложенные циклы по символам элементов спрайта
		if [ $X -gt 1 ]; then
			for (( k=0; k<${L}; k++ )); do
				XY $[${k}+${X}] ${YY} 
				"${color[$k]}${sprite[$p]:$k:1}"
			done
		else
			for (( k=1; k<${sl}; k++ )); do
				XY ${k} ${YY} 
				"${color[$[$k-$X]]}${sprite[$p]:$[$k-$X]}"
			done
		fi
	done
done

Теперь все символы могут быть разноцветными:
image

Отлично! Я решил проверить как это будет рисоваться поверх другого спрайта. Добавил статический спрайт в основной цикл:

for (( p=0; p<${sn}; p++ )); do
	XY 3 $[${endy}/2+${p}] "${sprite[$p]}"
done

И проверил:
image

Нормально, но если сзади «маска» получается сама собой, то передняя часть рисуется квадратом, затирая все, опять присядания.

#!/bin/bash

. ~/SCR/color

# у спрайта появились индексы смещения
sprite=(
	5'      /¯¯¯¯¯ '
	0' -----|12345|-------- '
	3'    --|12345|--- '
	1'  ----|12345|---- '
	5'      _____/ '
)

# немного изменил таблицу расцветки
sprite_color=( 
"${DEF}"
"${DEF} ${DEF} ${DEF} ${DEF} ${DEF} ${DEF} ${DEF} ${MGN} ${YLW} ${grn} ${RED} ${MGN} ${DEF}"
"${DEF} ${DEF} ${DEF} ${DEF} ${DEF} ${DEF} ${DEF} ${BLU} ${GRN} ${cyn} ${ylw} ${blu} ${DEF}"
"${DEF} ${DEF} ${DEF} ${DEF} ${DEF} ${DEF} ${DEF} ${YLW} ${RED} ${BLU} ${grn} ${YLW} ${DEF}"
"${DEF}"
)

function bye {
	stty echo
	printf "${CON}${DEF}"
	clear
	ls --color=auto
	exit
}

function get_dimensions {
	size=($(stty size))
	endx=${size[1]}
	endy=${size[0]}
}

get_dimensions
X=$endx
Y=$[$endy/2]
L=1
sn=${#sprite[@]}
sl=${#sprite[1]}

trap bye INT; stty -echo; printf "${COF}"; clear

while true; do sleep 0.1; get_dimensions

	# фоновый спрайт
	for (( p=0; p<${sn}; p++ )); do
		XY 3 $[${endy}/2+${p}] "${sprite[$p]}"
	done

	# увеличиваю циферки
	((L++)); ((X--))
	[ $X -lt -${sl} ] && { X=$endx; L=1; }

	for (( p=0; p<${sn}; p++ )); do

		# определяю цвет
		color=(${sprite_color[p]})
		# координату Y
		YY=$[$Y+${p}]
		# смещение начала
		stp=${sprite[$p]:0:1}
		# срез спрайта
		spr=${sprite[$p]:1}

		if [ $X -gt 1 ]; then
			for (( k=0; k<${L};  k++ )); do
				if [ $k -ge $stp ]; then
					XY $[${k}+${X}] ${YY} 
					"${DEF}${color[$k]}${spr:$k:1}"
				fi
			done
		else
			for (( k=1; k<${sl}; k++ )); do
				XY ${k} ${YY} 
				"${color[$[$k-$X]]}${spr:$[$k-$X]}"
			done
		fi
	done
done

Красота!
image

Небольшое лирическое отступление

Механика посимвольного вывода построена на срезах. Срезы вообще очень удобны. Их можно использовать для всяких прикольных штук при работе с текстом.
Баш позволяет делать срезы как переменных, так и массивов. Пример, срез переменной:

a=1234567890

# отрезали 3 символа вначале
echo ${a:3}
4567890

# вырезали 3 символа из тела
echo ${a:3:3}
456

# можно указывать отрицательное значение второго элемента среза, это подрежет конец
echo ${a:(-4)}
123456

echo ${a:(-5):(-2)}
678

А теперь срез массива:

a=( 1 2 3 4 5 6 7 8 9 0 )

# отрезали 3 элемента вначале
echo ${a[*]:3}
4 5 6 7 8 9 0

# вырезали 3 символа из тела
echo ${a[*]:3:3}

# срез с конца
echo ${a[@]:(-5)}
6 7 8 9 0

# а так почему-то нельзя
echo ${a[@]:(-5):(-1)}
bash: (-1): выражение подстроки < 0 

Помню, во времена Спектрума ты не мог называться крутым кодером, если не сделал крутой скроллер. И все крутые парни делали их(мы тоже). Во всех демках были скроллеры с бесконечными гритингсами, матами, шутками-прибаутками. Хорошее было время. Попробуем запилить скроллер на BASH'е, используя срезы конечно:

#!/bin/bash

# подключаю палитру и функцию рисования XY
. ~/SCR/color

# текст, можно задать параметром
text=${1:-'Hello Wo00000o0000000o000orld! '}; N=${#text}

# опять эти функции
function bye {
	stty echo
	printf "${CON}${DEF}"
	clear
	ls --color=auto
	exit
}

function get_dimensions {
	size=($(stty  size))
	endx=${size[1]}
	endy=${size[0]}
}

# инициализация
get_dimensions
trap bye INT
stty -echo
printf "${COF}"
X=$[$endx+1]
Y=$[$endy/2]
L=0; clear

# цикл
while true; do sleep 0.05; get_dimensions

	[ $X -gt   1   ] 
		&& XY $X $Y "${text:0:$L}" 
		|| XY  1 $Y "${text:$[1-$X]:$L}"

	[ $X -lt  -$N  ] && { X=$endx; L=0; } || ((X--))
	[ $L -lt $endx ] && ((L++))

done

Привет мир!
image

А если прикрутить сюда figlet, получится вообще хорошо:

#!/bin/bash

. ~/SCR/color

text=${1:-'Hello Wo00000o0000000o000orld! '}; N=${#text}

function bye {
	stty echo
	printf "${CON}${DEF}"
	clear
	ls --color=auto
	exit
}

function get_dimensions {
	size=($(stty  size))
	endx=${size[1]}
	endy=${size[0]}
}

get_dimensions
trap bye INT
stty -echo
printf "${COF}"
IFSOLD=$IFS
IFS=$'n'

# добавился фиглет
figlet_text=( $(figlet -w$[$N*10] "${text}") )
IFS=$IFSOLD
NF=${#figlet_text[1]}
NFL=${#figlet_text[*]}
X=$[$endx+1]
Y=$[$endy/2-$NFL/2]
L=0; clear

while true; do sleep 0.05; get_dimensions

	if [ $X -gt 1 ]; then
		# цикл по элементам фиглетового текста
		for ((i=0; i<$NFL; i++)); do
			XY $X $[$Y+$i] "${figlet_text[$i]:0:$L}"
		done
	else
		for ((i=0; i<$NFL; i++)); do
			XY 1  $[$Y+$i] "${figlet_text[$i]:$[1-$X]:$L}"
		done
	fi

	[ $X -lt  -$NF ] && { X=$endx; L=0; } || ((X--))
	[ $L -lt $endx ] && ((L++))

done

Тут есть одна тонкость. Я переопределяю символ «разделитель». По умолчанию разделителями являются: пробел, таб, переход строки. Все это хранится в системной переменной IFS. Я запоминаю старое значение:

IFSOLD=$IFS

Затем устанавливю разделителем только переход строки n:

IFS=$'n'

Офиглеваю текст в массив figlet_text командой:

figlet_text=( $(figlet -w$[$N*10] "${text}") )

Ключ -w задает фиглету ширину строки, т.к. по умолчанию он вписывает текст в экран. А это добавляет ненужные переходы строки, и чуда не происходит. Ну и возвращаю старое значение разделителя:

IFS=$IFSOLD

Фиглетовый скроллер!
image

Итак, спрайты режутся, скорей впихивай этот алгоритм в игру! Я попробовал и о… очень сильно расстроился. Скорость, мягко говоря, оставляла желать лучшего:
image

Множественные вложенные циклы отрицательно сказываются на быстродействии (внезапно). А ведь я пробовал без деревьев, облаков и прочей мишуры, мда.
Но после такого количества присяданий не хотелось выбрасывать идею на помойку. Моск начал работать. Решение было найдено. Использовать оба метода! Юху! Появлениеисчезновение посимвольно а полет по экрану «быстрым» методом. Цикл объектов теперь выглядит так:

NO=${#OBJ[@]}
for (( i=0; i<$NO; i++ )); do

	OI=(${OBJ[$i]})
	OX=${OI[0]}
	OY=${OI[1]}
	cuter=${OI[2]}
	type=${OI[3]}

	case $type in

		# объекты с меняющимися спрайтами(быстрыймедленный)

		# дерево 1  медленный спрайт
		"tree1" )	sprite=("${tree1[@]}")

				# палитра медленного спрайта
				sprite_color=("${tree1_color[@]}")

				# быстрый спрайт
				sprite_fast=("${tree12[@]}")

			#           функция - двигатель
			#     +---------+------+------+------+
			#     | функция |таймер|высота|ширина|
			#     +---------+------+------+------+
				mover      $Q     4      4;;

		# дерево 2
		"tree2" )	sprite=("${tree2[@]}")
				sprite_color=("${tree2_color[@]}")
				sprite_fast=("${tree22[@]}")
				mover $W 6 6;;

		# дерево 3
		"tree3" )	sprite=("${tree3[@]}")
				sprite_color=("${tree3_color[@]}")
				sprite_fast=("${tree32[@]}")
				mover $E 9 10;;

		# облако 1
		"cloud1")	sprite=("${cloud1[@]}")
				sprite_color=("${cloud1_color[@]}")
				sprite_fast=("${cloud12[@]}")
				mover $Q 3 7;;

		# облако 2
		"cloud2")	sprite=("${cloud2[@]}")
				sprite_color=("${cloud2_color[@]}")
				sprite_fast=("${cloud22[@]}")
				mover $W 3 9;;

		# облако 3
		"cloud3")	sprite=("${cloud3[@]}")
				sprite_color=("${cloud3_color[@]}")
				sprite_fast=("${cloud32[@]}")
				mover $E 3 12;;

		# враги
		"alien" )	sprite=("${alien[@]}")
				sprite_color=("${alien_color[@]}")
				sprite_fast=("${alien2[@]}")
				mover 0 3 5;;

		# объекты с только быстрыми спрайтами

		# выстрел босса
		#           быстрый спрайт
		"bfire" )	sprite=("${bfire[@]}")

			#           функция - двигатель
			#     +---------+------+------+------+
			#     | функция |таймер|высота|ширина|
			#     +---------+------+------+------+
				mover      0      6      4;;

		# бонус - патроны
		"ammo"  )	sprite=("${ammob[@]}")
				mover 0 3 4;;

		# бонус - жизнь
		"life"  )	sprite=("${lifep[@]}")
				mover 0 3 4;;

		# бонус - усилитель ствола
		"gunup" )	sprite=("${gunup[@]}")
				mover 0 3 4;;

		# взрывы, не латают, рисуем 1 раз
		"boom"  )	er=${boomC}
			for part in "${boom[@]:$B:$boomC}"; do
				stp=${part:0:1}
				spr=${part:1}
				XY $[${OX} + $stp] ${OY} "${spr}"; ((OY++))
			done
			[ ${E} = 0 ] && { ((B+=${boomC}))
			[ $B -gt ${boomN} ] && { B=0; erase_obj ${i}; }; };;
esac; done

А вот функция mover:

function mover () {

	er=$2 		# кол-во линий спрайта
	width=$3	# ширина спрайта

	# плюсуем циферки
	[ ${1} = 0 ] && {
		((OX--))
		((cuter++))
		OBJ[$i]="$OX $OY $cuter $type"
	}

	# не улетел ли объект
	case ${type} in

		'alien'|'tree'[1-3]|'cloud'[1-3])
			[ $OX -lt -$width ] && {
				remove_obj ${i}
				case ${type} in
					"alien") ((enumber--));;
				esac; return
			};;
		*)
			[ $OX -lt 1 ] && {
				erase_obj ${i}
				case ${type} in
					"alien") ((enumber--));;
				esac; return; };;
	esac

	# рисовалка
	for (( p=0; p<${er}; p++ )); do

	case ${type} in

	# быстрыемедленные спрайты
	'alien'|'tree'[1-3]|'cloud'[1-3])
		color=(${sprite_color[$p]})
		YY=$[$OY+${p}]
		stp=${sprite[$p]:0:1}
		spr=${sprite[$p]:1}

		# прилетаетлетит
		if [ $OX -gt 1 ]; then

			if [ $cuter -lt $width ]; then

				# прилетает, посимвольный вывод
				for (( k=0; k<${cuter}; k++ )); do

				if [ $k -ge $stp ]; then
					XY $[$k+$OX] $YY 
					"${color[$k]}${spr:$k:1}"
				fi

				done

			else

				# летит, переключение на быстрый спрайт
				stp=${sprite_fast[$p]:0:1}
				spr=${sprite_fast[$p]:1}
				XY $[${OX} + $stp] $[$OY + $p] "${spr}"

			fi

		# улетает
		else

			# опять посимвольно
			for (( k=1; k<${width}; k++ )); do

				x=$[$k-$OX]
				XY $k $YY "${color[$x]}${spr:$x}"

			done

		fi;;

	# только быстрые спрайты
	*) XY ${OX} $[$OY + $p] "${sprite[$p]}";;

	esac

		# проверка коллизий
		case ${type} in

			"gunup" )
				case "$[$OY + $p] $OX" in
					"$HY $HX")
						[ ${G} -lt 4 ] && ((G++))
						erase_obj ${i}
						break;;
				esac;;

			"life"  )
				case "$[$OY + $p] $OX" in
					"$HY $HX")
						((life++))
						erase_obj ${i}
						break;;
				esac;;

			"ammo"  )
				case "$[$OY + $p] $OX" in
					"$HY $HX")
						((ammo+=100))
						erase_obj ${i}
						break;;
				esac;;

			"bfire" )
				case "$OY $OX" in
					"$HY $HX")
						((life--))
						erase_obj ${i}
						break;;
				esac;;

			"alien" )
				# столкновение с пулей
				for (( t=0; t<${NP}; t++ )); do

				case "$[$OY + 1] $[$OX + $p]" in

				"${PIU[$t]}") # есть бонус?
					if [ $[RANDOM % $rnd] -eq 0 ]; then

					OBJ+=("$OX $OY 0 ${bonuses[$[RANDOM % 
					${#bonuses[@]}]]}")

					((frags++))
					((enumber--))
					remove_obj ${i}
					remove_piu ${t}
					OBJ+=("${OX} ${OY} 0 boom")
					break

					fi;;

				esac

				done

				# столкновение с героем
				case "$[$OY + 1] $[$OX + $p]" in
					"$HY $HX")
						((life--))
						((frags++))
						((enumber--))
						remove_obj ${i}
						OBJ+=("${OX} ${OY} 0 boom")
						break;;
				esac;;
		esac
	done
}

Пришлось нарисовать по 2 комплекта спрайтов для обоих методов вывода. И ограничить количество одновременно вылетающих объектов. Для чужих я добавил в условие появления тайминг. А деревьямоблакам уменьшил вероятность появления. Вот какие спрайты получились, на примере дерева:

# "медленный" спрайт
tree3=(
	3'   _._ '
	2'  /    '
	1' _ | / '
	0'/  ║/__ '
	0'_/║/   '
	3'   ║|/_/ '
	4'    ║/ '
	4'    ║ '
	4'    ║ ')

# основной цвет меняется в зависимости от времени года
case $month in
	0[1-4]|12) CLR=${cyn}      ;; # зима
	0[5-8]   ) CLR=${BLD}${GRN};; # лето
	09|1[0-1]) CLR=${DIM}${red};; # осень
esac

CM1=${SKY}${BLK}
tree3_color=(
"${SKY} ${SKY} ${SKY} ${CLR} ${CLR} ${CLR} ${SKY}"
"${SKY} ${SKY} ${CLR} ${SKY} ${SKY} ${SKY} ${CLR} ${SKY}"
"${SKY} ${CLR} ${CLR} ${SKY} ${CM1} ${SKY} ${CLR} ${SKY}"
"${CLR} ${SKY} ${SKY} ${CLR} ${CM1} ${CLR} ${CLR} ${CLR} ${SKY}"
"${CLR} ${CLR} ${CM1} ${CLR} ${CM1} ${CLR} ${SKY} ${SKY} ${CLR} ${SKY}"
"${SKY} ${SKY} ${SKY} ${CM1} ${CM1} ${CLR} ${CM1} ${CLR} ${CLR} ${SKY}"
"${SKY} ${SKY} ${SKY} ${SKY} ${CM1} ${CM1} ${SKY}"
"${SKY} ${SKY} ${SKY} ${SKY} ${CM1} ${SKY}"
"${SKY} ${SKY} ${SKY} ${SKY} ${CM1} ${SKY}")

# "быстрый" спрайт
tree32=(   
   3${CLR}'_._ '${SKY}
  2${CLR}'/    '${SKY}
 1${CLR}'_ '${CM1}'|'${CLR}' / '${SKY}
0${CLR}'/  \'${CM1}'║'${CLR}'/__ '${SKY}
0${CLR}'_'${CM1}'\'${CLR}'/'${CM1}'║'${CLR}'/   '${SKY}
   3${BLK}'║'${CLR}'|'${CM1}'/'${CLR}'_/ '${SKY}
    4${BLK}'║/ '${SKY}
    4${BLK}'║ '${SKY}
    4${BLK}'║ '${SKY})

И тут мы плавно переходим к следующей фиче. Основной цвет деревьев, облаков и фона меняется в зависимости от времени года. Время года определяется по месяцу. Месяц определяется date'ом:

month=$(date +'%m')

В разное время года игра будет выглядеть по разному. Сейчас так, осень:
image

А скоро будет так, зима — начало весны:
image

А так будет совсем не скоро. Весна — лето:
image

Но читеры могут сделать так:

month=07

Да, пулялка может увеличиваться до х5, и босс немного подрос.
А вот такое интроменю появилось в игре:
image

Но тут тоже не все было гладко в плане быстродействия. Большие спрайты даже поодиночке жутко тормозили в посимвольном режиме вывода. Пришлось делать присядания опять, да.
Я решил нарезать спрайты на куски по 3 символа и выводить их «быстрым» методом:

D=$DEF; C1=$BLU; C2=$RED; C3=$YLW; C4=$red
C5=$BLD$YLW; C6=$BLD$GRN; C7=$blu; C8=$BLD$RED

#   ░   ░░░ ░░     ░  ░░      ░░  ░░ ░
# ░   ░  ▒██████ ░▒██████ ▒██░  ▒██
#   ░ ░░▒▓██ ▒▓██ ▓▓▓██▓ ▒▓██ ░▒▓██░░ ░
#░ ░ ░░▒▓██ ▒▓██ ▒▒▒▓██ ░▒▓██░ ▒▓██░
#      ▒▓██████░ ░▒▓██ ░▒▓██ ░▒▓██░░ ░ ░
#  ░  ▒▓██▓▓▓▓  ░ ▒▓██░ ▒▓██░░▒▓██ ░
#   ▒▓████▒▒▒░ ░ ▓██████ ▒▓██████░░ ░
#░ ░▒▓▓▓▓ ░ ░░▒▓▓▓▓▓▓ ░ ▒▓▓▓▓▓░
# ░ ▒▒ ░ ░ ░ ░▒▒▒▒▒▒ ░ ░░▒▒▒▒░  ░

piu=(
"$C1   " "░  "    " ░░"    "░ ░" "░  " "   " "░  " "░░ " "   " "  ░" "░  " "░░ " "░$D  " "   "
"$C1 ░ " "  ░" "  $C2▒" "$C3███" "███" " $C1░$C2▒" "$C3███" "███" " $C2▒$C3█" "█$C1░ " " $C2▒$C3█" "█$D  " "   " "   "
"$C1   " "░ ░" "░$C2▒$C4▓" "$C3██ " "$C2▒$C4▓$C3█" "█ $C4▓" "▓▓$C3█" "█$C4▓ " "$C1▒$C4▓$C3█" "█ $C1░" "$C2▒$C4▓$C3█" "█$C1░░" " ░$D " "   "
"$C1░ ░" " ░░" "$C2▒$C4▓$C3█" "█ $C2▒" "$C4▓$C3██" " $C2▒▒" "▒$C4▓$C3█" "█ $C2░" "▒$C4▓$C3█" "█$C1░ " "$C2▒$C4▓$C3█" "█$C1░$D " "   " "   "
"$C1   " "   " "$C2▒$C4▓$C5█" "███" "██$C1░" " ░$C2▒" "$C4▓$C3██" " $C1░$C2▒" "$C4▓$C3██" " $C1░$C2▒" "$C4▓$C3██" "$C1░░ " "░ ░$D" "   "
"$C1  ░" "  $C2▒" "$C4▓$C3██" "$C4▓▓▓" "▓  " "$C1░ $C2▒" "$C4▓$C3██" "$C1░ $C2▒" "$C4▓$C3██" "$C1░░$C2▒" "$C4▓$C3██" " $C1░$D " "   " "   "
"$C2   " "▒$C4▓$C3█" "███" "$C2▒▒▒" "$C1░ ░" " $C4▓$C3█" "███" "██ " "$C1▒$C4▓$C3█" "███" "██$C1░" "░ ░$D" "   " "   "
"$C1░ ░" "$C2▒$C4▓▓" "▓▓ " "$C1░ ░" "░$C2▒$C4▓" "▓▓▓" "▓▓ " "$C1░ $C2▒" "$C4▓▓▓" "▓▓$C1░$D" "   " "   " "   " "   "
"$C1 ░ " "▒▒ " "░ ░" " ░ " "░▒▒" "▒▒▒" "▒ ░" " ░░" "▒▒▒" "▒░ " " ░ " "   " "   " "$D   ")
piuN=${#piu[*]}; piuC=14

#          ░░ ▒▒███░
#     ░░░ ▒████ ▒▓███░
# ░ ▒████▒▓▓▓▓ ░░▒▓▓███ ░░ ░
#░ ▒▓▓▓▓ ▒████░▒▓███░░
# ▒ ▒▒  ▒▓▓▓▓▒▓███░
#    ░░░ ▒▒▒ ░░▒▒░

arr=(
"   " "   " "   " "   " " $C7░░" " $C3▒▒" "$C6███" "$C7░$D  " ""    ""
"   " "   " "  $C7░" "░░ " "$C2▒$C8██" "██ " "$C7▒$C3▓$C6█" "██$C7░$D" ""    ""
"   " " $C7░ " "▒$C8██" "██$D$C2▒" "$C3▓▓▓" "▓ $C7░" "░$C7▒$C3▓" "▓$C6██" "█ $C7░" "░ ░$D"
"   " "$C7░ $C2▒" "$C3▓▓▓" "▓ $C2▒" "$C8███" "█$D$C7░$C3▒" "$C3▓$C6██" "█$C7░░$D" "   " ""
"   " " $C7▒ " "▒▒ " " $C2▒$C3▓" "▓▓▓" "$C7▒$C3▓$C6█" "██$C7░$D" "   " ""    ""
"   " "   " " $C7░░" "░ ▒" "▒▒ " "░░▒" "▒░$D " "   " ""    "")
arrN=${#arr[*]}; arrC=10

Рисуется вся эта красота своими функциями left, right и intro:

function left () { N=$1; C=$2

		# move
		[ $OX -ge $end ] && {

			((OX-=3)); [ $cuter -ne $C ] && ((cuter++))

			for ((j=0; j<$N; j+=$C)); do

				line=("${sprite[@]:$j:$cuter}")
				YY=$[$OY+$j/$C]; spr=
				for part in "${line[@]}"; dospr+="${part}"; done
				XY $OX $YY "${spr}"

			done; OBJ[$i]="$OX $OY $cuter $end $type $pause"
		} || { remove_obj $i; ((Q++)); OBJ+=("${scenario[$Q]}"); }
}

function right () { N=$1; C=$2

		# move
		[ $OX -le $end ] && {

			[ $cuter -ne $C ] && ((cuter++)) || ((OX+=3))

			for ((j=0; j<$N; j+=$C)); do

				line=("${sprite[@]:$j:$C}")
				YY=$[$OY+$j/$C]; spr=
				for ((k=$[$C-$cuter]; k<$C; k++)); do spr+="${line[k]}"; done
				XY $OX $YY "${spr}"

			done; OBJ[$i]="$OX $OY $cuter $end $type $pause"
		} || { remove_obj $i; ((Q++)); OBJ+=("${scenario[$Q]}"); }
}

function intro { get_dimensions; Q=0

	scenario=(
	# сценарий появления объектов
	#----------+-------+-----+------------+----+
	# старт X  |старт Y|резак|  конец X   |тип |
	#----------+-------+-----+------------+----+
	"$[$endx+1]   3       0   $[endx/2-34] piu"
	"$[$endx+1]   3       0   $[endx/2+2]  piu"
	"-2           12      0   $[endx/2-16] arr"
	"0            0       0   0            end")
	OBJ=("${scenario[$Q]}")

	while true; do sleep 0.005; NO=${#OBJ[@]}
		for (( i=0; i<$NO; i++ )); do
			OI=(${OBJ[$i]}); OX=${OI[0]}; OY=${OI[1]}
			cuter=${OI[2]}; end=${OI[3]}; type=${OI[4]}

			case $type in
				#-----+-------------------+------+------+------+
				#тип  |        спрайт     |функц.|высота|ширина|
				#-----+-------------------+------+------+------+
				"arr") sprite=("${arr[@]}"); right $arrN $arrC;;
				"piu") sprite=("${piu[@]}"); left  $piuN $piuC;;
				"end")                       return           ;;

			esac
		done
	done
}

Последовательность появления объектов задается в массиве scenario. Пункт меню conf дает возможность кастомизации (небольшой) самолетика. Можно изменить цвет самолетика и символ на хвосте (и его цвет).
image

И еще кучка мелких доработокисправлений по ходу написания двух статей (к слову о пользе статей). Дальше хочется поработать на доптимизацией, приподнять фпс и попробовать реализовать кооператив по сети, продолжение следует. На этом пока все (где-то я это слышал).

С праздником! Пиу, пиу, пиу!)

Автор: vaniacer

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js