I'm trying to program a macro for BTD6 using Python with pydirectinput
and pyautogui
. I'm having problems when trying to implement a way to break out of a while
loop while outside the terminal. If you have a solution please let me know. Here is the source code:
import pyautogui
import pydirectinput
import keyboard
import time
print("""
Welcome too...
____ _____ ____ __ __ __ _ __ __ __
| __ )_ _| _ \ / /_ | \/ | |/ / | \/ | __ _ ___ _ __ ___
| _ \ | | | | | | '_ \| |\/| | ' / | |\/| |/ _` |/ __| '__/ _ \
| |_) || | | |_| | (_) | | | | . \ | | | | (_| | (__| | | (_) |
|____/ |_| |____/ \___/|_| |_|_|\_\ |_| |_|\__,_|\___|_| \___/
By MMW Studios
""")
print("\nTo Begin, Get to the BTD6 Home screen.")
dart_monkey = "q"
boomerang_monkey = "w"
bomb_shooter = "e"
tack_shooter = "r"
ice_monkey = "t"
glue_gunner = "y"
sniper_monkey = "z"
submarine_monkey = "x"
buccaneer_monkey = "c"
ace_monkey = "v"
helicopter_monkey = "b"
mortar_monkey = "n"
dartling_gunner = "m"
wizard_monkey = "a"
super_monkey = "s"
ninja_monkey = "d"
alchemist = "f"
druid = "g"
mermonkey = "o"
banana_farm = "h"
spike_factory = "j"
monkey_village = "k"
engineer_monkey = "l"
beast_handler = "i"
hero = "u"
def click(position, clicks=1):
pyautogui.moveTo(position, duration=0.2)
time.sleep(0.2)
pyautogui.click(clicks=clicks, interval=0.2)
def summon_tower(monkey, position):
pydirectinput.press(monkey)
time.sleep(0.2)
pyautogui.moveTo(position, duration=0.2)
time.sleep(0.2)
pyautogui.click()
def upgrade_tower(position, upgrades):
pyautogui.moveTo(position, duration=0.2)
time.sleep(0.2)
pyautogui.click()
pydirectinput.press(",", presses=upgrades[0], interval=0.2)
pydirectinput.press(".", presses=upgrades[1], interval=0.2)
pydirectinput.press("/", presses=upgrades[2], interval=0.2)
print("Press the spacebar to begin")
keyboard.wait("space")
time.sleep(1)
#This is the while loop I want to break out of with a key press.
while True:
click((825, 925))
click((1350, 950))
click((1400, 550))
click((630, 400))
click((1300, 450))
time.sleep()
click((965, 750))
summon_tower(monkey_village, (1582, 673))
upgrade_tower((1582, 673), [2, 0, 2])
summon_tower(sniper_monkey, (1527, 595))
upgrade_tower((1527, 595), [0, 2, 4])
summon_tower(alchemist, (1603, 592))
upgrade_tower((1603, 592), [4, 2, 0])
click((1830, 1000), 2)
time.sleep(300)
click((960, 900))
click((725, 850))
Here is what I have tried so far:
while True:
if keyboard.is_pressed("a"):
break
This would work in most cases, but my program makes use of multiple time.sleep(duration)
. Because of this, the program will almost never pick up the keypresses to quit.
I have also tried:
try:
while True:
do_somthing()
time.sleep(10)
except KeyboardInterrupt:
pass
This works, but not in the desired way. The while loop will break when Crtl+C is pressed even with the time.sleep()
running but only when you are in the terminal. Outside of the terminal Crtl+C does nothing to stop it.
You should listen for keypresses on a separate thread because time.sleep()
blocks operations in the current thread.
To do this, create a thread with a global variable and adjust your while
loop condition based on that.
Here is a simple demo:
import keyboard
import time
import threading
stop = False # flag to stop the loop
# the function that listens to the key press
def key_listener():
global stop
keyboard.wait("a")
stop = True
# the thread that runs the above function
t = threading.Thread(target=key_listener)
t.start()
while not stop: # loop until the stop flag is set
print("op1")
time.sleep(2)
print("op2")
time.sleep(4)
print("done")
Another alternative to keyboard
with more compatibility (e.g., on macOS) is the pynput
library:
from pynput import keyboard
def key_listener():
def on_press(key):
global stop
if key.char == "a":
stop = True
return False
with keyboard.Listener(on_press=on_press) as listener:
listener.join()