Threading Help - stop_event is not set

Question:
So I am making a template/module for an easy to use Idle Game Maker (basically a game engine). I am trying to use threading for when I take input and will still be able to reprint with the currency changing every second from bought items. I don’t technically get an error in the console, but it does say → stop_event is not set and the program stops running, it should say → stop_event is set and run like it should. I have asked several AI’s like ChatGPT and Phind but they haven’t been able to fix it.

Repl link:

https://replit.com/@SalladShooter/IdleGameMaker

__init__.py

from termcolor import colored
from replit import clear
import time
import threading
from threading import Event, Lock
from queue import Queue, Empty

class Game:
    def __init__(self, gameName, author: str, value=None, collectSpeed=1, rebirths=False, rebirthName="Rebirth"):
        self.gameName = gameName
        self.author = author
        self.actions = ["Shop"]
        self.currency = value['symbol'] if value else None
        self.rebirths = rebirths

        if value and value.get('rebirths', False):
            self.rebirths = True
            self.rebirthName = value.get('rebirthName', 'Rebirth')
            self.actions.append(self.rebirthName)

        self.currencyColor = value.get('color', 'white')
        self.collectSpeed = value.get('collectSpeed', 1)
        self.title = colored(f"{self.gameName}\n", self.currencyColor)

        self.items = []
        self._money = 0
        self._perSecond = 0
        self.moneyAdd = 1
        self.type = None
        self.color = None
        self.shop = "closed"
        self.actions.append("Credits")
        self.item_name = []
        self.item_cost = []
        self.item_gives_type = []
        self.item_amount = []
        self.item_color = []
        self.running = True
        self.lock = Lock()

        self._last_money_update = time.time()
        self.stop_event = Event()
        self.input_queue = Queue()
        
    @property
    def money(self):
        with self.lock:
            elapsed_time = time.time() - getattr(self, '_last_money_update', time.time())
            self._money += elapsed_time * self._perSecond
            self._last_money_update = time.time()
            return round(self._money)

    @money.setter
    def money(self, value):
        with self.lock:
            self._last_money_update = time.time()
            self._money = value

    @property
    def perSecond(self):
        with self.lock:
            return self._perSecond

    @perSecond.setter
    def perSecond(self, value):
        with self.lock:
            self.money
            self._perSecond = value
            
    def stop(self):
       self.stop_event.set()
        
    def add(self, type: str, value: dict):
        if type.lower() == "buy":
            item_name = value['name']
            self.item_name.append(item_name)
            item_cost = value['cost']
            self.item_cost.append(item_cost)
            gives_type = value.get('givesType', 'perSecond')
            self.item_color.append(value['color'])
            self.item_gives_type.append(gives_type)
            amount = value.get('amount', 1)
            self.item_amount.append(amount)

    def click(self):
        for i in range(len(self.item_name)):
            if self.item_gives_type[i] == 'perClick':
                self.money += self.moneyAdd

    def buy(self, item):
        item_index = int(item) - 1
        if 0 <= item_index < len(self.item_name):
            item_cost = self.item_cost[item_index]
            item_name = self.item_name[item_index]
            gives_type = self.item_gives_type[item_index]
            amount = self.item_amount[item_index]
            item_color = self.item_color[item_index]
            if self.money >= item_cost:
                self.money -= item_cost
                self.items.append((item_name, item_cost))

                if gives_type == 'perSecond':
                    self.perSecond += amount
                elif gives_type == 'perClick':
                    self.moneyAdd += amount
                clear()
                print(self.title)
                print(f"{self.money} - {colored(self.currency, self.currencyColor)}\n")
            else:
                print(f"You don't have enough {colored(self.currency, self.currencyColor)}'s to buy {colored(item_name, item_color)}.")
                input("Press [" + colored("ENTER", "cyan") + "] to continue...")
        else:
            print("Invalid item selection.")
            input("Press [" + colored("ENTER", "cyan") + "] to continue...")

    def input_thread(self):
        while not self.stop_event.is_set():
            if self.stop_event.is_set():
                print("stop_event is set")
            else:
                print("stop_event is not set")
                choice = input()
            if choice:
                self.input_queue.put(choice)
            elif self.stop_event.is_set():
                break
    
    def handle_input(self, choice):
        if choice.isdigit():
            choice = int(choice)
            if 1 <= choice <= len(self.actions):
                action = self.actions[choice - 1]
                if action == "Shop" and self.shop == "closed":
                    clear()
                    print(self.title)
                    print(f"{round(self.money)} - {colored(self.currency, self.currencyColor)}\n")
                    self.display_items()
                    print(f"{len(self.item_name) + 1}. Exit Shop")
                    player_action = input("> ")
                    if player_action == str(len(self.item_name) + 1):
                        self.shop = "closed"
                        clear()
                    elif player_action.isdigit() and 1 <= int(player_action) <= len(self.item_name):
                        self.buy(player_action)
                elif action == "Credits":
                    clear()
                    print(
                        f"""
    Game Made By - {self.author}
    Made With {colored("Idle Game Maker", "red")} - SalladShooter""")
                    input(f"Press [" + colored("ENTER", "cyan") + "] to continue...")

    def game_loop(self):
       threading.Thread(target=self.input_thread, daemon=True).start()
       while self.stop_event and not self.stop_event.is_set():
           try:
               choice = self.input_queue.get(timeout=0.1) 
               self.handle_input(choice)
           except Empty:
               self.stop()
                               
    def display_items(self):
        for i, item in enumerate(self.item_name, start=1):
            print(
                f"{i}. {colored(item, self.item_color[i - 1])} {colored(self.currency, self.currencyColor)} {self.item_cost[i - 1]} ({self.item_gives_type[i - 1]} {self.item_amount[i - 1]} {colored(self.currency, self.currencyColor)})") 

