Игра в жизнь с использованием JavaScript

Изучение клеточных автоматов и ядра JavaScript

Игра жизни изначально была разработана Джоном Конвеем для изучения клеточных автоматов.

Мне нравилось писать эту программу на всех языках, которые я выучил, и я всегда использовал ее в качестве учебного пособия при обучении программированию; поскольку он использует переменные, массивы, циклы, условные выражения, функции и т. д.

Наша цель - создать забавное приложение, использующее много базового JavaScript.

Предпосылка игры

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

Первоначальная конфигурация, приведенная выше, выделена жирным шрифтом, потому что это единственное «воспроизведение», которое может сделать пользователь. Затем они расслабляются и смотрят, как разворачивается эволюция.

Конечный продукт

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

Я знаю, что ваше время ценно, и эта статья может быть длинной. Хотя я рекомендую вам писать код, при необходимости скопируйте и вставьте код, а также изучите его.

Полный код JavaScript также находится в конце статьи.

Правила

Правила игры в жизнь просты. Мы начинаем с того, что заполняем «мир» таким количеством «живых» ячеек, какое мы выберем, - исходная конфигурация. Затем, основываясь на приведенных ниже правилах, мы видим, как наше население растет, меняется и вымирает по мере смены поколений.

  • Любая живая клетка с менее чем двумя живыми соседями умирает, как если бы это было вызвано недостаточным населением.
  • Любая живая клетка с двумя или тремя живыми соседями доживает до следующего поколения.
  • Любая живая клетка с более чем тремя живыми соседями умирает, как будто от переполненности.
  • Любая мертвая клетка с ровно тремя живыми соседями становится живой клеткой, как бы в результате размножения.

Мы будем использовать двумерные массивы, чтобы отслеживать текущее поколение и следующее поколение, которое создается после применения правил.

Мы будем использовать таблицу для построения сетки для отображения текущего поколения. Это изображение для игрока.

Давай начнем!

Я буду использовать Visual Studio Code, но подойдет любой редактор, в котором вы можете создавать HTML, CSS и JavaScript.

Настраивать

  1. Создайте HTML-страницу с именем index.html.

2. Создайте файл JavaScript с именем gol.js, который будет содержать весь необходимый нам код JavaScript.

3. Создайте файл таблицы стилей с именем gol.css, который будет содержать все необходимые стили.

HTML

Создайте файл с именем index.html.

  • Добавьте ссылку на скрипт в наш gol.js в теле.
  • Добавьте ссылку на таблицу стилей в заголовок.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=
    , initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Game of Life</title>
    <!-- Our Style Sheet -->
    <link rel="stylesheet" href='gol.css'>
</head>
<body>
<!-- Our JavaScript -->
    <script src='gol.js'></script>
</body>
</html>

Шаги

Шаг первый - построим мир

Сначала мы хотим построить наш «мир», мировую сетку, где наше население будет процветать (или нет). Это будет сделано с помощью таблицы HTML, построенной с помощью JavaScript. Опять же, это визуализация для игрока.

Сначала мы добавим элемент ‹div›, содержащий мировую сетку в index.html, а затем создадим таблицу HTML и добавим ее в div.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=
    , initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Game of Life</title>
    <!-- Our Style Sheet -->
    <link rel="stylesheet" href='gol.css'>
</head>
<body>
    <div id='world'>
    </div>
<!-- Our JavaScript -->
    <script src='gol.js'></script>
</body>
</html>

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

Мир будет таблицей HTML. В каждой ячейке может находиться житель. Обитаема ли клетка (жива) или нет (мертва) - это часть начальной конфигурации, а также эволюционного процесса, определяемого правилами.

Примечание: в оригинальной Game of Life мир должен быть бесконечным. Извините. Не наш.

Нам нужны некоторые переменные, чтобы хранить, сколько строк и столбцов мы хотим в нашей таблице.

Затем мы создадим функцию createWorld (), которая будет строить таблицу строка за строкой, столбец за столбцом и ячейка за ячейкой.

