• Python 生命游戏(tkinter版)


    生命游戏(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如下:

    1. from tkinter import messagebox as msgbox
    2. import tkinter as tk
    3. import webbrowser
    4. import random
    5. class Lifes:
    6. def __init__(self, rows=38, cols=38):
    7. self.row = rows
    8. self.col = cols
    9. self.items = [[0]*self.col for _ in range(self.row)]
    10. self.histroy = []
    11. self.histroySize = 30
    12. self.running = False
    13. self.runningSpeed = 100
    14. def rndinit(self, rate=0.1):
    15. self.histroy = []
    16. for i in range(self.row):
    17. for j in range(self.col):
    18. rnd = random.random()
    19. if rnd > 1-rate:
    20. self.items[i][j]=1
    21. def reproduce(self):
    22. new = [[0]*self.col for _ in range(self.row)]
    23. self.add_histroy()
    24. if len(self.histroy) > self.histroySize:
    25. self.histroy.pop(0)
    26. for i in range(self.row):
    27. for j in range(self.col):
    28. if i*j==0 or i==self.row-1 or j==self.col-1:
    29. new[i][j]=0
    30. else:
    31. lifes=0
    32. for m in range(i-1,i+2):
    33. for n in range(j-1,j+2):
    34. if m==i and n==j:
    35. continue
    36. lifes += self.items[m][n]
    37. if self.items[i][j]:
    38. if lifes==2 or lifes==3:
    39. new[i][j]=1
    40. else:
    41. new[i][j]=0
    42. else:
    43. if lifes==3:
    44. new[i][j]=1
    45. for idx,narray in enumerate(new):
    46. self.items[idx] = narray
    47. def is_stable(self):
    48. if len(self.histroy)
    49. return False
    50. arr = []
    51. for i in self.histroy:
    52. if i not in arr:
    53. arr.append(i)
    54. if len(arr)<10:
    55. return True
    56. def add_histroy(self, Items=None):
    57. arr = []
    58. if Items==None:
    59. Items=self.items[:]
    60. for item in Items:
    61. b = 0
    62. for i,n in enumerate(item[::-1]):
    63. b += n*2**i
    64. arr.append(b)
    65. self.histroy.append(arr)
    66. def drawCanvas():
    67. global tv,rect
    68. tv = tk.Canvas(win, width=win.winfo_width(), height=win.winfo_height())
    69. tv.pack(side = "top")
    70. for i in range(36):
    71. coord = 40, 40, 760, i*20 + 40
    72. tv.create_rectangle(coord)
    73. coord = 40, 40, i*20 + 40, 760
    74. tv.create_rectangle(coord)
    75. coord = 38, 38, 760, 760
    76. tv.create_rectangle(coord,width=2)
    77. coord = 39, 39, 760, 760
    78. tv.create_rectangle(coord,width=2)
    79. coord = 38, 38, 762, 762
    80. tv.create_rectangle(coord,width=2)
    81. R,XY = 8,[50+i*20 for i in range(36)]
    82. rect = [[0]*36 for _ in range(36)]
    83. for i,x in enumerate(XY):
    84. for j,y in enumerate(XY):
    85. rect[i][j] = tv.create_rectangle(x-R,y-R,x+R,y+R,tags=('imgButton1'))
    86. tv.itemconfig(rect[i][j],fill='lightgray',outline='lightgray')
    87. tv.tag_bind('imgButton1','',on_Click)
    88. def drawLifes():
    89. R,XY = 8,[50+i*20 for i in range(36)]
    90. if Life.running:
    91. for i,x in enumerate(XY):
    92. for j,y in enumerate(XY):
    93. if Life.items[i+1][j+1]:
    94. tv.itemconfig(rect[i][j],fill='green',outline='green')
    95. else:
    96. tv.itemconfig(rect[i][j],fill='lightgray',outline='lightgray')
    97. tv.update()
    98. Life.reproduce()
    99. if Life.is_stable():
    100. Life.running = False
    101. if sum(sum(Life.items,[])):
    102. msgbox.showinfo('Message','生命繁殖与湮灭进入稳定状态!!!')
    103. else:
    104. msgbox.showinfo('Message','生命全部湮灭,进入死亡状态!!!')
    105. win.after(Life.runningSpeed, drawLifes)
    106. def StartLife():
    107. if sum(sum(Life.items,[])):
    108. Life.histroy = []
    109. Life.running = True
    110. else:
    111. msgbox.showinfo('Message','请点击小方块填入生命细胞,或者使用随机功能!')
    112. def BreakLife():
    113. Life.running = not Life.running
    114. if Life.running:
    115. Life.histroy.clear()
    116. Life.add_histroy()
    117. def RandomLife():
    118. Life.rndinit()
    119. Life.running = True
    120. def ClearLife():
    121. Life.running = False
    122. Life.histroy = []
    123. Life.items = [[0]*38 for _ in range(38)]
    124. for x in range(36):
    125. for y in range(36):
    126. tv.itemconfig(rect[x][y],fill='lightgray',outline='lightgray')
    127. def on_Enter(event):
    128. tCanvas.itemconfig(tVisit, fill='magenta')
    129. def on_Leave(event):
    130. tCanvas.itemconfig(tVisit, fill='blue')
    131. def on_Release(event):
    132. url = 'https://blog.csdn.net/boysoft2002?type=blog'
    133. webbrowser.open(url, new=0, autoraise=True)
    134. def on_Click(event):
    135. x,y = (event.x-40)//20,(event.y-40)//20
    136. if not Life.running:
    137. if Life.items[x+1][y+1]:
    138. tv.itemconfig(rect[x][y],fill='lightgray',outline='lightgray')
    139. else:
    140. tv.itemconfig(rect[x][y],fill='red',outline='red')
    141. Life.items[x+1][y+1] = not Life.items[x+1][y+1]
    142. def on_Close():
    143. if msgbox.askokcancel("Quit","Do you want to quit?"):
    144. Life.running = False
    145. print(Copyright())
    146. win.destroy()
    147. def Introduce():
    148. txt = '''【生命游戏】\n\n生存定律:
    149. (1)当前细胞为湮灭状态时,当周围有3个存活细胞时,则迭代后该细胞变成存活状态(模拟繁殖)。
    150. (2)当前细胞为存活状态时,当周围的邻居细胞少于2个存活时,该细胞变成湮灭状态(数量稀少)。
    151. (3)当前细胞为存活状态时,当周围有3个以上的存活细胞时,该细胞变成湮灭状态(数量过多)。
    152. (4)当前细胞为存活状态时,当周围有2个或3个存活细胞时,该细胞保持原样。'''
    153. return txt
    154. def Copyright():
    155. return 'Lifes Game Ver1.0\nWritten by HannYang, 2022/08/01.'
    156. if __name__ == '__main__':
    157. win = tk.Tk()
    158. X,Y = win.maxsize()
    159. W,H = 1024,800
    160. winPos = f'{W}x{H}+{(X-W)//2}+{(Y-H)//2}'
    161. win.geometry(winPos)
    162. win.resizable(False, False)
    163. win.title('生命游戏 Ver1.0')
    164. win.update()
    165. drawCanvas()
    166. Life = Lifes()
    167. drawLifes()
    168. tLabel = tk.Label(win, width=30, height=20, background='lightgray')
    169. tLabel.place(x=780, y=38)
    170. tLabel.config(text='\n\n\n'.join((Introduce(),Copyright())))
    171. tLabel.config(justify=tk.LEFT,anchor="nw",borderwidth=10,wraplength=210)
    172. bX,bY,dY = 835, 458, 50
    173. tButton0 = tk.Button(win, text=u'开始', command=StartLife)
    174. tButton0.place(x=bX, y=bY+dY*0 ,width=120,height=40)
    175. tButton1 = tk.Button(win, text=u'暂停', command=BreakLife)
    176. tButton1.place(x=bX, y=bY+dY*1 ,width=120,height=40)
    177. tButton2 = tk.Button(win, text=u'随机', command=RandomLife)
    178. tButton2.place(x=bX, y=bY+dY*2 ,width=120,height=40)
    179. tButton3 = tk.Button(win, text=u'清空', command=ClearLife)
    180. tButton3.place(x=bX, y=bY+dY*3 ,width=120,height=40)
    181. tCanvas = tk.Canvas(win, width=200, height=45)
    182. tCanvas.place(x=800,y=716)
    183. tVisit = tCanvas.create_text((88, 22), text=u"点此访问Hann's CSDN主页!")
    184. tCanvas.itemconfig(tVisit, fill='blue', tags=('btnText'))
    185. tCanvas.tag_bind('btnText','',on_Enter)
    186. tCanvas.tag_bind('btnText','',on_Leave)
    187. tCanvas.tag_bind('btnText','',on_Release)
    188. win.protocol("WM_DELETE_WINDOW", on_Close)
    189. win.mainloop()

    编译命令

    D:\> pyinstaller -F lifegame.pyw --noconsole

    运行界面

    使用简介

    在生存空间里点击方格种植细胞(甚至可以画出你要表达的图形),然后点击开始;点下暂停键,则可以任意编辑生命图形,再点开始继续运行;点随机键则由软件随机生成细胞位置;清空键可以在任何时候清空生存空间,进入暂停编辑状态。 

    后续改进

    Lifes类预留了histroy属性,后续可以增加回退功能;代码缺点是生存空间的行列被我写死了,以后版本中可以改进成任意定义行列数;另一个缺点是对稳定状态的判断比较简单,之后可以增加计算变化周期的功能。

    优点就是可以任意编辑你的图形,最后以一张自己的网名画的图作收尾:


    前部分介绍性文字来源于百度百科等网络资源 

  • 相关阅读:
    [质量认证管理系统]优化升级,解决多种格式文档高效审批认证难题
    Unity中Shader模板测试使用到的二进制
    单元测试我们需要知道哪些?
    如何判断mysql innodb buffer pool内存配置不足
    Mysql
    生产服务器变卡怎么排查
    [MATLAB]结构化程式与自定义函数
    SpringBoot + layui 框架实现一周免登陆功能
    Python 字符串格式化方法记录
    面试经常问的Linux进程到底是什么呢?1W字从0到1详解进程的所有细节!
  • 原文地址:https://blog.csdn.net/boysoft2002/article/details/126131069