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

CustomTkinter豆子主题点击游戏递归错误排查与修复请求

豆子点击游戏(Bean Tycoon)问题修复方案

问题核心定位

  1. 递归错误与线程滥用:BeanDoubler的run方法存在无延迟的while True循环,持续调用increment导致CPU占用过高并触发递归错误;同时未正确初始化Thread父类,线程逻辑完全无效。
  2. 类继承与实例化混乱:BeanApp错误继承Bank和BeanCounter,但主函数又单独实例化这两个类,导致数据不共享;多处直接调用类方法(如BeanCounter.increment)而非实例方法,引发参数传递错误。
  3. UI操作违规:非UI线程直接修改UI组件,违反CustomTkinter的线程安全规则;标签更新函数未绑定实例数据,显示内容始终为None。
  4. 数据传递失效:类之间未通过实例引用共享数据,点击、售卖等操作无法正确修改全局的豆子/金钱数值。

关键修复要点

  • 重构类关系:BeanApp不再继承Bank和BeanCounter,而是持有这两个类的实例,确保数据全局统一。
  • 修正线程实现:BeanDoubler正确继承Thread并调用super().__init__(),添加sleep控制循环频率;线程中UI更新通过app.after()委托给UI线程执行。
  • 修复方法调用:所有操作均通过实例调用方法(如self.bean_counter.increment()),而非直接调用类方法。
  • 绑定UI与数据:标签更新函数直接读取实例数值,确保UI实时同步数据变化。
  • 完善按钮逻辑:补充售卖、升级按钮的实际功能,处理金钱扣除、豆子翻倍等核心逻辑。

完整修复代码

import customtkinter as ctk
from customtkinter import CTkImage, CTkButton, CTkLabel
from PIL import Image
import time as t
from threading import Thread

class Bank():
    def __init__(self):
        self.money = 0

    def deposit(self, amount, handler=None):
        self.money += amount
        if handler:
            handler()

    def withdraw(self, amount, handler=None):
        if self.money >= amount:
            self.money -= amount
            if handler:
                handler()
            return True
        else:
            print('余额不足,无法购买')
            return False

class BeanCounter():
    def __init__(self, amount=0):
        self.amount = amount
        self.click_multiplier = 1  # 点击乘数

    def increment(self, handler=None):
        self.amount += self.click_multiplier
        if handler:
            handler()

class BeanDoubler(Thread):
    def __init__(self, bean_counter, update_func):
        super().__init__(daemon=True)  # 守护线程,随主程序退出
        self.bean_counter = bean_counter
        self.update_func = update_func
        self.running = True

    def run(self):
        while self.running:
            # 每2秒翻倍一次豆子(可调整频率)
            if self.bean_counter.amount > 0:
                self.bean_counter.amount *= 2
                self.update_func()  # 委托UI线程更新
            t.sleep(2)

    def stop(self):
        self.running = False