Затем мы добавим это в наш div. Мы будем вызывать эту функцию из файла window.onload.

Добавьте следующее в gol.js.

const rows = 40;
const cols = 40;
function createWorld() {
    let world = document.querySelector('#world');
    
    let tbl = document.createElement('table');
    tbl.setAttribute('id','worldgrid');
for (let i = 0; i < rows; i++) {
        let tr = document.createElement('tr');
        for (let j = 0; j < cols; j++) {
            let cell = document.createElement('td');
                 
            tr.appendChild(cell);
        }
        tbl.appendChild(tr);
    }
    world.appendChild(tbl);
}
window.onload=()=>{
    createWorld();
}

Вы не увидите многого, пока мы не добавим стили к нашей таблице, поэтому

добавьте следующее в gol.css

#world {
    padding-bottom: 10px;
}
table {
    background-color: whitesmoke;
}
td {
    border: 1px solid darkgray;
    width: 10px;
    height: 10px;
}

Тестовый забег

Откройте index.html, и вы должны увидеть свой дивный новый мир, ожидающий своего заселения!

Шаг второй - клетки

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

Итак, нам нужно прикрепить к ячейкам таблицы пару атрибутов.

  • В каждую ячейку необходимо добавить атрибут id a, чтобы мы могли их отслеживать. Мы будем использовать i и j в форме i_j. Таким образом, верхний левый угол будет 0_0 и т. Д.
  • В каждую ячейку необходимо добавить класс мертвого. Этот класс можно изменить на активный во время начальной настройки или во время развития.

Добавьте в ячейки следующие атрибуты (выделены жирным шрифтом),

const rows = 40;
const cols = 40;
function createWorld() {
    let world = document.querySelector('#world');
    
    let tbl = document.createElement('table');
    tbl.setAttribute('id','worldgrid');
for (let i = 0; i < rows; i++) {
        let tr = document.createElement('tr');
        for (let j = 0; j < cols; j++) {
            let cell = document.createElement('td');
            cell.setAttribute('id', i + '_' + j);
            cell.setAttribute('class', 'dead');
                     
            tr.appendChild(cell);
        }
        tbl.appendChild(tr);
    }
    world.appendChild(tbl);
}
window.onload=()=>{
    createWorld();
}

Заставляем клетки оживать

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

Позже нам нужно будет отслеживать текущее поколение, а также следующее поколение, созданное после применения правил. Для этого нам понадобятся два двумерных массива. Один для текущего и один для следующего поколения.

Примечание: JavaScript напрямую не поддерживает двухмерные массивы. Мы должны построить их как массив массивов. Более подробную информацию о двумерных массивах можно найти здесь.

Добавьте следующий прослушиватель и функцию cellClick ().

const rows = 40;
const cols = 40;
function createWorld() {
    let world = document.querySelector('#world');
    
    let tbl = document.createElement('table');
    tbl.setAttribute('id','worldgrid');
for (let i = 0; i < rows; i++) {
        let tr = document.createElement('tr');
        for (let j = 0; j < cols; j++) {
            let cell = document.createElement('td');
            cell.setAttribute('id', i + '_' + j);
            cell.setAttribute('class', 'dead');
            cell.addEventListener('click',cellClick);            
            tr.appendChild(cell);
        }
        tbl.appendChild(tr);
    }
    world.appendChild(tbl);
}
function cellClick() {
    let loc = this.id.split("_");
    let row = Number(loc[0]);//Get i
    let col = Number(loc[1]);//Get j
// Toggle cell alive or dead
    if (this.className==='alive'){
        this.setAttribute('class', 'dead');
       
    }else{
        this.setAttribute('class', 'alive');
       
    }
}
window.onload=()=>{
    createWorld();
}

Добавьте следующее в gol.css

td.dead {
    background-color: transparent;
}
td.alive {
    background-color:blue;
}

Тестовый забег

