Bash (Bourne Again Shell) — это язык сценариев, созданный для операционных систем GNU (включая UNIX-подобные системы, например, Linux). Его можно использовать для взаимодействия с ОС из командной строки и для написания программ для автоматизации различных задач.

Игра жизни Конвея

Игра Конвея «Жизнь» — это игра с нулевым игроком, состоящая из доски с некоторой начальной конфигурацией. Доска разделена по ячейкам, которые могут быть как живыми, так и мертвыми. После каждого этапа состояние игры меняется, так как некоторые клетки регенерируются, а некоторые умирают.

Вот правила игры:

  1. Любая живая клетка, у которой меньше 2 соседей, умирает (как будто из-за недонаселения)
  2. Любая живая клетка, имеющая более 3-х соседей, тоже умирает (как бы из-за перенаселения)
  3. Любая мертвая ячейка с ровно 3 соседями оживает на следующем этапе.
  4. Любая живая ячейка с 2 или 3 соседями остается в живых

Примечание. Соседняя ячейка считается активной.

Реализация игры в Bash

#!/usr/bin/bash

Первая строка — это всегда путь к интерпретатору bash. Вы можете найти его местоположение на своем компьютере, набрав

which bash

Далее мы определяем константы для ширины и высоты доски.

HEIGHT=20
WIDTH=20

Нам также нужно создать двумерный массив для хранения состояния каждой ячейки на доске. К сожалению, многомерные массивы не поддерживаются в bash. Существует обходной путь этой проблемы с помощью ассоциативных массивов.

Ассоциативные массивы используют строки вместо целых чисел для индексации хранимых данных в массиве. Например :

array[0,1]=1

Обратите внимание, что «0,1» на самом деле является строкой, и, используя запятую в качестве разделителя, мы можем имитировать поведение при доступе к ячейке 0-й строки и 1-го столбца.

Чтобы использовать массив, мы должны сначала объявить его, используя следующий синтаксис

declare -A name_of_the_array

Убедитесь, что вы используете заглавную букву «А», которая указывает интерпретатору объявить ассоциативный массив вместо массива с целочисленным индексом.

#creating the board and initializing all values to zero
declare -A board
for (( i=-1; i<$(( $HEIGHT+1 )); i++ )) do
    for (( j=-1; j<$(( $WIDTH+1 )); j++ )) do
        board[$i,$j]=0
    done
done

При доступе к переменной перед ней должен стоять знак «$». Мы используем вложенный цикл for в стиле C, чтобы обнулить значения всех ячеек доски. Это необходимо, потому что, в отличие от многих других языков программирования, bash не инициализирует значения массива нулем. Если вы попытаетесь получить доступ к переменной или массиву до их инициализации, вы получите пустое значение.

Любое математическое выражение должно быть заключено в двойные скобки, как показано ниже. Это сообщает интерпретатору, что мы хотим выполнить математическую операцию вместо конкатенации строк.

$(( math expression ))

Также вам может быть интересно, почему во вложенном цикле я итерирую от «-1 к переменной +1», а не от «0 к переменной». Причина в том, что когда мы будем считать соседей каждой ячейки, мы можем получить доступ к элементам, которые не были определены. Например, мы можем попытаться получить доступ к самому верхнему левому соседу ячейки (0,0), который будет (-1,-1). Теперь, если мы не инициализируем ячейку (-1,-1) и используем ее в операторе if, мы получим ошибку. Итак, мы инициализируем все граничные элементы доски нулями (в любом случае это будет то же самое, что мертвые клетки, и нам не нужно писать сложную логику, чтобы потом избежать граничных ячеек).

Далее мы определяем функцию для печати платы в окне терминала.

function function_name {
    # some task
}
or 
function_name() {
    # some task
}

Оба метода определения функции допустимы, и вы можете использовать любой из них.

function printBoard {
    for (( i=0; i<$HEIGHT; i++ )) do
        line=""
        for (( j=0; j<$WIDTH; j++ )) do
            line+="|"
            if [ ${board[$i,$j]} -eq 1 ]; then
                line+="*"
            else 
                line+=" "
            fi      
        done
        line+="|"
        echo $line 
    done
}

В приведенной выше функции для каждой строки доски мы объявляем строку «линия». Для каждой ячейки в текущей строке мы проверяем, содержит ли ячейка значение 1. В этом случае мы добавляем звездочку «*» к строковой переменной «строка». В противном случае добавляем пустое место. «*» означает живую ячейку, а пустая ячейка означает мертвую ячейку. Каждая ячейка отделяется с помощью «|» в качестве разделителя. Наконец, мы печатаем конкретную строку с помощью

echo $line

который выводит строку в окно терминала. Это повторяется для всех строк на доске.

