0

I am new to programming and I have an interest in cellular automata, so I decided to try to script one using JavaScript both for coding practice and so that I could make a personalised one. The cellular automata project I created is for a simple binary (black and white) 2D table CA which looks at the colours of the 8 nearest neighbors of a cell and the colour of the cell itself and updates its colour depending on the rules given in the 'ruleset' table below the CA table. Only problem is the code that I wrote takes forever to process each iteration, clearly because of all the large loops it needs. In fact as I am writing this I realise that I can reduce the processing power needed by stopping the comparison search between the current neighbour colour configuration and the set of all possible configurations when the if statement finds the correct configuration, but this will probably not reduce the processing power needed by the amount that I would prefer and I am sure that there are more ways to make it faster. If anybody could give me some advice on how to reduce the processing power even more I would really appreciate it. Also, please explain your answers in laymans terms. Thank you! Here is the code:

<!DOCTYPE html>
<html>
<head>
<title></title>


<style>
table {border-collapse: collapse;}
table, td, th {border: 1px solid black;}
td {width:1px; height:1px;}
</style>

</head>
<body>

<button onclick="Toggle()">Toggle</button>  

<!-- Toggle runs the Iterate function with a setInterval -->

<button onclick="Iterate()">Iterate</button>
<br>


<script>


document.write("<table>")
for (row=0; row<100; row++) {
document.write("<tr>")
for (col=0; col<100; col++) 
{document.write("<td id = 'R" + row + "C" + col + "'  style='background-color: white' ondblclick='MouseDown(this)' onmousedown='MouseDown(this)' onmouseover='MouseUp(this)'>" + "</td>")}
document.write("</tr>")}
document.write("</table>")

// This is the cellular automata table

document.write("<br>")

document.write("<table>")
for (row=0; row<16; row++) {
document.write("<tr>")
for (col=0; col<32; col++) 
{document.write("<td id = 'id" + (col+32*row) + "'  style='background-color: white' ondblclick='MouseDown(this)' onmousedown='MouseDown(this)' onmouseover='MouseUp(this)'>" + "</td>")}
document.write("</tr>")}
document.write("</table>")

// This is the 'ruleset' table


let determiner = 0
function MouseDown(cell) {determiner = 1
if (cell.style.backgroundColor == "white") {cell.style.backgroundColor = "black"}
else {cell.style.backgroundColor = "white"}}
window.addEventListener('mouseup', function(event){determiner = 0})
function MouseUp(cell) {if (determiner == 1) {
if (cell.style.backgroundColor == "white") {cell.style.backgroundColor = "black"}
else {cell.style.backgroundColor = "white"}}}

// This section provides the click & drag cell colour changing functions



for (i=0; i<512; i++) {
if (i % 512 < 256){this["j1"] = "white"} else {this["j1"] = "black"}
if (i % 256 < 128){this["j2"] = "white"} else {this["j2"] = "black"}
if (i % 128 < 64){this["j3"] = "white"} else {this["j3"] = "black"}
if (i % 64 < 32){this["j4"] = "white"} else {this["j4"] = "black"}
if (i % 32 < 16){this["j5"] = "white"} else {this["j5"] = "black"}
if (i % 16 < 8){this["j6"] = "white"} else {this["j6"] = "black"}
if (i % 8 < 4){this["j7"] = "white"} else {this["j7"] = "black"}
if (i % 4 < 2){this["j8"] = "white"} else {this["j8"] = "black"}
if (i % 2 < 1){this["j9"] = "white"} else {this["j9"] = "black"}
this["compare"+i] = {unit00: j1,unit01: j2,unit02: j3,unit10: j4,unit11: j5,unit12: j6,unit20: j7,unit21: j8,unit22: j9}
}

// This creates an object for each possible block of 9 cells to compare with the actual blocks of cells around each cell in the Iterate() function


