scripts

Scripts for general automations
git clone git://git.laack.co/scripts.git
Log | Files | Refs

pipes.sh (11325B)


      1 #!/usr/bin/env bash
      2 # pipes.sh: Animated pipes terminal screensaver.
      3 # https://github.com/pipeseroni/pipes.sh
      4 #
      5 # Copyright (c) 2015-2018 Pipeseroni/pipes.sh contributors
      6 # Copyright (c) 2013-2015 Yu-Jie Lin
      7 # Copyright (c) 2010 Matthew Simpson
      8 #
      9 # Permission is hereby granted, free of charge, to any person obtaining a copy
     10 # of this software and associated documentation files (the "Software"), to deal
     11 # in the Software without restriction, including without limitation the rights
     12 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     13 # copies of the Software, and to permit persons to whom the Software is
     14 # furnished to do so, subject to the following conditions:
     15 #
     16 # The above copyright notice and this permission notice shall be included in
     17 # all copies or substantial portions of the Software.
     18 #
     19 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     20 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     21 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     22 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     23 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     24 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     25 # SOFTWARE.
     26 
     27 
     28 VERSION=1.3.0
     29 
     30 M=32768  # Bash RANDOM maximum + 1
     31 p=1      # number of pipes
     32 f=75     # frame rate
     33 s=13     # probability of straight fitting
     34 r=2000   # characters limit
     35 t=0      # iteration counter for -r character limit
     36 w=80     # terminal size
     37 h=24
     38 
     39 # ab -> sets[][idx] = a*4 + b
     40 # 0: up, 1: right, 2: down, 3: left
     41 # 00 means going up   , then going up   -> ┃
     42 # 12 means going right, then going down -> ┓
     43 sets=(
     44     "┃┏ ┓┛━┓  ┗┃┛┗ ┏━"
     45     "│╭ ╮╯─╮  ╰│╯╰ ╭─"
     46     "│┌ ┐┘─┐  └│┘└ ┌─"
     47     "║╔ ╗╝═╗  ╚║╝╚ ╔═"
     48     "|+ ++-+  +|++ +-"
     49     "|/ \/-\  \|/\ /-"
     50     ".. ....  .... .."
     51     ".o oo.o  o.oo o."
     52     "-\ /\|/  /-\/ \|"  # railway
     53     "╿┍ ┑┚╼┒  ┕╽┙┖ ┎╾"  # knobby pipe
     54 )
     55 SETS=()  # rearranged all pipe chars into individul elements for easier access
     56 
     57 # pipes'
     58 x=()  # current position
     59 y=()
     60 l=()  # current directions
     61       # 0: up, 1: right, 2: down, 3: left
     62 n=()  # new directions
     63 v=()  # current types
     64 c=()  # current escape codes
     65 
     66 # selected pipes'
     67 V=()  # types (indexes to sets[])
     68 C=()  # color indices for tput setaf
     69 VN=0  # number of selected types
     70 CN=0  # number of selected colors
     71 E=()  # pre-generated escape codes from BOLD, NOCOLOR, and C
     72 
     73 # switches
     74 RNDSTART=0  # randomize starting position and direction
     75 BOLD=1
     76 NOCOLOR=0
     77 KEEPCT=0    # keep pipe color and type
     78 
     79 
     80 # print help message in 72-char width
     81 print_help() {
     82     local cgap
     83     printf -v cgap '%*s' $((15 - ${#COLORS})) ''
     84     cat <<HELP
     85 Usage: $(basename $0) [OPTION]...
     86 Animated pipes terminal screensaver.
     87 
     88   -p [1-]               number of pipes (D=1)
     89   -t [0-$((${#sets[@]} - 1))]              pipe type (D=0)
     90   -t c[16 chars]        custom pipe type
     91   -c [0-$COLORS]${cgap}pipe color INDEX (TERM=$TERM), can be
     92                         hexadecimal with '#' prefix
     93                         (D=-c 1 -c 2 ... -c 7 -c 0)
     94   -f [20-100]           framerate (D=75)
     95   -s [5-15]             going straight probability, 1 in (D=13)
     96   -r [0-]               reset after (D=2000) characters, 0 if no reset
     97   -R                    randomize starting position and direction
     98   -B                    no bold effect
     99   -C                    no color
    100   -K                    keep pipe color and type when crossing edges
    101   -h                    print this help message
    102   -v                    print version number
    103 
    104 Note: -t and -c can be used more than once.
    105 HELP
    106 }
    107 
    108 
    109 # parse command-line options
    110 # It depends on a valid COLORS which is set by _CP_init_termcap_vars
    111 parse() {
    112     # test if $1 is a natural number in decimal, an integer >= 0
    113     is_N() {
    114         [[ -n $1 && -z ${1//[0-9]} ]]
    115     }
    116 
    117 
    118     # test if $1 is a hexadecimal string
    119     is_hex() {
    120         [[ -n $1 && -z ${1//[0-9A-Fa-f]} ]]
    121     }
    122 
    123 
    124     # print error message for invalid argument to standard error, this
    125     # - mimics getopts error message
    126     # - use all positional parameters as error message
    127     # - has a newline appended
    128     # $arg and $OPTARG are the option name and argument set by getopts.
    129     pearg() {
    130         printf "%s: -$arg invalid argument -- $OPTARG; %s\n" "$0" "$*" >&2
    131     }
    132 
    133 
    134     OPTIND=1
    135     while getopts "p:t:c:f:s:r:RBCKhv" arg; do
    136     case $arg in
    137         p)
    138             if is_N "$OPTARG" && ((OPTARG > 0)); then
    139                 p=$OPTARG
    140             else
    141                 pearg 'must be an integer and greater than 0'
    142                 return 1
    143             fi
    144             ;;
    145         t)
    146             if [[ "$OPTARG" = c???????????????? ]]; then
    147                 V+=(${#sets[@]})
    148                 sets+=("${OPTARG:1}")
    149             elif is_N "$OPTARG" && ((OPTARG < ${#sets[@]})); then
    150                 V+=($OPTARG)
    151             else
    152                 pearg 'must be an integer and from 0 to' \
    153                       "$((${#sets[@]} - 1)); or a custom type"
    154                 return 1
    155             fi
    156             ;;
    157         c)
    158             if [[ $OPTARG == '#'* ]]; then
    159                 if ! is_hex "${OPTARG:1}"; then
    160                     pearg 'unrecognized hexadecimal string'
    161                     return 1
    162                 fi
    163                 if ((16$OPTARG >= COLORS)); then
    164                     pearg 'hexadecimal must be from #0 to' \
    165                           "#$(printf '%X' $((COLORS - 1)))"
    166                     return 1
    167                 fi
    168                 C+=($((16$OPTARG)))
    169             elif is_N "$OPTARG" && ((OPTARG < COLORS)); then
    170                 C+=($OPTARG)
    171             else
    172                 pearg "must be an integer and from 0 to $((COLORS - 1));" \
    173                       'or a hexadecimal string with # prefix'
    174                 return 1
    175             fi
    176             ;;
    177         f)
    178             if is_N "$OPTARG" && ((OPTARG >= 20 && OPTARG <= 100)); then
    179                 f=$OPTARG
    180             else
    181                 pearg 'must be an integer and from 20 to 100'
    182                 return 1
    183             fi
    184             ;;
    185         s)
    186             if is_N "$OPTARG" && ((OPTARG >= 5 && OPTARG <= 15)); then
    187                 s=$OPTARG
    188             else
    189                 pearg 'must be an integer and from 5 to 15'
    190                 return 1
    191             fi
    192             ;;
    193         r)
    194             if is_N "$OPTARG"; then
    195                 r=$OPTARG
    196             else
    197                 pearg 'must be a non-negative integer'
    198                 return 1
    199             fi
    200             ;;
    201         R) RNDSTART=1;;
    202         B) BOLD=0;;
    203         C) NOCOLOR=1;;
    204         K) KEEPCT=1;;
    205         h)
    206             print_help
    207             exit 0
    208             ;;
    209         v) echo "$(basename -- "$0") $VERSION"
    210             exit 0
    211             ;;
    212         *)
    213             return 1
    214         esac
    215     done
    216 
    217     shift $((OPTIND - 1))
    218     if (($#)); then
    219         printf "$0: illegal arguments -- $*; no arguments allowed\n" >&2
    220         return 1
    221     fi
    222 }
    223 
    224 
    225 cleanup() {
    226     # clear out standard input
    227     read -t 0.001 && cat </dev/stdin>/dev/null
    228 
    229     tput reset  # fix for konsole, see pipeseroni/pipes.sh#43
    230     tput rmcup
    231     tput cnorm
    232     stty echo
    233     printf "$SGR0"
    234     exit 0
    235 }
    236 
    237 
    238 resize() {
    239     w=$(tput cols) h=$(tput lines)
    240 }
    241 
    242 
    243 init_pipes() {
    244     # +_CP_init_pipes
    245     local i
    246 
    247     ci=$((KEEPCT ? 0 : CN * RANDOM / M))
    248     vi=$((KEEPCT ? 0 : VN * RANDOM / M))
    249     for ((i = 0; i < p; i++)); do
    250         ((
    251             n[i] = 0,
    252             l[i] = RNDSTART ? RANDOM % 4 : 0,
    253             x[i] = RNDSTART ? w * RANDOM / M : w / 2,
    254             y[i] = RNDSTART ? h * RANDOM / M : h / 2,
    255             v[i] = V[vi]
    256         ))
    257         c[i]=${E[ci]}
    258         ((ci = (ci + 1) % CN, vi = (vi + 1) % VN))
    259     done
    260     # -_CP_init_pipes
    261 }
    262 
    263 
    264 init_screen() {
    265     stty -echo
    266     tput smcup
    267     tput civis
    268     tput clear
    269     trap cleanup HUP TERM
    270 
    271     resize
    272     trap resize SIGWINCH
    273 }
    274 
    275 
    276 main() {
    277     # simple pre-check of TERM, tput's error message should be enough
    278     tput -T "$TERM" sgr0 >/dev/null || return $?
    279 
    280     # +_CP_init_termcap_vars
    281     COLORS=$(tput colors)  # COLORS - 1 == maximum color index for -c argument
    282     SGR0=$(tput sgr0)
    283     SGR_BOLD=$(tput bold)
    284     # -_CP_init_termcap_vars
    285 
    286     parse "$@" || return $?
    287 
    288     # +_CP_init_VC
    289     # set default values if not by options
    290     ((${#V[@]})) || V=(0)
    291     VN=${#V[@]}
    292     ((${#C[@]})) || C=(1 2 3 4 5 6 7 0)
    293     CN=${#C[@]}
    294     # -_CP_init_VC
    295 
    296     # +_CP_init_E
    297     # generate E[] based on BOLD (SGR_BOLD), NOCOLOR, and C for each element in
    298     # C, a corresponding element in E[] =
    299     #   SGR0
    300     #   + SGR_BOLD, if BOLD
    301     #   + tput setaf C, if !NOCOLOR
    302     local i
    303     for ((i = 0; i < CN; i++)) {
    304         E[i]=$SGR0
    305         ((BOLD))    && E[i]+=$SGR_BOLD
    306         ((NOCOLOR)) || E[i]+=$(tput setaf ${C[i]})
    307     }
    308     # -_CP_init_E
    309 
    310     # +_CP_init_SETS
    311     local i j
    312     for ((i = 0; i < ${#sets[@]}; i++)) {
    313         for ((j = 0; j < 16; j++)) {
    314             SETS+=("${sets[i]:j:1}")
    315         }
    316     }
    317     unset i j
    318     # -_CP_init_SETS
    319 
    320     init_screen
    321     init_pipes
    322 
    323     # any key press exits the loop and this script
    324     trap 'break 2' INT
    325 
    326     local i
    327     while REPLY=; do
    328         read -t 0.0$((1000 / f)) -n 1 2>/dev/null
    329         case "$REPLY" in
    330             P) ((s = s <  15 ? s + 1 : s));;
    331             O) ((s = s >   3 ? s - 1 : s));;
    332             F) ((f = f < 100 ? f + 1 : f));;
    333             D) ((f = f >  20 ? f - 1 : f));;
    334             B) ((BOLD = (BOLD + 1) % 2));;
    335             C) ((NOCOLOR = (NOCOLOR + 1) % 2));;
    336             K) ((KEEPCT = (KEEPCT + 1) % 2));;
    337             ?) break;;
    338         esac
    339         for ((i = 0; i < p; i++)); do
    340             # New position:
    341             # l[] direction = 0: up, 1: right, 2: down, 3: left
    342             # +_CP_newpos
    343             ((l[i] % 2)) && ((x[i] += -l[i] + 2, 1)) || ((y[i] += l[i] - 1))
    344             # -_CP_newpos
    345 
    346             # Loop on edges (change color on loop):
    347             # +_CP_warp
    348             ((!KEEPCT && (x[i] >= w || x[i] < 0 || y[i] >= h || y[i] < 0))) \
    349             && { c[i]=${E[CN * RANDOM / M]}; ((v[i] = V[VN * RANDOM / M])); }
    350             ((x[i] = (x[i] + w) % w,
    351               y[i] = (y[i] + h) % h))
    352             # -_CP_warp
    353 
    354             # new turning direction:
    355             # $((s - 1)) in $s, going straight, therefore n[i] == l[i];
    356             # and 1 in $s that pipe makes a right or left turn
    357             #
    358             #     s * RANDOM / M - 1 == 0
    359             #     n[i] == -1
    360             #  => n[i] == l[i] + 1 or l[i] - 1
    361             # +_CP_newdir
    362             ((
    363                 n[i] = s * RANDOM / M - 1,
    364                 n[i] = n[i] >= 0 ? l[i] : l[i] + (2 * (RANDOM % 2) - 1),
    365                 n[i] = (n[i] + 4) % 4
    366             ))
    367             # -_CP_newdir
    368 
    369             # Print:
    370             # +_CP_print
    371             printf '\e[%d;%dH%s%s'                      \
    372                    $((y[i] + 1)) $((x[i] + 1)) ${c[i]}  \
    373                    "${SETS[v[i] * 16 + l[i] * 4 + n[i]]}"
    374             # -_CP_print
    375             l[i]=${n[i]}
    376         done
    377         ((r > 0 && t * p >= r)) && tput reset && tput civis && t=0 || ((t++))
    378     done
    379 
    380     cleanup
    381 }
    382 
    383 
    384 # when being sourced, $0 == bash, only invoke main when they are the same
    385 [[ "$0" != "$BASH_SOURCE" ]] ||  main "$@"