100 lines of python code to implement cellular automata (Conway's game of life)

 British mathematician John Horton Conway invented cellular automata in 1970, which is a simulation program that simulates and displays the self-evolution of images by setting some basic rules, which looks quite like the birth of life and the process of reproduction, so it is called the "game of life".

complete effect

Third-party libraries used

pygame

basic rules

Conway's game of life is played on a grid. A filled grid represents life, or understood as a cell. There are only four rules of the game:

1 When there is only one or no living cells around, the original living cells enter the state of death. (too few cells)

2 When there are 2 or 3 living cells around, the grid remains as it is.

3 When there are 4 or more surviving cells around, the original surviving cells also enter the state of death. (Cells are too crowded)

4 When there are 3 living cells around, the blank grid becomes a living cell. (reproduction of new cells)

Code

First define two constants to represent the raw or blank state of a cell (grid):

ALIVE = (124, 252, 0)  # 绿色
EMPTY = (0, 0, 0)      # 黑色

I took a trick here and directly used RGB colors to represent the two states of cell survival or death, because in the following pygame display, ALIVE cells are represented by green, and EMPTY areas are represented by black.

The following variables are the parameters used in pygame, which are the size of the screen, the number of grids in the x and y directions, and the size of a single cell:

SCREEN_WIDHT = 600
SCREEN_HEIGHT = 600
X = 100   # X方向的网格数量
Y = 100   # Y方向的网格数量
CELL_WIDTH = SCREEN_WIDHT / X
CELL_HEIGHT = SCREEN_HEIGHT / Y

Now let's define a cell, which is a grid:

import pygame
from pygame.locals import *


class Cell:
    '''单个细胞'''
    def __init__(self, x, y):
        self.state = EMPTY
        self.rect = Rect(x * CELL_WIDTH, y * CELL_HEIGHT, 
                         CELL_WIDTH, CELL_HEIGHT)

    def draw(self, screen):
        pygame.draw.rect(screen, self.state, self.rect)

The attributes of cells are very simple, state represents the current state, we default that each cell is initially dead; the rect attribute is constructed with the Rect object in pygame, representing a rectangular area. Finally, there is a method draw, which can "draw" itself to the corresponding screen.

 Next define the entire grid:

class Grid:
    def __init__(self, X, Y):
        self.X = X
        self.Y = Y
        self.rows = []
        for y in range(Y):
            self.rows.append([])
            for x in range(X):
                self.rows[y].append(Cell(x, y))

    def get_state(self, y, x):
        return self.rows[y % self.Y][x % self.X].state

    def set_state(self, y, x, state):
        self.rows[y % self.Y][x % self.X].state = state

    def draw(self, screen):
        for row in self.rows:
            for cell in row:
                cell.draw(screen)

The core of the grid object is its rows property, which is a two-dimensional list, and each position in the list is a cell object, which can be located by coordinates (x, y). In addition, three methods are defined, get_state and set_state are used to obtain and change the state of the cell in a certain coordinate. It should be noted here that because the cell automaton spontaneously diffuses and evolves, there will be a situation where the length of the list is exceeded (that is, it is caused by exceeding the screen. error), so the subscript of the list does not simply use x, y, but has the effect of being able to turn back.

The following two module-level functions are used to implement the logic of the Game of Life:

def count_neighbors(y, x, get_state):
    n_ = get_state(y - 1, x + 0)  # North
    ne = get_state(y - 1, x + 1)  # Northeast
    e_ = get_state(y + 0, x + 1)  # East
    se = get_state(y + 1, x + 1)  # Southeast
    s_ = get_state(y + 1, x + 0)  # South
    sw = get_state(y + 1, x - 1)  # Southwest
    w_ = get_state(y + 0, x - 1)  # West
    nw = get_state(y - 1, x - 1)  # Northwest
    neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw]
    count = 0
    for state in neighbor_states:
        if state == ALIVE:
            count += 1
    return count

def next_state(state, neighbors):
    if state == ALIVE:
        if neighbors < 2:
            return EMPTY
        elif neighbors > 3:
            return EMPTY
    else:
        if neighbors == 3:
            return ALIVE
    return state

The count_neighbors function receives a coordinate and a function to obtain the state, which is used to calculate how many surviving cells are in the neighbor coordinates adjacent to the coordinate; the next_state function describes the core rules of the game of life, which receives the current state of the cell and the survival in the surrounding neighbor coordinates The number of cells, output the next state. With these two functions, you can write the logic of a single cell and the state change of the entire grid.

The following two module-level functions are to set the new state of a single cell and the entire grid

def step_cell(y, x, get_state, set_state):
    state = get_state(y, x)
    neighbors = count_neighbors(y, x, get_state)
    new_state = next_state(state, neighbors)
    set_state(y, x, new_state)

def simulate(grid):
    new_grid = Grid(grid.X, grid.Y)
    for y in range(grid.Y):
        for x in range(grid.X):
            step_cell(y, x, grid.get_state, new_grid.set_state)
    return new_grid

Among them, step_cell is used to set the state of the next cell, and simulate is used to return the next generation grid.

The main code has been written, and the test is started below:

if __name__ == "__main__":
    # pygame初始化的相关内容
    pygame.init()
    screen = pygame.display.set_mode((SCREEN_WIDHT, SCREEN_HEIGHT))
    pygame.display.set_caption('Game Of Live')
    framerate = pygame.time.Clock()

    # 设定网格的一个初始状态
    grid = Grid(X, Y)
    grid.set_state(2, 4, ALIVE)
    grid.set_state(2, 2, ALIVE)
    grid.set_state(3, 3, ALIVE)
    grid.set_state(3, 4, ALIVE)
    grid.set_state(4, 4, ALIVE)

    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

        grid.draw(screen)      # 将网格画到屏幕上
        grid = simulate(grid)  # 获得下一代网格

        pygame.display.update()
        framerate.tick(10)     # 设置每秒10帧

The above code to realize the game of life should be very concise and clear, the code is only about 100 lines in total, and as long as you learn some basic knowledge of the pygame library, you can achieve this very magical effect.

Guess you like

Origin blog.csdn.net/weixin_49775731/article/details/126030305