Как говорится: «Не было бы счастья, да несчастье помогло.» Внезапно у меня появилось немного свободного времени и я продолжил разработку пиу-пиу.
Не слышали про пиу-пиу? Можно познакомиться тут:
Первая статья И. BASH'им в начало
Вторая статья И. BASH'им дальше
Третья статья И. BASH'им вместе
Посмотретьпоиграть можно, скачав игру тут, или установить игру apt'ом:
sudo apt install -y piu-piu
Что изменилось? Внешне не заметно, но внутри изменилось все. Движок переписан полностью. Если помните, в первой версии для каждого спрайта было два варианта. «Медленный» спрайт для появления из-за экрана, который собирался из обрезки отдельных массивов, массива с символами и массива с цветами, чтобы коды разукрашивания не обрезались при появлении. И «быстрый», весь спрайт рисовался сразу с кодами разукрашивания и перемещения для отрисовки перемещения по основной части экрана. Это была основная боль, от которой хотелось уйти. Благодаря новой схеме пропала необходимость поддержки двух версий спрайта. Работает это так же быстро, а на узком экране даже быстрей, и разметка спрайта упростилась в разы. Пример спрайта «деревце2»:
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
Появились новые фоновые объекты(домики и птички):
#0123456
#1▁▂▃▂▁
#2│▘▝▝|
########
#0123456789ABCDEFJHI
#1 ▖
#2 ▂▄▆██▆▄▂
#3 ┃╒╕╒╕╒╕┃ ┮━┭
#4╠╬┃┵┘┵┶└┶┃╬╣▓╠╬╣▖
####################
#0123456789ABCDEFJHIJKLMNOPQRST
#1 ╂
#2 ╧
#3 ╱ ╲
#4 |▐|
#5 ┮┷━┷┭
#6 │ █ |
#7 ┮┷━━━┷┭
#8 │ ▛▔▜ │▄▄▄▄▂▁
#9▂ ╬ ▃ ┃ ▌▒▐ ┃ ▘▗▘┿┃▗ ╂ ╪ ▄ ▖
###############################
# ⌄ _
# ^
И как вы уже заметили появился новый вариант для спрайтов врагов и босса — вирус:
#0123456
#1 ._,
#2-(*)-
#3 ´‾`
########
#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 ╚═╩═╝
}
И функции '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$@"
}
Роль почтальона взял на себя рекламный самолёт, ящики вылетают из него, и он тоже претерпел небольшие изменения. Текст пишется одной строкой, но размер больше не ограничен ничем, кроме здравого смысла т.е. ничем, выглядит это как-то так:
Релиз уже скоро, ваша реклама может быть тут
Реализован новый механизм обработки коллизий, при помощи $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)
Это скриптик который создает меню(при помощи 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
Кроме подключения к хостам может выполнять ряд полезных функций:
Функционал может быть легко дополнен через конфайл ~/.sshtorc а цвета диалога мжно поменять так:
dialog --create-rc ~/.dialogrc
nano ~/.dialogrc
Творите, выдумывайте, пробуйте!)
Автор: Иван Маров