dotfiles: leviathan: arch-openbox-20231010
This commit is contained in:
554
.local/bin/bonsai
Executable file
554
.local/bin/bonsai
Executable file
@ -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
|
Reference in New Issue
Block a user