-1

I am making a game using the customtkinter library the game is a clicker tycoon game that involves beans. quite similar to cookie clicker. below is the entire code

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

# stores money
money = 0
# variables for bean generation
beans = 0
click = 1
#toggles bean collection
toggle=False
#Application
class BeanApp(ctk.CTk):
    def __init__(self):
        super().__init__()
        # sets window and frames
        self.title("Bean Tycoon")
        self.geometry("500x500+700+200")

        #click modes
        multiplier_lbl=CTkLabel(self,text= "Multipliers")
        multiplier_lbl.place(x=250,y=1,anchor="n",)
        def xOne():#x1 multiplier toggle
            global click
            click=1
            one_click_mode_btn.configure(state="disabled")
            two_click_mode_btn.configure(state="enable")
            click_multiplyer_lbl.configure(text=f"  Beans/click: x{click}   ")
        def xTwo():#x2 multiplier toggle
            global click
            click=2
            one_click_mode_btn.configure(state="enable")
            two_click_mode_btn.configure(state="disabled")
            click_multiplyer_lbl.configure(text=f"  Beans/click: x{click}   ")

        one_click_mode_btn= CTkButton(self,text="x1",width=20,height=10,command=xOne,state="disabled")
        one_click_mode_btn.place(x=145,y=25,anchor="nw")
        two_click_mode_btn=CTkButton(self, text="x2",width=20,height=10,command=xTwo,state="disabled")
        two_click_mode_btn.place(x=173,y=25,anchor="nw")
        click_multiplyer_lbl=CTkLabel(self,text=f"  Beans/click: x{click}   ")
        click_multiplyer_lbl.place(x=3,y=45,anchor="nw",)
        # Bean generator
        #beanbtn = CTkImage(Image.open("Pictures\TheBean.png"),size=(200,200))
        bean_amt_lbl = CTkLabel(self,text= f"  Beans: {beans}  ",)
        bean_amt_lbl.place(x=3,y=5,anchor="nw")
        def clicked():
            global beans
            global click
            beans += click
            bean_amt_lbl.configure(text= f"  Beans: {beans}  ")

        sell_beans_btn = CTkButton(self,text= "",image=None, command= clicked,width=180,height=180) 
        sell_beans_btn.place(x=250,y=330, anchor="s")
        # Sell Beans
        money_amt_lbl = CTkLabel(self,text=f"  Money: ${money}  ", )
        money_amt_lbl.place(x=3,y=25,anchor='nw')
        def sell():
            global money
            global beans
            while beans >0:
                money = money+beans
                beans = 0
                bean_amt_lbl.configure(text=f"  Beans: {beans}  ")
                money_amt_lbl.configure(text=f"  Money: ${money  }")
                
        sell_bean_btn = CTkButton(self,text="Sell Beans",image=None, command=sell)
        sell_bean_btn.place(x=250,y=360,anchor='s')
        #2 times multiplier
        def d_b_u():#double_bean_upgrade
            global click,money
            if money>=100:
                click=2
                money=money-100
                money_amt_lbl.configure(text=f"  Money: ${money  }")
                double_bean_upgrade_btn.configure(state="disabled")
                two_click_mode_btn.configure(state="disabled")
                one_click_mode_btn.configure(state="enable")
                click_multiplyer_lbl.configure(text=f"  Beans/click: x{click}   ")
        #automatic bean collector
        def a_b_c_u():#automatic_bean_collector_upgrade
            global beans,money,toggle
            if money>=20:
                money=money-20
                money_amt_lbl.configure(text=f"  Money: ${money  }")
                auto_collect_bean_btn.configure(state="disabled")
                while beans >=0 :
                    beans=beans+1
                    bean_amt_lbl.configure(text=f"  Beans: {beans}  " )
                    t.sleep(3)
                    print(beans) 
                               
        #Shop
        shop_lbl= CTkLabel(self,text="Shop")
        shop_lbl.place(x=425,y=5,anchor="nw")
        double_bean_upgrade_btn = CTkButton(self,text="Bean Doubler\n$100",command=d_b_u,width=20,corner_radius=20)
        double_bean_upgrade_btn.place(x=390,y=30,anchor="nw")
        auto_collect_bean_btn = CTkButton(self,text="Auto Collect 1\n$200",command=a_b_c_u,width=20,corner_radius=20)
        auto_collect_bean_btn.place(x=390,y=70,anchor="nw")


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

the game involves an incremental counter that runs in the background, the loop is toggled via a button. however when the loop activates it freezes the application. how can this be fixed?

EDIT: I hashtagged the image library and image function since it does not effect the code in the slightest as of now.

EDIT 2:How do i properly implement Threads? i dont think i did this right...

  #automatic bean collector
    def b_c_():
        global beans
        while beans >=0 :
                beans=beans+1
                bean_amt_lbl.configure(text=f"  Beans: {beans}  " )
                t.sleep(3)
                print(beans) 
    bean_collector=Thread(target= b_c_)
    def a_b_c_u():#automatic_bean_collector_upgrade
        global beans,money,toggle
        if money>=20:
            money=money-20
            money_amt_lbl.configure(text=f"  Money: ${money  }")
            auto_collect_bean_btn.configure(state="disabled")
            bean_collector.run()