Откройте index.html, и вы сможете «переключать» ячейку между активной (синей) и мертвой (прозрачной), щелкая по ячейке. Так игрок создаст начальную конфигурацию жителей.

Поскольку объем кода становится все длиннее, не стесняйтесь размещать созданные нами функции в любом месте файла .js.

Настройка массивов текущего и следующего поколения

Когда мы делаем первый щелчок, мы настраиваем текущее (начальное) поколение.

После того, как мы начнем обрабатывать поколения, нам нужно будет применить правила для создания следующего поколения. Следующее поколение становится текущим поколением и так далее по мере развития мира.

Нам нужно настроить двумерные массивы для хранения текущего и следующего поколения (рассчитанных после применения правил для «жизни» и «смерти»).

Объявления массивов будут устанавливать одномерные массивы на основе количества строк. Затем мы создадим массив в каждом месте массива для создания наших требуемых двумерных массивов.

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

Изначально мы установим для каждого массива положение 0 (мертвое), чтобы оно соответствовало таблице.

Добавьте следующие объявления массивов и функции createGenArrays (), которые создают массивы и initGenArrays (), которые устанавливают значения массива в 0 ( мертвых).

Я разместил свой код после объявления строк и столбцов и над функцией createWorld ().

const rows = 40;
const cols = 40;
// Need 2D arrays. These are 1D
let currGen =[rows];
let nextGen =[rows];
// Creates two-dimensional arrays
function createGenArrays() {
    for (let i = 0; i < rows; i++) {
        currGen[i] = new Array(cols);
        nextGen[i] = new Array(cols);
    }
}
function initGenArrays() {
    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            currGen[i][j] = 0;
            nextGen[i][j] = 0;
        }
    }
}

Мы добавим вызовы функций createGenArrays () и initGenArrays () в нашу onload и добавим две строки в out Функция cellClick ().

Ваш код должен выглядеть следующим образом.

  • Обратите внимание на добавление currGen к cellClick ().
  • Обратите внимание на дополнения к onlaod.
const rows = 40;
const cols = 40;
// Need 2D arrays. These are 1D
let currGen =[rows];
let nextGen =[rows];
// Creates two-dimensional arrays
function createGenArrays() {
    for (let i = 0; i < rows; i++) {
        currGen[i] = new Array(cols);
        nextGen[i] = new Array(cols);
    }
}
function initGenArrays() {
    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            currGen[i][j] = 0;
            nextGen[i][j] = 0;
        }
    }
}
function createWorld() {
    let world = document.querySelector('#world');
    
    let tbl = document.createElement('table');
    tbl.setAttribute('id','worldgrid');
for (let i = 0; i < rows; i++) {
        let tr = document.createElement('tr');
        for (let j = 0; j < cols; j++) {
            let cell = document.createElement('td');
            cell.setAttribute('id', i + '_' + j);
            cell.setAttribute('class', 'dead');
            cell.addEventListener('click',cellClick);            
            tr.appendChild(cell);
        }
        tbl.appendChild(tr);
    }
    world.appendChild(tbl);
}
function cellClick() {
    let loc = this.id.split("_");
    let row = Number(loc[0]);
    let col = Number(loc[1]);
// Toggle cell alive or dead
    if (this.className==='alive'){
        this.setAttribute('class', 'dead');
        currGen[row][col] = 0;
    }else{
        this.setAttribute('class', 'alive');
        currGen[row][col] = 1;
    }
}
window.onload=()=>{
    createWorld();// The visual table
    createGenArrays();// current and next generations
    initGenArrays();//Set all array locations to 0=dead
}

Эволюция: считайте соседей и применяйте правила

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

Нам нужно,

  • Подсчитайте соседние ячейки / соседи каждой живой ячейки таблицы.
  • Примените правила на основе количества соседей, чтобы создать следующее поколение.
  • Сделайте новое поколение нынешним поколением.
  • Создать способ отображения текущего (нового) поколения (обновить мировую таблицу).
  • Создайте временную кнопку, чтобы запустить процесс и внести изменения в генерацию. Эта кнопка предназначена только для тестирования.

