Реализация игры Конвея «Жизнь» на Javascript — проблемы с клеточной логикой

Я решил написать собственную реализацию «Игры жизни» Конвея и выбрал Javascript. Это чистый DHTML. Я разобрался с основами, но это, кажется, не соответствует нормальному поведению Game of Life. Например, такие узоры, как планеры, не «скользят», осцилляторы не колеблются. Я провел много времени, пробуя различную логику, и сделал более десяти переписываний.

Так что, если бы я сделал шаблон планера, он просто превратился бы в квадратный натюрморт через одно поколение.

Вот код Javascript (извините за некомментированный код):

var chanceOfLiveCells = 0.1;
var gridReference = null;
var gridDimension = 15;
var cells = null;
var cellsAlive = 0;
var cellsDead = 0;
var currentGeneration = 0;

function init() {

    gridReference = document.getElementById('grid');
    cells = new Array();

    drawGrid();
    placeRandomCells();
    nextGeneration();

}

function drawGrid() {

    var gridArray = new Array();
    var counter = 0;

    for(var x = 0; x <= gridDimension - 1; x = x + 1) {

        gridArray.push('<tr>');

        for(var y = 0; y <= gridDimension - 1; y = y + 1) {

            //gridArray.push('<td id="' + counter + '">' + x + ', ' + y + ' (' + counter + ')' + '</td>');
            gridArray.push('<td id="' + counter + '"></td>');
            cells.push(counter);
            counter = counter + 1;

        }

        gridArray.push('</tr>');

    }

    grid = gridArray.join('');
    gridReference.innerHTML = grid;

}

function nextGeneration() {

    currentGeneration = currentGeneration + 1;

    for(var x = 0; x <= gridDimension - 1; x = x + 1) {

        for(var y = 0; y <= gridDimension - 1; y = y + 1) {

            var neighbours = cellNeighbourCount(x, y);

            if(isCellLive(x, y) == true) {

                if(neighbours < 2) {

                    setDeadCell(x, y);
                    cellsDead = cellsDead + 1;

                }

                if(neighbours == 2 || neighbours == 3) {

                    setLiveCell(x, y);
                    cellsAlive = cellsAlive + 1;

                }

                if(neighbours > 3) {

                    setDeadCell(x, y);
                    cellsDead = cellsDead + 1;

                }

            }

            else if(isCellLive(x, y) == false){

                if(neighbours == 3) {

                    setLiveCell(x, y);
                    cellsAlive = cellsAlive + 1;

                }

            }

        }

    }

    document.getElementById('currentGeneration').innerHTML = currentGeneration;
    document.getElementById('cellsAlive').innerHTML = cellsAlive;
    document.getElementById('cellsDead').innerHTML = cellsDead;

    cellsAlive = 0;
    cellsDead = 0;

    setTimeout('nextGeneration()', 200);

}

function placeRandomCells() {

    for(var x = 0; x <= gridDimension - 1; x = x + 1) {

        for(var y = 0; y <= gridDimension - 1; y = y + 1) {

            if(Math.random() < chanceOfLiveCells) {

                setLiveCell(x, y);

            }

            else {

                setDeadCell(x, y);

            }

        }

    }

}

function setLiveCell(x, y) {

    document.getElementById(getCell(x, y)).style.backgroundColor = 'red';

}

function setDeadCell(x, y) {

    document.getElementById(getCell(x, y)).style.backgroundColor = 'maroon';

}

function cellNeighbourCount(x, y) {

    var count = 0;

    if(isCellLive(x - 1, y) == true) {

        count = count + 1;

    }

    if(isCellLive(x - 1, y + 1) == true) {

        count = count + 1;

    }

    if(isCellLive(x, y + 1) == true) {

        count = count + 1;

    }

    if(isCellLive(x + 1, y + 1) == true) {

        count = count + 1;

    }

    if(isCellLive(x + 1, y) == true) {

        count = count + 1;

    }

    if(isCellLive(x + 1, y - 1) == true) {

        count = count + 1;

    }

    if(isCellLive(x, y - 1) == true) {

        count = count + 1;

    }

    if(isCellLive(x - 1, y - 1) == true) {

        count = count + 1;

    }

    return count;

}