main.py

from idlegamemaker import idlegamemaker

# Example usage
game = idlegamemaker.Game(
    "Idle Apple Collector",
    value={
        "symbol": "",
        "color": "green",
        "collectSpeed": 0,
        "rebirths": True,
        "rebirthName": "Ascend",
    },
    author="SalladShooter",
)

game.add(
    "buy",
    {
        "name": "Item1",
        "cost": 10,
        "color": "blue",
        "givesType": "perSecond",
        "amount": 1,
    },
)
game.add(
    "buy",
    {
        "name": "Item2",
        "cost": 20,
        "color": "cyan",
        "givesType": "perSecond",
        "amount": 5,
    },
)
game.add(
    "buy",
    {
        "name": "Item3", "cost": 50, "color": "red", "givesType": "perClick", "amount": 1
    },
)

game.game_loop() 

Do you mean that it’s not seeing the stop button? If so, then this is a Replit thing, you can’t hook the stop button.

Well I am trying to hook up a stop event since I am using threading, and if the game_loop doesn’t run it should stop, or run if it is set and what not.

You can’t hook the stop button. At all. I’ve tried, and everything that should have worked, doesn’t.

@Firepup650 so it’s a Replit thing, is there anyway I could fix it, or is it a bug? Is there something else I could use instead of threading to make it work?

AFAIK, there is no work around. You literally just cannot handle the stop button.

1 Like

So there is no way to fix my project and allow input and reprint?

Not sure what you mean here, what is checking for stopping supposed to do for you? As far as I can tell, it just has your code break the whole loop, which a stop does on it’s own.

So since I’m using threading I check if the stop_event is set and if it can run to run the program. I stop it otherwise. The reason I do this is for the input_thread, that is able to do its job if the stop_event is set. It should be setting but I cant get it to work.

Side track, but I think this:

Could be simplified to:

def input_thread(self):
        while not self.stop_event.is_set():
            if self.stop_event.is_set():
                print("stop_event is set")
                break
            else:
                print("stop_event is not set")
                choice = input()
                self.input_queue.put(choice)

And for the whole stopping thing:

def stop(self):
       self.is_stopped = True
def input_thread(self):
        while not self.is_stopped:
def game_loop(self):
       threading.Thread(target=self.input_thread, daemon=True).start()
       while not self.is_stopped:

This won’t handle the stop button of course, but it should work otherwise, I think.

1 Like

@Firepup650 I get this error →

