#!/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