function isCellLive(x, y) {

    if(document.getElementById(getCell(x, y)).style.backgroundColor == 'red') {

        return true;

    }

    return false;

}

function getCell(x, y) {

    if(x >= gridDimension) {

        x = 0;

    }

    if(y >= gridDimension) {

        y = 0;

    }

    if(x < 0) {

        x = gridDimension - 1;

    }

    if(y < 0) {

        y = gridDimension - 1;

    }

    return cells[x * gridDimension + y];

}

function createSquare() {

    setLiveCell(4, 4);
    setLiveCell(4, 5);
    setLiveCell(4, 6);
    setLiveCell(5, 6);
    setLiveCell(6, 6);
    setLiveCell(6, 5);
    setLiveCell(6, 4);
    setLiveCell(5, 4);

}

function createBlinker() {

    setLiveCell(4, 5);
    setLiveCell(5, 5);
    setLiveCell(6, 5);

}

function createBeacon() {

    setLiveCell(4, 4);
    setLiveCell(4, 5);
    setLiveCell(5, 4);
    setLiveCell(5, 5);
    setLiveCell(6, 6);
    setLiveCell(6, 7);
    setLiveCell(7, 6);
    setLiveCell(7, 7);

}

person Mark    schedule 15.05.2011    source источник


Ответы (1)


нет функции 'renderNextGeneration()'

setInterval() может быть более подходящим и поддерживаемым для того, что вы делаете.

«Консоль ошибок» Firefox очень помогает в отладке ошибок javascript.


Исправление этого должно заставить его работать, по крайней мере; это происходит в моей системе.

jcomeau@intrepid:~/stackoverflow/gameoflife$ bzr diff -r18 bad.js
=== modified file 'gameoflife/bad.js'
--- gameoflife/bad.js   2011-05-15 14:09:12 +0000
+++ gameoflife/bad.js   2011-05-16 04:14:40 +0000
@@ -1,10 +1,11 @@
-var chanceOfLiveCells = 0.1;
+var chanceOfLiveCells = 0.0;
 var gridReference = null;
 var gridDimension = 15;
 var cells = null;
 var cellsAlive = 0;
 var cellsDead = 0;
 var currentGeneration = 0;
+var timer = null;

 function init() {

@@ -13,7 +14,8 @@

     drawGrid();
     placeRandomCells();
-    nextGeneration();
+    createBlinker();
+    timer = setInterval(nextGeneration, 200);

 }

@@ -45,7 +47,7 @@
 }

 function nextGeneration() {
-
+    alert("next generation");
     currentGeneration = currentGeneration + 1;

     for(var x = 0; x <= gridDimension - 1; x = x + 1) {
@@ -93,15 +95,16 @@
         }

     }
-
+    
+    repaint();
     document.getElementById('currentGeneration').innerHTML = currentGeneration;
     document.getElementById('cellsAlive').innerHTML = cellsAlive;
     document.getElementById('cellsDead').innerHTML = cellsDead;
+    if (cellsAlive == 0) clearInterval(timer);

     cellsAlive = 0;
     cellsDead = 0;

-    setTimeout('nextGeneration()', 200);

 }

@@ -127,17 +130,40 @@

     }

+    repaint();
+
+}
+function repaint() {
+
+    for(var x = 0; x <= gridDimension - 1; x = x + 1) {
+
+        for(var y = 0; y <= gridDimension - 1; y = y + 1) {
+
+            repaintCell(x, y);
+
+        }
+
+    }
+
+}
+
+function repaintCell(x, y) {
+
+    cell = document.getElementById(getCell(x, y));
+    color = cell.style.color;
+    cell.style.backgroundColor = color;
+
 }

 function setLiveCell(x, y) {

-    document.getElementById(getCell(x, y)).style.backgroundColor = 'red';
+    document.getElementById(getCell(x, y)).style.color = 'red';

 }

 function setDeadCell(x, y) {

-    document.getElementById(getCell(x, y)).style.backgroundColor = 'maroon';
+    document.getElementById(getCell(x, y)).style.color = 'maroon';

 }

