diff options
author | Benjamin Chausse <benjamin@chausse.xyz> | 2020-10-03 19:27:15 -0400 |
---|---|---|
committer | Benjamin Chausse <benjamin@chausse.xyz> | 2020-10-03 19:27:15 -0400 |
commit | e371d9edd474bcf89cf5d462eaccb8638900b390 (patch) | |
tree | ed07118f5c514a55f23a779a1507640e46fd9ed2 /.local/bin/bonsai |
Initial commit
Diffstat (limited to '.local/bin/bonsai')
-rwxr-xr-x | .local/bin/bonsai | 554 |
1 files changed, 554 insertions, 0 deletions
diff --git a/.local/bin/bonsai b/.local/bin/bonsai new file mode 100755 index 0000000..ce74ab3 --- /dev/null +++ b/.local/bin/bonsai @@ -0,0 +1,554 @@ +#!/usr/bin/env bash + +# I'm a bonsai-making machine! + +################################################# +## +# author: John Allbritten +# my website: theSynAck.com +# +# repo: https://gitlab.com/jallbrit +# script can be found in the bin/bin/fun folder. +# +# license: this script is published under GPLv3. +# I don't care what you do with it, but I do ask +# that you leave this message please! +# +# inspiration: http://andai.tv/bonsai/ +# andai's version was written in JS and served +# as the basis for this script. Originally, this +# was just a port. +## +################################################# + +# ------ vars ------ +# CLI options + +flag_h=false +live=false +infinite=false + +termCols=$(tput cols) +termRows=$(tput lines) +geometry="$((termCols - 1)),$termRows" + +leafchar='&' +termColors=false + +message="" +flag_m=false +basetype=1 +multiplier=5 + +lifeStart=28 +steptime=0.01 # time between steps + +# non-CLI options +lineWidth=4 # words per line + +# ------ parse options ------ + +OPTS="hlt:ig:c:Tm:b:M:L:" # the colon means it requires a value +LONGOPTS="help,live,time:,infinite,geo:,leaf:,termcolors,message:,base:,multiplier:,life:" + +parsed=$(getopt --options=$OPTS --longoptions=$LONGOPTS -- "$@") +eval set -- "${parsed[@]}" + +while true; do + case "$1" in + -h|--help) + flag_h=true + shift + ;; + + -l|--live) + live=true + shift + ;; + + -t|--time) + steptime="$2" + shift 2 + ;; + + -i|--infinite) + infinite=true + shift + ;; + + -g|--geo) + geo=$2 + shift 2 + ;; + + -c|--leaf) + leafchar="$2" + shift 2 + ;; + + -T|--termcolors) + termColors=true + shift + ;; + + -m|--message) + flag_m=true + message="$2" + shift 2 + ;; + + -b|--basetype) + basetype="$2" + shift 2 + ;; + + -M|--multiplier) + multiplier="$2" + shift 2 + ;; + + -L|--life) + lifeStart="$2" + shift 2 + ;; + + --) # end of arguments + shift + break + ;; + + *) + echo "error while parsing CLI options" + flag_h=true + ;; + esac +done + +HELP="Usage: bonsai [-h] [-i] [-l] [-T] [-m message] [-t time] + [-g x,y] [ -c char] [-M 0-9] + +bonsai.sh is a static and live bonsai tree generator, written in bash. + +optional args: + -l, --live enable live generation + -t, --time time time between each step of growth [default: 0.01] + -m, --message text attach a message to the tree + -b, --basetype 0-2 which ascii-art plant base to use (0 for none) [default: 1] + -i, --infinite keep generating trees until quit (2s between each) + -T, --termcolors use terminal colors + -g, --geo geo set custom geometry [default: fit to terminal] + -c, --leaf char character used for leaves [default: &] + -M, --multiplier 0-9 branch multiplier; higher equals more branching [default: 5] + -L, --life int life of tree; higher equals more overall growth [default: 28] + -h, --help show help" + +# check for help +$flag_h && echo -e "$HELP" && exit 0 + +# geometry processing +cols=$(echo "$geometry" | cut -d ',' -f1) # width; X +rows=$(echo "$geometry" | cut -d ',' -f2) # height; Y + +IFS=$'\n' # delimit strings by newline +tabs 4 # set tabs to 4 spaces + +declare -A gridMessage + +# message processing +if [ $flag_m = true ]; then + + messageWidth=20 + + # make room for the message to go on the right side + cols=$((cols - messageWidth - 8 )) + + # wordwrap message, delimiting by spaces + message="$(echo "$message" | fold -sw $messageWidth)" + + # get number of lines in the message + messageLineCount=0 + for line in $message; do + messageLineCount=$((messageLineCount + 1)) + done + + messageOffset=$((rows - messageLineCount - 7)) + + # put lines of message into a grid + index=$messageOffset + for line in $message; do + gridMessage[$index]="$line" + index=$((index + 1)) + done +fi + +# define colors +if [ $termColors = true ]; then + LightBrown='\e[1;33m' + DarkBrown='\e[0;33m' + BrownGreen='\e[1;32m' + Green='\e[0;32m' +else + LightBrown='\e[38;5;172m' + DarkBrown='\e[38;5;130m' + BrownGreen='\e[38;5;142m' + Green='\e[38;5;106m' +fi +Grey='\e[1;30m' +R='\e[0m' + +# create ascii base in lines +base="" +case $basetype in + 0) + base="" ;; + + 1) + width=15 + art="\ +${Grey}:${Green}___________${DarkBrown}./~~\\.${Green}___________${Grey}: + \\ / + \\________________________/ + (_) (_)" + ;; + + 2) + width=7 + art="\ +${Grey}(${Green}---${DarkBrown}./~~\\.${Green}---${Grey}) + ( ) + (________)" + ;; +esac + +# get base height +baseHeight=0 +for line in $art; do + baseHeight=$(( baseHeight + 1 )) +done + +# add spaces before base so that it's in the middle of the terminal +iter=1 +for line in $art; do + filler='' + for (( i=0; i < $(( (cols / 2) - width )); i++)); do + filler+=" " + done + base+="${filler}${line}" + [ $iter -ne $baseHeight ] && base+='\n' + iter=$((iter+1)) +done +unset IFS # reset delimiter + +rows=$((rows - baseHeight)) + +declare -A grid # must be done outside function for unknown reason + +trap 'echo "press q to quit"' SIGINT # disable CTRL+C + +init() { + branches=0 + shoots=0 + + branchesMax=$((multiplier * 110)) + shootsMax=$multiplier + + # fill grid full of spaces + for (( row=0; row < $rows; row++ )); do + for (( col=0; col < $cols; col++ )); do + grid[$row,$col]=' ' + done + done + + # No echo stdin and hide the cursor + if [ $live = true ]; then + stty -echo + echo -ne "\e[?25l" + + echo -ne "\e[2J" + fi +} + +grow() { + local start=$((cols / 2)) + + local x=$((cols / 2)) # start halfway across the screen + local y=$rows # start just above the base + + branch $x $y trunk $lifeStart +} + +branch() { + # argument declarations + local x=$1 + local y=$2 + local type=$3 + local life=$4 + local dx=0 + local dy=0 + + # check if the user is hitting q + timeout=0.001 + [ $live = "false" ] && timeout=.0001 + read -n 1 -t $timeout input + [ "$input" = "q" ] && clean "quit" + + branches=$((branches + 1)) + + # as long as we're alive... + while [ $life -gt 0 ]; do + + life=$((life - 1)) # ensure life ends + + # case $life in + # [0]) type=dead ;; + # [1-4]) type=dying ;; + # esac + + # set dy based on type + case $type in + shoot*) # if this is a shoot, trend horizontal/downward growth + case "$((RANDOM % 10))" in + [0-1]) dy=-1 ;; + [2-7]) dy=0 ;; + [8-9]) dy=1 ;; + esac + ;; + + dying) # discourage vertical growth + case "$((RANDOM % 10))" in + [0-1]) dy=-1 ;; + [2-8]) dy=0 ;; + [9-10]) dy=1 ;; + esac + ;; + + *) # otherwise, let it grow up/not at all + dy=0 + [ $life -ne $lifeStart ] && [ $((RANDOM % 10)) -gt 2 ] && dy=-1 + ;; + esac + # if we're about to hit the ground, cut it off + [ $dy -gt 0 ] && [ $y -gt $(( rows - 1 )) ] && dy=0 + [ $type = "trunk" ] && [ $life -lt 4 ] && dy=0 + + # set dx based on type + case $type in + shootLeft) # tend left: dx=[-2,1] + case $(( RANDOM % 10 )) in + [0-1]) dx=-2 ;; + [2-5]) dx=-1 ;; + [6-8]) dx=0 ;; + [9]) dx=1 ;; + esac ;; + + shootRight) # tend right: dx=[-1,2] + case $(( RANDOM % 10 )) in + [0-1]) dx=2 ;; + [2-5]) dx=1 ;; + [6-8]) dx=0 ;; + [9]) dx=-1 ;; + esac ;; + + dying) # tend left/right: dx=[-3,3] + dx=$(( (RANDOM % 7) - 3)) ;; + + *) # tend equal: dx=[-1,1] + dx=$(( (RANDOM % 3) - 1)) ;; + + esac + + # re-branch upon conditions + if [ $branches -lt $branchesMax ]; then + + # branch is dead + if [ $life -lt 3 ]; then + branch $x $y dead $life + + # branch is dying and needs to branch into leaves + elif [ $type = trunk ] && [ $life -lt $((multiplier + 2)) ]; then + branch $x $y dying $life + + elif [[ $type = "shoot"* ]] && [ $life -lt $((multiplier + 2)) ]; then + branch $x $y dying $life + + # re-branch if: not close to the base AND (pass a chance test OR be a trunk, not have too man shoots already, and not be about to die) + elif [[ $type = trunk && $life -lt $((lifeStart - 8)) \ + && ( $(( RANDOM % (16 - multiplier) )) -eq 0 \ + || ($type = trunk && $(( life % 5 )) -eq 0 && $life -gt 5) ) ]]; then + + # if a trunk is splitting and not about to die, chance to create another trunk + if [ $((RANDOM % 3)) -eq 0 ] && [ $life -gt 7 ]; then + branch $x $y trunk $life + + elif [ $shoots -lt $shootsMax ]; then + + # give the shoot some life + tmpLife=$(( life + multiplier - 2 )) + [ $tmpLife -lt 0 ] && tmpLife=0 + + # first shoot is randomly directed + if [ $shoots -eq 0 ]; then + tmpType=shootLeft + [ $((RANDOM % 2)) -eq 0 ] && tmpType=shootRight + + + # secondary shoots alternate from the first + else + case $tmpType in + shootLeft) # last shoot was left, shoot right + tmpType=shootRight ;; + shootRight) # last shoot was right, shoot left + tmpType=shootLeft ;; + esac + fi + branch $x $y $tmpType $tmpLife + shoots=$((shoots + 1)) + fi + fi + else # if we're past max branches but want to branch... + char='<>' + fi + + # implement dx,dy + x=$((x + dx)) + y=$((y + dy)) + + # choose color + case $type in + trunk|shoot*) + color=${DarkBrown} + [ $(( RANDOM % 4 )) -eq 0 ] && color=${LightBrown} + ;; + + dying) color=${BrownGreen} ;; + + dead) color=${Green} ;; + esac + + # choose branch character + case $type in + trunk) + if [ $dx -lt 0 ]; then + char='\\' + elif [ $dx -eq 0 ]; then + char='/|' + elif [ $dx -gt 0 ]; then + char='/' + fi + [ $dy -eq 0 ] && char='/~' # not growing + #[ $dy -lt 0 ] && char='/~' # growing + ;; + + # shoots tend to look horizontal + shootLeft) + case $dx in + [-3,-1]) char='\\|' ;; + [0]) char='/|' ;; + [1,3]) char='/' ;; + esac + #[ $dy -lt 0 ] && char='/~' # growing up + [ $dy -gt 0 ] && char='/' # growing down + [ $dy -eq 0 ] && char='\\_' # not growing + ;; + + shootRight) + case $dx in + [-3,-1]) char='\\|' ;; + [0]) char='/|' ;; + [1,3]) char='/' ;; + esac + #[ $dy -lt 0 ] && char='' # growing up + [ $dy -gt 0 ] && char='\\' # growing down + [ $dy -eq 0 ] && char='_/' # not growing + ;; + + #dead) + # #life=$((life + 1)) + # char="${leafchar}" + # [ $dx -lt -2 ] || [ $dx -gt 2 ] && char="${leafchar}${leafchar}" + # ;; + + esac + + # set leaf if needed + [ $life -lt 4 ] && char="${leafchar}" + + # uncomment for help debugging + #echo -e "$life:\t$x, $y: $char" + + # put character in grid + grid[$y,$x]="${color}${char}${R}" + + # if live, print what we have so far and let the user see it + if [ $live = true ]; then + print + sleep $steptime + fi + done +} + +print() { + # parse grid for output + output="" + for (( row=0; row < $rows; row++)); do + + line="" + + for (( col=0; col < $cols; col++ )); do + + # this prints a space at 0,0 and is necessary at the moment + [ $live = true ] && echo -ne "\e[0;0H " + + # grab the character from our grid + line+="${grid[$row,$col]}" + done + + # add our message + if [ $flag_m = true ]; then + # remove trailing whitespace before we add our message + line=$(sed -r 's/[ \t]*$//' <(printf "$line")) + line+=" \t${gridMessage[$row]}" + fi + + line="${line}\n" + + # end 'er with the ol' newline + output+="$line" + done + + # add the ascii-art base we generated earlier + output+="$base" + + # output, removing trailing whitespace + sed -r 's/[ \t]*$//' <(printf "$output") +} + +clean() { + # Show cursor and echo stdin + if [ $live = true ]; then + echo -ne "\e[?25h" + stty echo + fi + + echo "" # ensure the cursor resets to the next line + + # if we wanna quit + if [ "$1" = "quit" ]; then + trap SIGINT + exit 0 + fi +} + +bonsai() { + init + grow + print + clean +} + +bonsai + +while [ $infinite = true ]; do + sleep 2 + bonsai +done |