Game of Life

In this post, I’ll cover the creation of a BASH script that play’s the Game of Life. Reading the previous link (which goes to the Wikipedia page for the Game of Life) may be helpful for understanding the code below.

Basic explanation of the game:

Using the limits of the 10×10 game board, check each of the 100 individual cells’ neighbors. Depending on the number of “alive” cells (X) and “dead” cells (O), either kill, keep alive, or reproduce the current cell.

For alive cells:

  1. <2 alive neighbors, kill the cell
  2. 2 or 3 alive neighbors, keep the cell alive
  3. >3 alive neighbors, kill the cell

For dead cells:

  1. 3 alive neighbors, make the cell alive

The code.

First off – I originally started with the intent of creating a generic NxM board with the user inputting the starting grid, then having the game run. Once I realized how much more time doing that would take, I switched over to hardcoding a 10×10 board.

Create the board, then duplicate it to a temporary board. This is necessary since if the cell changes state, you can’t have the unread cells’ neighbors be affected until all states are read. In other words, the entire board must change state at the same time, not incrementally, cell-by-cell.

Note that the rows_columns function was  originally for a square NxN board that I would expand into an NxM board, but now that it is hardcoded for 10×10, the variables ($rows, $columns) could simply be replaced by 10.

The code works as follows:

  1. Print the game board
  2. Set the arrays board_temp equal to board
  3. Run an iteration of the game.
  4. Set the arrays board equal to board_temp
  5. Sleep for 1 second
  6. Repeat from the beginning

Everything is pretty self explanatory (if ugly since I hardcoded the neighboring cell checks for each and every individual cell on the 10×10 board.

  1. Run the the array one element at a time and print it to the screen properly formatted
  2. Set the “holding” array to the “main array”
  3. Play the Game
    1. Each cell has different neighbors which needs coding
      1. Upper left = 3 neighbors (E, SE, S)
      2. Upper middle = 5 neighbors (W, SW, S, SE, E)
      3. Upper right = 3 neighbors (W, SW, S)
      4. Left middle = 5 neighbors (N, NE, E, SE, S)
      5. Middle = 8 neighbors (N, NE, E, SE, S, SW, W, NW)
      6. Right middle = 5 neighbors (N, NW, W, SW, S)
      7. Lower left = 3 neighbors (N, NE, E)
      8. Lower middle = 5 neighbors (W, NW, N, NE, E)
      9. Lower right = 3 neighbors (W, NW, N)
    2. Each cell needs to check the appropriate neighbors to see if it is alive (X) or dead (O)
    3. After checking all of each of the cell’s neighbors, count the total number, then kill, keep alive or make alive the current cell as the rules dictate
    4. Repeat for all 100 cells
    5. Once done, “flash” the entire board with the new state
  4. Set the “main” array to the “holding array”
  5. Sleep
  6. Repeat

Now onto the code itself. The initial board setup below is called the “Glider”.

#!/bin/bash
#set -xv

#board must be 10x10 square for "play_game" coding to find neighbors correctly
#are 'X' and 'O'

board=( O O X O O O O O O O
        X O X O O O O O O O
        O X X O O O O O O O
        O O O O O O O O O O
        O O O O O O O O O O
        O O O O O O O O O O
        O O O O O O O O O O
        O O O O O O O O O O
        O O O O O O O O O O
        O O O O O O O O O O )

board_temp=("${board[@]}")

rows_columns(){

local i
rows=""

for (( i=0; i<${#board[@]}; i++ ))
do
     (( $i * $i == ${#board[@]} )) && { rows=$i; break; }
done

}

print_board(){

local i
local j

for (( i=0; i<=${#board[@]}; i++ ))
do
     [[ "${board[$i]}" == "O" ]] && { echo -n " "; }
     [[ "${board[$i]}" == "X" ]] && { echo -n "X"; }
     (( $(( $i + 1 )) % $rows == 0 )) && { echo ""; }
done

echo ""

}

play_game(){

local i
local j
local neighbors

for (( i=0; i<${#board[@]}; i++ ))
do
     neighbors=0

     #case selection (stops as soon as it finds a match
     case $i in
     0)
          check_E
          check_SE
          check_S ;;
     [1-8])
          check_W
          check_SW
          check_S
          check_SE
          check_E ;;
          check_W
          check_SW
          check_S ;;
     10)
          check_N
          check_NE
          check_E
          check_SE
          check_S ;;
     19)
          check_N
          check_NW
          check_W
          check_SW
          check_S ;;
     20)
          check_N
          check_NE
          check_E
          check_SE
          check_S ;;
     29)
          check_N
          check_NW
          check_W
          check_SW
          check_S ;;
     30)
          check_N
          check_NE
          check_E
          check_SE
          check_S ;;
     39)
          check_N
          check_NW
          check_W
          check_SW
          check_S ;;
     40)
          check_N
          check_NE
          check_E
          check_SE
          check_S ;;
     49)
          check_N
          check_NW
          check_W
          check_SW
          check_S ;;
     50)
          check_N
          check_NE
          check_E
          check_SE
          check_S ;;
     59)
          check_N
          check_NW
          check_W
          check_SW
          check_S ;;
     60)
          check_N
          check_NE
          check_E
          check_SE
          check_S ;;
     69)
          check_N
          check_NW
          check_W
          check_SW
          check_S ;;
     70)
          check_N
          check_NE
          check_E
          check_SE
          check_S ;;
     79)
          check_N
          check_NW
          check_W
          check_SW
          check_S ;;
     80)
          check_N
          check_NE
          check_E
          check_SE
          check_S ;;
     89)
          check_N
          check_NW
          check_W
          check_SW
          check_S ;;
     90)
          check_N
          check_NE
          check_E ;;
     [91-98])
          check_W
          check_NW
          check_N
          check_NE
          check_E ;;
     99)
          check_W
          check_NW
          check_N ;;
     *)
          check_N
          check_S
          check_E
          check_W
          check_NE
          check_NW
          check_SE
          check_SW ;;
     esac