Edit 3: Removed as it strayed from the original problem

  • Have you looked into using threads / multiprocessing? – OneCricketeer Apr 19 '23 at 17:45
  • no i havent, mostly because i dont know what threads are other that their use in multiprocessing. – T.E.G Studios Apr 19 '23 at 17:57
  • Processes and threads are related, but different. Threads run within the context of one OS process.... Both can help un-block a UI, however. – OneCricketeer Apr 19 '23 at 18:14
  • You might want to see the example in this post https://stackoverflow.com/a/26703844/2308683 – OneCricketeer Apr 19 '23 at 18:15
  • i dont think i still understand how to properly implement it in my use case. i just have more questions now – T.E.G Studios Apr 19 '23 at 18:53
  • Not fully sure, but you can use the `.run()` (or stop the thread) functions from the `target` functions you've set on the buttons. If you want the thread to always run (e.g. the automatic money one), then you should put that after `app.mainloop()` since its a separate "loop" happening. The only issue is that global variable are a bad idea (especially with threads), and you need other functions that would actually update the gui. – OneCricketeer Apr 19 '23 at 19:03
  • Also, the `sell` action doesn't need a loop, for example. You just zero-out the beans. Add to the money, and you're done. – OneCricketeer Apr 19 '23 at 19:05

3 Answers3

0

You need to put the the loop into another thread so that the user interface does not freeze. You can use the thread library.

import thread

def foo(args):
  # loop logic

thread.start_new_thread(foo, (args))
Jacob
  • 388
  • 2
  • 7
0

The general solution to not block a UI is to pass around functions when the UI actually has to be updated, not use loops, at all, or very minimally.

You also have global state, which is not recommended. To solve both, add some more classes.

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

  def deposit(self, amount, handler=None)
    ''' accepts optional function with the amount deposited '''
    self.money += amount
    if handler:
      handler(amount)

  def withdraw(self, amount, handler=None)
    ''' accepts optional function with the amount withdrawn '''
    if self.money >= amount:
      self.money -= amount
      if handler:
        handler(amount)
    else:
      print('error: not enough money available')
class BeanCounter:
  def __init__(self, amount=0):
    self.amount = amount

  def increment(self, handler=None):
    ''' accepts optional function called after one bean added '''
    self.amount += 1
    if handler:
      handler()

Then update the app constructor and define some functionality with the above functions

class BeanApp(ctk.CTk):
    def __init__(self, bank, bean_counter):
      super().__init__()
      # sets window and frames
      self.title("Bean Tycoon")

      self.bank = bank
      self.bean_counter = bean_counter

      # add labels 

    def bean_label_updater(self):
      ''' updates the bean label with the current amount '''  
      self.bean_amt_lbl.configure(text= f"  Beans: {self.bean_counter.amount}  ")

    def on_click(self):
      self.bean_counter.increment(bean_label_updater)
    # TODO: button target=on_click

    def on_click_sell(self):
      self.bank.deposit(self.bean_counter.amount)  # times amount per bean ; todo: add bank label updater function
      self.bean_counter.amount = 0
      self.bean_label_updater()

And then

if __name__ == "__main__":
  bank = Bank()
  bean_counter = BeanCounter()

  app = BeanApp(bank, bean_counter)
  app.mainloop()

This wont fully solve the "automatic collector" from blocking the UI, but it at least points you in a general start for separating your "state/model" from your "view"

One solution, is to create a threaded wrapper around the beancounter class like this, then start that on the associated action.

class BeanDoubler(Thread):
  def __init__(self, bean_counter, update_func=None):
    self.bean_counter = bean_counter
    self.update_func = update_func
  def run(self):
    while True:
      # use a for loop to increment over the amount (thereby doubling)
      for _ in range(self.bean_counter.amount):
        self.bean_counter.increment(self.update_func)

(code has not been tested, sorry if there's typos)

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
  • OMG This helps tremendously, ill be sure to study up on these functions for future projects. Thank you soo much. – T.E.G Studios Apr 20 '23 at 13:05
  • well now that i am implementing the code there are a few errors being presented. just one question. does BeanCounter go under the bank class or is it outside of the classes? – T.E.G Studios Apr 20 '23 at 17:39
  • There are three separate classes. None are nested within each other. I see now I put `def` instead of `class` sorry about that – OneCricketeer Apr 20 '23 at 21:54
  • one last question, the handler functions, i assume they connect the UI elements such as buttons to the main classes is that correct? – T.E.G Studios May 15 '23 at 13:15
  • They can be used that way, yes, but they are generic callback function handles – OneCricketeer May 15 '23 at 15:43
  • I see, is there any way I can message you privately to discuss this further or do you know of anyone who may help me? there are still a few errors that I found that I need help with working out. ill update this thread accordingly as to help solve the initial problem. – T.E.G Studios May 15 '23 at 16:57
  • refer to edit 3 so you can see what im talking about – T.E.G Studios May 15 '23 at 17:31
  • I suggest creating a new post rather than edit the existing one, but 1) My `BeanDoubler` does not exist `BeanCounter`. Neither should `BeanApp`. 2) You'll need to show the stacktrace or add more logging to see where recursion happened – OneCricketeer May 15 '23 at 21:29
  • ill keep this in mind, thanks again for your help. – T.E.G Studios May 16 '23 at 01:43
  • @T.E.GStudios Edited the answer with an indentation fix – OneCricketeer May 17 '23 at 13:43
0

All I seem to get now is a defined object with no attributes to the answer I was given. I am now having trouble passing the objects and their attributes into class BeanApp() as shown here

.

I somewhat understand where I went wrong which is explained here. thankfully the UI now runs well.

To elaborate, bank and bean_doubler in class BeanApp(self,bank,bean_doubler) are empty variables when they should, to my understanding, be connected to the class Bank() and class BeanDoubler() to then use the functions in them in the window that class BeanApp()` initializes.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129