Python Forum
Trouble with threading and reading variable from a different script
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Trouble with threading and reading variable from a different script
#1
Hi.
As a learning exercise I decided to write a simple pixel bot for a game. It consists of 3 modules that I would like to run at the same time:
Walking(cavebot), targetting, healing. They work well as separate scripts, but I can't seem to "connect" them together.
Using some internet resources, I've decided to run them all using GUI script:
import tkinter as tk
import threading
from engine import cavebot, targetting, healing
def set_cavebot_running(value):
    global cavebot_running
    cavebot_running = value
    if value:
        threading.Thread(target=run_cavebot).start()

def set_targetting_running(value):
    global targetting_running
    targetting_running = value
    if value:
        threading.Thread(target=run_targetting).start()
		
def set_healing_running(value):
    global healing_running
    healing_running = value
    if value:
        threading.Thread(target=run_healing).start()

def run_cavebot():
    while cavebot_running:
        cavebot.walking()

def run_targetting():
    while targetting_running:
        targetting.attacking()

def run_healing():
    while healing_running:
        healing.leczenie()


root = tk.Tk()
root.title("Cavebot and Targetting Control")

cavebot_running = False
cavebot_start_button = tk.Button(root, text="Start Cavebot", command=lambda: set_cavebot_running(True))
cavebot_start_button.pack()
cavebot_stop_button = tk.Button(root, text="Stop Cavebot", command=lambda: set_cavebot_running(False))
cavebot_stop_button.pack()

targetting_running = False
targetting_start_button = tk.Button(root, text="Start Targetting", command=lambda: set_targetting_running(True))
targetting_start_button.pack()
targetting_stop_button = tk.Button(root, text="Stop Targetting", command=lambda: set_targetting_running(False))
targetting_stop_button.pack()

healing_running = False
healing_start_button = tk.Button(root, text="Start healing", command=lambda: set_healing_running(True))
healing_start_button.pack()
healing_stop_button = tk.Button(root, text="Stop healing", command=lambda: set_healing_running(False))
healing_stop_button.pack()

root.mainloop()
turning them on works, turning them off doesn't, also, they seem to only workif the GUI is "on top", while pausing if its hidden under the game window.

Targetting used to work solo:
import pyautogui
import time

global czekaj
target = False
zaatakowane = False
czekaj = False
confidence = 0.8
interval = 0.5

def attacking():
    while True:
        if not pyautogui.locateOnScreen('engine/empty.png', confidence=confidence):
            time.sleep(0.5)
            pyautogui.click(335, 315)
            time.sleep(0.1)
            pyautogui.press('space')
            target = True
            czekaj = True
            zaatakowane = True
            print("Target acquired, zaatakowane = True")
            time.sleep(interval)
            if zaatakowane:
                while pyautogui.locateOnScreen('engine/attk.png', confidence=confidence):
                    time.sleep(1)
                    print("Attacking")
                    zaatakowane = True
                else:
                    print("not attacking, proceed to loot")
                    zaatakowane = False
                    time.sleep(interval)
        else:
            czekaj = False
            target = False
            zaatakowane = False
            print("No target", czekaj, target, zaatakowane)
            time.sleep(interval)
Now it works when conditions for the last else are met, but crashes without error if the empty.png file is not found.

Cavebot is the most difficult one for me. It used to work solo, but I need it to do the actions only if the "czekaj" variable from targetting is False. I honestly have no idea how to properly import it from one script to another as nothing seems to work, especially considering the fact that I want it changed before the actions in targetting are done, not after that (this could work with "return True" but then the cavebot would work through the actions of the loop in targetting, atleast thats how I understand it)
import pyautogui 
import time 
from engine import targetting

global czekaj

minimap_center = (910, 103)
images = ['engine/lock.png', 'engine/teststar1.png']

def walking():
    while True:
        while czekaj is False:
            for image in images:
                if pyautogui.locateOnScreen(image, region=(857, 51, 115, 115), confidence=0.7) != None:
                    print("I can see it", image)
                    wptcenter = pyautogui.locateCenterOnScreen(image, confidence=0.7)
                    print(wptcenter)
                    pyautogui.click(wptcenter)
                    print("moved to", image)
                    time.sleep(2)
                    if wptcenter == minimap_center:
                        print("reached", image)
                        if image != 'lock.png': 
                            print('wpt walk done')
                            time.sleep(1)
                        else:
                            wptcenter = pyautogui.locateCenterOnScreen(image, confidence=0.7)
                            pyautogui.click(wptcenter)
                            time.sleep(2)
                            print('roping')
                            pyautogui.press('f12')
                            pyautogui.click(337,316) 
                            time.sleep(1)
                                
                    else:
                        while wptcenter != minimap_center:
                            wptcenter = pyautogui.locateCenterOnScreen(image, confidence=0.7)
                            pyautogui.click(wptcenter)
                            print("clicking")
                            time.sleep(1)
                else:
                    print("I am unable to see it")
                    time.sleep(0.5)
            else:
                time.sleep(1)