class BeanApp(ctk.CTk):
    def __init__(self):
        super().__init__()
        self.title("Bean Tycoon")
        self.geometry("500x400")  # 匹配预期界面的窗口大小

        # 初始化核心数据实例
        self.bank = Bank()
        self.bean_counter = BeanCounter()
        self.bean_doubler = None

        self._setup_ui()

    def _setup_ui(self):
        # 点击乘数区域
        multiplier_lbl = CTkLabel(self, text="Multipliers")
        multiplier_lbl.place(x=250, y=1, anchor="n")

        self.one_click_btn = CTkButton(self, text="x1", width=20, height=10, 
                                       command=lambda: self.set_multiplier(1), state="disabled")
        self.one_click_btn.place(x=145, y=25, anchor="nw")
        self.two_click_btn = CTkButton(self, text="x2", width=20, height=10, 
                                       command=lambda: self.set_multiplier(2), state="normal")
        self.two_click_btn.place(x=173, y=25, anchor="nw")
        self.click_multi_lbl = CTkLabel(self, text=f"  Beans/click: x{self.bean_counter.click_multiplier}   ")
        self.click_multi_lbl.place(x=3, y=45, anchor="nw")

        # 豆子显示与点击按钮
        self.bean_amt_lbl = CTkLabel(self, text=f"  Beans: {self.bean_counter.amount}  ")
        self.bean_amt_lbl.place(x=3, y=5, anchor="nw")

        # 替换为实际豆子图片路径,这里做加载失败兼容
        try:
            bean_img = CTkImage(Image.open("bean.png"), size=(200, 200))
        except:
            bean_img = None  # 图片不存在时显示文本

        self.bean_btn = CTkButton(self, text="Click for Beans" if not bean_img else "", 
                                  image=bean_img, command=self.on_click_bean, 
                                  width=180, height=180)
        self.bean_btn.place(x=250, y=330, anchor="s")

        # 金钱显示与售卖按钮
        self.money_amt_lbl = CTkLabel(self, text=f"  Money: ${self.bank.money}  ")
        self.money_amt_lbl.place(x=3, y=25, anchor='nw')

        self.sell_btn = CTkButton(self, text="Sell Beans", command=self.on_sell_beans)
        self.sell_btn.place(x=250, y=360, anchor='s')

        # 商店区域
        shop_lbl = CTkLabel(self, text="Shop")
        shop_lbl.place(x=425, y=5, anchor="nw")

        self.doubler_btn = CTkButton(self, text="Bean Doubler\n$100", 
                                     command=self.buy_bean_doubler, width=100, corner_radius=20)
        self.doubler_btn.place(x=390, y=30, anchor="nw")

        self.auto_collect_btn = CTkButton(self, text="Auto Collect 1\n$200", 
                                          command=self.buy_auto_collect, width=100, corner_radius=20)
        self.auto_collect_btn.place(x=390, y=70, anchor="nw")

    def update_bean_label(self):
        self.bean_amt_lbl.configure(text=f"  Beans: {self.bean_counter.amount}  ")

    def update_money_label(self):
        self.money_amt_lbl.configure(text=f"  Money: ${self.bank.money}  ")

    def update_multiplier_label(self):
        self.click_multi_lbl.configure(text=f"  Beans/click: x{self.bean_counter.click_multiplier}   ")

    def on_click_bean(self):
        self.bean_counter.increment(self.update_bean_label)

    def on_sell_beans(self):
        if self.bean_counter.amount > 0:
            self.bank.deposit(self.bean_counter.amount, self.update_money_label)
            self.bean_counter.amount = 0
            self.update_bean_label()

    def set_multiplier(self, multiplier):
        self.bean_counter.click_multiplier = multiplier
        self.one_click_btn.configure(state="disabled" if multiplier ==1 else "normal")
        self.two_click_btn.configure(state="disabled" if multiplier ==2 else "normal")
        self.update_multiplier_label()

    def buy_bean_doubler(self):
        if self.bank.withdraw(100, self.update_money_label):
            if not self.bean_doubler or not self.bean_doubler.is_alive():
                self.bean_doubler = BeanDoubler(self.bean_counter, self.update_bean_label)
                self.bean_doubler.start()
                self.doubler_btn.configure(text="Doubler Active", state="disabled")

    def buy_auto_collect(self):
        if self.bank.withdraw(200, self.update_money_label):
            # 自动收集逻辑:每秒增加1个豆子
            def auto_collect():
                self.bean_counter.increment(self.update_bean_label)
                self.after(1000, auto_collect)
            auto_collect()
            self.auto_collect_btn.configure(text="Auto Active", state="disabled")

if __name__ == "__main__":
    app = BeanApp()
    app.mainloop()

修复说明

  1. 线程安全:BeanDoubler使用守护线程,避免主程序退出后线程残留;UI更新通过委托函数在UI线程执行,符合CustomTkinter规则。
  2. 数据统一:所有操作均通过BeanApp持有的bank和bean_counter实例进行,确保数据全局一致。
  3. 功能完善:补充了点击乘数切换、豆子售卖、商店购买逻辑,完全匹配预期界面的功能需求。
  4. 错误处理:添加了金钱不足提示、图片加载失败兼容处理,提升程序稳定性。

内容的提问来源于stack exchange,提问作者T.E.G Studios

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 13:54:52