mirror of https://github.com/tLDP/LDP
389 lines
10 KiB
Bash
389 lines
10 KiB
Bash
#!/bin/bash
|
|
# life.sh: "Life in the Slow Lane"
|
|
# Author: Mendel Cooper
|
|
# License: GPL3
|
|
|
|
# Version 0.2: Patched by Daniel Albers
|
|
#+ to allow non-square grids as input.
|
|
# Version 0.2.1: Added 2-second delay between generations.
|
|
|
|
# ##################################################################### #
|
|
# This is the Bash script version of John Conway's "Game of Life". #
|
|
# "Life" is a simple implementation of cellular automata. #
|
|
# --------------------------------------------------------------------- #
|
|
# On a rectangular grid, let each "cell" be either "living" or "dead." #
|
|
# Designate a living cell with a dot, and a dead one with a blank space.#
|
|
# Begin with an arbitrarily drawn dot-and-blank grid, #
|
|
#+ and let this be the starting generation: generation 0. #
|
|
# Determine each successive generation by the following rules: #
|
|
# 1) Each cell has 8 neighbors, the adjoining cells #
|
|
#+ left, right, top, bottom, and the 4 diagonals. #
|
|
# #
|
|
# 123 #
|
|
# 4*5 The * is the cell under consideration. #
|
|
# 678 #
|
|
# #
|
|
# 2) A living cell with either 2 or 3 living neighbors remains alive. #
|
|
SURVIVE=2 #
|
|
# 3) A dead cell with 3 living neighbors comes alive, a "birth." #
|
|
BIRTH=3 #
|
|
# 4) All other cases result in a dead cell for the next generation. #
|
|
# ##################################################################### #
|
|
|
|
|
|
startfile=gen0 # Read the starting generation from the file "gen0" ...
|
|
# Default, if no other file specified when invoking script.
|
|
#
|
|
if [ -n "$1" ] # Specify another "generation 0" file.
|
|
then
|
|
startfile="$1"
|
|
fi
|
|
|
|
############################################
|
|
# Abort script if "startfile" not specified
|
|
#+ and
|
|
#+ default file "gen0" not present.
|
|
|
|
E_NOSTARTFILE=86
|
|
|
|
if [ ! -e "$startfile" ]
|
|
then
|
|
echo "Startfile \""$startfile"\" missing!"
|
|
exit $E_NOSTARTFILE
|
|
fi
|
|
############################################
|
|
|
|
|
|
ALIVE1=.
|
|
DEAD1=_
|
|
# Represent living and dead cells in the start-up file.
|
|
|
|
# -----------------------------------------------------#
|
|
# This script uses a 10 x 10 grid (may be increased,
|
|
#+ but a large grid will slow down execution).
|
|
ROWS=10
|
|
COLS=10
|
|
# Change above two variables to match desired grid size.
|
|
# -----------------------------------------------------#
|
|
|
|
GENERATIONS=10 # How many generations to cycle through.
|
|
# Adjust this upwards
|
|
#+ if you have time on your hands.
|
|
|
|
NONE_ALIVE=85 # Exit status on premature bailout,
|
|
#+ if no cells left alive.
|
|
DELAY=2 # Pause between generations.
|
|
TRUE=0
|
|
FALSE=1
|
|
ALIVE=0
|
|
DEAD=1
|
|
|
|
avar= # Global; holds current generation.
|
|
generation=0 # Initialize generation count.
|
|
|
|
# =================================================================
|
|
|
|
let "cells = $ROWS * $COLS" # How many cells.
|
|
|
|
# Arrays containing "cells."
|
|
declare -a initial
|
|
declare -a current
|
|
|
|
display ()
|
|
{
|
|
|
|
alive=0 # How many cells alive at any given time.
|
|
# Initially zero.
|
|
|
|
declare -a arr
|
|
arr=( `echo "$1"` ) # Convert passed arg to array.
|
|
|
|
element_count=${#arr[*]}
|
|
|
|
local i
|
|
local rowcheck
|
|
|
|
for ((i=0; i<$element_count; i++))
|
|
do
|
|
|
|
# Insert newline at end of each row.
|
|
let "rowcheck = $i % COLS"
|
|
if [ "$rowcheck" -eq 0 ]
|
|
then
|
|
echo # Newline.
|
|
echo -n " " # Indent.
|
|
fi
|
|
|
|
cell=${arr[i]}
|
|
|
|
if [ "$cell" = . ]
|
|
then
|
|
let "alive += 1"
|
|
fi
|
|
|
|
echo -n "$cell" | sed -e 's/_/ /g'
|
|
# Print out array, changing underscores to spaces.
|
|
done
|
|
|
|
return
|
|
|
|
}
|
|
|
|
IsValid () # Test if cell coordinate valid.
|
|
{
|
|
|
|
if [ -z "$1" -o -z "$2" ] # Mandatory arguments missing?
|
|
then
|
|
return $FALSE
|
|
fi
|
|
|
|
local row
|
|
local lower_limit=0 # Disallow negative coordinate.
|
|
local upper_limit
|
|
local left
|
|
local right
|
|
|
|
let "upper_limit = $ROWS * $COLS - 1" # Total number of cells.
|
|
|
|
|
|
if [ "$1" -lt "$lower_limit" -o "$1" -gt "$upper_limit" ]
|
|
then
|
|
return $FALSE # Out of array bounds.
|
|
fi
|
|
|
|
row=$2
|
|
let "left = $row * $COLS" # Left limit.
|
|
let "right = $left + $COLS - 1" # Right limit.
|
|
|
|
if [ "$1" -lt "$left" -o "$1" -gt "$right" ]
|
|
then
|
|
return $FALSE # Beyond row boundary.
|
|
fi
|
|
|
|
return $TRUE # Valid coordinate.
|
|
|
|
}
|
|
|
|
|
|
IsAlive () # Test whether cell is alive.
|
|
# Takes array, cell number, and
|
|
{ #+ state of cell as arguments.
|
|
GetCount "$1" $2 # Get alive cell count in neighborhood.
|
|
local nhbd=$?
|
|
|
|
if [ "$nhbd" -eq "$BIRTH" ] # Alive in any case.
|
|
then
|
|
return $ALIVE
|
|
fi
|
|
|
|
if [ "$3" = "." -a "$nhbd" -eq "$SURVIVE" ]
|
|
then # Alive only if previously alive.
|
|
return $ALIVE
|
|
fi
|
|
|
|
return $DEAD # Defaults to dead.
|
|
|
|
}
|
|
|
|
|
|
GetCount () # Count live cells in passed cell's neighborhood.
|
|
# Two arguments needed:
|
|
# $1) variable holding array
|
|
# $2) cell number
|
|
{
|
|
local cell_number=$2
|
|
local array
|
|
local top
|
|
local center
|
|
local bottom
|
|
local r
|
|
local row
|
|
local i
|
|
local t_top
|
|
local t_cen
|
|
local t_bot
|
|
local count=0
|
|
local ROW_NHBD=3
|
|
|
|
array=( `echo "$1"` )
|
|
|
|
let "top = $cell_number - $COLS - 1" # Set up cell neighborhood.
|
|
let "center = $cell_number - 1"
|
|
let "bottom = $cell_number + $COLS - 1"
|
|
let "r = $cell_number / $COLS"
|
|
|
|
for ((i=0; i<$ROW_NHBD; i++)) # Traverse from left to right.
|
|
do
|
|
let "t_top = $top + $i"
|
|
let "t_cen = $center + $i"
|
|
let "t_bot = $bottom + $i"
|
|
|
|
|
|
let "row = $r" # Count center row.
|
|
IsValid $t_cen $row # Valid cell position?
|
|
if [ $? -eq "$TRUE" ]
|
|
then
|
|
if [ ${array[$t_cen]} = "$ALIVE1" ] # Is it alive?
|
|
then # If yes, then ...
|
|
let "count += 1" # Increment count.
|
|
fi
|
|
fi
|
|
|
|
let "row = $r - 1" # Count top row.
|
|
IsValid $t_top $row
|
|
if [ $? -eq "$TRUE" ]
|
|
then
|
|
if [ ${array[$t_top]} = "$ALIVE1" ] # Redundancy here.
|
|
then # Can it be optimized?
|
|
let "count += 1"
|
|
fi
|
|
fi
|
|
|
|
let "row = $r + 1" # Count bottom row.
|
|
IsValid $t_bot $row
|
|
if [ $? -eq "$TRUE" ]
|
|
then
|
|
if [ ${array[$t_bot]} = "$ALIVE1" ]
|
|
then
|
|
let "count += 1"
|
|
fi
|
|
fi
|
|
|
|
done
|
|
|
|
|
|
if [ ${array[$cell_number]} = "$ALIVE1" ]
|
|
then
|
|
let "count -= 1" # Make sure value of tested cell itself
|
|
fi #+ is not counted.
|
|
|
|
|
|
return $count
|
|
|
|
}
|
|
|
|
next_gen () # Update generation array.
|
|
{
|
|
|
|
local array
|
|
local i=0
|
|
|
|
array=( `echo "$1"` ) # Convert passed arg to array.
|
|
|
|
while [ "$i" -lt "$cells" ]
|
|
do
|
|
IsAlive "$1" $i ${array[$i]} # Is the cell alive?
|
|
if [ $? -eq "$ALIVE" ]
|
|
then # If alive, then
|
|
array[$i]=. #+ represent the cell as a period.
|
|
else
|
|
array[$i]="_" # Otherwise underscore
|
|
fi #+ (will later be converted to space).
|
|
let "i += 1"
|
|
done
|
|
|
|
|
|
# let "generation += 1" # Increment generation count.
|
|
### Why was the above line commented out?
|
|
|
|
|
|
# Set variable to pass as parameter to "display" function.
|
|
avar=`echo ${array[@]}` # Convert array back to string variable.
|
|
display "$avar" # Display it.
|
|
echo; echo
|
|
echo "Generation $generation - $alive alive"
|
|
|
|
if [ "$alive" -eq 0 ]
|
|
then
|
|
echo
|
|
echo "Premature exit: no more cells alive!"
|
|
exit $NONE_ALIVE # No point in continuing
|
|
fi #+ if no live cells.
|
|
|
|
}
|
|
|
|
|
|
# =========================================================
|
|
|
|
# main ()
|
|
# {
|
|
|
|
# Load initial array with contents of startup file.
|
|
initial=( `cat "$startfile" | sed -e '/#/d' | tr -d '\n' |\
|
|
# Delete lines containing '#' comment character.
|
|
sed -e 's/\./\. /g' -e 's/_/_ /g'` )
|
|
# Remove linefeeds and insert space between elements.
|
|
|
|
clear # Clear screen.
|
|
|
|
echo # Title
|
|
setterm -reverse on
|
|
echo "======================="
|
|
setterm -reverse off
|
|
echo " $GENERATIONS generations"
|
|
echo " of"
|
|
echo "\"Life in the Slow Lane\""
|
|
setterm -reverse on
|
|
echo "======================="
|
|
setterm -reverse off
|
|
|
|
sleep $DELAY # Display "splash screen" for 2 seconds.
|
|
|
|
|
|
# -------- Display first generation. --------
|
|
Gen0=`echo ${initial[@]}`
|
|
display "$Gen0" # Display only.
|
|
echo; echo
|
|
echo "Generation $generation - $alive alive"
|
|
sleep $DELAY
|
|
# -------------------------------------------
|
|
|
|
|
|
let "generation += 1" # Bump generation count.
|
|
echo
|
|
|
|
# ------- Display second generation. -------
|
|
Cur=`echo ${initial[@]}`
|
|
next_gen "$Cur" # Update & display.
|
|
sleep $DELAY
|
|
# ------------------------------------------
|
|
|
|
let "generation += 1" # Increment generation count.
|
|
|
|
# ------ Main loop for displaying subsequent generations ------
|
|
while [ "$generation" -le "$GENERATIONS" ]
|
|
do
|
|
Cur="$avar"
|
|
next_gen "$Cur"
|
|
let "generation += 1"
|
|
sleep $DELAY
|
|
done
|
|
# ==============================================================
|
|
|
|
echo
|
|
# }
|
|
|
|
exit 0 # CEOF:EOF
|
|
|
|
|
|
|
|
# The grid in this script has a "boundary problem."
|
|
# The top, bottom, and sides border on a void of dead cells.
|
|
# Exercise: Change the script to have the grid wrap around,
|
|
# + so that the left and right sides will "touch,"
|
|
# + as will the top and bottom.
|
|
#
|
|
# Exercise: Create a new "gen0" file to seed this script.
|
|
# Use a 12 x 16 grid, instead of the original 10 x 10 one.
|
|
# Make the necessary changes to the script,
|
|
#+ so it will run with the altered file.
|
|
#
|
|
# Exercise: Modify this script so that it can determine the grid size
|
|
#+ from the "gen0" file, and set any variables necessary
|
|
#+ for the script to run.
|
|
# This would make unnecessary any changes to variables
|
|
#+ in the script for an altered grid size.
|
|
#
|
|
# Exercise: Optimize this script.
|
|
# It has redundant code.
|