@@ -249,7 +275,7 @@
     setLiveCell(6, 5);
     setLiveCell(6, 4);
     setLiveCell(5, 4);
-
+    repaint()
 }

 function createBlinker() {
@@ -257,7 +283,7 @@
     setLiveCell(4, 5);
     setLiveCell(5, 5);
     setLiveCell(6, 5);
-
+    repaint();
 }

 function createBeacon() {
@@ -270,5 +296,5 @@
     setLiveCell(6, 7);
     setLiveCell(7, 6);
     setLiveCell(7, 7);
-
+    repaint();
 }

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

person jcomeau_ictx    schedule 15.05.2011
comment
Просто предположим, что это nextGeneration() - person Mark; 15.05.2011
comment
Я бы не сказал, что это ошибка, это скорее проблема с логикой, так что консоль ошибок бесполезна. - person Mark; 15.05.2011
comment
Сегодня утром провел небольшое тестирование и выложил патч. - person jcomeau_ictx; 15.05.2011
comment
Это проблема с логикой. Правила, которые я пытаюсь смоделировать, таковы: - Если какая-либо живая ячейка имеет менее двух соседних ячеек, она умирает. - Если любая живая клетка имеет ровно две или три соседние клетки, она живая. - Если какая-либо живая клетка имеет более трех соседних ячеек, она умирает. - Если у любой мертвой клетки есть ровно три живые соседние клетки, она становится живой. Я протестировал все эти правила, и единственное, которое, похоже, работает не совсем так, как должно, — это четвертое правило. Ваш предполагаемый патч по-прежнему ведет себя так же. - person Mark; 15.05.2011
comment
Итак, на словах, как бы вы помешали ему убить соседние клетки? Остановить таймер? Я немного запутался. Спасибо за помощь, jcomeau_ictx. - person Mark; 15.05.2011
comment
Кроме того, как вы думаете, разделение процесса на два этапа устранит эту логическую ошибку? Итак, проверяя, какие клетки умрут, а какие будут жить, следующим шагом будет их рендеринг. Может быть, мне следует разделить ячейки, имея два массива, которые содержат живые и мертвые ячейки с их идентификаторами? - person Mark; 15.05.2011
comment
Кроме того, ваше обновление по-прежнему имеет такое же поведение. Я собираюсь переписать его, но разделю живые и мертвые клетки, а также разделю процесс на две части, подготовив клетки к смерти или оживлению, а затем визуализирую это изменение. - person Mark; 15.05.2011
comment
Я не обновлял патч; решение, как это исправить, должно было быть за вами. Я считаю, что разделение nextGeneration на два этапа, как вы сказали, решит проблему. Возможно, nextGeneration() может установить неиспользуемый атрибут 'color' (передний план), а repaint() может установить для фона каждой ячейки ее основной цвет. - person jcomeau_ictx; 16.05.2011
comment
Все еще с той же проблемой. - person Mark; 16.05.2011
comment
В настоящее время я испытываю другую проблему. Это переписывание, которое я делаю: 6012605/ - person Mark; 16.05.2011
comment
Хорошо, но прежде чем заняться этим, я реализовал то, что сказал вам сделать, и, похоже, это работает. Поэтому я собираюсь заменить патч выше тем, что работает. - person jcomeau_ictx; 16.05.2011
comment
Патч теперь работает, спасибо jcomeau_ictx. Я был бы не против узнать, что не так с моим последним переписыванием. Но я собираюсь использовать код, который я изначально сделал с вашим патчем. Благодарю вас! - person Mark; 16.05.2011