function getNeighbors {
    count=0
    if [ ${board[$(($i-1)),$j]} -eq 1 ]; then
        let count=$count+1
    fi
    if [ ${board[$(($i+1)),$j]} -eq 1 ]; then
        let count=$count+1
    fi
    if [ ${board[$i,$(($j-1))]} -eq 1 ]; then
        let count=$count+1
    fi
    if [ ${board[$i,$(($j+1))]} -eq 1 ]; then
        let count=$count+1
    fi
    if [ ${board[$(($i-1)),$(($j-1))]} -eq 1 ]; then
        let count=$count+1
    fi
    if [ ${board[$(($i-1)),$(($j+1))]} -eq 1 ]; then
        let count=$count+1
    fi
    if [ ${board[$(($i+1)),$(($j-1))]} -eq 1 ]; then
        let count=$count+1
    fi
    if [ ${board[$(($i+1)),$(($j+1))]} -eq 1 ]; then
        let count=$count+1
    fi
    return $count
}

Приведенная выше функция вычисляет общее количество соседей (живых), которые есть у каждой ячейки.

count=$(( $count + 1 ))

Использование ключевого слова «let» — это более простой способ обновить переменную. Вы также можете сделать это, используя двойные скобки.

declare -A regen
declare -A alive

Объявляем еще два ассоциативных массива: alive (для отслеживания клеток, которые останутся живыми для следующего этапа) и regen (для мертвых клеток, которые будут регенерированы на следующем этапе). этап).

for (( c=0; c<120; c++  )) do
    s1=$(( $RANDOM % $HEIGHT ))
    s2=$(( $RANDOM % $WIDTH ))
    board[$s1,$s2]=1
done

Приведенный выше цикл for используется для случайного заполнения доски. Здесь мы сохраняем 120 случайных ячеек, чтобы начать игру. Вы можете изменить количество случайных ячеек, но слишком низкое (или высокое) значение приведет к уничтожению всех ячеек из-за недонаселения (или перенаселения).

Вы можете сгенерировать случайное число, используя переменную $RANDOM, которая дает случайную величину от 0 до 32767. Следовательно, оператор по модулю используется для получения определенного диапазона, от 0 до HEIGHT - 1 в случае $СЛУЧАЙНАЯ % ВЫСОТА.

while : 
     do
     printBoard

Теперь мы создаем бесконечный цикл while, чтобы программа не останавливалась, пока не будет нажата клавиша Ctrl+C. В цикле мы вызываем функцию «printBoard» для отображения текущего состояния доски на экране.

for (( i=0; i<$HEIGHT; i++ )) do
    for (( j=0; j<$WIDTH; j++ )) do
        getNeighbors
        neighbors=$?
    if [ $neighbors -eq 2 ] || [ $neighbors -eq 3 ] && [ ${board[$i,$j]} -eq 1 ] ; then
        alive[$i,$j]=1
    else 
        alive[$i,$j]=0
    fi
    if [ $neighbors -eq 3 ] && [ ${board[$i,$j]} -eq 0 ]; then
        regen[$i,$j]=1
    else 
        regen[$i,$j]=0
    fi
    done
done

Затем мы перебираем все ячейки, вызывая функцию getNeighbors, которая возвращает общее количество живых соседей для каждой ячейки. Чтобы получить доступ к возвращаемому значению, $? используется. Здесь мы проверяем условия выживания и регенерации, а также обновляем значения массивов.

for (( i=0; i<$HEIGHT; i++ )) do
    for (( j=0; j<$WIDTH; j++ )) do
   
    if [ ${alive[$i,$j]} -eq 1 ] || [ ${regen[$i,$j]} -eq 1 ]; then
        board[$i,$j]=1
    else 
        board[$i,$j]=0
   fi
   
   done
done

Используйте другой вложенный цикл for для обновления значений платы на основе массивов «живой» и «регенерированный».

    sleep 0.2
    clear
done

Команда sleep создает некоторую задержку в программе в зависимости от предоставленного значения (в секундах). Использование очистки очистит экран со старым состоянием платы, чтобы мы могли распечатать обновленное в следующей итерации. Наконец, завершите бесконечный цикл while, добавив «done».

Вот полный код..

Запустить игру

Чтобы запустить программу, ее нужно сделать исполняемой. Для этого измените права доступа к файлу. (Предположим, что файл называется game.sh)

chmod u+x game.sh

«chmod» используется для изменения прав доступа к файлу. ‘u’ означает, что права изменены для пользователя (также можно изменить права для групп или для всех). «+x» означает, что мы добавляем разрешение на выполнение файла (другие варианты включают «r» для чтения и «w» для разрешения на запись).

./game.sh

Теперь файл превратился в исполняемый файл, bash-скрипт можно запустить, просто прописав путь к файлу в окне терминала.