使用ipywidgets开发井字棋时按钮状态无法保持的问题
问题描述
- 开发井字棋时按钮状态无法持久保存:点击第一个按钮后显示正常,但点击第二个按钮时,第一个按钮会重置为空状态,无法保持已设置的禁用状态与符号
- 点击任意按钮后组件状态异常变更,
Output组件未按预期工作 - 无报错信息,但b1、b2按钮功能异常
提供的原始代码
导入与组件初始化
from ipywidgets import * from IPython.display import * out = Output() b1 = Button() b1.style.button_color = 'black' b2 = Button() b2.style.button_color = 'black' b3 = Button() b3.style.button_color = 'black' b4 = Button() b4.style.button_color = 'black' b5 = Button() b5.style.button_color = 'black' b6 = Button() b6.style.button_color = 'black' b7 = Button() b7.style.button_color = 'black' b8 = Button() b8.style.button_color = 'black' b9 = Button() b9.style.button_color = 'black' board = HBox([VBox([b1,b2,b3]),VBox([b4,b5,b6]),VBox([b7,b8,b9])]) user1name = Text() user2name = Text() user1turn = widgets.Text(value="turn",description=user1name.value+':',disabled=False) user2turn = widgets.Text(value="",description=user1name.value+':',disabled=False) user1 = "turn" user2 = "" userturn = 1
事件处理函数
def u1nc(self): out.clear_output() with out: print("enter ❌ player's name:") display(user1name) def onsumbitu1(self): out.clear_output() user1name.disabled = True with out: print("enter ⭕ player's name:") display(user2name) def onsubmitu2(self): out.clear_output() user2name.disabled = True with out: display(board) def turnchooser(self): global userturn if userturn == 1: user2 = "turn" user1 = "" userturn = 2 elif userturn == 2: user1 = "turn" user2 = "" userturn = 1 else: out.clear_output print("game over") def b1func(self): if userturn == 1: out.clear_output() b1 = Button(description='❌',disabled=True) b1.style.button_color = "black" board =HBox([VBox([b1,b2,b3]),VBox([b4,b5,b6]),VBox([b7,b8,b9])]) with out: display(board) out elif userturn == 2: b1 = Button(description='⭕',disabled=True) b1.style.button_color = 'black' board = HBox([VBox([b1,b2,b3]),VBox([b4,b5,b6]),VBox([b7,b8,b9])]) with out: display(board) out def b2func(self): if userturn == 1: out.clear_output() b2 = Button(description='❌',disabled=True) b2.style.button_color = "black" board = HBox([VBox([b1,b2,b3]),VBox([b4,b5,b6]),VBox([b7,b8,b9])]) with out: display(board) out elif userturn == 2: out.clear_output() b2 = Button(description='⭕',disabled=True) b2.style.button_color = 'black' board = HBox([VBox([b1,b2,b3]),VBox([b4,b5,b6]),VBox([b7,b8,b9])]) with out: display(board) out
事件绑定与显示
user1name.on_submit(onsumbitu1) user2name.on_submit(onsubmitu2) out.on_displayed(u1nc) b1.on_click(b1func) b1.on_click(turnchooser) b2.on_click(b2func) b2.on_click(turnchooser) out
问题根源
- 重复创建按钮实例:在
b1func、b2func中重新定义b1 = Button(...),会覆盖全局变量,但棋盘组件引用的是最初的按钮实例,新按钮并未替换到棋盘中,导致显示旧按钮状态。 - 全局变量未正确声明:
turnchooser中修改user1、user2时未加global声明,实际修改的是局部变量,全局变量未更新。 - 冗余的棋盘重建:每次点击都重新创建
board,但未同步更新组件引用,导致状态丢失。
修复后的代码
from ipywidgets import * from IPython.display import * out = Output() # 批量初始化按钮,统一存储便于管理 buttons = [] for _ in range(9): btn = Button(style={'button_color': 'black'}) buttons.append(btn) # 构建棋盘,直接引用按钮列表 board = HBox([ VBox(buttons[0:3]), VBox(buttons[3:6]), VBox(buttons[6:9]) ]) user1name = Text() user2name = Text() user1turn = widgets.Text(value="turn", description='', disabled=False) user2turn = widgets.Text(value="", description='', disabled=False) user1 = "turn" user2 = "" userturn = 1 def u1nc(self): out.clear_output() with out: print("输入 ❌ 玩家名称:") display(user1name) def onsumbitu1(self): out.clear_output() user1name.disabled = True # 更新回合显示的玩家名称 user1turn.description = f"{user1name.value}:" with out: print("输入 ⭕ 玩家名称:") display(user2name) def onsubmitu2(self): out.clear_output() user2name.disabled = True user2turn.description = f"{user2name.value}:" with out: display(board) display(HBox([user1turn, user2turn])) def turnchooser(self): global userturn, user1, user2 if userturn == 1: user2 = "turn" user1 = "" userturn = 2 elif userturn == 2: user1 = "turn" user2 = "" userturn = 1 else: with out: out.clear_output() print("游戏结束") # 同步更新回合显示框 user1turn.value = user1 user2turn.value = user2 def handle_button_click(btn): # 跳过已被点击的禁用按钮 if btn.disabled: return global userturn # 直接修改原有按钮的属性,无需重建 btn.description = '❌' if userturn == 1 else '⭕' btn.disabled = True # 刷新显示内容 out.clear_output() with out: display(board) display(HBox([user1turn, user2turn])) # 切换回合 turnchooser(None) # 绑定事件 user1name.on_submit(onsumbitu1) user2name.on_submit(onsubmitu2) out.on_displayed(u1nc) # 给所有按钮绑定统一的点击事件 for btn in buttons: btn.on_click(handle_button_click) # 显示输出组件 out
修复说明
- 复用按钮实例:直接修改原有按钮的
description和disabled属性,棋盘组件会自动同步显示状态,避免实例重建导致的状态丢失。 - 统一事件处理:用单个函数处理所有按钮点击,减少重复代码,逻辑更清晰。
- 正确更新全局变量:
turnchooser中添加global声明,确保全局变量被正确修改,回合显示框同步更新。 - 优化组件结构:用列表存储按钮,简化初始化流程,便于后续扩展游戏逻辑(如胜负判断)。
内容的提问来源于stack exchange,提问作者A_V CODERZ
相关产品推荐
相关产品推荐

