Run multiple functions using turtle.screen.onkey() in Python - python

For the code:
from turtle import Screen, Turtle, bgcolor
# --- functions ---
def delSec(string):
if len(string) == 1:
return "0" + string
else:
return string
def tick():
global milisecs, ticking
turtle.clear()
if milisecs < 0:
turtle.write("TIMER DONE", align='center', font=FONT)
ticking = False
return
else:
turtle.write(delSec(str(milisecs//(60*60*10)))+":"+delSec(str((milisecs%(60*60*10))//(60*10)))+":"+delSec(str((milisecs%(60*10))//10))+":"+str(milisecs%10), align='center', font=FONT)
## turtle.write(delSec(str(milisecs//(60*60)))+":"+delSec(str((milisecs%(60*60))//(60)))+":"+delSec(str((milisecs%(60))//1)), align='center', font=FONT)
if not paused:
milisecs -= 1
screen.ontimer(tick, 100)
def reset():
global milisecs, ticking, key_reset, key_pause, key_both
#global paused
print("reset")
screen.onkey(None, key_reset) # Disable event handler inside handler
screen.onkey(None, key_pause) # Disable event handler inside handler
screen.onkey(None, key_both)
milisecs = sum(time*10)
if not ticking:
ticking = True
tick()
#paused = False
screen.onkey(reset, key_reset) # Reenable event handler
screen.onkey(pause, key_pause) # Reenable event handler
screen.onkey(reset, key_both)
screen.onkey(pause, key_both)
def pause():
global paused
print("pause/unpause")
paused = not paused
# --- main ---
bgcolor('dodgerblue')
FONT = ("Arial", 60, "normal")
strings = input("Please enter the time: ").strip().split(' ')
time = [60 ** (len(strings) - index - 1) * int(unit) for index, unit in enumerate(strings)]
milisecs = -1
ticking = False
paused = False
key_reset = "r"
key_pause = "p"
key_both = "b"
screen = Screen()
turtle = Turtle()
turtle.hideturtle()
turtle.color('white')
reset()
screen.listen()
screen.mainloop()
I want to both pause and reset the timer when I click the key "b". I tried doing this by running both functions reset and pause. However, as expected, it overwrites the first command of running the first function and only runs the second function when I click b (in this case, the function pause) I also tried the line screen.onkey(pause, reset, key_both), but that created an error.
Could someone help me figure out how to run multiple functions? I can't find any parameter of .onkey() where it doesn't disable previous key-based functions. Please help!

Frustratingly, what you're asking for is built-in to the mouse event functions:
onclick(fun, btn=1, add=None)
where the add argument is used to indicate whether fun overrides the current setting or simply adds another function to execute in this circumstance. However, this wasn't included on the various onkey*() event handlers.
Below is what I consider a generic way to go about this:
from turtle import Screen
KEY_RESET = 'r'
KEY_PAUSE = 'p'
KEY_BOTH = 'b'
def reset():
screen.onkey(None, KEY_RESET) # Disable event handlers inside handler
screen.onkey(None, KEY_BOTH)
print("reset")
screen.onkey(reset, KEY_RESET) # Reenable event handlers
screen.onkey(both, KEY_BOTH)
def pause():
screen.onkey(None, KEY_PAUSE) # Disable event handlers inside handler
screen.onkey(None, KEY_BOTH)
print("pause")
screen.onkey(pause, KEY_PAUSE) # Reenable event handlers
screen.onkey(both, KEY_BOTH)
def both():
reset()
pause()
screen = Screen()
reset()
screen.listen()
screen.mainloop()
such that I can run multiple functions when I click b with just one
line of code?
Yes, there's a hack we can do with lambda and a tuple but we shouldn't be thinking this way. "Just one line of code" should not be your goal at this point.

Related

Detect NO KEY PRESSES with keyboard module python

I'm making a tkinter program and want to detect a key press and do something when there is, however if no key is pressed the keyboard.read_key() will not execute and not let any other code run until a key is pressed.
Is there an alternative I can use or a way to check if no key was pressed?
def on_press():
global running
global turn
if keyboard.read_key() == 'q':
ws.destroy()
elif keyboard.read_key() == 'a':
if turn:
print('turning off')
running = False
turn = False
else:
print('turning on')
running = True
turn = True
start()
else:
start()
def start():
global running
global turn
print('start')
if running:
print('clicking')
mouse.click(Button.left, cps_val)
time.sleep(1)
on_press()
on_press()
Key is no pressed for the most of the time. Because computer is fast and human is slow so I would assumed that that key is no pressed at the beginnig and I would run code at once (without checking if it no pressed). And I would run "clicking code" all time in while at the beginning and I would use keys only to pause this loop (key a) or exit this loop (key q) - without checking if key is no pressed.
import keyboard
#import mouse
import time
# --- functions ---
def pause():
global turn
if turn:
print('turning off')
turn = False
else:
print('turning on')
turn = True
def finish():
global running
running = False
def main():
global counter
print('start')
while running:
if turn:
counter += 1
print('clicking', counter)
#mouse.click(Button.left, cps_val)
time.sleep(1)
# --- main ---
turn = True
running = True
counter = 0
keyboard.add_hotkey('q', finish)
keyboard.add_hotkey('a', pause)
main()
If it has to run with tkinter then while loop will block tkinter (tkinter will freeze because it will have no time to get key/mouse events from system, send them to widgets, update widgets, and redraw widgets in window).
I would use tkinter.after(milliseconds, function) instead of sleep() and while and tkinter will have time to work.
import tkinter as tk
import keyboard
#import mouse
import time
# --- functions ---
def pause():
global turn
if turn:
print('turning off')
turn = False
else:
print('turning on')
turn = True
def finish():
global running
running = False
root.destroy()
def main():
print('start')
loop() # run first time at once
#root.after(1000, loop) # run first time after 1s
def loop():
global counter
if turn:
counter += 1
#print('clicking', counter)
label['text'] = f'clicking {counter}'
#mouse.click(Button.left, cps_val)
if running:
root.after(1000, loop) # run again after 1s
# --- main ---
turn = True
running = True
counter = 0
keyboard.add_hotkey('q', finish)
keyboard.add_hotkey('a', pause)
root = tk.Tk()
label = tk.Label(root, text='clicking 0')
label.pack()
main()
root.mainloop()

Pygame: Adding an Escape key to exit whole game with stopwatch function

I'm making a game via Python on a Raspberry Pi. I'm using the GPIOs to light up an LED and detect a button switch.
I wanted to incorporate an ESC on the keyboard so we can exit at any time.
But whenever I add in the ESC key code into the main while loop. It doesn't work. The LED and Buttons work, but when I press on the ESC key, it doesn't do anything.
The loop runs to refresh/run a stopwatch and listen to an LED button via the GPIO.
I wanted some advice on how things like ESC key are handled in games. Especially with fast paced games where the loop and cycles are very fast.
Any tips or suggestions would be greatly appreciated. Thanks in advance.
Please see the code below:
# Importing all libraries
import RPi.GPIO as GPIO
import sys, time, atexit, pygame
# Setup GPIO and Pygame
GPIO.setmode(GPIO.BCM)
pygame.init()
# Define Tuples and Variables
leds = (16,17,22,9,5)
switches = (19,4,27,10,11)
button_pressed = False
taskcomplete = False
# Pygame visual variables
screen = pygame.display.set_mode( (1024,240) )
counterfont = pygame.font.Font('DSEG14Modern-Regular.ttf', 70)
# Set Pygame refresh rate variable = clock
clock = pygame.time.Clock()
# Clock variables
sec_val = 0
sec = 0
mins = 0
hours = 0
# Status variables
paused = False
running = True
# Start the clock
start_time = pygame.time.get_ticks()
# Defining Functions
# Function that renders segment display on screen
def time_convert(sec):
sec = sec % 60
sec_val = ("Timer: {0}".format(round((sec), 2)))
counting_text = counterfont.render(str(sec_val), 3, (134,145,255))
counting_rect = counting_text.get_rect(left = screen.get_rect().left)
screen.fill( (0,0,0) )
screen.blit(counting_text, (300,40))
pygame.display.update()
# Stopwatch function to compute for a SS:MS based stopwatch
def stop_Watch():
end_time = time.time()
time_lapsed = end_time - start_time
sec_val = time_convert(time_lapsed)
# Press Button 1 to start the game
def but_3():
while GPIO.input(switches[2]) == GPIO.LOW:
GPIO.output(leds[2],True)
time.sleep(0.01)
stop_Watch()
GPIO.output(leds[2],False)
print(" Button 3 is pressed! Exit")
start_time = time.time()
def buttonPress(channel):
# This function gets called every time a button is pressed, if the button pressed is the same as the button
# that is illuminated, then we set the "correct_button" variable to True,
# otherwise we set the "incorrect_button" variable to True.
# We need to set some variables to global so that this function can change their value.
button_pressed = True
def exit():
# This function gets called when we exit our script, using Ctrl+C
print("GPIO Clean Up!")
GPIO.cleanup()
pygame.quit()
# This tells our script to use the "exit()" without this, our "exit()" function would never be called.
atexit.register(exit)
#Loop through the leds to set them up
for led in leds:
# Set the led to be an ouput
GPIO.setup(led, GPIO.OUT)
# Turn the led off
GPIO.output(led,False)
# Loop through the switches to set them up
for switch in switches:
# Set the switch to be an input
GPIO.setup(switch, GPIO.IN)
# Add rising edge detection
GPIO.add_event_detect(switch, GPIO.RISING, bouncetime=300)
# Add the function "buttonPress" to be called when switch is pressed.
GPIO.add_event_callback(switch, buttonPress)
# Main sequence code
# Setup Pygame refresh rate to 120 fps
clock.tick(120)
# Start timer
start_time = time.time()
# Main loop
while running:
# Press Button 1 to start the game
while GPIO.input(switches[0]) == GPIO.LOW:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
print("escape pressed")
running = False
GPIO.output(leds[0],True)
time.sleep(0.01)
stop_Watch()
GPIO.output(leds[0],False)
print(" Button 1 is pressed! Exit")
running = False
exit()
It's because it's in another while loop I'm guessing, so it's not in the running while loop anymore. You can add pygame.quit() to make it quit that way though:
# Main loop
while running:
# Press Button 1 to start the game
while GPIO.input(switches[0]) == GPIO.LOW:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
print("escape pressed")
pygame.quit()
running = False
GPIO.output(leds[0],True)
time.sleep(0.01)
stop_Watch()
GPIO.output(leds[0],False)
print(" Button 1 is pressed! Exit")
running = False
exit()
Or, since you have a function named exit() that does the same thing, you can add exit() to those places instead.

Python Turtle `While True` in Event Driven Environment

I have read in several posts here on Stack Overflow that "An event-driven environment like turtle should never have while True: as it potentially blocks out events (e.g. keyboard)."
Here is a Python Turtle program that seems to work fine, but which uses the while True: construct.
Could someone please explain why this approach is wrong, what problems is creates and what the correct way is to achieve the same result?
import turtle
import time
def move_snake():
"""
This function updates the position of the snake's head according to its direction.
"""
if head.direction == "up":
head.sety(head.ycor() + 20)
def go_up():
"""
callback for up key.
"""
if head.direction != "down":
head.direction = "up"
# Set up screen
screen = turtle.Screen()
screen.tracer(0) # Disable animation so we can update screen manually.
# Event handlers
screen.listen()
screen.onkey(go_up, "Up")
# Snake head
head = turtle.Turtle()
head.shape("square")
head.penup()
head.direction = "stopped" # Cheeky use of instance property to avoid global variable.
while True:
move_snake()
screen.update()
time.sleep(0.2)
turtle.done()
I can provide a crude example. Run your code above as-is. Start the snake moving. Click on the window's close button. Count the number of lines of error messages you get in the console. It could easily exceed two dozen.
Now try this same experiment with the following code which eliminates the while True::
from turtle import Screen, Turtle
class Head(Turtle):
def __init__(self):
super().__init__(shape="square")
self.penup()
self.direction = "stopped"
def move_snake():
if head.direction == "up":
head.sety(head.ycor() + 20)
screen.update()
screen.ontimer(move_snake, 200)
def go_up():
if head.direction != "down":
head.direction = "up"
# Snake head
head = Head()
# Set up screen
screen = Screen()
screen.tracer(0) # Disable animation so we can update screen manually.
# Event handlers
screen.onkey(go_up, "Up")
screen.listen()
move_snake()
screen.mainloop()
Your count of error messages should drop to zero. This is because the window close event happens in the same event loop as turtle motion.
There are other effects that you'll end up chasing later. This is just a simple, easily visible one.

Python onkey not stopping loop

def startgame():
start.state = False
print start.state
def restart():
end.state = False
start.state = True
print game.state, end.state
s.listen()
s.onkey(startgame, "Return")
s.onkey(restart, 'r')
# This Loop stops when you hit Enter
while start.state:
start.enter()
s.reset()
# I tried repeating it here but it doesn't work
while end.state:
end.enter()
s.reset()
game.state = 'playing'
Both loops are nested in a main while loop but the second one is nested in another while loop (If that helps) so it would look something like this
while True:
while start.state:
start.flash()
s.reset()
while True:
# main game code here
while end.state:
end.flash()
s.reset()
game.state = 'playing'
break
I simply want it to show the end screen and have 'Press r to play again' flash on screen until the player hits r and then it should restart the game and go back to the start screen. The end.state variable wont update during the while loop, but start.state does during its while loop.
Your drawn out (in time) while loops have no place in an event-driven environment like turtle. In general, we need to control everything with events, specifically timer events. Let's rewrite your code as a state machine. (Since I don't have your object classes, entities like start.state become globals like start_state in my example code.)
from turtle import Screen, Turtle
start_state = True
end_state = False
def start_game():
global start_state
start_state = False
def end_game():
global end_state
if not start_state:
end_state = True
def restart_game():
global end_state, start_state
end_state = False
start_state = True
def flash(text):
turtle.clear()
turtle.write(text, align="center", font=('Arial', 24, 'bold'))
color = turtle.pencolor()
turtle.pencolor(turtle.fillcolor())
turtle.fillcolor(color)
def game_states():
if start_state:
flash("Start")
elif end_state:
flash("End")
else:
flash("Playing")
screen.ontimer(game_states, 500)
screen = Screen()
turtle = Turtle()
turtle.hideturtle()
turtle.fillcolor(screen.bgcolor())
screen.onkey(start_game, 'Return')
screen.onkey(restart_game, 'r')
screen.onkey(end_game, 'e')
screen.listen()
game_states()
screen.mainloop()
The state machine rules are:
Start via <Return> -> Playing
Playing via <e> -> End and via <r> -> Start
End via <r> -> Start

pygame.key.set_repeat for Joystick

I'm building a menu using pygame and I want to make it navigable using a specific gamepad. Ideally I want to be able to press and hold *down" on the D-pad repeatedly, or get something like on a keyboard where the first button press has a delay before repeatedly entering the same character (seemingly).
I'm trying to emulate the pygame.key.set_repeat(...) function for a Joystick. my approach so far has been
pygame.time.set_timer(pygame.USEREVENT, 10)
DELAY_TIME = 0.250 #ms
y_delay = True
while not done:
for event in pygame.event.get():
y_axis = gamepad.get_axis(1)
if y_axis > 0.5: # pushing down
main_menu.move_down()
redraw() #redraw everything on the surface before sleeping
if y_delay:
time.sleep(DELAY_TIME)
y_delay = False #don't delay the next time the y axis is used
elif y_axis < -0.5: #pushing up
# repetitive I know, but I'm still working on it
main_menu.move_up()
redraw()
if y_delay:
time.sleep(DELAY_TIME)
y_delay = False
else:
y_delay = True # delay the next time
my issue is if someone taps up or down faster than DELAY_TIME they are limited to the DELAY_TIME before they can move again. Also if someone releases and depresses the up/down button within the time.sleep interval, python never sees that it was released at all and doesn't allow for a delay.
Maybe there's a way to do this using events or mapping the joystick to keys somehow? qjoypad doesn't cut it for me, and joy2keys is trash. I would need to do the mapping within the python program.
Sleep causes the program to halt execution, so it's not a viable option. You can also do this without using set_timer and events. I did it using a couple of flags and pygame.time's get_ticks.
import pygame
from pygame.locals import *
def main():
pygame.init()
pygame.display.set_mode((480, 360))
gamepad = pygame.joystick.Joystick(0)
gamepad.init()
delay = 1000
neutral = True
pressed = 0
last_update = pygame.time.get_ticks()
while True:
for event in pygame.event.get():
if event.type == QUIT:
return
move = False
if gamepad.get_axis(1) == 0:
neutral = True
pressed = 0
else:
if neutral:
move = True
neutral = False
else:
pressed += pygame.time.get_ticks() - last_update
if pressed > delay:
move = True
pressed -= delay
if move:
print "move"
last_update = pygame.time.get_ticks()
if __name__ == "__main__":
main()
pygame.quit()
When get_axis indicates no motion, the neutral flag is set, and the pressed timer is reset, causing the move flag to remain unset. When the neutral flag is unset, if it's newly set, the move flag is set. If it's not newly set, the pressed timer increases, and move is set only if the pressed timer is greater than delay.

Categories