import numpy as np CELL_IS_ALIVE = 0b1 CELL_WILL_BE_ALIVE = 0b10 CELL_WILL_BE_DEAD = 0b100 def is_alive(model, i, j): return model[i][j] & CELL_IS_ALIVE def get_cells_around(model, i, j): # Most common case. Handle this first. # Fortunately the most common case is also the fastets to handle. if(i > 2 and j > 2 and i < model.shape[0] and j < model.shape[1]): return model[i - 2: i + 2, j - 2: j + 2] # Neither negative indices nor indices that overflow # are handled properly by numpy. So basically I have to do that manually: cells_around = np.zeros([3, 3], np.int8) for k, m in enumerate(range(i - 1, i + 2)): for l, n in enumerate(range(j - 1, j + 2)): real_m = m if(m >= model.shape[0]): real_m = m - model.shape[0] if(m < 0): real_m = m + model.shape[0] real_n = n if(n >= model.shape[1]): real_n = n - model.shape[1] if(n < 0): real_n = n + model.shape[1] cells_around[k][l] = model[real_m][real_n] return cells_around def count_living_cells_around(model, i, j): cells_around = get_cells_around(model, i, j) living_cells_around = (cells_around & CELL_IS_ALIVE) == 1 living_cells_around[1][1] = False _, counts = np.unique(living_cells_around, return_counts=True) print(counts) return counts[True] def handle_cell(model, i, j): living_cells_around = count_living_cells_around(model, i, j) if(not is_alive(model, i, j)): if(living_cells_around == 3): model[i][j] = CELL_WILL_BE_ALIVE return if(living_cells_around > 3 or living_cells_around < 2): model[i][j] = CELL_WILL_BE_DEAD def after_tick_finished(model): # kill the cells that have died model[(model & CELL_WILL_BE_DEAD) != 0] = 0 # create the new cells model[(model & CELL_WILL_BE_ALIVE) != 0] = 1 def total_living_cells(model): living_cells = (model & CELL_IS_ALIVE) == 1 _, counts = np.unique(living_cells, return_counts=True) return counts[True]