Играючи BASH'им дома

Как говорится: «Не было бы счастья, да несчастье помогло.» Внезапно у меня появилось немного свободного времени и я продолжил разработку пиу-пиу.

image
Не слышали про пиу-пиу? Можно познакомиться тут:
Первая статья И. BASH’им в начало
Вторая статья И. BASH’им дальше
Третья статья И. BASH’им вместе

Посмотреть\поиграть можно, скачав игру тут, или установить игру apt’ом:

sudo apt install -y piu-piu


Ворнинг, тяжелая гифка!
image


Что изменилось? Внешне не заметно, но внутри изменилось все. Движок переписан полностью. Если помните, в первой версии для каждого спрайта было два варианта. «Медленный» спрайт для появления из-за экрана, который собирался из обрезки отдельных массивов, массива с символами и массива с цветами, чтобы коды разукрашивания не обрезались при появлении. И «быстрый», весь спрайт рисовался сразу с кодами разукрашивания и перемещения для отрисовки перемещения по основной части экрана. Это была основная боль, от которой хотелось уйти. Благодаря новой схеме пропала необходимость поддержки двух версий спрайта. Работает это так же быстро, а на узком экране даже быстрей, и разметка спрайта упростилась в разы. Пример спрайта «деревце2»:
3_i3lc9c7rvxoe60ljsxys3qysu.png

sprite_tree2 () {
    mov - && BP2[i]="tree2 $OX $OY $OH $OW $SC $SM $CS $CL $AS $AL" \
          || { unset BP2[i]; return; }
    X1="_Y_$[OX+1]H" X2="_Y_$[OX+1]H" X3="_Y_$[OX+2]H" X4="_Y_$[OX+2]H"
    #0123456
    #1 _._  
    #2/   \ 
    #3\ | / 
    #4 \║/\ 
    #5  ║_/ 
    #6  ║   
    ####################  1     2        3       4       5    6  #0
    cut $SX  $OY    $T2C '' $X1'_'      '.'     '_'     ' '      #1
    cut $SX $[OY+1] $T2C '/'   ' '      ' '     ' '     '\\' ' ' #2
    cut $SX $[OY+2] $T2C '\\'  ' '  $blk'|'     ' ' $T2C'/'  ' ' #3
    cut $SX $[OY+3] $T2C '' $X2'\\' $blk'║' $T2C'/' $T2C'\\' ' ' #4
    cut $SX $[OY+4] $blk ''    ''    $X3'║' $T2C'_' $T2C'/'  ' ' #5
    cut $SX $[OY+5] $blk ''    ''    $X4'║'     ' ';;            #6
}


И функции 'mov' и 'cut':

mov () {
    case $1  in -) cuter=++;; +) cuter=--;; esac
    case $SC in
        0) [[ $OX -le -$OW   ]] && return 1
           [[ $OX -gt  $endx ]] && return 1
           [[ $AS -ge  $AL   ]] && AS=0  || ((AS++))
           [[ $OX -le  1     ]] && ((CS++))
           ((OX$1$1)); ((CL$cuter)); SC=$SM
           [[ $CL -lt 0 ]] && CL=0;;
        *) ((SC--));;
    esac
    [[ $OX -le 1 ]] && SX=0 || SX=$OX
}

cut () {
    xcord=$1 ycord=$2 color=$3; shift 3
    screen+="\e[$ycord;${xcord}H$color"
    printf -v spr %s "${@:$CS:$CL}"
    spr="${spr//_Y_/'\e['$ycord;}"
    screen+="${spr//⎕/ }"
}


Но пришлось заново «перерисовать» все спрайты… опять… О_о
Зато были исправлены небольшие косячки в старых спрайтах и добавлены новые в новых.
«Модель» объектов расширилась, вся информация теперь записывается в объект. Объект — это массив в массиве, на примере того же деревца2:

"tree2 $OX $OY $OH $OW $SC $SM $CS $CL $AS $AL"


Функция обработки объектов:

print_sprite () {
    OT=${OI[0]}   # object type
    OX=${OI[1]}   # X coordinate
    OY=${OI[2]}   # Y coordinate
    OH=${OI[3]}   # object hight
    OW=${OI[4]}   # object width
    SC=${OI[5]}   # speed counter
    SM=${OI[6]}   # speed max
    CS=${OI[7]}   # cuting start
    CL=${OI[8]}   # cuting lenght
    AS=${OI[9]}   # animation start
    AL=${OI[10]}  # animation lenght
    C1=${OI[11]}  # custom obj parameter 1
    C2=${OI[12]}  # custom obj parameter 2
    [[ $OT ]] && sprite_$OT
}


