Cellular automata works in Python, not Javascript

I’ve made two versions of the same celluar automata in JavaScript and Python.

The Python version works perfectly as expected, whereas the JavaScript doesn’t…

The problem is the third generation.

How it looks in Python:

How it looks in JavaScript:

enter image description here

Two cells (C and D) should change from defectors back to cooperators (blue cell). This is because these cells have a cooperator in their neighborhood that scores a 6 (6 > 5.7). On the other hand, cells A and B change rightfully back to cooperators. My confusion is that this automata is perfectly symmetrical. Why is A and B working, but not C and D when they are, in theory, existing under the exact same conditions?

Some where in the update function suspected I the cells C and D have a neighbor whose score in generation= 3 is 6 (a cooperator. Only a cooperator could score this. See picture of generation = 2 below to see what I mean) but this neighbor’s actual strategy is for some reason being written as a defector, when is should be a cooperator.

 this.updateCell = function(row, col) {
        let player = this.active_array[row][col];
        let best_strat = 0;
        let highest_score = 0; 
        player.neighbours.forEach(coords => {
            let x_coord = coords[0];
            let y_coord = coords[1];
            let neighbour = this.active_array[x_coord][y_coord];
            if (neighbour.score > highest_score) {
                highest_score = neighbour.score;
                best_strat = neighbour.strat;
            }
        });
        if (player.score < highest_score) {
            return best_strat;
        }
        if (player.score >= highest_score) {
            return player.strat;
        }
    }

I ran some console logs in the third generation and it seems to be the case. When cells C and D update, highest_score is returning a 6 (hence, a cooperator) but best_strategy is returning a 1 (meaning a defector). Meaning, cells C and D must have a neighbor whose strategy should be a cooperator but it’s actually a defector (even though the scores are correct). If I’m right, I’m not sure how to fix this.

What’s equally annoying is that I’ve tried basically copying the logic from the working Python version in JavaScript exactly, but no matter what I change, nothing seems to fix it. I can’t find any difference between the two! :<

Again, the first two generations of the JavaScript version work perfectly.

enter image description here

enter image description here

enter image description here

For reference, the working Python code:

from calendar import c
from random import choices, sample, shuffle
import numpy as np
import pandas as pd
import copy
import matplotlib.pyplot as plt
from collections import Counter

v = 1.96
generations = 100
width = 100
height = 100
# cooperate = 0, defect = 1 
strategies = ['0', '1']
next_cells = {}
temp = [0.2, 0.8]
payoffs = np.array([[1, 0], [1 + v, 0]])
rpm = pd.DataFrame(payoffs, columns=['0', '1'])
rpm.index = ['0', '1']
cpm = rpm.T
output2 = []

for x in range(width):
    for y in range(height):
        if (x == width/2) and (y == height/2):
            strat="1"
        else:
            strat="0"
        next_cells[(x, y)] = {
            'strategy': strat, 
            'prev_strat': None, 
            'score': 0, 
            'neighbours': [((x + 1) % width, y), ((x - 1) % width, y), (x, (y - 1) % height), (x, (y + 1) % height),
            ((x + 1) % width, (y - 1) % height), ((x + 1) % width, (y + 1) % height), ((x - 1) % width, (y - 1) % height), ((x - 1) % width, (y + 1) % height)
            ]
        }

for gen in range(generations):
    output = np.zeros(shape=(width, height))
    cells = copy.deepcopy(next_cells)
    for coord, cell in cells.items():
        score = 0
        if cell['strategy'] == '0':
                score += 1
        for neighbour in cell['neighbours']:
            if cell['strategy'] == '0' and cells[neighbour]['strategy'] == '0':
                score += 1
            if cell['strategy'] == '1' and cells[neighbour]['strategy'] == '0':
                score += v
        cell['score'] = score
                
    for coord, cell in cells.items():
        highest_score = 0
        best_strat = None
        for neighbour in cell['neighbours']:
            if cells[neighbour]['score'] > highest_score:
                highest_score = cells[neighbour]['score']
                best_strat = cells[neighbour]['strategy']
        if cell['score'] < highest_score:
            next_cells[coord]['strategy'] = best_strat
            next_cells[coord]['prev_strat'] = cell['strategy']
        if cell['score'] >= highest_score:
            next_cells[coord]['strategy'] = cell['strategy']
            next_cells[coord]['prev_strat'] = cell['strategy']      
        
        x, y = coord[0], coord[1]
        if next_cells[coord]['strategy'] == '0' and next_cells[coord]['prev_strat'] == '1':
            output[x][y] = 2
        elif next_cells[coord]['strategy'] == '1' and next_cells[coord]['prev_strat'] == '0':
            output[x][y] = 3
        else:
            output[x][y] = int(next_cells[coord]['strategy'])
    

    plt.imshow(output, interpolation='nearest')
    plt.colorbar()
    plt.pause(0.01)
    plt.savefig(f"images/foo{gen}.png")
    plt.close("all")

plt.show()

JavaScript code:

class Cell4 {

