I'm trying to make a kind of text-based game in Python 3. For the game I will need to listen for keyboard input, in particular measuring how long a key is held down, while printing things to the screen. I'm trying to start by making a working minimal example.
First, the following code, using pynput, appears to successfully measures the length time for which the user holds down a key:
from pynput import keyboard
import time
print("Press and hold any key to measure duration of keypress. Esc ends program")
# A dictionary of keys pressed down right now and the time each was pressed down at
keys_currently_pressed = {}
def on_press(key):
global keys_currently_pressed
# Record the key and the time it was pressed only if we don't already have it
if key not in keys_currently_pressed:
keys_currently_pressed[key] = time.time()
def on_release(key):
global keys_currently_pressed
if key in keys_currently_pressed:
animate = False
duration = time.time() - keys_currently_pressed[key]
print("The key",key," was pressed for",str(duration)[0:5],"seconds")
del keys_currently_pressed[key]
if key == keyboard.Key.esc:
# Stop the listener
return False
with keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True) as listener:
listener.join()
Now what I'd like to do is, only while a key is pressed down by the user, print a text-based "animation" to the screen. In the following example my "animation" is simply printing "*" every half second.
So far I've tried to have the "animation" handled by a second thread but I am a total novice when it comes to multithreading. The following code will start the animation at the correct time but won't stop it.
from pynput import keyboard
import sys
import time
import threading
print("Press and hold any key to measure duration of keypress. Esc ends program")
# A dictionary of keys pressed down right now and the time each was pressed down at
keys_currently_pressed = {}
def my_animation():
# A simple "animation" that prints a new "*" every half second
limit = 60 # just in case, don't do more than this many iterations
j = 0
while j<limit:
j += 1
sys.stdout.write("*")
time.sleep(0.5)
anim = threading.Thread(target=my_animation)
def on_press(key):
global keys_currently_pressed
# Record the key and the time it was pressed only if we don't already have it
if key not in keys_currently_pressed:
keys_currently_pressed[key] = time.time()
anim.start()
def on_release(key):
global keys_currently_pressed
if key in keys_currently_pressed:
animate = False
duration = time.time() - keys_currently_pressed[key]
print("The key",key," was pressed for",str(duration)[0:5],"seconds")
del keys_currently_pressed[key]
if key == keyboard.Key.esc:
# Stop the listener
return False
with keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True) as listener: listener.join()
Here's an approach (following @furas's comment) where the animation is coded after the with statement, however I cannot get this to work for me:
from pynput import keyboard
import time
print("Press and hold any key to measure duration of keypress. Esc ends program")
# A dictionary of keys pressed down right now and the time each was pressed down at
keys_currently_pressed = {}
# animation flag
anim_allowed = False
def on_press(key):
global keys_currently_pressed
global anim_allowed
# Record the key and the time it was pressed only if we don't already have it
if key not in keys_currently_pressed:
keys_currently_pressed[key] = time.time()
anim_allowed = True
def on_release(key):
global keys_currently_pressed
global anim_allowed
if key in keys_currently_pressed:
animate = False
duration = time.time() - keys_currently_pressed[key]
print("The key",key," was pressed for",str(duration)[0:5],"seconds")
del keys_currently_pressed[key]
anim_allowed = False
if key == keyboard.Key.esc:
# Stop the listener
return False
with keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True) as listener:
while anim_allowed:
sys.stdout.write("*")
time.sleep(0.5)
listener.join()
Ultimately I want to be able to do this with more complex animations. For example
def mysquare(delay):
print("@"*10)
time.sleep(delay)
for i in range(8):
print("@" + " "*8 + "@")
time.sleep(delay)
print("@"*10)
What's the right way to approach this? Many thanks!
listeneralready runs in separated thread so there is no need to use another thread for animation. You can run your code in current thread betweenwith ... as listenerandlistener.join()- and it can be long running loop which check variables and update screen. It will works similar to PyGame which also runs loops which all time check there is something to move and redraws elements