Я пытаюсь создать своего рода текстовую игру на Python 3. Для игры мне нужно будет слушать ввод с клавиатуры, в частности, измеряя, как долго удерживается клавиша при печати на экране. Я пытаюсь начать с создания рабочего минимального примера.
Во-первых, следующий код с использованием pynput
успешно измеряет время, в течение которого пользователь удерживает клавишу:
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()
Теперь то, что я хотел бы сделать, это только тогда, когда пользователь нажимает клавишу, печатать текстовую «анимацию» на экране. В следующем примере моя «анимация» просто печатает "*"
каждые полсекунды.
До сих пор я пытался обрабатывать «анимацию» вторым потоком, но я новичок, когда дело доходит до многопоточности. Следующий код запустит анимацию в нужное время, но не остановит ее.
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()
Вот подход (после комментария @furas), в котором анимация кодируется после оператора with
, однако я не могу заставить это работать на меня:
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()
В конечном счете, я хочу иметь возможность делать это с более сложной анимацией. Например
def mysquare(delay):
print("@"*10)
time.sleep(delay)
for i in range(8):
print("@" + " "*8 + "@")
time.sleep(delay)
print("@"*10)
Как правильно подойти к этому? Большое спасибо!
@furas спасибо за совет, я пытался сделать то, что вы предложили, теперь показано в последнем редактировании вопроса, но, похоже, я не могу заставить его работать
Listener
уже использует thread
, поэтому нет необходимости запускать анимацию в отдельном потоке. Вы можете запустить его в текущем протекторе в
with keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True) as listener:
#... your code ...
listener.join()
или без with ... as ...
listener = keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True)
listener.start()
#... your code ...
#listener.wait()
listener.join()
Вы можете запускать там даже длинный исполняемый код - т.е. бесконечный цикл while
, который проверяет, является ли переменная animate
True
, и записывает новую *
.
Мне пришлось добавить sys.stdout.flush()
в Linux, чтобы увидеть *
на экране.
Моя версия:
Он запускает анимацию все время, когда вы нажимаете любую кнопку, но также есть код с переменной counter
, чтобы ограничить анимацию 6 ходами. Если вы нажмете новую клавишу, когда она запускает анимацию, она сбрасывает этот счетчик, и анимация будет длиннее.
Этот цикл должен выполняться все время, чтобы проверить, есть ли новая анимация — вы не можете закончить этот цикл, когда анимация завершена.
from pynput import keyboard
import sys
import time
# --- functions ---
def on_press(key):
global keys_currently_pressed
global animate
#global counter
# Record the key and the time it was pressed only if we don't already have it
if key not in keys_currently_pressed and key != keyboard.Key.esc:
keys_currently_pressed[key] = time.time()
animate = True
#counter = 0 # reset counter on new key
def on_release(key):
global keys_currently_pressed
global animate
if key in keys_currently_pressed:
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 not keys_currently_pressed:
animate = False
if key == keyboard.Key.esc:
# Stop the listener
return False
# --- main ---
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 = {}
animate = False # default value at start (to use in `while` loop)
#limit = 6 # limit animation to 6 moves
#counter = 0 # count animation moves
with keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True) as listener:
while listener.is_alive(): # infinite loop which runs all time
if animate:
#sys.stdout.write("\b *") # animation with removing previous `*`
sys.stdout.write("*") # normal animation
sys.stdout.flush() # send buffer on screen
#counter += 1
#if counter >= limit:
# counter = 0
# animate = False
time.sleep(0.5)
listener.join()
Спасибо, это очень полезно, и это работает для меня. Но предположим, что анимация более сложна, чем просто печать одного и того же символа снова и снова — например, предположим, что она рисует какую-то форму с помощью ascii-арта. Я отредактировал вопрос, чтобы иметь пример чего-то подобного. Можно ли этот подход адаптировать для этого?
если вам нужна более сложная анимация, вам понадобится более сложный код внутри while loop
. И снова эта анимация должна отрисовывать только один кадр в каждом цикле - не запускайте внутренние циклы.
если вы хотите рисовать mysquare
со сном, то это неправильная идея - у вас должен быть только один цикл - ` while listener.is_alive()` - и он должен контролировать время и рисовать новые элементы.
Хорошо, спасибо за совет. Я думаю, что могу отслеживать состояние анимации с помощью своего рода счетчика, который сбрасывается при запуске новой анимации. Я приму это как ответ.
listener
уже работает в отдельном потоке, поэтому нет необходимости использовать другой поток для анимации. Вы можете запустить свой код в текущем потоке междуwith ... as listener
иlistener.join()
- и это может быть длительный цикл, который проверяет переменные и обновляет экран. Он будет работать аналогично PyGame, который также запускает циклы, которые все время проверяют, есть ли что перемещать, и перерисовывают элементы.