I have the following code in which I have an infinite loop that updates the state of a variable running in a thread, and in another thread, I have an infinite loop that prints the state of the said variable each second, the problem is that although the state updates correctly in the first thread, the state remains unaffected in the second one.
What could I do to solve this? I thought that using threads would solve the issue, also I'm not that well versed in asynchronous programing
inside main.py
import cv2 as cv
from utils import WindowCapture
import numpy as np
import os
import time
from threading import Thread
from threading import Event
import pyautogui
from queue import Queue
os.chdir(os.path.dirname(os.path.abspath(__file__)))
wincap = WindowCapture("DragonBallOnline")
global toggle
global enemyState
# >>> pyautogui.mouseDown(); pyautogui.mouseUp() # does the same thing as a left-button mouse click
# >>> pyautogui.mouseDown(button='right') # press the right button down
# >>> pyautogui.mouseUp(button='right', x=100, y=200) # move the mouse to 100, 200, then release the right button up.
def targetIsFullHealth(enemy):
return (enemy == np.array([65, 27, 234])).all()
def targetIsDead(enemy):
return (enemy == np.array([26, 10, 95])).all()
def targetIsUndefined(enemy):
return (enemy != np.array([65, 27, 234])).all() and (enemy != np.array([97, 155, 146])).all()
def combatLoop(): # unused
## target enemy
## if target is full health attack
## if target is dead wait and loot
## if target is undefined search new target
pass
def bot(): # unused
# Debug function not used currently
while True:
##do stuff
EnemyLife = wincap.get_screenshot(902, 70, 120, 16)
Items = wincap.get_screenshot(621, 163, 662, 749)
Radar = wincap.get_screenshot(1750, 53, 157, 157)
cv.imshow("EnemyLife", EnemyLife)
cv.imshow("Items", Items)
cv.imshow("Radar", Radar)
# print(EnemyLife[10][5])
if (EnemyLife[10][5] == np.array([65, 27, 234])).all():
print("target is full health")
rotateCursor()
elif (EnemyLife[10][5] == np.array([97, 155, 146])).all():
print("Target is dead")
else:
print("No target")
if cv.waitKey(1) == ord("-"):
cv.destroyAllWindows()
break
print("Done!")
def rotateCursor(): # unused
# pyautogui.mouseDown(button="right")
# pyautogui.mouseUp(button="right", x=10, y=1)
pass
def updateEnemyState(threadname):
global toggle
global enemyState
toggle = True
while True:
EnemyLife = wincap.get_screenshot(902, 70, 120, 16)
cv.imshow("EnemyLife", EnemyLife)
enemy = EnemyLife[10][5]
if targetIsDead(enemy):
enemyState = "dead"
elif targetIsFullHealth(enemy):
enemyState = "alive"
else:
enemyState = "undefined"
Event().wait(1.0 / 60)
print("Thread1 :", enemyState)
if cv.waitKey(1) == ord("-"):
cv.destroyAllWindows()
toggle = False
break
def macros(threadname):
while toggle:
Event().wait(1.0)
print("Thread2 :", enemyState)
if __name__ == "__main__":
print("test")
# WindowCapture.list_window_names();
thread1 = Thread(target=updateEnemyState, args=("Thread-1",))
thread2 = Thread(target=macros, args=("Thread-1",))
thread1.start()
thread2.start()
thread1.join()
thread2.join()
inside utils.py
import numpy as np
import win32gui, win32ui, win32con
class WindowCapture:
# properties
w = 0
h = 0
hwnd = None
cropped_x = 0
cropped_y = 0
offset_x = 0
offset_y = 0
# constructor
def __init__(self, window_name=None):
# find the handle for the window we want to capture
if window_name is None:
self.hwnd = win32gui.GetDesktopWindow()
else:
self.hwnd = win32gui.FindWindow(None, window_name)
if not self.hwnd:
raise Exception('Window not found: {}'.format(window_name))
# get the window size
window_rect = win32gui.GetWindowRect(self.hwnd)
self.w = window_rect[2] - window_rect[0]
self.h = window_rect[3] - window_rect[1]
# account for the window border and titlebar and cut them off
#border_pixels = 8
#titlebar_pixels = 30
border_pixels = 0
titlebar_pixels = 0
self.w = self.w - (border_pixels * 2)
self.h = self.h - titlebar_pixels - border_pixels
self.cropped_x = border_pixels
self.cropped_y = titlebar_pixels
# set the cropped coordinates offset so we can translate screenshot
# images into actual screen positions
self.offset_x = window_rect[0] + self.cropped_x
self.offset_y = window_rect[1] + self.cropped_y
def get_screenshot(self,startX=None,startY=None,width=None,height=None):
if startX is None : startX = self.cropped_x
if startY is None : startY = self.cropped_y
if width is None : width = self.w
if height is None : height = self.h
# get the window image data
wDC = win32gui.GetWindowDC(self.hwnd)
dcObj = win32ui.CreateDCFromHandle(wDC)
cDC = dcObj.CreateCompatibleDC()
dataBitMap = win32ui.CreateBitmap()
dataBitMap.CreateCompatibleBitmap(dcObj, width, height)
cDC.SelectObject(dataBitMap)
cDC.BitBlt((0, 0), (width, height), dcObj, (startX, startY), win32con.SRCCOPY)
# convert the raw data into a format opencv can read
#dataBitMap.SaveBitmapFile(cDC, 'debug.bmp')
signedIntsArray = dataBitMap.GetBitmapBits(True)
img = np.fromstring(signedIntsArray, dtype='uint8')
img.shape = (height, width, 4)
# free resources
dcObj.DeleteDC()
cDC.DeleteDC()
win32gui.ReleaseDC(self.hwnd, wDC)
win32gui.DeleteObject(dataBitMap.GetHandle())
img = img[...,:3]
img = np.ascontiguousarray(img)
return img
@staticmethod
def list_window_names():
def winEnumHandler(hwnd, ctx):
if win32gui.IsWindowVisible(hwnd):
print(hex(hwnd), win32gui.GetWindowText(hwnd))
win32gui.EnumWindows(winEnumHandler, None)
# translate a pixel position on a screenshot image to a pixel position on the screen.
# pos = (x, y)
# WARNING: if you move the window being captured after execution is started, this will
# return incorrect coordinates because the window position is only calculated in
# the __init__ constructor.
def get_screen_position(self, pos):
return (pos[0] + self.offset_x, pos[1] + self.offset_y)
I have provided all the code one needs to recreate an example, although the most important parts are hardcoded hence it is a pixel detector, you might need to use another window name and another pixel position and color values
The initial and first state change:
Thread1 : alive
Thread1 : alive
Thread1 : alive
Thread1 : alive
Thread1 : alive
Thread2 : alive
Thread1 : alive
Thread1 : alive
Thread1 : alive
Thread1 : alive
Thread1 : alive
Debug info
Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread2 : alive
Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread1 : dead
Thread2's enemyState should have been dead My guess is that enemyState remains locked inside thread2 for some reason.
imports) and several parts – namely screen filling and capturing – that seem unrelated to the stated problem but will seriously complicate analysing the problem.timr.sleep(1)in thread2. EVERY TIME you see "Thread1: alive", you have pushed another item onto your queue. but thread2 can only pull them out once a second. By the time thread2 pulls one, you have 12 copies of "alive" in the queue before it gets to "dead". You should (a) only push a queue item when the state changes, and (b) forget thetime.sleep(1), since queue.get() will block.