#!/bin/bash

# game of life

# usage: see --help

if [[ "$*" == "-h" ]] || [[ "$*" == "--help" ]]; then

cat <<EOF
Examples:

    gameOfLife             # 5x5   
    gameOfLife +           # 10x10 
    gameOfLife ++          # 20x20  
    gameOfLife +++         # 30x30 
    gameOfLife ++++        # 40x40 
    gameOfLife +++++       # 50x50 
    gameOfLife ++++++      # 160x90

    gen="200" gameOfLife   # user defined maximum generations (default=100)
    seed="123" gameOfLife  # user defined seed for first random population (otherwise random)
    sleep="0.3" gameOfLife # user defined sleep between refresh (default=0)

    --noscroll             # will clear screen and refresh on the face of the place
    -h or --help           # this screen

Other, zsh:

    repeat 150 gameOfLife + --noscroll

Other, store output using tee:

    gameOfLife | tee -a output.txt

EOF

exit 

fi

# var
width="5"; height="5"

dead="⬜"
life="⬛"

[[ -z "$sleep" ]] && sleep="0"

debug="0"

declare -A grid
declare -A grid2

[[ -z "$gen" ]] && gen="100"

if [[ $1 == "+" ]]; then width="10"; height="10"; fi
if [[ $1 == "++" ]]; then width="20"; height="20"; fi
if [[ $1 == "+++" ]]; then width="30"; height="30"; fi
if [[ $1 == "++++" ]]; then width="40"; height="40"; fi
if [[ $1 == "+++++" ]]; then width="50"; height="50"; fi
if [[ $1 == "++++++" ]]; then width="90"; height="160"; fi

if [[ "$*" == *"--noscroll"* ]]
then
    scroll="0"
    tput civis
else
    scroll="1"
    tput cnorm
fi

function cleanup() {
    tput cnorm
}
trap cleanup EXIT

if [[ ! $scroll -eq 1 ]]; then
    clear
    tput cup 0 0
fi

# 'seed' is user input or random
[[ -z "$seed" ]] && seed="$RANDOM$RANDOM$RANDOM$RANDOM" 
RANDOM="$seed"

# populate array named grid
for (( x = 1; x <= width; x++ ))
do
    for (( y = 1; y <= height; y++ ))
    do
        grid[${x},${y}]="$(( RANDOM % 2 ))"
        #echo -n "${grid[${x},${y}]}" #debug
    done
    #echo
done

draw () {
    # echo grid array
    #echo "1 2 3 4 5 6 7 8 9 10"
    for (( x = 1; x <= width; x++ ))
    do
        for (( y = 1; y <= height; y++ ))
        do     
            tmp="${grid[${x},${y}]}"
            (( tmp )) && echo -n "$life" || echo -n "$dead"
        done
        echo
    done
    #echo "$seed "
}
draw # first random population

if [[ ! $scroll -eq 1 ]]; then
    tput cup 0 0
else
    echo
fi
sleep "$sleep"

fixborders () {
    xtmp="$1"; ytmp="$2" # global
    if (( xtmp < 1 )); then # left
        xtmp="$(( width ))"
    fi
    if (( ytmp < 1 )); then # top
        ytmp="$(( height ))"
    fi
    if (( xtmp > width )); then # right
        xtmp="1"
    fi
    if (( ytmp > height )); then # bottom
        ytmp="1"
    fi        
}

# alive?
alive () {
    if [[ ${grid[${xtmp},${ytmp}]} == "1" ]]; then
    ((neighbours+=1)) # global, one more live neighbour
    fi
}


# main loop
gridalive="0"
for i in $(seq $gen); do
    #echo "$i" # gen
    for (( x = 1; x <= width; x++ ))
    do
        for (( y = 1; y <= height; y++ ))
        do
            # 8 possible neighbours
            neighbours="0"
            # top left
            fixborders "$(( x - 1 ))" "$(( y - 1 ))"; alive
            # bottom left
            fixborders "$(( x - 1 ))" "$(( y + 1 ))"; alive
            # bottom right
            fixborders "$(( x + 1 ))" "$(( y + 1 ))"; alive
            # top center
            fixborders "$(( x ))" "$(( y - 1 ))"; alive
            # top right
            fixborders "$(( x + 1 ))" "$(( y - 1 ))"; alive
            # left
            fixborders "$(( x - 1 ))" "$(( y ))"; alive
            # right
            fixborders "$(( x + 1 ))" "$(( y ))"; alive
            # bottom center
            fixborders "$(( x ))" "$(( y + 1 ))"; alive 

            (( debug )) && echo "$x $y $xtmp $ytmp bottom center=${grid[${xtmp},${ytmp}]}"
            (( debug )) && echo "$x $y alive neighbours $neighbours"
            (( debug )) && echo "--------"
            # rules can be condensed into the following:
            # 1 - Any live cell with two or three live neighbours survives.
            # 2 - Any dead cell with three live neighbours becomes a live cell.
            # 3 - All other live cells die in the next generation. 
            #     Similarly, all other dead cells stay dead.
            
            # if alive and neigbours are 2 or 3
            if [[ ${grid[${x},${y}]} == "1" ]]; then # alive at the moment
                if (( neighbours > 1 && neighbours < 4 )); then # stays alive
                    grid2[${x},${y}]="1"; (( gridalive+=1 ))
                else # dies
                    grid2[${x},${y}]="0"
                fi
            fi
            # if dead and neigbours are 3
            if [[ ${grid[${x},${y}]} == "0" ]]; then # dead at the moment
                if (( neighbours == 3 )); then # becomes alive
                    grid2[${x},${y}]="1"; (( gridalive+=1 ))
                else # dies
                    grid2[${x},${y}]="0"
                fi
            fi
        done

    done

    # if all dead, then exit
    if (( gridalive == 0 )); then
        exit
    fi

    # if same as old one, also exit
    if [[ "${grid[*]}" == "${grid2[*]}" ]]; then 
        break
    fi

    # copy grid2 back to grid (the big swap)
    for key in "${!grid2[@]}"; do
        grid[$key]="${grid2[$key]}"
    done

    draw
    if [[ ! $scroll -eq 1 ]]; then
        tput cup 0 0
    else
        echo
    fi
    sleep "$sleep"

    # if no ones, then exit
    { echo "${grid[@]}" | grep 1 || break; } >/dev/null

done # end for loop

# defeat tput cup clearin the screen on last draw
if [[ ! $scroll -eq 1 ]]; then
    draw
fi