#!/usr/local/bin/bash # «Арканоид» на bash. Евгений Степанищев http://bolknote.ru/ 2011 # Bash Arcanoid. Copyright by Evgeny Stepanischev http://bolknote.ru/ 2011 PID=$$ # Bloquear colores de niveles MAPCOLORS=("38;5;"{34,24,204}) # Mapas de niveles declare -a MAPS # X Y Tipo (color) Cantidad MAPS=(\ "4 4 0 12 4 5 0 12 4 6 1 12 4 7 1 12 4 8 0 12 4 9 2 12 4 10 2 12" "13 2 1 2 8 3 1 1 24 3 1 1 5 4 1 1 27 4 1 1 5 5 1 1 27 5 1 1 8 6 1 1 24 6 1 1 13 7 1 2 13 4 0 2 16 5 2 1 7 1 1 1 25 1 1 1 70 2 1 1 69 3 1 1 33 5 1 6 35 6 1 1 35 7 1 1 33 8 1 1 44 6 1 1 44 7 1 1 42 8 1 1 68 4 1 1 55 6 1 1 62 6 1 1 57 7 1 1 64 7 1 1 54 8 1 1 63 8 1 1 33 4 1 6" "28 2 1 4 16 3 1 2 52 3 1 2 10 4 0 1 22 4 0 1 34 4 0 2 52 4 0 1 64 4 0 1 4 5 2 1 16 5 2 3 46 5 2 3 70 5 2 1 4 6 1 1 22 6 1 1 34 6 0 2 52 6 1 1 70 6 1 1 10 7 0 4 46 7 0 4 22 8 1 6 4 9 2 1 70 9 2 1 16 10 1 8" "2 1 0 1 2 2 0 1 2 3 0 1 2 4 0 1 2 5 0 1 2 6 0 1 2 7 0 1 2 8 0 1 2 9 0 2 16 1 1 2 16 2 1 1 16 3 1 1 16 4 1 1 16 5 1 2 16 6 1 1 16 7 1 1 16 8 1 1 16 9 1 2 30 1 2 1 42 1 2 1 30 2 2 1 42 2 2 1 30 3 2 1 42 3 2 1 30 4 2 1 42 4 2 1 30 5 2 1 42 5 2 1 30 6 2 1 42 6 2 1 32 7 2 1 40 7 2 1 32 8 2 1 40 8 2 1 36 9 2 1 50 1 1 2 50 2 1 1 50 3 1 1 50 4 1 1 50 5 1 2 50 6 1 1 50 7 1 1 50 8 1 1 50 9 1 2 64 1 0 1 64 2 0 1 64 3 0 1 64 4 0 1 64 5 0 1 64 6 0 1 64 7 0 1 64 8 0 1 64 9 0 2" "10 2 1 10 10 9 1 10 10 3 1 1 64 3 1 1 10 4 1 1 64 4 1 1 10 5 1 1 64 5 1 1 10 6 1 1 64 6 1 1 10 7 1 1 64 7 1 1 10 8 1 1 64 8 1 1 16 4 2 8 16 7 2 8 16 5 2 1 16 6 2 1 58 5 2 1 58 6 2 1 34 5 0 2 34 6 0 2" "6 2 0 1 6 3 0 1 6 4 0 1 6 5 0 1 6 6 0 1 6 7 0 1 6 8 0 1 6 9 0 1 24 2 0 1 24 3 0 1 24 4 0 1 24 5 0 1 24 6 0 1 24 7 0 1 24 8 0 1 24 9 0 1 15 7 0 1 12 8 0 2 34 2 1 2 34 9 1 2 37 3 1 1 37 4 1 1 37 5 1 1 37 6 1 1 37 7 1 1 37 8 1 1 50 2 2 1 50 3 2 1 50 4 2 1 50 5 2 1 50 6 2 1 50 7 2 1 50 8 2 1 50 9 2 1 67 2 2 1 67 3 2 1 67 4 2 1 67 5 2 1 67 6 2 1 67 7 2 1 67 8 2 1 67 9 2 1 56 4 2 1 57 5 2 1 59 6 2 1 61 7 2 1" ) # Control SCORE=0 # Numero de vidas LIVES=5 # nro. de bloques por nivel MAPQUANT= # nro de mapa MAPNUMBER=1 # ¿Se pega la pelota a la raqueta? STICKY= # Crea un carro de una longitud determinada, completa global # variables function CreateСarriage { CW=$1 # Каретка, забитая пробелами и ☰, для ускорения CSPACES=$(printf "% $(($CW+2))s") CBLOCKS=$(printf "%0$(($CW-2))s" | sed 's/0/☰/g') } CreateСarriage 5 # Coordenadas de la raqueta CX=2 OCX= # Coordenadas y tipo de regalo que caen GX= GY= GT= # Координаты мяча BX=5 BY=2900 # coordenadas pelota BAX=0 BAY=0 # version de bash BASH=(${BASH_VERSION/./ }) # cuadricula de pantalla virtual declare -a XY # Reemplazar say si no está allí which say &>/dev/null || function say { : } # Grafica un nivel x nro function DrawMap { local i j x y t q map=(${MAPS[$1]}) c MAPQUANT=0 for ((i=0; i<${#map[@]}; i+=4)); do x=${map[$i]} y=${map[$i+1]} t=${map[$i+2]} q=${map[$i+3]} let "MAPQUANT+=$q" c="\033[${MAPCOLORS[$t]}m☲" while [ $q -gt 0 ]; do for j in {0..3}; do XY[$x+100*$y+$j]=$c done let 'x+=6, q--' done done } # Maneja eventos de teclado function KeyEvent { case $1 in LEFT) if [ $CX -gt 2 ]; then [ -z "$OCX" ] && OCX=$CX let "CX--" fi ;; RIGHT) if [ $CX -lt $((75-$CW)) ]; then [ -z "$OCX" ] && OCX=$CX let "CX++" fi ;; SPACE) SpaceEvent ;; esac } # Dinujar cuadro en pantalla virtual function DrawBox { local x y b="\033[38;5;8m♻" for (( x=0; x<78; x+=2 )); do XY[$x]=$b XY[$x+3100]=$b XY[$x+1]=' ' XY[$x+3101]=' ' done for (( y=100; y<=3000; y+=100)) do XY[$y]=$b XY[$y+1]=' ' XY[$y+76]=$b XY[$y+75]=' ' done } function PrintСarriage { # Si las posiciones anterior y actual son las mismas, entonces solo necesita # dibujar un signo de intercalación if [ -z "$OCX" ]; then echo -ne "\033[$(($CX+1))G" else # Borra el carro del lugar donde estaba, # espacios adicionales alrededor de los bordes borran fallos echo -ne "\033[${OCX}G${CSPACES}" echo -ne "\033[$(($CX+1))G" fi echo -ne "\033[38;5;160m☗\033[38;5;202m$CBLOCKS\033[38;5;160m☗" OCX= } # Нажали на space function SpaceEvent { # если мяч прилеплен к каретке, стартуем if [ $BAX -eq 0 ]; then BAY=-100 [ $CX -gt 38 ] && BAX=1 || BAX=-1 SoundSpace return fi } # Мячик ушёл в аут function MissBall { SoundOut BAX=0 BAY=0 let BX="$CX+4" BY=2900 # Сбрасываем размер ракетки CreateСarriage 5 # Очищаем каретку echo -ne "\033[2G" printf "% 75s" STICKY= let 'LIVES--' PrintLives if [ $LIVES -le 0 ]; then SoundGameover echo -ne "\033[18A\033[29G\033[48;5;15;38;5;16m G A M E O V E R " echo -ne "\033[20B\033[1G\033[0m" kill -HUP $PID while true; do sleep 0.3 done fi } # Игрок победил function YouWin { SoundWin DrawBox DrawMap $(($MAPNUMBER-1)) PrintScreen WIN echo -ne "\033[18A\033[31G\033[48;5;15;38;5;16m Y O U W I N " echo -ne "\033[20B\033[1G\033[0m" kill -HUP $PID while true; do sleep 0.3 done } # Рисуем виртуальный экран на экран function PrintScreen { local x y xy [ -z "$1" ] && SoundWelcome for y in {0..31}; do for x in {0..76}; do xy=$(($x+$y*100)) echo -ne "${XY[$xy]:- }" done echo done if [ -z "$1" ]; then # Пишем и стираем номер уровня echo -ne "\033[20A\033[31G\033[48;5;15;38;5;16m L E V E L $MAPNUMBER " sleep 1.3 echo -ne "\033[31G\033[0m " fi # Курсор в нижний угол (по y=линия каретки) echo -ne "\033[2A\033[20B" } # Игра окончена function SoundGameover { (say -v Zarvox "Loo Loo Loo" &>/dev/null) & } # Нажатие на Space function SoundSpace { (say -v Whisper -r 1000 forfor &>/dev/null) & } # Столкновение мяча function SoundBoom { (say -v Whisper -r 1000 1 &>/dev/null) & } # Звук прилипания function SoundStick { (say -v Junior -r 1200 chpock &>/dev/null) & } # Звук ракетка стала длинее function SoundWide { (say -v Whisper -r 400 heh &>/dev/null) & } # Звук шарик в аут function SoundOut { (say -v Whisper -r 1000 2 uo &>/dev/null) & } # Звук заставки function SoundWelcome { (say -v Zarvox "eueir" &>/dev/null) & } # Звук, когда жизнь увеличивается function SoundLives { (say -r 1200 -v Princess yes &>/dev/null ) & } # Победитель function SoundWin { (say -v Hysterical 'Das kewl man!' &>/dev/null) & } # Очистка уровня function ClearLevel { local i for i in {1..30}; do printf "\033[1G% 75s\033[1A" done echo -ne "\033[1G" } # Убрать блок function RemoveBlock { local y for y in {0..3}; do unset XY[$1+$2+$y] done y=$((30-$2/100)) echo -ne "\033[$(($1+1))G\033[${y}A \033[${y}B" let 'MAPQUANT--' # Разбили все блоки, следующий уровень if [ $MAPQUANT -le 0 ]; then let 'MAPNUMBER++' ClearLevel if [ $MAPNUMBER -ge ${#MAPS[@]} ]; then # Игра окончена, игрок выиграл YouWin ! else NextLevel fi fi } # Роняем подарок function StartGift { local r=$(( $RANDOM % 20 )) if [ $r -ge 17 ]; then GX=$1 GY=$((30-$2/100+1)) local gifts=(S W L) GT=${gifts[$r-17]} fi } # Рисуем мяч, должен рисоваться после всех объектов function PrintBall { # Чистим предыдущую позицию local y=$((30-$BY/100)) echo -ne "\033[$(($BX+1))G\033[${y}A${XY[$BX+$BY]:- }\033[${y}B" # Если мяч не двигается, следуем за кареткой if [ $BAX -eq 0 ]; then let BX="$CX+$CW/2" else local bx=$(($BX+$BAX)) local by=$(($BY+$BAY)) # Мяч коснулся каретки или дна if [[ $by -eq 3000 ]]; then # Каретки if [[ $bx -ge $CX && $bx -le $(($CX+$CW)) ]]; then if [ -z "$STICKY" ]; then SoundBoom let BAY="-$BAY" let "BX+=$BAX" let "BY+=$BAY" # Ракетка «липкая» else SoundStick BAX=0 BAY=0 let BX="$CX+4" BY=2900 fi # Дна else MissBall return fi else # Проверяем, не наткнулись ли мы на какое-то препятствие local c=${XY[$bx+$by]:-0} if [[ "$c" == "0" ]]; then # Нет BX=$bx BY=$by else SoundBoom local h=0 v=0 declare -i h v [[ "${XY[$bx+$by+100]:-0}" != "0" ]] && v=1 [[ $by > 100 && "${XY[$bx+$by-100]:-0}" != "0" ]] && v="1$v" [[ "${XY[$bx+$by+1]:-0}" != "0" ]] && h=1 [[ $bx > 1 && "${XY[$bx+$by-1]:-0}" != "0" ]] && h="1$h" if [ $h -ge $v ]; then let BAY="-$BAY" fi if [ $h -le $v ]; then let BAX="-$BAX" fi let "BX+=$BAX" let "BY+=$BAY" # Проверка на столкновение с блоком if [[ $c =~ ☲ ]]; then # Ищем начало блока while [[ ${XY[$bx+$by-1]} =~ ☲ ]]; do let 'bx--' done # Выясняем цвет блока case ${XY[$bx+$by]} in # Этот блок будет преобразован в другой цвет *${MAPCOLORS[1]}* ) for y in {0..3}; do XY[$bx+$by+$y]="\033[${MAPCOLORS[2]}m☲" done y=$((30-$by/100)) echo -ne "\033[$(($bx+1))G\033[${y}A\033[${MAPCOLORS[2]}m☲☲☲☲\033[${y}B" PrintScores 2 ;; # Этот блок исчезает *${MAPCOLORS[2]}* ) RemoveBlock $bx $by PrintScores ;; # Этот блок исчезает, но даёт подарки *${MAPCOLORS[0]}* ) RemoveBlock $bx $by [ -z "$GT" ] && StartGift $BX $by PrintScores ;; esac fi fi fi fi local y=$((30-$BY/100)) echo -ne "\033[$(($BX+1))G\033[${y}A\033[38;5;15m◯\033[${y}B" } # Рисуем падающий подарок function PrintGift { echo -en "\033[$(($GX+1))G\033[${GY}A${XY[$GX+(30-$GY)*100]:- }" if [ $GY -le 1 ]; then echo -ne "\033[${GY}B" # Поймали подарок if [[ $GX -ge $CX && $GX -le $(($CX+$CW)) ]]; then PrintScores 5 case $GT in W) CreateСarriage 7 if [ $CX -gt $((75-$CW)) ]; then CX=$((75-$CW)) fi PrintLives SoundWide ;; S) STICKY=1 SoundStick ;; L) SoundLives let 'LIVES++' PrintLives esac fi GT= else let 'GY--' echo -ne "\n\033[38;5;34m\033[$(($GX+1))G☲\033[${GY}B" fi } # Печать жизней function PrintLives { echo -ne "\033[31A\033[3G\033[0;1m${LIVES} " echo -ne "\033[38;5;160m☗\033[38;5;202m$CBLOCKS\033[38;5;160m☗ \033[31B" } # Copyright function PrintCopy { echo -ne "\033[2B\033[52G\033[0;1mhttp://bolknote.ru © 2011\033[2A" } # Печать счёта function PrintScores { let "SCORE+=${1:-1}" PrintCopy echo -ne "\033[31A\033[$((69-${#SCORE}))G\033[0mScore: \033[1m$SCORE\033[31B" } # Переход на следующий уровень function NextLevel { XY=() CreateСarriage 5 CX=2 OCX= GX= GY= GT= BX=5 BY=2900 BAX=0 BAY=0 STICKY= DrawBox DrawMap $(($MAPNUMBER-1)) PrintScreen PrintLives PrintScores 0 } # Очистка клавиатурного буфера function ClearKeyboardBuffer { # Быстро — через bash 4+ [ $BASH -ge 4 ] && while read -t0.1 -n1 -rs; do :; done && return # Быстро — через zsh which zsh &>/dev/null && zsh -c 'while {} {read -rstk1 || break}' && return # Медленно — через bash 3- local delta while true; do delta=`(time -p read -rs -n1 -t1) 2>&1 | awk 'NR==1{print $2}'` [[ "$delta" == "0.00" ]] || break done } function Arcanoid { exec 2>&- CHLD= trap 'KeyEvent LEFT' USR1 trap 'KeyEvent RIGHT' USR2 trap 'KeyEvent SPACE' HUP trap "kill $PID" EXIT trap exit TERM echo -e "\033[J\n\n" NextLevel local i j while true; do [ -n "$GT" ] && PrintGift for i in {1..2}; do PrintСarriage PrintBall for j in {1..5}; do sleep 0.02; PrintСarriage done sleep 0.02 done done } function Restore { [ -n "$CHILD" ] && kill $CHILD wait stty "$ORIG" echo -e "\033[?25h\033[0m" (bind '"\r":accept-line') &>/dev/null CHILD= trap '' EXIT HUP ClearKeyboardBuffer exit } # Запрещаем печатать вводимое на экран ORIG=`stty -g` stty -echo (bind -r '\r') &>/dev/null trap 'Restore' EXIT HUP trap '' TERM # Убирам курсор echo -en "\033[?25l\033[0m" Arcanoid & CHILD=$! # Клавиатурные комбинации извстной длины SEQLEN=(1b5b4. [2-7]. [cd]... [89ab].{5} f.{7}) # Проверка совпадения с известной клавиатурной комбинацией function CheckCons { local i for i in ${SEQLEN[@]}; do if [[ $1 =~ ^$i ]]; then return 0 fi done return 1 } # Функция реакции на клавиатуру, вызывает React на каждую нажатую клавишу function PressEvents { local real code action ch # Цикл обработки клавиш, здесь считываются коды клавиш, # по паузам между нажатиями собираются комбинации и известные # обрабатываются сразу while true; do # измеряем время выполнения команды read и смотрим код нажатой клавиши # akw NR==1||NR==4 забирает только строку №1 (там время real) и №4 (код клавиши) eval $( (time -p read -r -s -n1 ch; printf 'code %d\n' "'$ch") 2>&1 | awk 'NR==1||NR==4 {print $1 "=" $2}' | tr '\r\n' ' ') # read возвращает пусто для Enter и пробела, присваиваем им код 20, # а так же возвращаются отрицательные коды для UTF8 if [ "$code" = 0 ]; then React 20 else [ $code -lt 0 ] && code=$((256+$code)) code=$(printf '%02x' $code) fi # Если клавиши идут подряд (задержки по времени нет) if [[ $real =~ ^0[.,]00$ ]]; then seq="$seq$code" if CheckCons $seq; then React $seq seq= fi # Клавиши идут с задержкой (пользователь не может печатать с нулевой задержкой), # значит последовательность собрана, надо начинать новую else [ "$seq" ] && React $seq seq=$code # возможно последовательность состоит из одного символа if CheckCons $seq; then React $seq seq= fi fi done } function React { case $1 in 1b5b44) kill -USR1 $CHILD ;; 1b5b43) kill -USR2 $CHILD ;; *) kill -HUP $CHILD ;; esac &>/dev/null } PressEvents