random-scripts/BashCanoid.sh
iAmInAction 58693ec78f
Add files via upload
Fully playable bash implementation of the Arcanoid game. Uses beep for sound.
The game requires a terminal with a minimal size of 78x34 and UTF-8 support.
2023-07-08 13:02:21 +00:00

625 lines
11 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# BashCanoid
# A version of arcanoid written in bash.
# Made by mueller_minki in 2022.
if [[ $(tput lines) -le 33 ]]
then
echo "This game requires a terminal with a minimal size of 78x34."
exit 4
fi
if [[ $(tput cols) -le 77 ]]
then
echo "This game requires a terminal with a minimal size of 78x34."
exit 4
fi
PID=$$
MAPCOLORS=("38;5;"{34,24,204})
declare -a MAPS
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"
)
SCORE=0
LIVES=5
MAPQUANT=
MAPNUMBER=1
STICKY=
function CreateСarriage {
CW=$1
CSPACES=$(printf "% $(($CW+2))s")
CBLOCKS=$(printf "%0$(($CW-2))s" | sed 's/0/☰/g')
}
CreateСarriage 5
CX=2 OCX=
GX= GY= GT=
BX=5 BY=2900
BAX=0 BAY=0
BASH=(${BASH_VERSION/./ })
declare -a XY
which say &>/dev/null || function say {
:
}
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
}
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
}
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 {
if [ -z "$OCX" ]; then
echo -ne "\033[$(($CX+1))G"
else
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=
}
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
echo -ne "\033[2A\033[20B"
}
function SoundGameover {
(beep -l 80 -f 60 -n -l 10 -n -l 80 -f 60 -n -l 10 -n -l 80 -f 60 -n -l 10 -n -l 80 -f 60 &>/dev/null) &
}
function SoundSpace {
(beep -l 20 -f 800 &>/dev/null) &
}
function SoundBoom {
(beep -l 20 -f 600 &>/dev/null) &
}
function SoundStick {
(beep -l 20 -f 120 &>/dev/null) &
}
function SoundWide {
(beep -l 20 -f 120 &>/dev/null) &
}
function SoundOut {
(beep -l 20 -f 600 &>/dev/null) &
}
function SoundWelcome {
(beep -l 100 -f 430 -d 100 -n -l 200 -f 520 -d 100 -n -l 200 -f 603 &>/dev/null) &
}
function SoundLives {
(beep -l 100 -f 659 -n -l 100 -f 783 -n -l 100 -f 1318 -n -l 100 -f 1046 -n -l 100 -f 1174 -n -l 100 -f 1567 &>/dev/null ) &
}
function SoundWin {
(beep -l 100 -f 392 -n -l 600 -f 392 &>/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"
}
# Print falling down gift
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"
}
function PrintCopy {
echo -ne "\033[2B\033[52G\033[0;1m made by mueller_minki\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 -ge 4 ] && while read -t0.1 -n1 -rs; do :; done && return
which zsh &>/dev/null && zsh -c 'while {} {read -rstk1 || break}' && return
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
}
function PressEvents {
local real code action ch
while true; do
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' ' ')
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