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

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

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

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

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

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

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

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

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

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

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

Но тут тоже не все было гладко в плане быстродействия. Большие спрайты даже поодиночке жутко тормозили в посимвольном режиме вывода. Пришлось делать присядания опять, да.
Я решил нарезать спрайты на куски по 3 символа и выводить их «быстрым» методом:
Рисуется вся эта красота своими функциями left, right и intro:
Последовательность появления объектов задается в массиве scenario. Пункт меню conf дает возможность кастомизации (небольшой) самолетика. Можно изменить цвет самолетика и символ на хвосте (и его цвет).

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

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

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

Управляющие коды (цвет) срежутся, и вместо цветного символа получится каша. Необходимо пересобрать элемент спрайта посимвольно, вставляя нужные управляющие коды перед каждым символом. Поприсядав изрядное количество времени со срезами я, наконец, разработал похожий алгоритм. Тестовый сценарий:
#!/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
Получилось так:

Но этот алгоритм позволяет «красить» только строки целиком, это не было пределом мечтаний и не соответствовало существующей раскраске спрайтов. Пришлось поприседать еще. Таблица цветов расширилась, для каждого символа определен свой цвет. Каждый элемент массива цветов преобразуется в отдельный массив, индексы которого совпадают с индексами элемента спрайта. Затем элемент цвета и элемент спрайта рисуются в нужном месте и в нужное время.
#!/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
Теперь все символы могут быть разноцветными:

Отлично! Я решил проверить как это будет рисоваться поверх другого спрайта. Добавил статический спрайт в основной цикл:
for (( p=0; p<${sn}; p++ )); do XY 3 $[${endy}/2+${p}] "${sprite[$p]}" done
И проверил:

Нормально, но если сзади «маска» получается сама собой, то передняя часть рисуется квадратом, затирая все, опять присядания.
#!/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
Красота!

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

А если прикрутить сюда figlet, получится вообще хорошо:
Тут есть одна тонкость. Я переопределяю символ «разделитель». По умолчанию разделителями являются: пробел, таб, переход строки. Все это хранится в системной переменной IFS. Я запоминаю старое значение:
Затем устанавливю разделителем только переход строки \n:
Офиглеваю текст в массив figlet_text командой:
Ключ -w задает фиглету ширину строки, т.к. по умолчанию он вписывает текст в экран. А это добавляет ненужные переходы строки, и чуда не происходит. Ну и возвращаю старое значение разделителя:
Фиглетовый скроллер!

Баш позволяет делать срезы как переменных, так и массивов. Пример, срез переменной:
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
Привет мир!

А если прикрутить сюда 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
Фиглетовый скроллер!

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

Множественные вложенные циклы отрицательно сказываются на быстродействии (внезапно). А ведь я пробовал без деревьев, облаков и прочей мишуры, мда.
Но после такого количества присяданий не хотелось выбрасывать идею на помойку. Моск начал работать. Решение было найдено. Использовать оба метода! Юху! Появление\исчезновение посимвольно а полет по экрану «быстрым» методом. Цикл объектов теперь выглядит так:
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')
В разное время года игра будет выглядеть по разному. Сейчас так, осень:

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

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

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

Но тут тоже не все было гладко в плане быстродействия. Большие спрайты даже поодиночке жутко тормозили в посимвольном режиме вывода. Пришлось делать присядания опять, да.
Я решил нарезать спрайты на куски по 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 дает возможность кастомизации (небольшой) самолетика. Можно изменить цвет самолетика и символ на хвосте (и его цвет).

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