Работает это так: создано несколько массивов: BP1–3 для фоновых объектов, PIU для снарядов героев, HER сами герои и ENM враги и бонусы. Массивы обрабатываются в заданной последовательности (показано ниже), за счет этого реализовано «слоеное» отображение объектов, более крупные (ближе к нам) спрайты рисуются на переднем плане, перекрывая более мелкие (дальше от нас). Новые объекты добавляются в соотв. массивы, вот кусочек движка с обработкой массивов:

#-{ Move bitplans }----------------------------------------------------
BP1=("${BP1[@]}"); for i in ${!BP1[@]}; { OI=(${BP1[i]}); print_sprite; } # small(far) background objects
BP2=("${BP2[@]}"); for i in ${!BP2[@]}; { OI=(${BP2[i]}); print_sprite; } # medium background objects
PIU=("${PIU[@]}"); for i in ${!PIU[@]}; { OI=(${PIU[i]}); print_sprite; } # heroes fire
HER=("${HER[@]}"); for i in ${!HER[@]}; { OI=(${HER[i]}); print_sprite; } # heroes
ENM=("${ENM[@]}"); for i in ${!ENM[@]}; { OI=(${ENM[i]}); print_sprite; } # enemies
BP3=("${BP3[@]}"); for i in ${!BP3[@]}; { OI=(${BP3[i]}); print_sprite; } # big(closer) background objects


Появились новые фоновые объекты (домики и птички):
famtbmms21f46_6qwu0yfb0oygu.png

#0123456
#1▁▂▃▂▁ 
#2│▘▝▝| 
########


a6t0hqwfdctcbqyqed3-rglpqy0.png

#0123456789ABCDEFJHI
#1     ▖            
#2  ▂▄▆██▆▄▂        
#3  ┃╒╕╒╕╒╕┃ ┮━┭    
#4╠╬┃┵┘┵┶└┶┃╬╣▓╠╬╣▖ 
####################


jjha_lyujjsd9yh1rby6ug61iqm.png

#0123456789ABCDEFJHIJKLMNOPQRST
#1         ╂                    
#2         ╧                    
#3        ╱ ╲                   
#4        |▐|                   
#5       ┮┷━┷┭                  
#6       │ █ |                  
#7      ┮┷━━━┷┭                 
#8      │ ▛▔▜ │▄▄▄▄▂▁           
#9▂ ╬ ▃ ┃ ▌▒▐ ┃ ▘▗▘┿┃▗ ╂ ╪ ▄  ▖ 
###############################


e6efu2umrmpladcj4cm5e1uneos.png

# ⌄ _ 
#     ^


И как вы уже заметили появился новый вариант для спрайтов врагов и босса — вирус:
sqjoeygvchdn0firwlmlz3tnhiq.png

#0123456
#1 ._,  
#2-(*)- 
#3 ´‾`  
########


wfajpjbumq1x5rzhqtfby5ht1ay.png

#0123456789ABCD
#1             
#2   . ___,   
#3   /`   `\  
#4 +| *   o |+
#5  \   +   /  
#6  *`-.,.-`+  
#7             
###############


Геймплей остался практически прежним, изменилась скорость пришельцев, некоторые движутся быстрей. И в режиме дуэли тоже появились бонусы. Но механизм появления бонусов в дуэли отличается от основного режима. Бонус должен появлятся на середине экрана, чтобы у обоих игроков были одинаковые шансы добраться до него раньше соперника. Поэтому их скидывают в центре экрана с пролетающего мимо самолета в виде ящика на парашюте со случайным бонусом внутри. Для этого пришлось реализовать механизм вертикального появления\перемещения спрайтов, который пригодится в следующей части игры…
Спрайт ящика:

sprite_crate () {
    movy && ENM[$i]="crate $OX $OY $OH $OW $SC $SM $CS $CL 0 0 0 0 $i" \
         || { unset ENM[$i]; boom $OX $boomendy; boom $OX $[boomendy-3]; crate=0; }
    CM1=$SKY$ylw CM2=$SKY$blk CM3=$SKY$BLU SS="$RED ? $CM3"
    ########################1234567###01234567
    cuty  $OX    $OY 7 $SKY'       ' #1       
    cuty  $OX    $OY 6 $CM1' ¸.—.¸ ' #2 ¸.—.¸ 
    cuty  $OX    $OY 5 $CM1'(︹_︹)'  #3(︹_︹)
    cuty $[OX+1] $OY 4  $CM2'╲╱ ╲╱'  #4 ╲╱ ╲╱ 
    cuty $[OX+1] $OY 3  $CM3'╔╧╦╧╗'  #5 ╔╧╦╧╗ 
    cuty $[OX+1] $OY 2  $CM3"╠$SS╣"  #6 ╠ ? ╣ 
    cuty $[OX+1] $OY 1  $CM3'╚═╩═╝'  #7 ╚═╩═╝ 
}


