1

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()

Robin Andrews
  • 3,514
  • 11
  • 43
  • 111

1 Answers1

2

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.

Hans
  • 2,419
  • 2
  • 30
  • 37
  • 1
    To follow that, in the main, for last_grid, you should probably also deep copy. – Juan Carlos Ramirez Nov 26 '21 at 13:35
  • Good point about the last_grid - I missed that. (And I don't fully understand why it just worked on my machine without that.) – Hans Nov 26 '21 at 13:50
  • 1
    It would improve readability to use explicitely [copy.deepcopy](https://docs.python.org/3/library/copy.html#copy.deepcopy): `new_board = copy.deepcopy(grid)`. – Demi-Lune Nov 26 '21 at 13:58