В конце концов, нам нужны кнопки для запуска автоматического процесса генерации.

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

Подсчет соседей

Правила того, становится ли ячейка живой или умирающей, зависят от количества соседей, которые есть у данной ячейки.

Давайте добавим функцию getNeighborCount () для подсчета количества соседей для данной ячейки.

После этого в другой функции a мы будем перебирать ячейки в текущем поколении, подсчитывать соседей с помощью getNeighborCount () и применять правила, создавая следующее поколение, который хранится в массиве nextGen.

Мы должны посмотреть восемь соседних ячеек / соседей,

  • Выше
  • Верхний левый
  • Верхний правый
  • Левый
  • Верно
  • Нижняя левая
  • Внизу справа
  • Нижний

Когда мы считаем соседей, мы должны быть уверены, что не смотрим «за пределы», за пределы мировой сетки. Для этого мы всегда будем проверять, глядя на соседние ячейки, что текущая строка и / или столбец не меньше 0.

Вы можете добавить следующую функцию где угодно. Я поместил свой в конец JavaScript.

function getNeighborCount(row, col) {
    let count = 0;
    let nrow=Number(row);
    let ncol=Number(col);
    
        // Make sure we are not at the first row
        if (nrow - 1 >= 0) {
        // Check top neighbor
        if (currGen[nrow - 1][ncol] == 1) 
            count++;
    }
        // Make sure we are not in the first cell
        // Upper left corner
        if (nrow - 1 >= 0 && ncol - 1 >= 0) {
        //Check upper left neighbor
        if (currGen[nrow - 1][ncol - 1] == 1) 
            count++;
    }
// Make sure we are not on the first row last column
        // Upper right corner
        if (nrow - 1 >= 0 && ncol + 1 < cols) {
        //Check upper right neighbor
            if (currGen[nrow - 1][ncol + 1] == 1) 
                count++;
        }
// Make sure we are not on the first column
    if (ncol - 1 >= 0) {
        //Check left neighbor
        if (currGen[nrow][ncol - 1] == 1) 
            count++;
    }
    // Make sure we are not on the last column
    if (ncol + 1 < cols) {
        //Check right neighbor
        if (currGen[nrow][ncol + 1] == 1) 
            count++;
    }
// Make sure we are not on the bottom left corner
    if (nrow + 1 < rows && ncol - 1 >= 0) {
        //Check bottom left neighbor
        if (currGen[nrow + 1][ncol - 1] == 1) 
            count++;
    }
// Make sure we are not on the bottom right
    if (nrow + 1 < rows && ncol + 1 < cols) {
        //Check bottom right neighbor
        if (currGen[nrow + 1][ncol + 1] == 1) 
            count++;
    }
    
    
        // Make sure we are not on the last row
    if (nrow + 1 < rows) {
        //Check bottom neighbor
        if (currGen[nrow + 1][ncol] == 1) 
            count++;
    }
    
    
    return count;
}

Создание следующего поколения

Теперь мы создадим функцию createNextGen (), которая будет перебирать каждую ячейку, получать счетчик соседей с помощью getNeighborCount () и использовать его с правилами, чтобы определить, текущая ячейка должна остаться в живых, ожить, остаться мертвой или умереть.

Создайте следующую функцию, разместив ее в любом месте вашего JavaScript. Я поставил свой выше getNeighborCount ().

function createNextGen() {
    for (row in currGen) {
        for (col in currGen[row]) {
           
            let neighbors = getNeighborCount(row, col);
         
            // Check the rules
            // If Alive
            if (currGen[row][col] == 1) {
              
                if (neighbors < 2) {
                    nextGen[row][col] = 0;
                } else if (neighbors == 2 || neighbors == 3) {
                    nextGen[row][col] = 1;
                } else if (neighbors > 3) {
                    nextGen[row][col] = 0;
                }
            } else if (currGen[row][col] == 0) {
                // If Dead or Empty
            
                if (neighbors == 3) {
                    // Propogate the species
                    nextGen[row][col] = 1;//Birth?
                }
            }
        }
    }
    
}

