生命游戏(Game of Life)
由剑桥大学约翰·何顿·康威设计的计算机程序。美国趣味数学大师马丁·加德纳(Martin Gardner,1914-2010)通过《科学美国人》杂志,将康威的生命游戏介绍给学术界之外的广大渎者,一时吸引了各行各业一大批人的兴趣,这时细胞自动机课题才吸引了科学家的注意。
游戏概述
用一个二维表格表示“生存空间”,空间的每个方格中都可放置一个生命细胞,每个生命细胞只有两种状态:“生”或“死”。用绿色方格表示该细胞为“生”,空格(白色)表示该细胞为“死”。或者说方格网中绿色部分表示某个时候某种“生命”的分布图。生命游戏想要模拟的是:随着时间的流逝,这个分布图将如何一代一代地变化。死亡太沉重,我想称它为“湮灭”状态。
生存定律
生存空间的每个方格都存在一个细胞,它的周边紧邻的8个方格上的称为邻居细胞。
(1)当前细胞为湮灭状态时,当周围有3个存活细胞时,则迭代后该细胞变成存活状态(模拟繁殖)。
(2)当前细胞为存活状态时,当周围的邻居细胞少于2个存活时,该细胞变成湮灭状态(数量稀少)。
(3)当前细胞为存活状态时,当周围有3个以上的存活细胞时,该细胞变成湮灭状态(数量过多)。
(4)当前细胞为存活状态时,当周围有2个或3个存活细胞时,该细胞保持原样。
简单来说,活细胞Cell看作是‘1’,死Cell看作‘0’,8个邻居的累加和Sum决定了下一轮的状态:
“繁殖”:死Cell=0,若Sum=3,则Cell=1。
“稀少”:活Cell=1,若Sum<2,则Cell=0。
“过多”:活Cell=1,若Sum>3,则Cell=0。
“正常”:活Cell=1,若Sum=2或3,则Cell=1。
生存空间中生命的繁殖和湮灭,如下图所示:
图形结构
在游戏进行中,杂乱无序的细胞会逐渐演化出各种精致、有形的图形结构;这些结构往往有很好的对称性,而且每一代都在变化形状。一些形状一经锁定就不会逐代变化。有时,一些已经成形的结构会因为一些无序细胞的“入侵”而被破坏。但是形状和秩序经常能从杂乱中产生出来。
通常会有以下四种状态:
不动点(fixed points):变化终结于恒定图像
交替态(alternation):图像出现周期性变化
随机态(randomness):图像变化近乎随机
复杂态(complexity):图像存在某种复杂规律
常见的不动结构:
周期变化的结构:
逐步趋向湮灭的结构:
由一根10个连续细胞演化出来的周期结构:
动态变化后全部湮灭的结构:
移动的飞船:周期变化且逐渐向右平移
飞船到了边界变成向对角线移动的“滑翔者”,滑翔者到了边界成为不动的方块。
更多有趣的图形结构有待发现,用代码来辅助这项工作还是比较方便的.....
代码实现
用tkinter库实现了软件界面,画布、按钮、标签等控件都是配角,全是为表现生命繁殖演化的核心代码类方法 Lifes.reproduce() 作帮手的,源代码lifegame.pyw如下:
from tkinter import messagebox as msgbox import tkinter as tk import webbrowser import random class Lifes: def __init__(self, rows=36, cols=36): self.row = rows+2 self.col = cols+2 self.items = [[0]*self.col for _ in range(self.row)] self.histroy = [] self.histroySize = 30 self.running = False self.runningSpeed = 100 def rndinit(self, rate=0.1): self.histroy = [] for i in range(self.row): for j in range(self.col): rnd = random.random() if rnd > 1-rate: self.items[i][j]=1 def reproduce(self): new = [[0]*self.col for _ in range(self.row)] self.add_histroy() if len(self.histroy) > self.histroySize: self.histroy.pop(0) for i in range(self.row): for j in range(self.col): if i*j==0 or i==self.row-1 or j==self.col-1: new[i][j]=0 else: lifes=0 for m in range(i-1,i+2): for n in range(j-1,j+2): if m==i and n==j: continue lifes += self.items[m][n] if self.items[i][j]: if lifes==2 or lifes==3: new[i][j]=1 else: new[i][j]=0 else: if lifes==3: new[i][j]=1 for idx,narray in enumerate(new): self.items[idx] = narray def is_stable(self): if len(self.histroy)<self.histroySize: return False arr = [] for i in self.histroy: if i not in arr: arr.append(i) if len(arr)<10: return True def add_histroy(self, Items=None): arr = [] if Items==None: Items=self.items[:] for item in Items: b = 0 for i,n in enumerate(item[::-1]): b += n*2**i arr.append(b) self.histroy.append(arr) def drawCanvas(): global tv,rect tv = tk.Canvas(win, width=win.winfo_width(), height=win.winfo_height()) tv.pack(side = "top") for i in range(36): coord = 40, 40, 760, i*20 + 40 tv.create_rectangle(coord) coord = 40, 40, i*20 + 40, 760 tv.create_rectangle(coord) coord = 38, 38, 760, 760 tv.create_rectangle(coord,width=2) coord = 39, 39, 760, 760 tv.create_rectangle(coord,width=2) coord = 38, 38, 762, 762 tv.create_rectangle(coord,width=2) R,XY = 8,[50+i*20 for i in range(36)] rect = [[0]*36 for _ in range(36)] for i,x in enumerate(XY): for j,y in enumerate(XY): rect[i][j] = tv.create_rectangle(x-R,y-R,x+R,y+R,tags=('imgButton1')) tv.itemconfig(rect[i][j],fill='lightgray',outline='lightgray') tv.tag_bind('imgButton1','<Button-1>',on_Click) def drawLifes(): R,XY = 8,[50+i*20 for i in range(36)] if Life.running: for i,x in enumerate(XY): for j,y in enumerate(XY): if Life.items[i+1][j+1]: tv.itemconfig(rect[i][j],fill='green',outline='green') else: tv.itemconfig(rect[i][j],fill='lightgray',outline='lightgray') tv.update() Life.reproduce() if Life.is_stable(): Life.running = False if sum(sum(Life.items,[])): msgbox.showinfo('Message','生命繁殖与湮灭进入稳定状态!!!') else: msgbox.showinfo('Message','生命全部湮灭,进入死亡状态!!!') win.after(Life.runningSpeed, drawLifes) def StartLife(): if sum(sum(Life.items,[])): Life.histroy = [] Life.running = True else: msgbox.showinfo('Message','请点击小方块填入生命细胞,或者使用随机功能!') def BreakLife(): Life.running = not Life.running if Life.running: Life.histroy.clear() Life.add_histroy() def RandomLife(): Life.rndinit() Life.running = True def ClearLife(): Life.running = False Life.histroy = [] Life.items = [[0]*38 for _ in range(38)] for x in range(36): for y in range(36): tv.itemconfig(rect[x][y],fill='lightgray',outline='lightgray') def btnCommand(i): if i==0: return StartLife elif i==1: return BreakLife elif i==2: return RandomLife elif i==3: return ClearLife def on_Enter(event): tCanvas.itemconfig(tVisit, fill='magenta') def on_Leave(event): tCanvas.itemconfig(tVisit, fill='blue') def on_Release(event): url = 'https://blog.csdn.net/boysoft2002?type=blog' webbrowser.open(url, new=0, autoraise=True) def on_Click(event): x,y = (event.x-40)//20,(event.y-40)//20 if not Life.running: if Life.items[x+1][y+1]: tv.itemconfig(rect[x][y],fill='lightgray',outline='lightgray') else: tv.itemconfig(rect[x][y],fill='red',outline='red') Life.items[x+1][y+1] = not Life.items[x+1][y+1] def on_Close(): if msgbox.askokcancel("Quit","Do you want to quit?"): Life.running = False print(Copyright()) win.destroy() def Introduce(): txt = '''【生命游戏】\n\n生存定律: (1)当前细胞为湮灭状态时,当周围有3个存活细胞时,则迭代后该细胞变成存活状态(模拟繁殖)。 (2)当前细胞为存活状态时,当周围的邻居细胞少于2个存活时,该细胞变成湮灭状态(数量稀少)。 (3)当前细胞为存活状态时,当周围有3个以上的存活细胞时,该细胞变成湮灭状态(数量过多)。 (4)当前细胞为存活状态时,当周围有2个或3个存活细胞时,该细胞保持原样。''' return txt def Copyright(): return 'Lifes Game Ver1.0\nWritten by HannYang, 2022/08/01.' if __name__ == '__main__': win = tk.Tk() X,Y = win.maxsize() W,H = 1024,800 winPos = f'{W}x{H}+{(X-W)//2}+{(Y-H)//2}' win.geometry(winPos) win.resizable(False, False) win.title('生命游戏 Ver1.0') win.update() drawCanvas() Life = Lifes() drawLifes() tLabel = tk.Label(win, width=30, height=20, background='lightgray') tLabel.place(x=780, y=38) tLabel.config(text='\n\n\n'.join((Introduce(),Copyright()))) tLabel.config(justify=tk.LEFT,anchor="nw",borderwidth=10,wraplength=210) tButton = [None]*4 bX,bY,dY = 835, 458, 50 txt = ['开始','暂停','随机','清空'] for i in range(4): tButton[i] = tk.Button(win, text=txt[i], command=btnCommand(i)) tButton[i].place(x=bX, y=bY+dY*i, width=120, height=40) tCanvas = tk.Canvas(win, width=200, height=45) tCanvas.place(x=800,y=716) tVisit = tCanvas.create_text((88, 22), text=u"点此访问Hann's CSDN主页!") tCanvas.itemconfig(tVisit, fill='blue', tags=('btnText')) tCanvas.tag_bind('btnText','<Enter>',on_Enter) tCanvas.tag_bind('btnText','<Leave>',on_Leave) tCanvas.tag_bind('btnText','<ButtonRelease-1>',on_Release) win.protocol("WM_DELETE_WINDOW", on_Close) win.mainloop()
编译命令
D:\> pyinstaller -F lifegame.pyw --noconsole
运行界面
使用简介
在生存空间里点击方格种植细胞(甚至可以画出你要表达的图形),然后点击开始;点下暂停键,则可以任意编辑生命图形,再点开始继续运行;点随机键则由软件随机生成细胞位置;清空键可以在任何时候清空生存空间,进入暂停编辑状态。
后续改进
Lifes类预留了histroy属性,后续可以增加回退功能;代码缺点是生存空间的行列被我写死了,以后版本中可以改进成任意定义行列数;另一个缺点是对稳定状态的判断比较简单,之后可以增加计算变化周期的功能。
优点就是可以任意编辑你的图形,最后以一张自己的网名画的图作收尾: