Shop not Working Anymore

Question:
So I am building a template/module for an easy to use idle game maker. It includes many things, the shop is a big part, and it doesn’t seem to work anymore after I added threading, I also have an issue with input()’s in general wether it be in the shop or in my Credits.

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

__init__.py

from termcolor import colored
from replit import clear
import time
import threading
from threading import 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):
       try:
           while not self.is_stopped:
               choice = input()
               self.input_queue.put(choice)
       except KeyboardInterrupt:
           pass
       finally:
           self.stop()

    def handle_input(self, choice):
        if not self.is_stopped:
            if choice == "":
                self.money += self.moneyAdd
                clear()
            elif 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("\n> ", end='', flush=True)
                        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""")
                        print("Press [" + colored("ENTER", "cyan") + "] to continue...")
                        input()

    def display_actions(self):
        for i, item in enumerate(self.actions, start=1):
            print(f"{i}. {item}")
        
    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)})"
            )
        print(f"{len(self.item_name) + 1}. Exit Shop")

    def display_start(self):
        print(f"{colored(self.gameName, self.currencyColor)}\n")
        print(f"{self.money} - {colored(self.currency, self.currencyColor)}\n")
        print(f"Press [{colored('ENTER', 'cyan')}] to gain {colored(self.currency, self.currencyColor)}'s")
        self.display_actions()

    def game_loop(self):
        threading.Thread(target=self.input_thread, daemon=True).start()
        try:
            while not self.is_stopped:
                self.display_start()
                print("\n> ", end='', flush=True)
                choice = self.input_queue.get()
                self.handle_input(choice)
        except Empty:
            pass
        finally:
            self.stop() 

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() 

Can you be more specific about the problem? Like, is there any error inputs that pop up in the console? Or a specific behaviour that makes this happen?

@WindLother There isn’t an error message, just nothing happens, it should as I have an if statement in my handle_input() function.

hmm

Can you check if your input_thread is actually running? Like, add some prints, for example

def input_thread(self):
    print("Input thread starting...", flush=True) #forgot to add flush=true 
    try:
        while not self.is_stopped:
            print("Input thread awaiting input...", flush=True)  
            choice = input()
            self.input_queue.put(choice)
            print(f"Input received: {choice}", flush=True)  
    except KeyboardInterrupt:
        print("Input thread received a keyboard interrupt.", flush=True)  
    finally:
        self.stop()
        print("Input thread stopping...", flush=True) 

@WindLother it is running, since I can access my Credits (I just can’t leave). I think the problem lies in my handle_input() function →

def handle_input(self, choice):
        if not self.is_stopped:
            if choice == "":
                self.money += self.moneyAdd
                clear()
            elif 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("\n> ", end='', flush=True)
                        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""")
                        print("Press [" + colored("ENTER", "cyan") + "] to continue...")
                        input() 

Actually, I’d expect that to be a problem, as both the input_thread and the handle_input functions are trying to take input at the same time.

How could I fix this? I would need some way to get an input to work for pressing enter and not fight with input_thread and handle_input.

Since you have a dedicated thread for handling input, you should not call input() anywhere else in your code. Instead, you can use the queue to get user input, just like you do in the game_loop .

2 Likes

@WindLother I mostly fixed my problem (I can exit credits, and exit shop, etc.), except for buying items, when I enter a number for the item I want to buy nothing happens.

def handle_input(self, choice):
        if not self.is_stopped:
            if choice == "":
                self.money += self.moneyAdd
                clear()
            elif 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("\n> ", end='', flush=True)
                        player_action = self.input_queue.get()
                        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\n""")
                        print("Press [" + colored("ENTER", "cyan") + "] to continue...")
                        choice = self.input_queue.get()
                        self.handle_input(choice) 

After displaying the items and waiting for the player’s input, you are immediately trying to get a player action from the queue with player_action = self.input_queue.get()

It’s better if the input is being processed in the same loop that’s already handling inputs, like, set the shop to an “open” state and then let the main game loop continue.

For example, use the shop attribute to keep track of the state of the shop (whether it’s open, closed, or showing credits). And when an input is received, the handle_input function will check the shop state and handle the whole thing.

With that, the buy function will be called with the correct item number when the shop is open and a valid item number is entered.

1 Like

I tried to use ChatGPT to figure out how I could integrate this and it ended up breaking the entire thing, how could I make what you suggested work?

I tried my best to not break anything, I commented some sections too to better explain the process

def handle_input(self, choice):
    if not self.is_stopped:
        if choice == "":
            self.money += self.moneyAdd
            clear()
        elif choice.isdigit():
            choice = int(choice)
            if 1 <= choice <= len(self.actions):
                action = self.actions[choice - 1]
                if action == "Shop":
                    if self.shop == "closed":
                        self.shop = "open"  # For example, you state the shop to open here
                        clear()
                        print(self.title)
                        print(
                            f"{round(self.money)} -"
                            f" {colored(self.currency, self.currencyColor)}\n"
                        )
                        self.display_items()
                        print("\n> ", end='', flush=True)
                    else:  # And since the shop is already open, now you handle the input
                        if choice == len(self.item_name) + 1:
                            self.shop = "closed"
                            clear()
                        elif 1 <= choice <= len(self.item_name):
                            self.buy(str(choice))  # I assume that your buy function exepcts a string
                        else:
                            print("Invalid item selection.")
                            # There's no need to get from the queue here, since the next input will be processed in the next loop iteration
                elif action == "Credits":
                    clear()
                    print(f"""
Game Made By - {self.author}
Made With {colored("Idle Game Maker", "red")} - SalladShooter\n""")
                    print("Press [" + colored("ENTER", "cyan") + "] to continue...")
                    self.shop = "credits"  # Than you set the state to credits
            else:
                print("Invalid action.")
        elif self.shop == "open":  # And if an input is made while the shop is open
            if choice == str(len(self.item_name) + 1):
                self.shop = "closed"
                clear()
            elif choice.isdigit() and 1 <= int(choice) <= len(self.item_name):
                self.buy(choice)
            else:
                print("Invalid item selection.")
        elif self.shop == "credits":  # Likewise if an input is made while in credits
            self.shop = "closed"
            clear()

1 Like

@WindLother so I can access the shop but when it’s opened it reruns the display_start() and doesn’t work, same thing for the credits. (The stuff outlined in red should be the only thing there)

It looks like the game loop is not waiting for the input once the shop is displayed…

I think that after the shop is displayed, the game loop continues to run, immediately calls display_start() again without waiting for the user to select an item to buy.

Try to open a new thread and post your def game_loop(self) explaining this. I need to attend a meeting right now and will be unavailable for some hours.

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.