Уф. Повесить там!

Обновление мира

Теперь, когда мы вычислили следующее поколение, сохраненное в массиве nextGen, нам нужно обновить массив currGen значениями в nextGen и соответствующим образом обновить нашу мировую таблицу.

Мы также создадим временную кнопку для тестирования поколений.

Мы добавим функцию updateCurrGen (), которая будет принимать значения массива nextGen и помещать их в массив currGen.

Мы также добавим функцию updateWorld (), которая обновит визуальное отображение нашего мира.

Добавьте следующие функции.

function updateCurrGen() {
       
        for (row in currGen) {
            for (col in currGen[row]) {
                // Update the current generation with
                // the results of createNextGen function
                currGen[row][col] = nextGen[row][col];
                // Set nextGen back to empty
                nextGen[row][col] = 0;
            }
        }
     
    }
function updateWorld() {
        let cell='';
        for (row in currGen) {
            for (col in currGen[row]) {
                cell = document.getElementById(row + '_' + col);
                if (currGen[row][col] == 0) {
                    cell.setAttribute('class', 'dead');
                } else {
                    cell.setAttribute('class', 'alive');
                }
            }
        }
    }

Ручное тестирование

Теперь мы создадим простую функцию evolve (), которая будет вызываться из нашей временной кнопки (которая будет добавлена).

Добавьте следующую функцию.

function evolve(){
      
        createNextGen();//Apply the rules
        updateCurrGen();//Set Current values from new generation
        updateWorld();//Update the world view
}

Обновите тело index.html, как показано ниже, с помощью временной кнопки.

<body>
    <div id='world'>
</div>
<div>
    <input type='button' value='Evolve' onclick='evolve()'.>
</div>
    <!-- Our JavaScript -->
    <script src='gol.js'></script>
</body>

Тестовый запуск

Откройте index.html и щелкните несколько ячеек, чтобы начать свою цивилизацию. Затем нажимайте кнопку «Развитие» столько раз, сколько хотите, чтобы увидеть, как она развивается.

Вот мои начальное, первое, второе и десятое поколения.

Добавление некоторых элементов управления плеером

Теперь, когда мы убедились, что это работает, мы можем добавить некоторые элементы управления плеером. Мы добавим пользователю возможность,

  • Запустите процесс воспроизводства - поколения, и пусть он продолжает развиваться.
  • Остановите процесс воспроизведения. Прекратите развиваться.
  • Перезагрузить мир.

Пуск и остановка будут одной и той же кнопкой. После нажатия кнопки «Пуск» воспроизведение изменится на «Остановить».

Примечание. Важно, чтобы игрок нажимал кнопку «Стоп», даже если все жители вымерли. Процесс выполняется независимо.

Удалите временную кнопку и добавьте следующие кнопки в тело index.html.

<body>
    <div id='world'>
</div>
<div>
       
 <Input type='button' id='btnstartstop' value='Start Reproducing'/>
 <Input type='button' id='btnreset' value='Reset World'/>
           
      
</div>
    <!-- Our JavaScript -->
    <script src='gol.js'></script>
</body>

Кнопка Пуск

Для работы Start / Stop нам потребуются некоторые переменные.

  • Нам нужно знать, была ли нажата кнопка Пуск / Стоп (знать, находится ли она в состоянии Пуск или Стоп).
  • Нам нужно иметь возможность повторить процесс эволюции.
  • Нам нужно контролировать скорость эволюции.

Добавьте следующее в верхней части вашего JavaScript.

let started=false;// Set to true when use clicks start
let timer;//To control evolutions
let evolutionSpeed=1000;// One second between generations

Далее нам нужна функция для запуска и остановки эволюционного процесса.