I know its a lot of useless code and I've probably done it in the most illogical way possible, but any suggestion would be appreciated as a learning experience.
Reply
#2
tkinter is single threaded. You cannot execute tkinter code in a thread that did not call tkinter.Tk(). You can have tkinter events start threads to do work in parallel, but those threads cannot interact with tkinter widgets. For example, you cannot have a button click start a thread that appends strings to a text box.

The way to get around this restriction is use queues or some other type of signaling device to pass information from the background thread to the tkinter thread. I did this with a couple of class variables in this example:

https://python-forum.io/thread-39656-pos...#pid168425
Reply
#3
(Apr-17-2023, 02:23 PM)deanhystad Wrote: tkinter is single threaded. You cannot execute tkinter code in a thread that did not call tkinter.Tk(). You can have tkinter events start threads to do work in parallel, but those threads cannot interact with tkinter widgets. For example, you cannot have a button click start a thread that appends strings to a text box.

The way to get around this restriction is use queues or some other type of signaling device to pass information from the background thread to the tkinter thread. I did this with a couple of class variables in this example:

https://python-forum.io/thread-39656-pos...#pid168425
Thanks! I will check it out.
Reply
#4
(Apr-17-2023, 02:23 PM)deanhystad Wrote: tkinter is single threaded. You cannot execute tkinter code in a thread that did not call tkinter.Tk(). You can have tkinter events start threads to do work in parallel, but those threads cannot interact with tkinter widgets. For example, you cannot have a button click start a thread that appends strings to a text box.

The way to get around this restriction is use queues or some other type of signaling device to pass information from the background thread to the tkinter thread. I did this with a couple of class variables in this example:

https://python-forum.io/thread-39656-pos...#pid168425

I'm not exactly sure I follow here.
From my understanding, tkinter is used only to create a simple GUI with several buttons, and then if pressed it is used to start those 3 scripts in separate threads. Does this mean that tkinter is now threaded aswell and it bugs out? Or you mean that it can start but not end (kill) those newly opened threads? For the moment for test purposes I can cope with no way to turn those threads off from gui buttons, as long as the rest does their tasks.
Does this also impact the fact that those scripts are now no longer working properly?
Reply
#5
Quote:I'm not exactly sure I follow here.
Yeah, I was doing a good job answering a question you didn't ask. I'll do better this time.

In two different files you have a variable named "czekaj'. These are completely different variables. If you want the two files to reference the same variable, you declare it in one file, and import it in the other. Like this:

filea.py
variable = 5

def print_variable():
    print("my variable =", variable)
fileb.py
import filea

print("filea.variable =", filea.variable)
filea.print_variable()
filea.variable = 7
filea.print_variable()
Output:
filea.variable = 5 my variable = 5 my variable = 7
Do not try to do this:

fileb.py
from filea import variable, print_variable

print("filea.variable =", variable)
print_variable()
variable = 7
print_variable()
filea.variable = 5
my variable = 5
my variable = 5
Assigning "variable = 7" inside fileb.py creates a new global variable if fileb.py. The same problem you were having. You need to use the module.variable_name so Python knows you are assigning a value to a variable in another module.

These problems go away if you put all the code in one file. Why are you splitting it into 3 files? It's not like there are many lines of code.

A few other things I noticed.

This does nothing outside a function:
global czekaj
The global keyword tells Python to look for this variable in the global scope. When you use it at the top of a file, outside a function, it does nothing. Also, global only refers to a single module, not all modules in the project. There is no such thing as a project wide variable. You can import a variable from another module, but the scope of the variable is only global to the module that contains the variable (which is why you use the module.variable_name when assigning a value.)

This is how you use global:
def attacking():
    global czekaj, target
    while True:
        if not pyautogui.locateOnScreen('engine/empty.png', confidence=confidence):
            . . snip . .
        else:
            czekaj = False   # Without the global czekaj, target in function, these assignments
            target = False   # create function variables instead of changing the global variable.
            zaatakowane = False
            print("No target", czekaj, target, zaatakowane)
            time.sleep(interval)
Reply
#6
Thank you for your awesome reply, I really appreciate it.
Quote:These problems go away if you put all the code in one file. Why are you splitting it into 3 files? It's not like there are many lines of code.
It seems like I unnecesarily overcomplicated things here. I started this small project at the complete beginning of my python learning where I didn't know about threading, etc. and based it on some already existing examples and thought it was "the way to go". Back then I thought that a simple file will go from top to bottom only, so 3 functions of walking, targetting, healing will be done in sequence while I need healing to go constantly, targetting aswell, and walking if the conditions from targetting are met.

Should I try to connect everything, starting from GUI, through all the rest into one file and use threading from there, or leave GUI, healing and targetting+cavebot(which will remove the problems with czekaj variable) as 3 separate files?
Reply
#7
Could you describe what your program is supposed to do? I don't understand how the program decides if it is time to run, walk, attack, heal, whatever.

Can you post a link or tell me where to look for any of these examples you mention:
Quote:based it on some already existing examples
I think most of the examples I find online are worthless. About half are pretty old and don't use new python features or ideas. Half of the remaining is regurgitating some other post/tutorial, and half of the remaining 25% is poorly written or designed, or just full of errors. The trick these days is finding the good 12.5%.
Reply
#8
Quote:Can you post a link or tell me where to look for any of these examples you mention
I can't find the exact github repository that I used, but looking for "Tibia pixel bot" brings several similar projects, although vastly more complicated. The one I learned from/modified was much more complicated than my scripts, and it worked after few changes, but I decided to try my strength at creating one from scratch, from a simple one, maybe to more complicated ones, but it seems like I've bitten more than I can chew.

Quote:Could you describe what your program is supposed to do? I don't understand how the program decides if it is time to run, walk, attack, heal, whatever.
Sure.
As I mentioned, everything the bot does is divided into - healing, targetting, walking(cavebot).
- Healing is supposed to work constantly, checking pixel values of 2 pixels I specified, and doing actions depending on their color.

- Targeting is also supposed to work constantly. This one looks for an image on the screen. If the "Battle list" is empty it should continue to check each second but send the info to walking script that the "czekaj" variable is False, allowing the Walking script to do its job. If the Battle list is not empty, it should send the signal that "czekaj" variable is now true, for walking script to stop what it was doing, and then perform several actions, like, starting to attack, check if the target is still alive, etc.

- If the variable "czekaj" from targetting is false, walking script has a list of images, its task is to take first image, look for it in a specified area of the screen and click on it each second or 2 until the center of said image reaches the center of the area, then go to the next image, and so on. (this only changed slightly for some specified images as they need another action performed). If the variable changes to True, I don't want to stop the walking script completely and restart it, I want to pause it so that it continues from when it was forced to stop by the variable.

As I mentioned before, I managed to write all of them so each script works alone and does the task its supposed to do. Problems start with running all 3 of them at the same time.
Reply
#9
(Apr-18-2023, 08:31 PM)deanhystad Wrote: Could you describe what your program is supposed to do?

Sorry to disturb you again. I followed your advice and modified everything so that all 3 functions are inside of one .py file. I also created threads for all 3 of them and they work at the same time, the only problem that persists is the fact, that walking function still doesn't care about the czekaj variable from attacking. At this point I don't what to do anymore.
The code is below, I ". . snip . ."ed unnecessary stuff but they don't contain anything of impact to the scripts as a whole.
from pyautogui import * 
import pyautogui 
import time 
from PIL import ImageGrab
import threading

def attacking():
    global czekaj, target, zaatakowane
    czekaj = False
    target = False
    zaatakowane = False
    looting = False
    confidence = 0.8
    interval = 0.5
    while True:
        if not pyautogui.locateOnScreen('engine/empty.png', confidence=confidence):
            time.sleep(0.5)
            pyautogui.click(335, 315)
            time.sleep(0.1)
            pyautogui.press('space')
            target = True
            czekaj = True
            zaatakowane = True
            print("Target acquired ", zaatakowane)
            time.sleep(interval)
            if zaatakowane:
                while pyautogui.locateOnScreen('engine/attk.png', confidence=confidence):
                    time.sleep(1)
                    print("Attacking")
                    zaatakowane = True
                else:
                    looting = True
                    . . snip . .
                    looting = False
                    zaatakowane = False
                    time.sleep(interval)

        else:
            if looting is True:
                czekaj = True
                time.sleep(0.5)
            else: 
                czekaj = False
                target = False
                zaatakowane = False
                print("No target ", czekaj)
                time.sleep(interval)
    else:
        sys.exit()