Exception in thread Thread-1 (input thread):
Traceback (most recent call last):
File "/nix/store/×f54733x4chbawkh
1qvy9i1i4mlscy1c-python3-3.10.11/li b/python3.10/threading.py", line 10 16, in bootstrap_inner self. run()
File "/nix/store/×f54733x4chbawkh
1qvy91i4mlscy1c-python3-3.10.11/li b/python3.10/threading .py", line 95 3, in run
self._target(*self ._args, **sel f._kwargs)
File "/home/runner/ IdleGameMaker/ idlegamemaker/idlegamemaker/__init_ _•py", line 117, in input_thread while not self.is_stopped:
AttributeError: 'Game obiect has n o attribute 'is_stopped'
Traceback (most recent call last):
File "/home/runner/IdleGameMaker/ main.py", line 43, in <module> game.game_loop()
File "/home/runner / IdleGameMaker/ idlegamemaker / idlegamemaker/_ init _•py", line 153, in game_ loop while not self.is_stopped:
AttributeError: 'Game" obiect has n o attribute 'is_stopped' 

I then changed →
self.stop_event= Event()
to
self.is_stopped = Event()
and I have the same problem as before, did I do something wrong?
__init__.py

from termcolor import colored
from replit import clear
import time
import threading
from threading import Event, Lock
from queue import Queue, Empty

class Game:
    def __init__(self, gameName, author: str, value=None, collectSpeed=1, rebirths=False, rebirthName="Rebirth"):
        self.gameName = gameName
        self.author = author
        self.actions = ["Shop"]
        self.currency = value['symbol'] if value else None
        self.rebirths = rebirths

        if value and value.get('rebirths', False):
            self.rebirths = True
            self.rebirthName = value.get('rebirthName', 'Rebirth')
            self.actions.append(self.rebirthName)

        self.currencyColor = value.get('color', 'white')
        self.collectSpeed = value.get('collectSpeed', 1)
        self.title = colored(f"{self.gameName}\n", self.currencyColor)

        self.items = []
        self._money = 0
        self._perSecond = 0
        self.moneyAdd = 1
        self.type = None
        self.color = None
        self.shop = "closed"
        self.actions.append("Credits")
        self.item_name = []
        self.item_cost = []
        self.item_gives_type = []
        self.item_amount = []
        self.item_color = []
        self.running = True
        self.lock = Lock()

        self._last_money_update = time.time()
        self.is_stopped = Event()
        self.input_queue = Queue()
        
    @property
    def money(self):
        with self.lock:
            elapsed_time = time.time() - getattr(self, '_last_money_update', time.time())
            self._money += elapsed_time * self._perSecond
            self._last_money_update = time.time()
            return round(self._money)

    @money.setter
    def money(self, value):
        with self.lock:
            self._last_money_update = time.time()
            self._money = value

    @property
    def perSecond(self):
        with self.lock:
            return self._perSecond

    @perSecond.setter
    def perSecond(self, value):
        with self.lock:
            self.money
            self._perSecond = value

    def stop(self):
           self.is_stopped = True
        
    def add(self, type: str, value: dict):
        if type.lower() == "buy":
            item_name = value['name']
            self.item_name.append(item_name)
            item_cost = value['cost']
            self.item_cost.append(item_cost)
            gives_type = value.get('givesType', 'perSecond')
            self.item_color.append(value['color'])
            self.item_gives_type.append(gives_type)
            amount = value.get('amount', 1)
            self.item_amount.append(amount)

    def click(self):
        for i in range(len(self.item_name)):
            if self.item_gives_type[i] == 'perClick':
                self.money += self.moneyAdd

    def buy(self, item):
        item_index = int(item) - 1
        if 0 <= item_index < len(self.item_name):
            item_cost = self.item_cost[item_index]
            item_name = self.item_name[item_index]
            gives_type = self.item_gives_type[item_index]
            amount = self.item_amount[item_index]
            item_color = self.item_color[item_index]
            if self.money >= item_cost:
                self.money -= item_cost
                self.items.append((item_name, item_cost))

                if gives_type == 'perSecond':
                    self.perSecond += amount
                elif gives_type == 'perClick':
                    self.moneyAdd += amount
                clear()
                print(self.title)
                print(f"{self.money} - {colored(self.currency, self.currencyColor)}\n")
            else:
                print(f"You don't have enough {colored(self.currency, self.currencyColor)}'s to buy {colored(item_name, item_color)}.")
                input("Press [" + colored("ENTER", "cyan") + "] to continue...")
        else:
            print("Invalid item selection.")
            input("Press [" + colored("ENTER", "cyan") + "] to continue...")

    def input_thread(self):
        while not self.is_stopped:
            if self.stop_event.is_set():
                print("stop_event is set")
                break
            else:
                print("stop_event is not set")
                choice = input()
                self.input_queue.put(choice)
    
    def handle_input(self, choice):
        if choice.isdigit():
            choice = int(choice)
            if 1 <= choice <= len(self.actions):
                action = self.actions[choice - 1]
                if action == "Shop" and self.shop == "closed":
                    clear()
                    print(self.title)
                    print(f"{round(self.money)} - {colored(self.currency, self.currencyColor)}\n")
                    self.display_items()
                    print(f"{len(self.item_name) + 1}. Exit Shop")
                    player_action = input("> ")
                    if player_action == str(len(self.item_name) + 1):
                        self.shop = "closed"
                        clear()
                    elif player_action.isdigit() and 1 <= int(player_action) <= len(self.item_name):
                        self.buy(player_action)
                elif action == "Credits":
                    clear()
                    print(
                        f"""
    Game Made By - {self.author}
    Made With {colored("Idle Game Maker", "red")} - SalladShooter""")
                    input(f"Press [" + colored("ENTER", "cyan") + "] to continue...")

    def game_loop(self):
        threading.Thread(target=self.input_thread, daemon=True).start()
        while not self.is_stopped:
           try:
               choice = self.input_queue.get(timeout=0.1) 
               self.handle_input(choice)
           except Empty:
               self.stop()
                               
    def display_items(self):
        for i, item in enumerate(self.item_name, start=1):
            print(
                f"{i}. {colored(item, self.item_color[i - 1])} {colored(self.currency, self.currencyColor)} {self.item_cost[i - 1]} ({self.item_gives_type[i - 1]} {self.item_amount[i - 1]} {colored(self.currency, self.currencyColor)})") 

