基于Tkinter的Conway's Game of Life运行崩溃,退出码3489660927或0
问题描述
使用Python的Tkinter库复刻Conway's Game of Life(生命游戏),代码运行时卡顿并最终崩溃,退出码为3489660927或0。以下是代码:
import tkinter as tk from time import sleep class Application(tk.Frame): def __init__(self, master = None): super().__init__(master) self.master = master self.playing = False; self.spaces = [[None, None, None, None, None, None, None, None, None, None], [None, None, None, None, None, None, None, None, None, None], [None, None, None, None, None, None, None, None, None, None], [None, None, None, None, None, None, None, None, None, None], [None, None, None, None, None, None, None, None, None, None], [None, None, None, None, None, None, None, None, None, None], [None, None, None, None, None, None, None, None, None, None], [None, None, None, None, None, None, None, None, None, None], [None, None, None, None, None, None, None, None, None, None], [None, None, None, None, None, None, None, None, None, None]] self.maxRows = 10 self.maxColumns = 10 self.drawSpaces() self.clickedrow = 0 self.clickedcolumn = 0 self.neighbors = 0 def getNeighbors(self, row, column): new = [] for r in range(max(0, row-1), min(self.maxRows, row+2)): for c in range(max(0, column-1), min(self.maxColumns, column+2)): if (r, c) != (row, column): new.append(self.spaces[r][c]) return new def drawSpaces(self): for r in range(10): for c in range(10): self.spaces[r][c] = tk.Button(text = '', width = 4, height = 2, bg = 'light grey', command = lambda r = r, c = c: self.change(r,c)) self.spaces[r][c].grid(row = r, column = c) self.play = tk.Button(text = 'Start', width = 30, height = 2, command = lambda: self.start()) self.play.grid(row = 0, column = 11) def change(self, row, column): if self.spaces[row][column].cget('bg') == 'light grey': self.spaces[row][column].config(bg = 'black') elif self.spaces[row][column].cget('bg') == 'black': self.spaces[row][column].config(bg = 'light grey') def start(self): if self.play.cget('text') == 'Start': self.play.config(text = 'Stop') else: self.drawSpaces() return while self.play.cget('text') == 'Stop': for r in range(10): for c in range(10): self.neighbors = 0 neighborlist = self.getNeighbors(r, c) for i in range(len(neighborlist)): if neighborlist[i].cget('bg') == 'black': self.neighbors += 1 if self.spaces[r][c].cget('bg') == 'light grey': if self.neighbors == 3: self.spaces[r][c].config(bg = 'black') elif self.spaces[r][c].cget('bg') == 'black': if self.neighbors != 2 or self.neighbors != 3: self.spaces[r][c].config(bg = 'light grey') sleep(0.5) root = tk.Tk() root.title('Conway\'s Game Of Life') app = Application(master = root) app.mainloop()
猜测可能是计算量过大导致问题,使用Thonny运行代码。
问题分析
- 阻塞Tkinter主循环:
start()方法里的while循环和sleep(0.5)会完全阻塞Tkinter的事件主循环,导致GUI无法响应任何操作(包括界面更新、按钮点击),这是卡顿和崩溃的核心原因。Tkinter必须让主循环持续运行才能处理事件。 - 生命游戏规则逻辑错误:判断活细胞死亡的条件
if self.neighbors != 2 or self.neighbors != 3逻辑永远为真,因为一个数不可能同时等于2和3,导致所有活细胞都会被错误杀死。正确的条件应该是if self.neighbors < 2 or self.neighbors > 3。 - 实时修改状态导致计算错误:在遍历细胞时直接修改按钮的背景色,会让后续细胞的邻居计算使用已经更新的状态,违反了生命游戏“所有细胞状态同时更新”的规则。
- 停止按钮逻辑错误:点击Stop时调用
drawSpaces()会重建所有按钮,丢失当前游戏状态,且没有正确终止运行中的游戏循环。
修复后的代码
import tkinter as tk class Application(tk.Frame): def __init__(self, master=None): super().__init__(master) self.master = master self.playing = False # 初始化细胞状态矩阵,用布尔值存储(True为活,False为死),避免频繁操作GUI组件 self.cell_states = [[False for _ in range(10)] for _ in range(10)] self.maxRows = 10 self.maxColumns = 10 self.draw_spaces() self.play_button = None def get_neighbors_count(self, row, column): """计算指定细胞的活邻居数量""" count = 0 # 遍历周围8个方向 for r in range(max(0, row-1), min(self.maxRows, row+2)): for c in range(max(0, column-1), min(self.maxColumns, column+2)): if (r, c) != (row, column) and self.cell_states[r][c]: count += 1 return count def draw_spaces(self): """绘制网格按钮和控制按钮""" self.buttons = [[None for _ in range(10)] for _ in range(10)] for r in range(self.maxRows): for c in range(self.maxColumns): bg_color = 'black' if self.cell_states[r][c] else 'light grey' self.buttons[r][c] = tk.Button( text='', width=4, height=2, bg=bg_color, command=lambda r=r, c=c: self.toggle_cell(r, c) ) self.buttons[r][c].grid(row=r, column=c) self.play_button = tk.Button( text='Start', width=30, height=2, command=self.toggle_play ) self.play_button.grid(row=0, column=11) def toggle_cell(self, row, column): """切换单个细胞的生死状态""" self.cell_states[row][column] = not self.cell_states[row][column] bg_color = 'black' if self.cell_states[row][column] else 'light grey' self.buttons[row][column].config(bg=bg_color) def toggle_play(self): """切换游戏的开始/停止状态""" if self.playing: self.playing = False self.play_button.config(text='Start') else: self.playing = True self.play_button.config(text='Stop') self.update_game() def update_game(self): """更新游戏状态,使用Tkinter的after方法避免阻塞主循环""" if not self.playing: return # 先计算所有细胞的下一个状态,避免实时修改影响计算 next_states = [[False for _ in range(self.maxColumns)] for _ in range(self.maxRows)] for r in range(self.maxRows): for c in range(self.maxColumns): neighbors = self.get_neighbors_count(r, c) current_state = self.cell_states[r][c] # 生命游戏规则 if current_state: # 活细胞:邻居数为2或3则存活,否则死亡 next_states[r][c] = neighbors in (2, 3) else: # 死细胞:邻居数为3则复活 next_states[r][c] = neighbors == 3 # 更新细胞状态和GUI self.cell_states = next_states for r in range(self.maxRows): for c in range(self.maxColumns): bg_color = 'black' if self.cell_states[r][c] else 'light grey' self.buttons[r][c].config(bg=bg_color) # 0.5秒后再次调用更新,实现循环 self.master.after(500, self.update_game) root = tk.Tk() root.title('Conway\'s Game Of Life') app = Application(master=root) app.mainloop()
修复说明
- 替换阻塞循环:用Tkinter的
after()方法代替while循环和sleep,让主循环能持续处理事件,避免卡顿崩溃。 - 修正规则逻辑:将活细胞死亡条件改为
neighbors not in (2, 3)(等价于neighbors <2 or neighbors>3),符合生命游戏规则。 - 分离状态存储:用布尔矩阵
cell_states存储细胞生死状态,避免频繁从GUI组件读取状态,同时实现“同时更新所有细胞”的逻辑——先计算所有下一个状态,再统一更新GUI。 - 优化停止逻辑:通过
self.playing标志控制游戏运行状态,点击Stop时不再重建按钮,而是终止后续的after()调用,保留当前游戏状态。
内容的提问来源于stack exchange,提问作者GoudyuGood
相关产品推荐
相关产品推荐

