Первая статья И. 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

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

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

Комментарии (39)


  1. Incognito4pda
    13.09.2017 21:51
    +1

    Красота!


    1. vaniacer Автор
      13.09.2017 23:03

      Спасибо)


  1. ZubroZuu
    13.09.2017 22:27
    +2

    Спасибо за статью, не останавливайтесь, будем ждать продолжения развития проекта.


  1. samizdam
    13.09.2017 22:42
    +1

    Теперь нужен мультиплеер)


    1. vaniacer Автор
      13.09.2017 23:02

      Да, сам очень хочу запилить.


      1. equand
        14.09.2017 20:15

        На nc?


        1. vaniacer Автор
          14.09.2017 22:21

          Возможно, попробую варианты.


  1. qwertKI
    13.09.2017 23:01

    :))) super mario запилить бы


  1. Self_Perfection
    13.09.2017 23:07

    Так-с, со второй статьи я не удержался и попробовал посмотреть код. shellcheck вам бы на него натравить было бы неплохо

    *ушёл пилить пулреквесты*


    1. vaniacer Автор
      13.09.2017 23:30

      Попробую, уже 2-я полезная наколочка от Вас, спасибо.


      1. bustEXZ
        14.09.2017 08:17

        Не могу найти ссылку на репозиторий, он уже есть где нибудь? Спасибо за отличную статью


        1. vaniacer Автор
          14.09.2017 08:33

          1. bustEXZ
            14.09.2017 12:03

            Спасибо!


  1. saboteur_kiev
    13.09.2017 23:22
    +3

    А если повернуть игру на 90 градусов, и лететь сверху вниз, это разве не увеличит быстродействие естественным способом?


    1. vaniacer Автор
      13.09.2017 23:30
      +2

      Да, но это будет вертикальный скроллер а мне больше нравятся горизонтальные.
      Забегая вперед, я уже поработал немного над быстродействием, новый алгоритм почти в 2 раза быстрей. Вероятно, и это не предел. Но об этом в следующей серии)


    1. synedra
      14.09.2017 06:47
      +1

      Сверху вниз для шмапа очень нетрадиционно и может расстраивать многих игроков. Обычно или слева направо, или снизу вверх.


  1. ZyXI
    14.09.2017 01:17
    +1

    А почему «цветов не хватает»? Во многих терминалах есть поддержка 24?битного цвета, что больше либо равно числу цветов, поддерживаемых большинством мониторов.


    PS: не думали попробовать написать что?то сложное под POSIX shell? Я как?то аналог getopt --longoptions писал, использовал много eval для «массивов», но вопрос о скорости, к счастью, не стоял.


    1. vaniacer Автор
      14.09.2017 08:42

      Да, я уже думаю о изменении палитры. Eval пробовал, очень тормозно получается.


  1. synedra
    14.09.2017 06:49
    +4

    Потрясающий проект. Вы не думали отправить его в репозитории дебиана/убунты? Названия `piu` и `piupiu` свободны, а желающего скачать подобное народу наверняка найдётся немало.


    1. vaniacer Автор
      14.09.2017 08:42

      Хорошая идея, спасибо.


    1. Areso
      15.09.2017 08:06

      По-английски правильно будет pew-pew.
      vaniacer


  1. BerkutEagle
    14.09.2017 08:39
    +2

    Зимой деревья должны быть голыми! )


    1. vaniacer Автор
      14.09.2017 08:45

      Конечно, вы правы. Поленился делать доп. спрайты. Попробую оголить их в след. версиях.


      1. gmini
        14.09.2017 09:53
        +3

        а в южном полушарии времена года наоборот.


        1. vaniacer Автор
          14.09.2017 09:54

          Тут я облажался.


  1. sach3000
    14.09.2017 09:20

    вспомнил сразу 98й и паскаль


    1. Trottle
      14.09.2017 10:44

      98?!!! 8-О
      Да тут вспоминать надо Радио-86РК, Микрошу и Апогей-БК01!


  1. barsuksergey
    14.09.2017 12:55

    Класс. Поддерживаю synedra, игра уже заслуживает официального репозитория.
    Очень красивые очереди летят теперь, если зажать огонь. Всё замедляется, как в Матрице.
    После Well Done, может, хочется снова в игру, а она или завершается, или вылетает, не понял.


    1. vaniacer Автор
      14.09.2017 13:43

      Проверка коллизий сильно замедляет процесс, да, работаю над этим.


      1. vaniacer Автор
        14.09.2017 16:01

        Добавил возвращение в меню.


  1. nikola_sa
    14.09.2017 13:41

    Не сразу заметил что в игре есть ещё и самолёт.


  1. AVX
    14.09.2017 15:34

    Жесть конечно. Напомнило про вот это: pinguem.ru/event/bash-golf
    Вот где народ поизвращался знатно… (а, да, я тоже поучаствовал).
    Правда, там не было ограничений что внутри скрипта будет, и все перешли на python, perl.


  1. azymohliad
    14.09.2017 16:15

    Невероятно! Восхищаюсь! Залип, очень играбельно.


  1. CyberSoft
    14.09.2017 20:16

    Одно слово: вау!


    Запилите для cmd.exe? :D


  1. t3mnikov
    14.09.2017 22:11

    Проект зрелещный! А звуковые эффект можно добавить?


    1. vaniacer Автор
      14.09.2017 22:13
      +1

      Теоретически, да. Но мне хочется эм, keep it simple.


      1. Areso
        15.09.2017 08:26

        звуковые эффекты midi были бы в тему.


        1. t3mnikov
          15.09.2017 10:54

          можно и так

          spd-say 'piu piu'



          1. vaniacer Автор
            15.09.2017 21:03

            Мммм