Try just defining it as False in the __init__.

Also I probably missed this:

Should be:

def input_thread(self):
        while not self.is_stopped:
            if self.is_stopped:
                print("is_stopped is true")
                break
            else:
                print("is_stopped is false")
                choice = input()
                self.input_queue.put(choice)
2 Likes

@Firepup650 I have this now →

from termcolor import colored
from replit import clear
import time
import threading
from threading import Event, Lock
from queue import Queue, Empty


class Game:
    def __init__(
        self,
        gameName,
        author: str,
        value=None,
        collectSpeed=1,
        rebirths=False,
        rebirthName="Rebirth",
    ):
        self.gameName = gameName
        self.author = author
        self.actions = ["Shop"]
        self.currency = value["symbol"] if value else None
        self.rebirths = rebirths

        if value and value.get("rebirths", False):
            self.rebirths = True
            self.rebirthName = value.get("rebirthName", "Rebirth")
            self.actions.append(self.rebirthName)
        self.currencyColor = value.get("color", "white")
        self.collectSpeed = value.get("collectSpeed", 1)
        self.title = colored(f"{self.gameName}\n", self.currencyColor)

        self.items = []
        self._money = 0
        self._perSecond = 0
        self.moneyAdd = 1
        self.type = None
        self.color = None
        self.shop = "closed"
        self.actions.append("Credits")
        self.item_name = []
        self.item_cost = []
        self.item_gives_type = []
        self.item_amount = []
        self.item_color = []
        self.running = True
        self.lock = Lock()

        self._last_money_update = time.time()
        self.is_stopped = False
        self.input_queue = Queue()

    @property
    def money(self):
        with self.lock:
            elapsed_time = time.time() - getattr(
                self, "_last_money_update", time.time()
            )
            self._money += elapsed_time * self._perSecond
            self._last_money_update = time.time()
            return round(self._money)

    @money.setter
    def money(self, value):
        with self.lock:
            self._last_money_update = time.time()
            self._money = value

    @property
    def perSecond(self):
        with self.lock:
            return self._perSecond

    @perSecond.setter
    def perSecond(self, value):
        with self.lock:
            self.money
            self._perSecond = value

    def stop(self):
        self.is_stopped = True

    def add(self, type: str, value: dict):
        if type.lower() == "buy":
            item_name = value["name"]
            self.item_name.append(item_name)
            item_cost = value["cost"]
            self.item_cost.append(item_cost)
            gives_type = value.get("givesType", "perSecond")
            self.item_color.append(value["color"])
            self.item_gives_type.append(gives_type)
            amount = value.get("amount", 1)
            self.item_amount.append(amount)

    def click(self):
        for i in range(len(self.item_name)):
            if self.item_gives_type[i] == "perClick":
                self.money += self.moneyAdd

    def buy(self, item):
        item_index = int(item) - 1
        if 0 <= item_index < len(self.item_name):
            item_cost = self.item_cost[item_index]
            item_name = self.item_name[item_index]
            gives_type = self.item_gives_type[item_index]
            amount = self.item_amount[item_index]
            item_color = self.item_color[item_index]
            if self.money >= item_cost:
                self.money -= item_cost
                self.items.append((item_name, item_cost))

                if gives_type == "perSecond":
                    self.perSecond += amount
                elif gives_type == "perClick":
                    self.moneyAdd += amount
                clear()
                print(self.title)
                print(f"{self.money} - {colored(self.currency, self.currencyColor)}\n")
            else:
                print(
                    "You don't have enough"
                    f" {colored(self.currency, self.currencyColor)}'s to buy"
                    f" {colored(item_name, item_color)}."
                )
                input("Press [" + colored("ENTER", "cyan") + "] to continue...")
        else:
            print("Invalid item selection.")
            input("Press [" + colored("ENTER", "cyan") + "] to continue...")

    def input_thread(self):
        while not self.is_stopped:
            if self.is_stopped:
                print("is_stopped is true")
                break
            else:
                print("is_stopped is false")
                choice = input()
                self.input_queue.put(choice)

    def handle_input(self, choice):
        if choice.isdigit():
            choice = int(choice)
            if 1 <= choice <= len(self.actions):
                action = self.actions[choice - 1]
                if action == "Shop" and self.shop == "closed":
                    clear()
                    print(self.title)
                    print(
                        f"{round(self.money)} -"
                        f" {colored(self.currency, self.currencyColor)}\n"
                    )
                    self.display_items()
                    print(f"{len(self.item_name) + 1}. Exit Shop")
                    player_action = input("> ")
                    if player_action == str(len(self.item_name) + 1):
                        self.shop = "closed"
                        clear()
                    elif player_action.isdigit() and 1 <= int(player_action) <= len(
                        self.item_name
                    ):
                        self.buy(player_action)
                elif action == "Credits":
                    clear()
                    print(f"""
    Game Made By - {self.author}
    Made With {colored("Idle Game Maker", "red")} - SalladShooter""")
                    input(f"Press [" + colored("ENTER", "cyan") + "] to continue...")

    def game_loop(self):
        threading.Thread(target=self.input_thread, daemon=True).start()
        while not self.is_stopped:
            try:
                choice = self.input_queue.get(timeout=0.1)
                self.handle_input(choice)
            except Empty:
                self.stop()

    def display_items(self):
        for i, item in enumerate(self.item_name, start=1):
            print(
                f"{i}."
                f" {colored(item, self.item_color[i - 1])} {colored(self.currency, self.currencyColor)} {self.item_cost[i - 1]} ({self.item_gives_type[i - 1]} {self.item_amount[i - 1]} {colored(self.currency, self.currencyColor)})"
            )

And in the console it says → is_stopped is false, did I do it correctly?

Based on the way I understand your code, yes, that’s what you want to see.

@Firepup650 nothing else happens, shouldn’t it be asking for an input?

It should, and looking at your code it does:

But there’s no prompt.

@Firepup650 once it says is_stopped is false it stops running.

Huh. Did it end up here by chance?

Sine that’s in a while loop, it would kick itself out once that happens.

I suppose add print("is_stopped is true, and loop is broken") after the while loop here so we can trace what’s happening.

1 Like

@Firepup650 I added it and when I run it, like I expected, it says it in the console ->console

is_stopped is false
is_stopped is true, and loop is broken

Then I suspect the input function throwing that Empty error is the cause here.

For testing:

Add print("waiting on queue") before you check the queue, and print("Error: Empty") in the except block.

1 Like