在 Python Matplotlib 中绘制静态生命游戏模式
Plotting static Game of Life patterns in Python Matplotlib
有许多 Python 程序可以播放经典 'Game of Life',其中大多数都提供漂亮的图形动画,让人们可以跟随游戏中出现的奇妙模式。这样的程序非常适合在计算机上进行交互式使用,但我发现很难找到一个可以为打印页面提供静态图形输出的程序(例如 Martin Gardner 在 1970 年向世界介绍生命游戏的文章中展示的那些程序: The fantastic combinations of John Conway's new solitaire game "life")。有许多程序提供基于文本的生活模式输出,但我还没有发现任何一个程序能够用 Matplolib 图形做同样的事情。于是,我开始写一个,如下代码所示:
import matplotlib.pyplot as plt
def iterate(Z):
shape = len(Z), len(Z[0])
N = [[0,]*(shape[0]+2) for i in range(shape[1]+2)]
# Compute number of neighbours for each cell
for x in range(1,shape[0]-1):
for y in range(1,shape[1]-1):
N[x][y] = Z[x-1][y-1]+Z[x][y-1]+Z[x+1][y-1] \
+ Z[x-1][y] +Z[x+1][y] \
+ Z[x-1][y+1]+Z[x][y+1]+Z[x+1][y+1]
# Update cells
for x in range(1,shape[0]-1):
for y in range(1,shape[1]-1):
if Z[x][y] == 0 and N[x][y] == 3:
Z[x][y] = 1
elif Z[x][y] == 1 and not N[x][y] in [2,3]:
Z[x][y] = 0
return Z
# The 'beehive' pattern
Z = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
n_generations = 10
fig, axes = plt.subplots(2, 5, figsize=(8, 8))
for i in range(n_generations):
iterate(Z)
ax = axes.flat[i]
ax.imshow(Z, interpolation='nearest', cmap=plt.cm.binary)
ax.set_axis_off()
ax.set_title('Generation {}'.format(i+1))
plt.grid(True)
plt.tight_layout()
plt.show()
这可行,但远非如此,因为:
(1)我希望每个图都显示网格线,这样他们就可以重现加德纳文章中的原始图形,但我一直没能找到如何做到这一点;
(2) 我还希望能够使用球体而不是正方形来表示活细胞(就像它们出现在 Gardner 的文章中一样);
任何有助于改进此代码的帮助都将不胜感激!
第一个问题,是因为你把axes off了。我打开它并做了一些调整:
n_generations = 10
fig, axes = plt.subplots(2, 5, figsize=(16, 8))
for i in range(n_generations):
iterate(Z)
ax = axes.flat[i]
ax.imshow(Z, interpolation='nearest', cmap=plt.cm.binary)
# ax.set_axis_off()
ax.set_xticks(np.arange(10)+.5)
ax.set_yticks(np.arange(10)+.5)
ax.set_xticklabels('')
ax.set_yticklabels('')
ax.set_title('Generation {}'.format(i+1))
plt.tight_layout()
plt.show()
输出:
对于另一个问题。我认为 imshow
不可能。您可能需要编写自定义函数 my_plot(Z, ax)
.
要生成网格线,您需要在相应位置进行刻度。使用 MultipleLocator
会产生 1 的倍数的刻度。
圆圈是标准的散点标记。您可以将数据绘制为散点图而不是图像。
放在一起可能看起来像下面这样,我也让代码更紧凑了。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
def iterate(Z):
# http://www.labri.fr/perso/nrougier/from-python-to-numpy/code/game_of_life_numpy.py
N = (Z[0:-2, 0:-2] + Z[0:-2, 1:-1] + Z[0:-2, 2:] +
Z[1:-1, 0:-2] + Z[1:-1, 2:] +
Z[2: , 0:-2] + Z[2: , 1:-1] + Z[2: , 2:])
birth = (N == 3) & (Z[1:-1, 1:-1] == 0)
survive = ((N == 2) | (N == 3)) & (Z[1:-1, 1:-1] == 1)
Z[...] = 0
Z[1:-1, 1:-1][birth | survive] = 1
return Z
# The 'beehive' pattern
Z = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
Z = np.array(Z)
X, Y = np.meshgrid(np.arange(Z.shape[1])+.5, np.arange(Z.shape[0])+.5)
fig, axes = plt.subplots(2, 5, figsize=(8, 4))
for i, ax in enumerate(axes.flat):
Z = iterate(Z)
ax.scatter(X[Z > 0], Y[Z > 0], color="k")
ax.grid(True, color="k")
ax.xaxis.set_major_locator(mticker.MultipleLocator())
ax.yaxis.set_major_locator(mticker.MultipleLocator())
ax.tick_params(size=0, length=0, labelleft=False, labelbottom=False)
ax.set(xlim=(0, Z.shape[1]), ylim=(Z.shape[0], 0),
title='Generation {}'.format(i+1), aspect="equal")
plt.tight_layout()
plt.show()
有许多 Python 程序可以播放经典 'Game of Life',其中大多数都提供漂亮的图形动画,让人们可以跟随游戏中出现的奇妙模式。这样的程序非常适合在计算机上进行交互式使用,但我发现很难找到一个可以为打印页面提供静态图形输出的程序(例如 Martin Gardner 在 1970 年向世界介绍生命游戏的文章中展示的那些程序: The fantastic combinations of John Conway's new solitaire game "life")。有许多程序提供基于文本的生活模式输出,但我还没有发现任何一个程序能够用 Matplolib 图形做同样的事情。于是,我开始写一个,如下代码所示:
import matplotlib.pyplot as plt
def iterate(Z):
shape = len(Z), len(Z[0])
N = [[0,]*(shape[0]+2) for i in range(shape[1]+2)]
# Compute number of neighbours for each cell
for x in range(1,shape[0]-1):
for y in range(1,shape[1]-1):
N[x][y] = Z[x-1][y-1]+Z[x][y-1]+Z[x+1][y-1] \
+ Z[x-1][y] +Z[x+1][y] \
+ Z[x-1][y+1]+Z[x][y+1]+Z[x+1][y+1]
# Update cells
for x in range(1,shape[0]-1):
for y in range(1,shape[1]-1):
if Z[x][y] == 0 and N[x][y] == 3:
Z[x][y] = 1
elif Z[x][y] == 1 and not N[x][y] in [2,3]:
Z[x][y] = 0
return Z
# The 'beehive' pattern
Z = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
n_generations = 10
fig, axes = plt.subplots(2, 5, figsize=(8, 8))
for i in range(n_generations):
iterate(Z)
ax = axes.flat[i]
ax.imshow(Z, interpolation='nearest', cmap=plt.cm.binary)
ax.set_axis_off()
ax.set_title('Generation {}'.format(i+1))
plt.grid(True)
plt.tight_layout()
plt.show()
这可行,但远非如此,因为:
(1)我希望每个图都显示网格线,这样他们就可以重现加德纳文章中的原始图形,但我一直没能找到如何做到这一点;
(2) 我还希望能够使用球体而不是正方形来表示活细胞(就像它们出现在 Gardner 的文章中一样);
任何有助于改进此代码的帮助都将不胜感激!
第一个问题,是因为你把axes off了。我打开它并做了一些调整:
n_generations = 10
fig, axes = plt.subplots(2, 5, figsize=(16, 8))
for i in range(n_generations):
iterate(Z)
ax = axes.flat[i]
ax.imshow(Z, interpolation='nearest', cmap=plt.cm.binary)
# ax.set_axis_off()
ax.set_xticks(np.arange(10)+.5)
ax.set_yticks(np.arange(10)+.5)
ax.set_xticklabels('')
ax.set_yticklabels('')
ax.set_title('Generation {}'.format(i+1))
plt.tight_layout()
plt.show()
输出:
对于另一个问题。我认为 imshow
不可能。您可能需要编写自定义函数 my_plot(Z, ax)
.
要生成网格线,您需要在相应位置进行刻度。使用 MultipleLocator
会产生 1 的倍数的刻度。
圆圈是标准的散点标记。您可以将数据绘制为散点图而不是图像。
放在一起可能看起来像下面这样,我也让代码更紧凑了。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
def iterate(Z):
# http://www.labri.fr/perso/nrougier/from-python-to-numpy/code/game_of_life_numpy.py
N = (Z[0:-2, 0:-2] + Z[0:-2, 1:-1] + Z[0:-2, 2:] +
Z[1:-1, 0:-2] + Z[1:-1, 2:] +
Z[2: , 0:-2] + Z[2: , 1:-1] + Z[2: , 2:])
birth = (N == 3) & (Z[1:-1, 1:-1] == 0)
survive = ((N == 2) | (N == 3)) & (Z[1:-1, 1:-1] == 1)
Z[...] = 0
Z[1:-1, 1:-1][birth | survive] = 1
return Z
# The 'beehive' pattern
Z = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
Z = np.array(Z)
X, Y = np.meshgrid(np.arange(Z.shape[1])+.5, np.arange(Z.shape[0])+.5)
fig, axes = plt.subplots(2, 5, figsize=(8, 4))
for i, ax in enumerate(axes.flat):
Z = iterate(Z)
ax.scatter(X[Z > 0], Y[Z > 0], color="k")
ax.grid(True, color="k")
ax.xaxis.set_major_locator(mticker.MultipleLocator())
ax.yaxis.set_major_locator(mticker.MultipleLocator())
ax.tick_params(size=0, length=0, labelleft=False, labelbottom=False)
ax.set(xlim=(0, Z.shape[1]), ylim=(Z.shape[0], 0),
title='Generation {}'.format(i+1), aspect="equal")
plt.tight_layout()
plt.show()