atbm6ofzydlee5ovmoeljopv-u4.png

И функции 'movy' и 'cuty':

movy () {
    case $SC in
        0) [[ $OY -ge $[endy-2] ]]    && return 1
           [[ $AS -ge $AL  ]] && AS=0 || ((AS++))
           [[ $CS -le $OH  ]] && ((CS++))
           ((OY++)); SC=$SM;;
        *) ((SC--));;
    esac
}

cuty () {
    xcord=$1 ycord=$2 cuter=$3; shift 3
    [[ $CS -ge $cuter ]] && screen+="\e[$[ycord-cuter];${xcord}H$@"
}


Роль почтальона взял на себя рекламный самолёт, ящики вылетают из него, и он тоже претерпел небольшие изменения. Текст пишется одной строкой, но размер больше не ограничен ничем, кроме здравого смысла т.е. ничем, выглядит это как-то так:
rj_-x7l7evwkk28k-1dx6rjsbhy.png
Релиз уже скоро, ваша реклама может быть тут)

Реализован новый механизм обработки коллизий, при помощи $BASH_REMATCH. Раньше колизии проверялись циклом. Для каждого пришельца в цикле по объектам из массива «PIU»(пульки) проверялось есть ли попадание, эти вложенные циклы очень сильно снижали фпс. Сейчас проверка выполняется для пулек (попадание в чужих) и для героев (столкновения с чужими, бонусы), и делается это одной (почти) командой, поэтому новый метод работает заметно быстрей, практически не оказывая влияния на фпс. Регулярное выражение для обработки коллизий героя:

 re="(life|ammo|gunup) ($[OX+12]|$[OX+13]) ($[OY+1]|$[OY+2]|$[OY+3]|$[OY+4]).*|"
re+="alien ($[OX+12]|$[OX+13]) ($[OY+1]|$[OY+2]).*|"
re+="bfire ($[OX+12]|$[OX+13]) ($[OY+2]|$[OY+3]).*|"
re+="boss ($[OX+12]|$[OX+13]) $[OY-1].*"


Функция:

collision 1 "$re" && { BOOM $OX $OY; ((life--)); }

collision () {
    hero=$1 re="$2"
    case $type in
        'duel') target=("${HER[@]}" "${ENM[@]}");;
             *) target=("${ENM[@]}");;
    esac
    [[ ${target[@]} =~ $re ]] && {
        match=($BASH_REMATCH)
        obj_i=${match[@]:11:1}
        obj_x=${match[@]:1:1}
        obj_y=${match[@]:2:1}
        case $match:$hero in
              bfire:*) collbooom; return 0;;
               boss:*) ((bhealth--)); boom $3 $4; return 0;;
               life:1) collbooom; ((life++))    ; return 1;;
               life:2) collbooom; ((life2++))   ; return 1;;
               ammo:1) collbooom; ((ammo+=100)) ; return 1;;
               ammo:2) collbooom; ((ammo2+=100)); return 1;;
              gunup:1) collbooom; [[ $G  -lt 5 ]] && ((G++))  ; return 1;;
              gunup:2) collbooom; [[ $G2 -lt 5 ]] && ((G2++)) ; return 1;;
              alien:1) collbooom; ((enumber--)) ; ((frags++)) ; bonus; return 0;;
              alien:2) collbooom; ((enumber--)) ; ((frags2++)); bonus; return 0;;
              hero1:2) ((frags2++)); ((life--)) ; BOOM $obj_x $obj_y ; return 0;;
              hero2:1) ((frags++)) ; ((life2--)); BOOM $obj_x $obj_y ; return 0;;
              crate:*) collbooom; crate=0; boom $obj_x $[obj_y-3]; boom $obj_x $[obj_y-6]; rndmbonus $hero; return 1;;
        esac
    }
}


В предыдущей версии было около 2К строк, сейчас 1491, для упрощения разработки я разбил скрипт на 4 части: main (основная), functions (функции), sprites (спрайты), messages (сообщения). Функции, спрайты и сообщения сорсятся в основной скрипт вот так:

. ./messages  #_MESSAGES_
. ./sprites   #_SPRITES_
. ./functions #_FUNCTIONS_


А финальный скрипт piu-piu «склеивается» воедино вот таким скриптиком:

