Search code examples
pythonnumpyconways-game-of-life

Conway's Game of Life: check if a cell is in the corner/border


I am trying to implement Game Of Life in Python. The new_state function should count the neighbours of a cell and decide, based on the rules (if statements) if it will die(turn 0) or stay alive(turn 1) in the next generation.

Is there a way to check if a value is in the corner/border of an array? The neighbouring cells of a cell will then be the immediate surrounding cells, there is no need to wrap the array. Right now, new_state throws out an index error. I am using numPy for this function.

import numpy

def new_state(array):
    updated_array=[]
    for x in range(len(array)):
        for y in range(len(array[x])):
            cell = array[x][y]
            neighbours = (array[x-1][y-1], array[x][y-1], array[x+1][y-1], array[x+1][y], array[x+1][y+1], array[x][y+1],
                          array[x-1][y+1], array[x-1][y])
            neighbours_count = sum(neighbours)
            if cell == 1:
                if neighbours_count == 0 or neighbours_count == 1:
                    updated_array.append(0)
                elif neighbours_count == 2 or neighbours_count == 3:
                    updated_array.append(1)
                elif neighbours_count > 3:
                    updated_array.append(0)
            elif cell == 0:
                if neighbours_count == 3:
                    updated_array.append(1)
    return updated_array

Solution

  • To avoid the index error you can pad the array with zeroes:

    def new_state(array):
        array_padded = np.pad(array, 1)
        updated_array=array.copy()
        for x in range(1, array.shape[0]+1):
            for y in range(1, array.shape[1]+1):
                cell = array[x-1][y-1]
                neighbours = (array_padded[x-1][y-1], array_padded[x][y-1],
                              array_padded[x+1][y-1], array_padded[x+1][y],
                              array_padded[x+1][y+1], array_padded[x][y+1],
                              array_padded[x-1][y+1], array_padded[x-1][y])
                neighbours_count = sum(neighbours)
                if cell == 1 and (neighbours_count < 2 or neighbours_count > 3):
                    updated_array[x-1, y-1] = 0
                elif cell == 0 and neighbours_count == 3:
                    updated_array[x-1, y-1] = 1
        return updated_array
    

    Here's a much faster vectorized version:

    from scipy.signal import correlate2d
    
    def new_state_v(array):
        kernel = np.ones((3,3))
        kernel[1,1] = 0
        neighbours = correlate2d(array, kernel, 'same')
    
        updated_array=array.copy()
        updated_array[(array == 1) & ((neighbours < 2) | (neighbours > 3))] = 0
        updated_array[(array == 0) & (neighbours == 3)] = 1
        return updated_array
    

    Let's test

    >>> array = np.random.randint(2, size=(5,5))
    >>> array
    array([[0, 0, 1, 1, 0],
           [0, 0, 1, 0, 0],
           [0, 0, 0, 0, 1],
           [1, 1, 0, 0, 1],
           [0, 1, 1, 1, 0]])
    
    >>> new_state_v(array)
    array([[0, 0, 1, 1, 0],
           [0, 0, 1, 0, 0],
           [0, 1, 0, 1, 0],
           [1, 1, 0, 0, 1],
           [1, 1, 1, 1, 0]])
    

    Speed comparison:

    array = np.random.randint(2, size=(1000,1000))
    
    %timeit new_state(array)
    7.76 s ± 94.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    %timeit new_state_v(array)
    90.4 ms ± 703 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)