summaryrefslogtreecommitdiff
path: root/.local/bin/bonsai
diff options
context:
space:
mode:
Diffstat (limited to '.local/bin/bonsai')
-rwxr-xr-x.local/bin/bonsai554
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