#!/bin/bash
target=piu-piu
sed -n '1,/_SPRITES_/p'   main    >  $target
sed '1d' messages                 >> $target
sed '1d' sprites                  >> $target
sed '1d' functions                >> $target
sed -n '/_FUNCTIONS_/,$p' main    >> $target
sed -i '/_SPRITES_/d;/_FUNCTIONS_/d;/_MESSAGES_/d' $target
chmod +x $target


Отдельный цикл для режима дуэли больше не требуется, все в основном игровом цикле, который уместился в 33 строки вместе с коментариями:

#-{ Empty screen, print game status adnd FPS }-------------------------
screen=; status; [[ $showfps ]] && fps_counter
#-{ Add background }---------------------------------------------------
[[ $background ]] && add_backgound
#-{ Move bitplans }----------------------------------------------------
BP1=("${BP1[@]}"); for i in ${!BP1[@]}; { OI=(${BP1[i]}); print_sprite; } # small(far) background objects
BP2=("${BP2[@]}"); for i in ${!BP2[@]}; { OI=(${BP2[i]}); print_sprite; } # medium background objects
PIU=("${PIU[@]}"); for i in ${!PIU[@]}; { OI=(${PIU[i]}); print_sprite; } # heroes fire
HER=("${HER[@]}"); for i in ${!HER[@]}; { OI=(${HER[i]}); print_sprite; } # heroes
ENM=("${ENM[@]}"); for i in ${!ENM[@]}; { OI=(${ENM[i]}); print_sprite; } # enemies
BP3=("${BP3[@]}"); for i in ${!BP3[@]}; { OI=(${BP3[i]}); print_sprite; } # big(closer) background objects
#-{ BOSS }-------------------------------------------------------------
if [[ $[frags+frags2] -ge $tillboss ]]; then # add boss object
    [[ $bossborn ]] || { ENM+=("boss $endx $halfendy 6 14 0 0 1 0 $[RANDOM%3] 3 20 10"); bossborn=true; }
    boss_health # Print boss' health bar
    #-{ Add aliens }---------------------------------------------------
    [[ $bhealth -gt 0 ]] && add_enm $EX $EY 5 # aliens come out from boss
else                                          #
    add_enm $endx $[4+RANDOM%enmyendy]        # aliens come out from edge
fi
#-{ Print everything }-------------------------------------------------
printf "$screen"
#-{ Check the end and send data to client }----------------------------
case $game$type in
      *team) [[ ${HER[@]} ]] || the_end=lose;;&
      *duel) [[ ${HER[1]} ]] || if [[ $life -gt 0 ]]; then listener; sender $caddr $cport lose; mess win
                                                      else listener; sender $caddr $cport win ; mess lose; fi;;&
    server*) listener; sender $caddr $cport "$screen";;
esac
if [[ $the_end ]]; then
    case $game in 'server') listener; sender $caddr $cport $the_end;; esac
    mess $the_end
fi


Клиент 7 строк:

read_input
sender $saddr $sport $input
screen="$(nc -l -p $cport)"
case $screen in
    'win' | 'lose') mess   "$screen";;
    *             ) printf "$screen";;
esac


Справедливости ради надо сказать, что клиент практически не изменился). Вырисовывыется вполне себе рабочий игровой движок на bash’е, еще надо будет сделать редактор спрайтов, кат сцены, диалоги, сюжет… Но это уже совсем другая история. Идеи по дальнейшему развитию событий в piu-piu есть, но эпидемия потихоньку заканчивается, наконец-то начинается лето, дачи и вот это вот все). Так что придется ждать очередного апокалипсиса для продолжения, а пока закончу тестирование новой версии и обновлю piu-piu. Берегите себя и своих близких, оставайтесь дома и играйте в piu-piu)

Рекламная акция
Пользуясь случаем хочу попиарить еще одну свою поделку sshto, которая тоже потихоньку набирает популярность на github’е (92 звезды)
image

Это скриптик который создает меню (при помощи dialog’а) из вашего ~/.ssh/config файла, работает и с дефолтным конфигом, но для полного функционала необходимо добавить коментарии к хостам вот так:

#Host DUMMY #Rybinsk#

Host rybserver1 #First server
HostName localhost

Host rybserver2 #Second server
HostName localhost

Host rybserver3 #Third server
HostName localhost

#Host DUMMY #Moscow#

Host moserver1 #First server
HostName localhost

Host moserver2 #Second server
HostName localhost

Host moserver3 #Third server
HostName localhost

Кроме подключения к хостам может выполнять ряд полезных функций:
image

Функционал может быть легко дополнен через конфайл ~/.sshtorc, а цвета диалога мжно поменять так:

dialog --create-rc ~/.dialogrc
nano ~/.dialogrc

Творите, выдумывайте, пробуйте!)

© Habrahabr.ru