pythonpuzzleconways-game-of-lifelogic-error

Logic Error in Chessboard Virus Simulation


The Chessboard virus problem is similar to Conway's "Game of Life". The rule is that any two squares with infected neighbours will become infected.

I have tried to implement the simulation in Python, but there is a logic error. I think what is happening is that the board is being updated within a single round, so intermediate steps are missed. For example, with

grid = [
    [0, 1, 0, 1],
    [1, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0]
]

I get following output:

Initial State of board:

 0 1 0 1
 1 0 0 0
 0 0 0 0
 0 0 0 0

State 2 of board:

 1 1 1 1
 1 1 1 1
 0 0 0 0
 0 0 0 0

Infinite loop entered.

The first round should only show

1 1 1 1
1 1 0 0
0 0 0 0
0 0 0 0

Unless I am mistaken.

Can anyone help me to fix the logic error please?

grid = [
    [0, 1, 0, 1],
    [1, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0]
]


def write(grid):
    """
    Writes the lists of lists of booleans
    in grid using 0 for False and 1 for True.
    """
    for row in grid:
        for item in row:
            print(f" {item}", end="")
        print()


def neighbors(grid, i, j):
    """
    Returns the number of live cells
    next to grid[i][j]. Does not include diagonals.
    """
    num_neighbours = 0
    if i > 0:
        if grid[i - 1][j]:
            num_neighbours = num_neighbours + 1
    if i < len(grid) - 1:
        if grid[i + 1][j]:
            num_neighbours = num_neighbours + 1
    if j > 0:
        if grid[i][j - 1]:
            num_neighbours = num_neighbours + 1
    if j < len(grid[i]) - 1:
        if grid[i][j + 1]:
            num_neighbours = num_neighbours + 1
    print(num_neighbours)
    return num_neighbours


def update(grid):
    """
    Applies the rule of the chessboard virus to grid
    and returns a new grid.
    """
    new_board = grid[:]
    for i in range(len(grid)):
        for j in range(len(grid[i])):
            num_neighbours = neighbors(grid, i, j)
            if num_neighbours >= 2:
                new_board[i][j] = 1
    return new_board


def check_all_ones(a):
    return not any(c != 1 for r in a for c in r)


def main():
    """
    Runs the simulation.
    """
    global grid
    grid = grid
    print("Initial State of board:")
    print()
    write(grid)
    state_num = 1
    while True:
        last_grid = grid[:]  # Clone grid.
        grid = update(grid)
        state_num = state_num + 1
        print()
        print(f"State {state_num} of board:")
        print()
        write(grid)
        if check_all_ones(grid):
            print()
            print("Virus has spread to whole board.")
            break
        if grid == last_grid:
            print()
            print("Infinite loop entered.")
            break


if __name__ == "__main__":
    main()


Solution

  • I believe the problem is that new_board = grid[:] is not a 'deep' copy - the individual row lists are the original list objects from grid, so the update steps set + find all the newly filled neighbours in the first sweep.

    It appears to work with a deep copy, e.g., using new_board = [r.copy() for r in grid] or new_board = copy.deepcopy(grid) (thnx @Demi-Lune).

    As noted in the comment by @Ramirez below, the same applies to the clone in the main loop.