def walking():
    minimap_center = (910, 103)
    images = ['engine/checkmark.png', 'engine/teststar1.png', 'engine/cross.png']
    while True:
        while czekaj is False:
            for image in images:
                if pyautogui.locateOnScreen(image, region=(857, 51, 115, 115), confidence=0.7) != None:
                    print("I can see it", image)
                    wptcenter = pyautogui.locateCenterOnScreen(image, confidence=0.7)
                    print(wptcenter)
                    pyautogui.click(wptcenter)
                    print("moved to", image)
                    time.sleep(2)
                    if wptcenter == minimap_center:
                        print("reached", image)
                        if image != 'lock.png':
                            print('wpt walk reached')
                            time.sleep(1)
                        else:
                            wptcenter = pyautogui.locateCenterOnScreen(image, confidence=0.7)
                            pyautogui.click(wptcenter)
                            time.sleep(2)
                            print('roping')
                            pyautogui.press('f12')
                            pyautogui.click(337,316)
                            time.sleep(1)
                                
                    else:
                        while wptcenter != minimap_center:
                            wptcenter = pyautogui.locateCenterOnScreen(image, confidence=0.7)
                            pyautogui.click(wptcenter)
                            print("clicking")
                            time.sleep(1)
                else:
                    print("I am unable to see it")
                    time.sleep(0.5)
        else: 
            time.sleep(1)
    else: 
        sys.exit()


def healing():
    . . snip . .
    while True:
        mana = ImageGrab.grab().getpixel((640, 55))
        hp = ImageGrab.grab().getpixel((15,55))
        if hp == hpfull:
            if mana == manafull:
                time.sleep(1)
                print("mana full")
            else:
                pyautogui.press('')#mana hotkey
                time.sleep(1)
        . . snip . .
        else:
            print('you probably dead')
            time.sleep(1)
    else:
        sys.exit()

# create threads for each function
t1 = threading.Thread(target=attacking)
t2 = threading.Thread(target=walking)
t3 = threading.Thread(target=healing)

# start the threads
t1.start()
t2.start()
t3.start()
Reply
#10
I don't understand why you insist on running with threads when the problem clearly cries out for you to do one thing at a time. You are either walking or attacking, one at a time. You do not do them at the same time. The only thing you do all the time is healing. So I see only two thrreads and really no need for two.

I'm pretty sure the body does not run while czekaj is not False.
while czekaj is False:
    # body
But the body of the walking function has a bunch of waits and pyauto commands that are going to run until it reaches the top of the while.

If you insist of this folly, a few suggestions.

Declare global functions in the global scope, before you start any of your tasks. You are lucky you didn't start the walking thread first because "czekaj' is not defined untill the attacking function starts. It also contains this while loop:
                       while wptcenter != minimap_center:
Nothing in the loop is checking the value of "czekaj", so it is going to run until wptcenter != minmap_center regardless of what attacking is doing.

I don't know what you think this will do.
while True:
    # do stuff
else:
    sys.exit()
In a "while condition-else" statement the else block only runs when the while condition is not true. True will always be True. the sys.exit() code can never run.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  trouble reading string/module from excel as a list popular_dog 0 432 Oct-04-2023, 01:07 PM
Last Post: popular_dog
  Concurrent futures threading running at same speed as non-threading billykid999 13 1,865 May-03-2023, 08:22 AM
Last Post: billykid999
Question How can I import a variable from another script without executing it ThomasFab 12 7,839 May-06-2022, 03:21 PM
Last Post: bowlofred
  Tutorials on sockets, threading and multi-threading? muzikman 2 2,130 Oct-01-2021, 08:32 PM
Last Post: muzikman
  Trouble reading files using pyexcel codebeacon 2 2,202 Feb-08-2021, 05:53 AM
Last Post: codebeacon
  Python reading variable in another py file wrongly _vertig0 2 1,983 Nov-21-2020, 07:19 AM
Last Post: _vertig0
  Trouble with reading csv file and putting it into a file Milfredo 3 2,278 Sep-04-2020, 05:30 AM
Last Post: Milfredo
  Use dynamic variable from parallel running python script Sicksym 0 1,854 May-15-2020, 02:52 PM
Last Post: Sicksym
  [split] script: remove all "carriage return" from my json variable pete 2 2,814 May-05-2020, 03:22 PM
Last Post: deanhystad
  Trouble reading Excel file. Shembeginner 2 2,308 Apr-07-2020, 04:55 AM
Last Post: Shembeginner

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020