You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

问题根源

  1. 重复创建按钮实例:在b1func、b2func中重新定义b1 = Button(...),会覆盖全局变量,但棋盘组件引用的是最初的按钮实例,新按钮并未替换到棋盘中,导致显示旧按钮状态。
  2. 全局变量未正确声明:turnchooser中修改user1、user2时未加global声明,实际修改的是局部变量,全局变量未更新。
  3. 冗余的棋盘重建:每次点击都重新创建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

修复说明

  1. 复用按钮实例:直接修改原有按钮的description和disabled属性,棋盘组件会自动同步显示状态,避免实例重建导致的状态丢失。
  2. 统一事件处理:用单个函数处理所有按钮点击,减少重复代码,逻辑更清晰。
  3. 正确更新全局变量:turnchooser中添加global声明,确保全局变量被正确修改,回合显示框同步更新。
  4. 优化组件结构:用列表存储按钮,简化初始化流程,便于后续扩展游戏逻辑(如胜负判断)。

内容的提问来源于stack exchange,提问作者A_V CODERZ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 09:50:27