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.