Если мы находимся в состоянии Start, нам нужен цикл для запуска, вызов функции evolve () и использование таймера для управления скоростью эволюции.

Мы создадим функцию startStopGol () и добавим код в нашу функцию evolve ().

Добавьте следующую функцию.

function startStopGol(){
        let startstop=document.querySelector('#btnstartstop');
       
        if (!started) {
           started = true;
           startstop.value='Stop Reproducing';
           evolve();
         
         } else {
            started = false;
            startstop.value='Start Reproducing';
            clearTimeout(timer); 
        }
    }

Обратите внимание, что это вызывает функцию evolve (). Оказавшись в функции evolve (), нам нужно продолжить развитие, пока пользователь не нажмет Стоп.

Измените evolve () следующим образом:

function evolve(){
      
        createNextGen();//Apply the rules
        updateCurrGen();//Set Current values from new generation
        updateWorld();//Update the world view
        if (started) {
            timer = setTimeout(evolve, evolutionSpeed);
        }
}

Кнопка сброса

Для сброса мы просто перезагрузим страницу. Это все переинициализирует.

Добавьте следующую функцию.

function resetWorld() {
        location.reload();
    }

Последний шаг - прикрепить к кнопкам функции startStopGol () и resetWorld ().

Для этого мы будем придерживаться старой школы и будем использовать onclick. Мы также добавим некоторые инструкции.

Измените тело index.html следующим образом.

<h2>Click on table cells to toggle the cells as alive or dead.</h2>
<h3>Click the Start Reproducing button to Start and Stop</h3>
<Input type='button' id='btnstartstop' value='Start Reproducing' onclick='startStopGol();'/>
<Input type='button' id='btnreset' value='Reset World' onclick='resetWorld();'/>

Выполнено!

Теперь у вас должна быть полная Игра Жизни!

Весь код gol.js находится в конце статьи.

Заключение

Game Of Life - это клеточные автоматы, поэтому я рекомендую вам изучить концепцию и поиграть с правилами. Посмотрите на некоторые вариации Игры Жизни.

Но это также делает игру интересной, а также позволяет нам использовать отличный JavaScript. Я также рекомендую вам поэкспериментировать с приложением и улучшить код.

Спасибо за чтение и удачного программирования!

Прочтите все статьи на Medium, которые вам нужны, и помогите мне продолжать писать, став участником Medium всего за 5 долларов в месяц.



Это может вас заинтересовать.



gol.js

