Пример A-11. "Игра "Жизнь""

Пример A-11. "Игра "Жизнь""

  1. #!/bin/bash
  2. # life.sh: Игра "Жизнь"
  3. # ##################################################################### #
  4. # Это Bash-версия известной игры Джона Конвея (John Conway) "Жизнь".    #
  5. # --------------------------------------------------------------------- #
  6. # Прямоугольное игровое поле разбито на ячейки, в каждой ячейке может   #
  7. #+ располагаться живая особь.                                           #
  8. # Соответственно, ячейка с живой особью отмечается точкой,              #
  9. #+ не занятая ячейка — остается пустой.                                #
  10. #  Изначально, ячейки заполняются из файла —                           #
  11. #+ это первое поколение, или "поколение 0"                              #
  12. # Воспроизводство особей, в каждом последующем поколении,               #
  13. #+ определяется следующими правилами                                    #
  14. # 1) Каждая ячейка имеет "соседей"                                      #
  15. #+   слева, справа, сверху, снизу и 4 по диагоналям.                    #
  16. #                       123                                             #
  17. #                       4*5                                             #
  18. #                       678                                             #
  19. #                                                                       #
  20. # 2) Если живая особь имеет 2 или 3 живых соседей, то она остается жить.#
  21. # 3) Если пустая ячейка имеет 3 живых соседей —                        #
  22. #+   в ней "рождается" новая особь                                      #
  23. SURVIVE=2                                                               #
  24. BIRTH=3                                                                 #
  25. # 4) В любом другом случае, живая особь "погибает"                      #
  26. # ##################################################################### #
  27. startfile=gen0   # Начальное поколение из файла по-умолчанию — "gen0".
  28.                  # если не задан другой файл, из командной строки.
  29.                  #
  30. if [ -n "$1" ]   # Проверить аргумент командной строки — файл с "поколениемn 0".
  31. then
  32.   if [ -e "$1" ] # Проверка наличия файла.
  33.   then
  34.     startfile="$1"
  35.   fi
  36. fi
  37. ALIVE1=.
  38. DEAD1=_
  39.                  # Представление "живых" особей и пустых ячеек в файле с "поколением 0".
  40. #  Этот сценарий работает с игровым полем 10 x 10 grid (может быть увеличено,
  41. #+ но большое игровое поле будет обрабатываться очень медленно).
  42. ROWS=10
  43. COLS=10
  44. GENERATIONS=10          #  Максимальное число поколений.
  45. NONE_ALIVE=80           #  Код завершения на случай,
  46.                         #+ если не осталось ни одной "живой" особи.
  47. TRUE=0
  48. FALSE=1
  49. ALIVE=0
  50. DEAD=1
  51. avar=                   # Текущее поколение.
  52. generation=0            # Инициализация счетчика поколений.
  53. # =================================================================
  54. let "cells = $ROWS * $COLS"
  55.                         # Количество ячеек на игровом поле.
  56. declare -a initial      # Массивы ячеек.
  57. declare -a current
  58. display ()
  59. {
  60. alive=0                 # Количество "живых" особей.
  61.                         # Изначально — ноль.
  62. declare -a arr
  63. arr=( `echo "$1"` )     # Преобразовать аргумент в массив.
  64. element_count=${#arr[*]}
  65. local i
  66. local rowcheck
  67. for ((i=0; i<$element_count; i++))
  68. do
  69.   # Символ перевода строки — в конец каждой строки.
  70.   let "rowcheck = $i % ROWS"
  71.   if [ "$rowcheck" -eq 0 ]
  72.   then
  73.     echo                # Перевод строки.
  74.     echo -n "      "    # Выравнивание.
  75.   fi
  76.   cell=${arr[i]}
  77.   if [ "$cell" = . ]
  78.   then
  79.     let "alive += 1"
  80.   fi
  81.   echo -n "$cell" | sed -e 's/_/ /g'
  82.   # Вывести массив, по пути заменяя символы подчеркивания на пробелы.
  83. done
  84. return
  85. }
  86. IsValid ()                            # Проверка корректности координат ячейки.
  87. {
  88.   if [ -z "$1"  -o -z "$2" ]          # Проверка наличия входных аргументов.
  89.   then
  90.     return $FALSE
  91.   fi
  92. local row
  93. local lower_limit=0                   # Запрет на отрицательные координаты.
  94. local upper_limit
  95. local left
  96. local right
  97. let "upper_limit = $ROWS * $COLS - 1" # Номер последней ячейки на игровом поле.
  98. if [ "$1" -lt "$lower_limit" -o "$1" -gt "$upper_limit" ]
  99. then
  100.   return $FALSE                       # Выход за границы массива.
  101. fi
  102. row=$2
  103. let "left = $row * $ROWS"             # Левая граница.
  104. let "right = $left + $COLS - 1"       # Правая граница.
  105. if [ "$1" -lt "$left" -o "$1" -gt "$right" ]
  106. then
  107.   return $FALSE                       # Выхол за нижнюю строку.
  108. fi
  109. return $TRUE                          # Координаты корректны.
  110. }
  111. IsAlive ()              # Проверка наличия "живой" особи в ячейке.
  112.                         # Принимает массив и номер ячейки в качестве входных аргументов.
  113. {
  114.   GetCount "$1" $2      # Подсчитать кол-во "живых" соседей.
  115.   local nhbd=$?
  116.   if [ "$nhbd" -eq "$BIRTH" ]  # "Живая".
  117.   then
  118.     return $ALIVE
  119.   fi
  120.   if [ "$3" = "." -a "$nhbd" -eq "$SURVIVE" ]
  121.   then                  # "Живая" если перед этим была "живая".
  122.     return $ALIVE
  123.   fi
  124.   return $DEAD          # По-умолчанию.
  125. }
  126. GetCount ()             # Подсчет "живых" соседей.
  127.                         # Необходимо 2 аргумента:
  128.                         # $1) переменная-массив
  129.                         # $2) cell номер ячейки
  130. {
  131.   local cell_number=$2
  132.   local array
  133.   local top
  134.   local center
  135.   local bottom
  136.   local r
  137.   local row
  138.   local i
  139.   local t_top
  140.   local t_cen
  141.   local t_bot
  142.   local count=0
  143.   local ROW_NHBD=3
  144.   array=( `echo "$1"` )
  145.   let "top = $cell_number - $COLS - 1"    # Номера соседних ячеек.
  146.   let "center = $cell_number - 1"
  147.   let "bottom = $cell_number + $COLS - 1"
  148.   let "r = $cell_number / $ROWS"
  149.   for ((i=0; i<$ROW_NHBD; i++))           # Просмотр слева-направо.
  150.   do
  151.     let "t_top = $top + $i"
  152.     let "t_cen = $center + $i"
  153.     let "t_bot = $bottom + $i"
  154.     let "row = $r"                        # Пройти по соседям в средней строке.
  155.     IsValid $t_cen $row                   # Координаты корректны?
  156.     if [ $? -eq "$TRUE" ]
  157.     then
  158.       if [ ${array[$t_cen]} = "$ALIVE1" ] # "Живая"?
  159.       then                                # Да!
  160.         let "count += 1"                  # Нарастить счетчик.
  161.       fi
  162.     fi
  163.     let "row = $r - 1"                    # По верхней строке.
  164.     IsValid $t_top $row
  165.     if [ $? -eq "$TRUE" ]
  166.     then
  167.       if [ ${array[$t_top]} = "$ALIVE1" ]
  168.       then
  169.         let "count += 1"
  170.       fi
  171.     fi
  172.     let "row = $r + 1"                    # По нижней строке.
  173.     IsValid $t_bot $row
  174.     if [ $? -eq "$TRUE" ]
  175.     then
  176.       if [ ${array[$t_bot]} = "$ALIVE1" ]
  177.       then
  178.         let "count += 1"
  179.       fi
  180.     fi
  181.   done
  182.   if [ ${array[$cell_number]} = "$ALIVE1" ]
  183.   then
  184.     let "count -= 1"        #  Убедиться, что сама проверяемая ячейка
  185.   fi                        #+ не была подсчитана.
  186.   return $count
  187. }
  188. next_gen ()               # Обновить массив, в котором содержится информация о новом "поколении".
  189. {
  190. local array
  191. local i=0
  192. array=( `echo "$1"` )     # Преобразовать в массив.
  193. while [ "$i" -lt "$cells" ]
  194. do
  195.   IsAlive "$1" $i ${array[$i]}   # "Живая"?
  196.   if [ $? -eq "$ALIVE" ]
  197.   then                           #  Если "живая", то
  198.     array[$i]=.                  #+ записать точку.
  199.   else
  200.     array[$i]="_"                #  Иначе — символ подчеркивания
  201.    fi                            #+ (который позднее заменится на пробел).
  202.   let "i += 1"
  203. done
  204. # let "generation += 1"   # Увеличить счетчик поколений.
  205. # Подготовка переменных, для передачи в функцию "display".
  206. avar=`echo ${array[@]}`   # Преобразовать массив в строку.
  207. display "$avar"           # Вывести его.
  208. echo; echo
  209. echo "Поколение $generation — живых особей $alive"
  210. if [ "$alive" -eq 0 ]
  211. then
  212.   echo
  213.   echo "Преждеверменное завершение: не осталось ни одной живой особи!"
  214.   exit $NONE_ALIVE        #  Нет смысла продолжать
  215. fi                        #+ если не осталось ни одной живой особи
  216. }
  217. # =========================================================
  218. # main ()
  219. # Загрузить начальное поколение из файла.
  220. initial=( `cat "$startfile" | sed -e '/#/d' | tr -d '\n' |\
  221. sed -e 's/\./\. /g' -e 's/_/_ /g'` )
  222. # Удалить строки, начинающиеся с символа '#' — комментарии.
  223. # Удалить строки перевода строки и вставить пробелы между элементами.
  224. clear          # Очистка экрана.
  225. echo #       Заголовок
  226. echo "======================="
  227. echo "    $GENERATIONS поколений"
  228. echo "           в"
  229. echo "      игре \" ЖИЗНЬ\""
  230. echo "======================="
  231. # -------- Вывести первое поколение. --------
  232. Gen0=`echo ${initial[@]}`
  233. display "$Gen0"           # Тлько вывод.
  234. echo; echo
  235. echo "Поколение $generation — живых особей $alive"
  236. # -------------------------------------------
  237. let "generation += 1"     # Нарастить счетчик поколений.
  238. echo
  239. # ------- Вывести второе поколение. -------
  240. Cur=`echo ${initial[@]}`
  241. next_gen "$Cur"          # Обновить и вывести.
  242. # ------------------------------------------
  243. let "generation += 1"     # Нарастить счетчик поколений.
  244. # ------ Основной цикл игры ------
  245. while [ "$generation" -le "$GENERATIONS" ]
  246. do
  247.   Cur="$avar"
  248.   next_gen "$Cur"
  249.   let "generation += 1"
  250. done
  251. # ==============================================================
  252. echo
  253. exit 0
  254. # --------------------------------------------------------------
  255. # Этот сценарий имеет недоработку.
  256. # Граничные ячейки сверху, снизу и сбоков  остаются пустыми.
  257. # Упражнение: Доработайте сценарий таким образом, чтобы ,
  258. # +         левая и правая стороны как бы "соприкасались",
  259. # +         так же и верхняя и нижняя стороны.