################################################################################
# CONWAY'S GAME OF LIFE... IN BASH!
#
# Author: Andrew McCluskey
# Licence: This code is licensed under the MIT Licence as follows:
#
# Copyright (c) 2013 Andrew McCluskey <andrew@ajmccluskey.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
################################################################################
declare -r DEAD_CELL_VALUE=""
declare -r LIVE_CELL_VALUE="@"
declare -i TICK_RATE_S=1
declare -i current_lines
declare -i current_cols
declare -i current_tick
declare -a current_state
declare -a next_state
# The following group of variables are actually return values for their corresponding functions. For example
# get_array_index will set array_index - this is an optimization to save on subshelling
declare -i array_index
declare -i line_from_index
declare -i col_from_index
declare -a surrounding_indexes
declare -i living_neighbours_count=0
declare next_cell_state
# Input array when setting current state - see set_current_state_from_input_state()
declare -a input_state
function log() {
# Dodgy Perl hackery para obtener una tiempo en milisegunbdos, que permita hacer algun perfilado primitivo.
printf "%s: %s\n" "$(perl -e 'use Time::HiRes qw(time); print time')" "$1" >> bash-life.log
}
function set_cursor_pos() {
typeset line=$1
typeset col=$2
printf "\033[${line};${col}H"
}
function print_at_pos() {
typeset line=$1
typeset col=$2
typeset to_print=$3
set_cursor_pos $line $col
printf "$to_print"
}
function print_cell() {
# Bump cells down a line to avoid header
print_at_pos $(($1 + 1)) $2 "$3"
}
function get_term_lines() {
printf $(tput lines)
}
function get_term_cols() {
printf $(tput cols)
}
function update_term_size() {
# Pretende que tenemos una linea menos para poder imprimir encabezado
current_lines=$(get_term_lines - 1)
current_cols=$(get_term_cols)
}
# Returns an array index given a line and column (both starting at 1).
function get_array_index() {
let array_index="($1-1) * (current_cols) + $2 - 1"
}
# Returns the line number from an array index in current_state.
function get_line_from_index() {
let line_from_index="$1/current_cols + 1"
}
# Returns the column number from an array index
function get_col_from_index() {
let col_from_index="$1 % current_cols + 1"
}
function init_current_state() {
for (( line=1; line<=current_lines; ++line )); do
for (( col=1; col<=current_cols; ++col )); do
get_array_index $line $col
current_state[$array_index]=$DEAD_CELL_VALUE
done
done
}
function init_game_state() {
current_tick=0
# Currently we only update the term size once. A possible enhancement would be to update it each tick so
# that we can adapt to changing terminal sizes
update_term_size
init_current_state
}
# Returns, by setting a variable, an array of indexes for cells that surround the given cell.
function get_living_neighbours_count() {
declare -i line=$1
declare -i col=$2
declare -i index=0
living_neighbours_count=0
# Yeah we could $(seq), but this should be faster
for l in $((line-1)) $line $((line+1)); do
(( l > 0 && l <= current_lines )) || continue
for c in $((col-1)) $col $((col+1)); do
(( c > 0 && c <= current_cols )) || continue
if (( !(l == line && c == col) )); then
get_array_index $l $c
[[ ${current_state[$array_index]} == $LIVE_CELL_VALUE ]] && ((++living_neighbours_count))
fi
done
done
}
# Given a cell's current state and its number of living neighbours, determines its next state by applying the
# following rules (taken from Conway's Game of Life wiki page)
# 1. Any living cell with fewer than 2 neighbours dies
# 2. Any live cell with 2 or 3 live neighbours survives
# 3. Any live cell with more than 3 neighbours dies
# 4. Any dead cell with exactly 3 neighbours comes to life
function get_next_cell_state() {
declare -i line=$1
declare -i col=$2
get_living_neighbours_count $line $col
get_array_index $line $col
if (( living_neighbours_count < 2 || living_neighbours_count > 3 )); then
# No cell is alive if it has fewer than 2, or greater than 3 living neighbours
next_cell_state=$DEAD_CELL_VALUE
elif [[ ${current_state[$array_index]} == $LIVE_CELL_VALUE ]]; then
# We know the cell has 2 or 3 live neighbours from the last condition, so if it's alive it survives
next_cell_state=$LIVE_CELL_VALUE
elif (( living_neighbours_count == 3 )); then
# We know the cell is dead from the last condition, so if it has 3 living neighbours it comes to life
next_cell_state=$LIVE_CELL_VALUE
else
# The cell must be dead and have only 2 live neigbours, so it stays dead.
next_cell_state=$DEAD_CELL_VALUE
fi
}
# Work out the next "frame" to display and store it in current_state, ready for displaying
function update() {
next_state=()
for (( line=1; line<=$current_lines; ++line )); do
for (( col=1; col<=$current_cols; ++col )); do
get_next_cell_state $line $col
get_array_index $line $col
next_state[$array_index]=$next_cell_state
done
done
current_state=("${next_state[@]}")
}
# Draw game information on the top line
function draw_header() {
print_at_pos 1 1 "Current tick: $current_tick"
}
# Draw whatever the current state is to screen
function draw() {
clear
draw_header
#log "drawing state of size ${#current_state[*]}"
for (( index=1; index<=${#current_state[*]}; ++index)); do
if [[ ${current_state[$index - 1]} == $LIVE_CELL_VALUE ]]; then
get_line_from_index $((index-1))
get_col_from_index $((index-1))
print_cell $line_from_index $col_from_index $LIVE_CELL_VALUE
fi
done
}
# Parses input_state array (line col pairs) and updates the current_state to match
function set_current_state_from_input_state() {
declare -i i=0
while ((i < ${#input_state[*]})); do
get_array_index ${input_state[$i]} ${input_state[$((i+1))]}
current_state[$array_index]=$LIVE_CELL_VALUE
((i+=2))
done
}
function set_current_state_parallels() {
input_state=(5 5 5 6 6 5 6 6 7 5 7 6)
set_current_state_from_input_state
}
function set_current_state_cross() {
input_state=(5 30 6 29 6 30 6 31 7 30)
set_current_state_from_input_state
}
function set_current_state_glider() {
input_state=(1 2 2 3 3 1 3 2 3 3)
set_current_state_from_input_state
}
function set_current_state_glider_gun() {
input_state=(2 26 3 24 3 26 4 14 4 15 4 22 4 23 4 36 4 37 5 13 5 17 5 22 5 23 5 36 5 37 6 2 6 3 6 12 6 18 6 22 6 23 7 2 7 3 7 12 7 16 7 18 7 19 7 24 7 26 8 12 8 18 8 26 9 13 9 17 10 14 10 15)
set_current_state_from_input_state
}
rm -f bash-life.log
init_game_state
set_current_state_glider_gun
draw
while ((1 == 1)); do
let current_tick=current_tick+1
update
draw
# lol, you thought this would run fast enough to need rate limiting
#sleep $TICK_RATE_S
done