const rows = 40;
const cols = 40;
let started=false;// Set to true when use clicks start
let timer;//To control evolutions
let evolutionSpeed=1000;// One second between generations
// Need 2D arrays. These are 1D
let currGen =[rows];
let nextGen =[rows];
// Creates two-dimensional arrays
function createGenArrays() {
    for (let i = 0; i < rows; i++) {
        currGen[i] = new Array(cols);
        nextGen[i] = new Array(cols);
      
    }
}
function initGenArrays() {
    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            currGen[i][j] = 0;
            nextGen[i][j] = 0;
        }
    }
}
function createWorld() {
    let world = document.querySelector('#world');
    
    let tbl = document.createElement('table');
    tbl.setAttribute('id','worldgrid');
for (let i = 0; i < rows; i++) {
        let tr = document.createElement('tr');
        for (let j = 0; j < cols; j++) {
            let cell = document.createElement('td');
            cell.setAttribute('id', i + '_' + j);
            cell.setAttribute('class', 'dead');
            cell.addEventListener('click',cellClick);            
            tr.appendChild(cell);
        }
        tbl.appendChild(tr);
    }
    world.appendChild(tbl);
}
function cellClick() {
    let loc = this.id.split("_");
    let row = Number(loc[0]);
    let col = Number(loc[1]);
// Toggle cell alive or dead
    if (this.className==='alive'){
        this.setAttribute('class', 'dead');
        currGen[row][col] = 0;
    }else{
        this.setAttribute('class', 'alive');
        currGen[row][col] = 1;
    }
}
function createNextGen() {
    for (row in currGen) {
        for (col in currGen[row]) {
           
            let neighbors = getNeighborCount(row, col);
         
            // Check the rules
            // If Alive
            if (currGen[row][col] == 1) {
              
                if (neighbors < 2) {
                    nextGen[row][col] = 0;
                } else if (neighbors == 2 || neighbors == 3) {
                    nextGen[row][col] = 1;
                } else if (neighbors > 3) {
                    nextGen[row][col] = 0;
                }
            } else if (currGen[row][col] == 0) {
                // If Dead or Empty
            
                if (neighbors == 3) {
                    // Propogate the species
                    nextGen[row][col] = 1;// Birth?
                }
            }
        }
    }
    
}
function getNeighborCount(row, col) {
    let count = 0;
    let nrow=Number(row);
    let ncol=Number(col);
    
        // Make sure we are not at the first row
        if (nrow - 1 >= 0) {
        // Check top neighbor
        if (currGen[nrow - 1][ncol] == 1) 
            count++;
    }
        // Make sure we are not in the first cell
        // Upper left corner
        if (nrow - 1 >= 0 && ncol - 1 >= 0) {
        //Check upper left neighbor
        if (currGen[nrow - 1][ncol - 1] == 1) 
            count++;
    }
// Make sure we are not on the first row last column
        // Upper right corner
        if (nrow - 1 >= 0 && ncol + 1 < cols) {
        //Check upper right neighbor
            if (currGen[nrow - 1][ncol + 1] == 1) 
                count++;
        }
// Make sure we are not on the first column
    if (ncol - 1 >= 0) {
        //Check left neighbor
        if (currGen[nrow][ncol - 1] == 1) 
            count++;
    }
    // Make sure we are not on the last column
    if (ncol + 1 < cols) {
        //Check right neighbor
        if (currGen[nrow][ncol + 1] == 1) 
            count++;
    }
// Make sure we are not on the bottom left corner
    if (nrow + 1 < rows && ncol - 1 >= 0) {
        //Check bottom left neighbor
        if (currGen[nrow + 1][ncol - 1] == 1) 
            count++;
    }
// Make sure we are not on the bottom right
    if (nrow + 1 < rows && ncol + 1 < cols) {
        //Check bottom right neighbor
        if (currGen[nrow + 1][ncol + 1] == 1) 
            count++;
    }
    
    
        // Make sure we are not on the last row
    if (nrow + 1 < rows) {
        //Check bottom neighbor
        if (currGen[nrow + 1][ncol] == 1) 
            count++;
    }
    
    
    return count;
}
    
    function updateCurrGen() {
       
        for (row in currGen) {
            for (col in currGen[row]) {
                // Update the current generation with
                // the results of createNextGen function
                currGen[row][col] = nextGen[row][col];
                // Set nextGen back to empty
                nextGen[row][col] = 0;
            }
        }
     
    }
function updateWorld() {
        let cell='';
        for (row in currGen) {
            for (col in currGen[row]) {
                cell = document.getElementById(row + '_' + col);
                if (currGen[row][col] == 0) {
                    cell.setAttribute('class', 'dead');
                } else {
                    cell.setAttribute('class', 'alive');
                }
            }
        }
    }
function evolve(){
      
        createNextGen();
        updateCurrGen();
        updateWorld();
if (started) {
            timer = setTimeout(evolve, evolutionSpeed);
        }
}
function startStopGol(){
        let startstop=document.querySelector('#btnstartstop');
       
        if (!started) {
           started = true;
           startstop.value='Stop Reproducing';
           evolve();
         
         } else {
            started = false;
            startstop.value='Start Reproducing';
            clearTimeout(timer); 
        }
    }
 
  
    function resetWorld() {
        location.reload();
  
    }
window.onload=()=>{
    createWorld();// The visual table
    createGenArrays();// current and next generations
    initGenArrays();//Set all array locations to 0=dead
}