if [[ "${board[$i]}" == "X" ]]
then
     #live checks:

     #live dies with <2 neighbors 
     (( $neighbors<2 )) && { board_temp[$i]="O"; }

     #live lives with 2 neighbors
     (( $neighbors==2 )) && { board_temp[$i]="X"; }

     #live lives with 3 neighbors
     (( $neighbors==3 )) && { board_temp[$i]="X"; }

     #live dies with >3 neighbors
     (( $neighbors>3 )) && { board_temp[$i]="O"; }

else
     #dead checks:

     #dead lives with 3 neighbors
     (( $neighbors==3 )) && { board_temp[$i]="X"; }

fi
done

}

check_N(){
     #check North square
     [[ "${board[$(( $i - $rows))]}" == "X" ]] && { neighbors=$(($neighbors+1)); }
}

check_S(){
     #check South square
     [[ "${board[$(( $i + $rows))]}" == "X" ]] && { neighbors=$(($neighbors+1)); }
}

check_E(){
     #check East square
     [[ "${board[$(( $i + 1 ))]}" == "X" ]] && { neighbors=$(($neighbors+1)); }
}

check_W(){
     #check West square
     [[ "${board[$(( $i - 1 ))]}" == "X" ]] && { neighbors=$(($neighbors+1)); }
}

check_NE(){
     #check North-East square
     [[ "${board[$(( $i - $rows + 1))]}" == "X" ]] && { neighbors=$(($neighbors+1)); }
}

check_NW(){
     #check North-West square
     [[ "${board[$(( $i - $rows - 1))]}" == "X" ]] && { neighbors=$(($neighbors+1)); }
}

check_SE(){
     #check South-East square
     [[ "${board[$(( $i + $rows + 1))]}" == "X" ]] && { neighbors=$(($neighbors+1)); }
}

check_SW(){
     #check South-West square
     [[ "${board[$(( $i + $rows - 1))]}" == "X" ]] && { neighbors=$(($neighbors+1)); }
}

rows_columns
while true
do
     print_board
     board_temp=("${board[@]}")
     play_game
     board=("${board_temp[@]}")
     sleep 1
done
Tagged , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published.

Protected by WP Anti Spam