function Iterate() {
this["groupvec"] = []
for (i=0; i<100; i++) {
for (j=0; j<100; j++) {
if (i !== 0 && i !== 99) {rownum = [i-1, i, i+1]}
else if (i == 0) {rownum = [99, 0, 1]}
else if (i == 99) {rownum = [98, 99, 0]}
if (j !== 0 && j !== 99) {colnum = [j-1, j, j+1]}
else if (j == 0) {colnum = [99, 0, 1]}
else if (j == 99) {colnum = [98, 99, 0]}
this["group"+"R"+i+"C"+j] = {}
for (r in rownum) {
for (c in colnum) {
this["group"+"R"+i+"C"+j]['unit'+r.toString()+c.toString()] = document.getElementById("R" + rownum[r] + "C" + colnum[c]).style.backgroundColor
}}
this["groupvec"].push( JSON.stringify(this["group"+"R"+i+"C"+j]) )
}}
for (i=0; i<100; i++) {
for (j=0; j<100; j++) {
for (k=0; k<512; k++) {
if (groupvec[j+(100*i)] == JSON.stringify(window["compare"+k.toString()])) {
document.getElementById("R"+i+"C"+j).style.backgroundColor = document.getElementById("id"+k).style.backgroundColor
}}}}}

// This function finds the colours of the cells in a block of 9 cells around each cell, compares them with the 'compare' objects and then changes their colour to the colour of the 'ruleset' table with the same index as the 'compare' object.

let toggler = null
function Toggle() {
if (toggler == null) {toggler = setInterval(Iterate.bind(null), 1000)}
else {clearInterval(toggler); toggler = null}
}

// This provides an automated run function for the CA

</script>


</body>
</html>
  • 2
    If you have working code and are looking to improve it, you should post it on https://codereview.stackexchange.com/ –  Nov 30 '18 at 11:57

1 Answers1

0

Your code loops 5 210 000 times for every iteration (100 rows * 100 columns * 3 cells * 3 cells + 100 rows * 100 columns * 512 ruleset cells). That's quite a lot of work for every iteration, and it is exacerbated by your use of an HTML <table>, from and to which you are constantly reading and writing styles.

If you want a more tenable solution, try using a Canvas for displaying your CA's state, and JavaScript arrays for handling the state. You could still perhaps use a table to set the ruleset at the start, and then store it in an array, so that when you check it you are checking an in-memory array of data.

Your code then might look something like this, which loops 80 000 times (although it might be more if you incorporate your rulesets):

const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const width = 100;
const height = 100;
const cellWidth = 4;
const cellHeight = 4;

// Access cells with automaton[row][column]
let automaton = Array(height).fill(Array(width).fill(0)));

// Initialise your automaton somehow
// ...

function iterate() {
    // Create the next automaton state to write updates to
    let nextAutomaton = Array(height).fill(Array(width).fill(0)));
    for (let y = 0; y < height; ++y) {
        for (let x = 0; x < width; ++x) {
            // Get the surrounding 8 cells
            // (n + MAX + 1) % MAX will wrap around
            // (n + MAX - 1) % MAX will wrap around
            const surroundingIndices = [
                { x: x, y: (y + height - 1) % height },                       // above
                { x: (x + width + 1) % width, y: (y + height - 1) % height }, // above right
                { x: (x + width + 1) % width, y: y },                         // right
                { x: (x + width + 1) % width, y: (y + height + 1) % height }, // below right
                { x: x, y: (y + height + 1) % height },                       // below
                { x: (x + width - 1) % width, y: (y + height + 1) % height }, // below left
                { x: (x + width - 1) % width, y: y },                         // left
                { x: (x + width - 1) % width, y: (y + height - 1) % height }  // above left
            ];
            for (int i = 0; i < 8; ++i) {
               const cell = automaton[surroundingIndices.y][surroundingIndices.x];
               // Do something based on the value of this surrounding cell
               // This could depend on your ruleset
               // ...
            }
            // Set this cell's value in the next state
            nextAutomaton[y][x] = 1;
            // Render the cell
            context.fillStyle = 'red';
            context.fillRect(x, y, cellWidth, cellHeight);
        }
    }
    // Overwrite the old automaton state
    automaton = nextAutomaton;
}

For animating your automaton, you will want to use window.requestAnimationFrame, which must call itself recursively with the iterate function, and which you can start or stop with your toggle button (see window.cancelAnimationFrame).

Billy Brown
  • 2,272
  • 23
  • 25