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 "$@"