Python 實現生命遊戲

康威生命遊戲(英語:

Conway's Game of Life),又稱康威生命棋,是英國數學家約翰·何頓·康威(John Horton Conway)在1970年發明。

生命遊戲是一個零玩家遊戲。它是一個二維矩形世界,這個世界中的每個方格是一個活著或死了的細胞。一個細胞在下一個時刻生死取決於相鄰八個方格中活著的或死了的細胞的數量。在遊戲的進行中,雜亂無序的細胞會逐漸演化出各種精緻、有形的結構;這些結構往往有很好的對稱性,而且每一代都在變化形狀。而一些形狀一經鎖定,便不再逐代變化。有時,一些已經成形的結構會因為一些無序細胞的“入侵”而被破壞。但是形狀和秩序經常能從雜亂中產生出來。

摘自 維基百科

生命遊戲是一個很具有“魔性”的數學遊戲。它通過幾條極為簡單的規則,就能演化出極具複雜性的“世界”。

發幾個效果給你們感受一下:

Python 實現生命遊戲

Python 實現生命遊戲

Python 實現生命遊戲

生命遊戲在計算機上被實現過無數個版本,也給多個領域的後來者帶來不少啟發,甚至催生了一個全新的數學分支:細胞自動機。

它的發明者康威是普林斯頓大學教授、英國皇家學會院士,在多個數學領域均有所建樹。然而不幸的是,在幾天前,這位傑出的數學家因感染新冠肺炎而離世,終年82歲。

Python 實現生命遊戲

今天我們分享一篇生命遊戲的Python實現,讓更多人瞭解這個有趣而偉大的發明,也以此作為對老爺子的紀念。

這次我們使用 Python 來實現生命遊戲,這是一種簡單的元胞自動機。基於一定規則,程序可以自動從當前狀態推演到下一狀態。

先來說說生命遊戲的規則:

在生命遊戲中,每個單元格有兩種狀態,生與死。在我們的實現中,黃色的單元格代表活著的細胞,紅色單元格表示死亡的細胞。而每一個細胞的下一狀態,是由該細胞及周圍的八個細胞的當前狀態決定的。

具體而言:

當前細胞為活細胞

  1. 周圍有兩個或者三個活細胞,下一世代,該細胞仍然活著。
  2. 周圍少於兩個活細胞,該細胞死於孤立。
  3. 周圍多於三個活細胞,該細胞死於擁擠。

當前細胞為死細胞

  • 周圍恰好三個活細胞,下一世代,活細胞將繁殖到該單元格。
Python 實現生命遊戲

所需模塊

無需安裝的標準庫:

  • argparse(命令行參數)
  • enum(枚舉)

第三方庫:

  • numpy
  • matplotlib

導入模塊:

<code>import argparse
from enum import IntEnum
import matplotlib.pyplot as plt
import matplotlib.animation as animation # 製作動圖
import numpy as np/<code>

編程要點

首先,我們要知道細胞的生存空間是 N * N 的方陣,每個細胞都有兩種狀態:on, off。on 為 255,off 為 0。我們使用 numpy 產生 N * N 的方陣。np.random.choice 是在 State.on 和 State.off ,等概率隨機抽取元素構造 N * N 的方陣。

<code>class State(IntEnum):
on = 255
off = 0

def random_data(length = 4, seed = 420) -> np.array:
np.random.seed(seed)
return np.random.choice([State.off, State.on], size=(length, length), p=[0.5, 0.5])/<code>

其次我們要明白如何計算細胞周圍活細胞的個數,尤其是邊界一圈的細胞。我們可以採用餘數的方式,假設棋盤大小為 9 * 9,那麼對於左右邊界而言,左邊界的左邊一個元素的計算方式:- 1 % 9 = 8,自動折到右邊界上。將細胞周圍八個單元格的數值加起來,除以 255,就可以得到細胞周圍活細胞的個數。

<code>def _count(data, row, col):
shape = data.shape[0]
up = (row - 1) % shape
down = (row + 1) % shape
right = (col + 1) % shape
left = (col - 1) % shape
return (data[up, right] + data[up, left] +
data[down, right] + data[down, left] +
data[row, right] + data[row, left] +
data[up, col] + data[down, col]) // 255/<code>

接下來是對規則的翻譯,即根據當前世代的狀態,推演出下一世代,細胞的狀態。initial 為當前世代的矩陣,data為下一世代的矩陣,我們根據 initial 的數值,計算出 data 的數值。total 為周圍活細胞的個數,如果當前為活細胞,total 大於三或者小於二,下一世代就會死去。如果當前為死細胞,total 等於三,下一世代活細胞就會繁殖到該單元格上。

<code>def count(initial, data, row, col):
total = _count(initial, row, col)
if initial[row, col]:
if (total < 2) or (total > 3):
data[row, col] = State.off
else:
if total == 3:
data[row, col] = State.on/<code>

接下來是製作動圖的過程,前面幾行是繪圖的基本操作。之後,我們使用到了 matplotlib.animation 的方法。其中,FuncAnimation 接受的參數含義:fig 為圖像句柄,generate 函數是我們更新每一幀數據的函數,下面會有介紹。fargs 為 genrate 函數的除去第一個參數的其他附加參數,而第一個參數由 FuncAnimation 指定的 framenum(幀數) 自動傳給 generate 函數。frames 是幀數,interval 是更新圖像的時間間隔,save_count 為從幀到緩存的值的數量。

如果指定保存路徑(html),則保存為 html 動畫。

<code>def update(data, save_name):
update_interval = 50
fig, ax = plt.subplots()
ax.set_xticks([])
ax.set_yticks([])
img = ax.imshow(data, cmap='autumn', interpolation='nearest')
ani = animation.FuncAnimation(fig, generate, fargs=(img, plt, data),
frames=20,
interval=update_interval,
save_count=50)
if save_name:
ani.save(save_name, fps=30, extra_args=['-vcodec', 'libx264'])
plt.show()/<code>

下面我們來看 generate 函數,NUM 為當迭代次數,frame_num 接收來自 FuncAnimation 的幀數。通過嵌套的 for 循環,我們逐個地更新方陣中各元素的狀態。

<code>NUM = 0

def generate(frame_num, img, plt, initial):
global NUM
NUM += 1
plt.title(f'{NUM} generation')
data = initial.copy()
rows, cols = data.shape
for row in range(rows):
for col in range(cols):
count(initial, data, row, col)
img.set_data(data)
initial[:] = data[:]
return img/<code>

最後,我們可以通過命令行參數,運行我們的程序:

-- size 參數為棋盤大小,--seed 為隨機種子,用於產生不同的隨機方陣。

<code>python conway.py --size 50 --seed 18/<code> 
Python 實現生命遊戲

高斯帕滑翔機(Gosper Glider Gun)

生命遊戲中演化不少特殊的模式,高斯帕滑翔機就是其中之一:這種圖形每15代產生一架“滑翔機”,無限延續。

我們在代碼中也實現了它。

另外可將 --gosper 更改為 --glider 滑翔機。--save 為動圖保存的地址。

<code>python conway.py --size 80 --gosper --save gosper.html/<code> 
Python 實現生命遊戲

完整代碼可以私信我


分享到:


相關文章: