pythonwhile-looppyautogui

How to break while loop while outside of the terminal


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.


Solution

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