    constructor() {

        this.score = 0;
        this.neighbours;
        this.strat;
        this.prev_strat;
        this.isMoore = nearestneighbours;
        this.neighbourhood_steps = neighbourhood_steps;

        this.initNeighbours = function(step, row, col) {
            let array = [];
            for (let i = 1; i <= step; i++) {

                if (this.isMoore == "true") {
                    array.push(
                    [(((row - i) % rows) + rows) % rows, (((col - i) % cols) + cols) % cols],
                    [(((row + i) % rows) + rows) % rows, (((col + i) % cols) + cols) % cols],
                    [(((row - i) % rows) + rows) % rows, (((col + i) % cols) + cols) % cols],
                    [(((row + i) % rows) + rows) % rows, (((col - i) % cols) + cols) % cols],
                   )
                }
                array.push(
                [(((row - i) % rows) + rows) % rows, col],
                [(((row + i) % rows) + rows) % rows, col],  
                [row, (((col - i) % cols) + cols) % cols],
                [row, (((col + i) % cols) + cols) % cols],
                )
            }
            this.neighbours = array;
        };
    }
}

class FractalPD {

    constructor() {

        this.u = 1.96;
        this.cooperator_color="blue";
        this.defect_color="red";
        this.was_defect_color="cyan";
        this.was_coop_color="yellow";
        this.active_array = [];
        this.inactive_array = [];
        
        this.makeGrid = function() {
            for (let i = 0; i < rows; i++) {
                this.active_array[i] = [];
                for (let j = 0; j < cols; j++) {
                   this.active_array[i][j] = 0;
                }
            }
            this.inactive_array = this.active_array;
        };

        this.randomizeGrid = function() {
            for (let i = 0; i < rows; i++) {
                for (let j = 0; j < cols; j++) {
                    const cell = new Cell4();
                    cell.strat = 0;
                    cell.prev_strat = 0;
                    cell.initNeighbours(cell.neighbourhood_steps, i, j);
                    this.active_array[i][j] = cell;
                    if (i === parseInt(rows/2) && j === parseInt(cols/2)) {
                        this.active_array[i][j].strat = 1;
                        this.active_array[i][j].prev_strat = 1;
                    }
                }
            }
        };

        this.fillGrid = function() {
            // cooperate = 0 defect = 1
            for (let i = 0; i < rows; i++) {
                for (let j = 0; j < cols; j++) {
                    if (this.active_array[i][j].strat === 0 && this.active_array[i][j].prev_strat === 0) {
                        ctx.fillStyle = this.cooperator_color;
                    }
                    
                    if (this.active_array[i][j].strat === 1 && this.active_array[i][j].prev_strat === 1) {
                        ctx.fillStyle = this.defect_color;
                        
                    }
                    if (this.active_array[i][j].strat === 1 && this.active_array[i][j].prev_strat === 0) {
                        ctx.fillStyle = this.was_coop_color;
                        
                    }
                    if (this.active_array[i][j].strat === 0 && this.active_array[i][j].prev_strat === 1) {
                        ctx.fillStyle = this.was_defect_color;
                        
                    }
                    ctx.fillRect(j * cell_size, i * cell_size, cell_size - 1, cell_size - 1);
                    ctx.textAlign="center"; 
                    ctx.textBaseline = "middle";
                    ctx.fillStyle = "black";
                    ctx.fillText(`${this.active_array[i][j].score.toFixed(1)}`, (j * cell_size) + cell_size/2 , (i * cell_size) + cell_size/2)
                }
            }
        };
        
        this.playRound = function(row, col) {
            const player = this.active_array[row][col];
            const neighbours = player.neighbours;
            let score = 0;
            if (player.strat === 0) {
                score += 1;
            }
            neighbours.forEach(coords => {
                let x_coord = coords[0];
                let y_coord = coords[1];
                let neighbour = this.active_array[x_coord][y_coord];
                if (player.strat === 0 && neighbour.strat === 0) {
                   score += 1;
                }
                if (player.strat === 1 && neighbour.strat === 0) {
                   score += this.u;
                }
            });
            player.score = score;
        };

        this.updateCell = function(row, col) {
            let player = this.active_array[row][col];
            let best_strat = 0;
            let highest_score = 0; 
            player.neighbours.forEach(coords => {
                let x_coord = coords[0];
                let y_coord = coords[1];
                let neighbour = this.active_array[x_coord][y_coord];
                if (neighbour.score > highest_score) {
                    highest_score = neighbour.score;
                    best_strat = neighbour.strat;
                }
            });
            if (player.score < highest_score) {
                return best_strat;
            }
            if (player.score >= highest_score) {
                return player.strat;
            }
        }

        this.updateGrid = function() {

            for (let i = 0; i < rows; i++) {
                for (let j = 0; j < cols; j++) {
                    this.playRound(i, j);
                }
            }
            for (let i = 0; i < rows; i++) {
                for (let j = 0; j < cols; j++) {
                    let old_state = this.active_array[i][j].strat;
                    let new_state = this.updateCell(i, j);
                    this.inactive_array[i][j].strat = new_state;
                    this.inactive_array[i][j].prev_strat = old_state;
                }
            }
            this.active_array = this.inactive_array;
        };

        this.gameSetUp = () => {
            this.makeGrid();
        };

        this.runGame = () => {
            this.updateGrid();
            this.fillGrid();
        };
    }    
}

Leave a Comment