02 Jul 2017 | Python
对康威的生命游戏很感兴趣,想试下用Python实现。很巧在实验楼上看到了这个项目。
康威生命游戏,又称康威生命棋,是英国数学家约翰·何顿·康威在1970年发明的细胞自动机。
生命游戏是一个零玩家游戏。它包括一个二维矩形世界,这个世界中的每个方格居住着一个活着的或死了的细胞。一个细胞在下一个时刻生死取决于相邻八个方格中活着的或死了的细胞的数量。
每个细胞有两种状态-存活或死亡,每个细胞与以自身为中心的周围八格细胞产生互动。
在游戏的进行中,杂乱无序的细胞会逐渐演化出各种精致、有形的结构。形状和秩序经常能从杂乱中产生出来。对于生成的形状和秩序我们称作 pattern。或者在这里,我们也把它称作 creature。
我们使用矩阵来记录我们的游戏世界,其中单位值为 0 代表细胞死亡。单位值为 1 代表细胞存活。
def next_generation():
nbrs_count = sum(np.roll(np.roll(pygame.world, i, 0), j, 1)
for i in (-1, 0, 1) for j in (-1, 0, 1)
if (i != 0 or j != 0))
由于我们的游戏世界是上下左右循环的,所以将矩阵往8个方向进行循环移位得到8个新矩阵,将8个新矩阵相加就能够得到每个细胞周围的活细胞数量的矩阵了。
np.roll
操作就是循环移位操作。np.roll(X, i, 0)
中的 X 代表输入矩阵,i 代表移位的大小,0 代表移位的维度,np.roll(X, 1, 0)
代表矩阵下移一格,np.roll(X, 1, 1) 代表右移一格,if (i != 0 or j != 0))
是为了将原矩阵从计算中去除。
通过活细胞数量矩阵根据更新规则更新我们的世界。因为矩阵单位只有两种状态,这里我们只考虑存活态就可以了。注意到存活的条件:
即细胞周围数量等于3 或者 本单元细胞存活的同时周围有2个存活细胞的时候。本单元细胞将在下一代存活(也可看作繁衍)。即:
(nbrs_count == 3) | (X & (nbrs_count == 2))
#-*- coding:utf8 -*-
import pygame, sys, time
import numpy as np
from pygame.locals import *
#矩阵宽与矩阵高
WIDTH = 80
HEIGHT = 40
#记录鼠标按键情况的全局变量
pygame.button_down = False
#记录游戏世界的矩阵
pygame.world=np.zeros((HEIGHT,WIDTH))
#创建 Cell 类方便细胞绘制
class Cell(pygame.sprite.Sprite):
size = 10
def __init__(self, position):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([self.size, self.size])
#填上黄色
self.image.fill((255,248,42))
# 创建一个以左上角为锚点的矩形
self.rect = self.image.get_rect()
self.rect.topleft = position
#绘图函数,注意到我们是把画布重置了再遍历整个世界地图,因此有很大的性能提升空间
def draw():
# 背景为白色
screen.fill((255,255,255))
for sp_col in range(pygame.world.shape[1]):
for sp_row in range(pygame.world.shape[0]):
if pygame.world[sp_row][sp_col]:
new_cell = Cell((sp_col * Cell.size,sp_row * Cell.size))
screen.blit(new_cell.image,new_cell.rect)
#根据细胞更新规则更新地图
def next_generation():
nbrs_count = sum(np.roll(np.roll(pygame.world, i, 0), j, 1)
for i in (-1, 0, 1) for j in (-1, 0, 1)
if (i != 0 or j != 0))
pygame.world = (nbrs_count == 3) | ((pygame.world == 1) & (nbrs_count == 2)).astype('int')
#地图初始化
def init():
pygame.world.fill(0)
draw()
return 'Stop'
#停止时的操作
def stop():
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN and event.key == K_RETURN:
return 'Move'
if event.type == KEYDOWN and event.key == K_r:
return 'Reset'
if event.type == MOUSEBUTTONDOWN:
pygame.button_down = True
pygame.button_type = event.button
if event.type == MOUSEBUTTONUP:
pygame.button_down = False
if pygame.button_down:
mouse_x, mouse_y = pygame.mouse.get_pos()
sp_col = mouse_x / Cell.size;
sp_row = mouse_y / Cell.size;
if pygame.button_type == 1: #鼠标左键
pygame.world[sp_row][sp_col] = 1
elif pygame.button_type == 3: #鼠标右键
pygame.world[sp_row][sp_col] = 0
draw()
return 'Stop'
#计时器,控制帧率
pygame.clock_start = 0
#进行演化时的操作
def move():
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN and event.key == K_SPACE:
return 'Stop'
if event.type == KEYDOWN and event.key == K_r:
return 'Reset'
if event.type == MOUSEBUTTONDOWN:
pygame.button_down = True
pygame.button_type = event.button
if event.type == MOUSEBUTTONUP:
pygame.button_down = False
if pygame.button_down:
mouse_x, mouse_y = pygame.mouse.get_pos()
sp_col = mouse_x / Cell.size;
sp_row = mouse_y / Cell.size;
if pygame.button_type == 1:
pygame.world[sp_row][sp_col] = 1
elif pygame.button_type == 3:
pygame.world[sp_row][sp_col] = 0
draw()
if time.clock() - pygame.clock_start > 0.02:
next_generation()
draw()
pygame.clock_start = time.clock()
return 'Move'
if __name__ == '__main__':
#状态机对应三种状态,初始化,停止,进行
state_actions = {
'Reset': init,
'Stop': stop,
'Move': move
}
state = 'Reset'
pygame.init()
pygame.display.set_caption('Conway\'s Game of Life')
screen = pygame.display.set_mode((WIDTH * Cell.size, HEIGHT * Cell.size))
while True: # 游戏主循环
state = state_actions[state]()
pygame.display.update()