Mixing events in a python turtle program - python

I am trying to write a python turtle program that behaves similarly to a regular event-driven program that uses a game loop. The program tries to mix mouse, keyboard and timer events as is as posted below.
My problem is that python doesn't seem to be able to mix the onkey() events with the ontimer() loop. When run, the program will animate the turtle and the onclick() event will work. The key press isn't even registered until the mouse is first clicked. Then, when the key is pressed to quit, I get a large list of errors in the shell. The bye() method seems to be terminating the program in a brutish fashion and not shutting down elegantly.
I think that I have the commands in the correct order.
Any suggestions will be appreciated!
import turtle
playGround = turtle.Screen()
playGround.screensize(800, 600, 'light blue')
bob = turtle.Turtle()
bob.color('red')
bob.pencolor('red')
bob.ht()
def teleport(x,y):
bob.goto(x,y)
def quitThis():
playGround.bye()
def moveAround():
bob.fd(10)
bob.rt(15)
playGround.ontimer(moveAround,30)
playGround.onclick(teleport,btn=1)
playGround.onkey(quitThis,'q')
moveAround()
playGround.listen()
playGround.mainloop()

One problem I see with your code is you need to keep the moveAround() from happening during the teleport() otherwise you get confusing visuals. I find with turtle that it helps to disable the event handler when inside the event hander and reenable it on the way out.
I believe the following will smooth out your events and allow them all to fire at the appropriate time. I've added a state variable to help control the activity:
from turtle import Turtle, Screen
def teleport(x, y):
global state
playGround.onclick(None) # disable handler inside handler
if state == "running":
state = "teleporting"
bob.goto(x, y)
state = "running"
if state != "quitting":
playGround.onclick(teleport)
def quitThis():
global state
state == "quitting"
playGround.onkey(None, 'q')
playGround.bye()
def moveAround():
if state == "running":
bob.fd(10)
bob.rt(15)
if state != "quitting":
playGround.ontimer(moveAround, 30)
playGround = Screen()
playGround.screensize(800, 600, 'light blue')
bob = Turtle(visible=False)
bob.color('red')
playGround.onclick(teleport)
playGround.onkey(quitThis, 'q')
playGround.listen()
state = "running"
playGround.ontimer(moveAround, 100)
playGround.mainloop()
when the key is pressed to quit, I get a large list of errors in the
shell. The bye() method seems to be terminating the program in a
brutish fashion
This is typical of turtle. If it really bothers you, see my answer to the question about Turtle window exit errors for one possible solution.

Related

How do event listeners work in Python's Turtle Module? And why are they non-blocking even though they are constantly listening in the background?

In this piece of code, I've created a program using the Turtle Module for Python, which I heard was built with Tkinter. This working code will make the turtle say "Ah!" when you click on it, then teleport. What perplexes me the most is the t.onclick() method. How does it manage to keep listening for clicks on the turtle while allowing the rest of the code to run, unlike the input() function, which waits for the user to enter an input before proceeding?
Additionally, how does an event handler work in Python? Does it constantly check for clicks in the background with some sort of forever loop? Is there a mechanism that allows it to stay idle and somehow activate when a click is received? Or is there something else entirely that makes it work the way it does?
from turtle import *
from time import sleep
from random import randint
t = Turtle()
t.color("red")
t.penup()
t.shape("turtle")
t.speed(100)
t.points = 0
w = 200
h = 150
def rand_move():
t.goto(randint(-w, w), randint(-h, h))
def catch(x, y):
t.write("Ah!", font=("Arial", 14, "normal"))
t.points = t.points + 1
rand_move()
t.onclick(catch)
while t.points < 3:
sleep(1.5)
rand_move()
t.write("WOW! You're good at catching me!", font=('Arial', 16, 'bold'))
t.hideturtle()
I have tried looking up the Turtle source code but failed to decipher it, and even googled quite a number of sites, but none seem to be specific enough to this question. Any help in clearing this up will be greatly appreciated!
The input() function is a Python built-in and not part of tkinter. The latter has its own mainloop() which normally handles all user input — unless its processing is interfered with by some function (such as input() or any long-running function). When that happens the GUI will "freeze".
I believe part of what makes it hard to understand how turtle events work is this code which doesn't belong in a event-driven turtle program:
while t.points < 3:
sleep(1.5)
rand_move()
Instead, we should be handing off control to mainloop(), either explicitly, or, in some programing environments, implicitly. Let's rewrite your code as an event-driven turtle program:
from turtle import Screen, Turtle
from random import randint
WIDTH, HEIGHT = 640, 480
CURSOR_SIZE = 20
def rand_move():
turtle.goto(randint(CURSOR_SIZE - WIDTH//2, WIDTH//2 - CURSOR_SIZE), randint(CURSOR_SIZE - HEIGHT//2, HEIGHT//2 - CURSOR_SIZE))
if turtle.points < 3:
screen.ontimer(rand_move, 1000) # delay in milliseconds
else:
turtle.home()
turtle.write("WOW! You're good at catching me!", align='center', font=('Arial', 16, 'bold'))
turtle.hideturtle()
def catch(x, y):
turtle.write("Ah!", font=('Arial', 14, 'normal'))
turtle.points += 1
screen = Screen()
screen.setup(WIDTH, HEIGHT)
turtle = Turtle()
turtle.shape('turtle')
turtle.color('red')
turtle.speed('fastest')
turtle.penup()
turtle.onclick(catch)
turtle.points = 0 # user defined property
rand_move()
screen.mainloop()
how does an event handler work in Python? Does it constantly check for
clicks in the background with some sort of forever loop? Is there a
mechanism that allows it to stay idle and somehow activate when a
click is received?
This desciption isn't far off. Turtle events fire from mainloop() which waits around for events to fire, such as mouse clicks, keyboard input, window closing, timer events, etc. See this answer for more information about mainloop() does in a turtle program.
The onclick() method only registers code for the main loop to call if/when a turtle-specific click comes in. Just as ontimer registers code for the main loop to call some time in the future. When we mix while ...: sleep() loops with turtle's main loop, we risk missing events during the sleep cycle.

Trying to create a mouse recorder, but it keeps looping endlessly?

I'm trying my hand at Pynput, and I'm starting off with creating a simple program to record the movements of a mouse, and then replay those movements once a button is clicked.
However, every time I click the mouse, it just starts to freak out and endlessly loop. I think it's going through the movements at a super high speed, but I eventually have to Alt-F4 the shell to stop it.
Any help would be appreciated.
import pynput
arr = []
from pynput import mouse
mou = pynput.mouse.Controller()
def on_move(x,y):
Pos = mou.position
arr.append(Pos)
def on_click(x, y, button, pressed):
listener.stop()
for i in arr:
mou.position = i
print("Done")
listener = mouse.Listener(on_move = on_move, on_click=on_click)
listener.start()
You have to be careful when using multiple threads (which is the case here, since mouse.Listener runs in its own thread). Apparently, as long as you are in the callback function, all events are still processed, even after you have called listener.stop(). So when replaying, for each mouse position you set, the on_move callback function is called, so that mouse position is added to your list again, which causes the endless loop.
In general, it's bad practice to implement too much functionality (in this case the "replaying") in a callback function. A better solution would be to use an event to signal another thread that the mouse button has been clicked. See the following example code. A few remarks:
I've added a few print statements to see what's happening.
I've added a small delay between the mouse positions to really see the playback. (NB: This also might make breaking out of the application a bit easier in case it hangs!)
I've changed a few variable names to make more sense. Calling an array "arr" is not a good idea. Try to use names that really describe the variable. In this case it is a list of positions, so I choose to call it positions.
I'm using return False to stop the mouse controller. The documentation states "Call pynput.mouse.Listener.stop from anywhere, raise StopException or return False from a callback to stop the listener.", but personally, I think returning False is the cleanest and safest solution.
import threading
import time
import pynput
positions = []
clicked = threading.Event()
controller = pynput.mouse.Controller()
def on_move(x, y):
print(f'on_move({x}, {y})')
positions.append((x, y))
def on_click(x, y, button, pressed):
print(f'on_move({x}, {y}, {button}, {pressed})')
# Tell the main thread that the mouse is clicked
clicked.set()
return False
listener = pynput.mouse.Listener(on_move=on_move, on_click=on_click)
listener.start()
try:
listener.wait()
# Wait for the signal from the listener thread
clicked.wait()
finally:
listener.stop()
print('*REPLAYING*')
for position in positions:
controller.position = position
time.sleep(0.01)
Note that when you run this in a Windows command prompt, the application might hang because you have pressed the mouse button and are then starting to send mouse positions. This causes a "drag" movement, which pauses the terminal. If this happens, you can just press Escape and the program will continue to run.
You got yourself an infinite loop. I think the listener you referred to in the on_click method might be null or undefined. Also according to some documentation I found you need to return false for the on_click method to stop listening
This is what I was looking at:
https://pythonhosted.org/pynput/mouse.html

Problem with using keyboard module and turtle together

I am writing a script to read my keystrokes and draw simple shapes in turtle.
To record keystrokes, I am using the keyboard module and I am using turtle for drawing.
I am getting struck due to the use of threading in the keyboard module.
What I am currently doing is-
I added hotkeys using keyboard.add_hotkey method.
If I am pressing a certain key, that letter is added to a list (named data) for later use.
When I press the combination of Ctrl+Shift+S, the save function is called. In the save function, a turtle window is instantiated, and the list data is popped one letter at a time. The shape is drawn according to the letter popped.
When the list gets empty, I save the drawing and close the turtle window.
The problem that I am facing is that once the save function is called, the program stops listening to other calls. It is perhaps due to the use of threads in keyboard module.
The code is attached here-
def start():
#turtle.mainloop()
s=turtle.Screen().setup( width = WIDTH, height = HEIGHT, startx = 0, starty = 0)
global t
t=turtle.Turtle()
turtle.ht()
t.ht()
def save():
start()
global t
global data
t.speed(0)
while data:
fun = data.pop()
if fun=='c':
draw_circle()
elif fun=='r':
draw_rectangle()
elif fun=='p':
draw_polygon()
elif fun=='h':
draw_hexagon()
elif fun=='t':
draw_triangle()
elif fun=='m':
draw_pentagon()
ts = turtle.getscreen()
ts.getcanvas().postscript(file="drawing.eps")
img = Image.open('drawing.eps')
img.save('drawing.png')
turtle.bye()
def push_fun(fun):
data.append(fun)
if __name__=='__main__':
keyboard.add_hotkey('ctrl+shift+s', save)
keyboard.add_hotkey('ctrl+shift+e', exit)
keyboard.add_hotkey('ctrl+shift+p', send_to_server)
# keyboard.add_hotkey('ctrl+shift+s', save, args=(data))
keyboard.add_hotkey('c', push_fun, args=('c',))
keyboard.add_hotkey('s', push_fun, args=('t',))
keyboard.add_hotkey('h', push_fun, args=('h',))
keyboard.add_hotkey('p', push_fun, args=('p',))
keyboard.add_hotkey('r', push_fun, args=('r',))
keyboard.wait()
After the save function is called, the program remains in the keyboard.wait() part but does not listen to any other key press.
you can end the wait by setting a key to press like:
keyboard.wait('space')
but keyboard.wait() without any keys given blocks all keystrokes forever
I'm not sure why you even put it there, I'm not even sure why it picks anything up at all, but I think you can do this:
if __name__=='__main__':
'''your keystrokes'''
while True: #or set a timer or something
pass
you didn't give me the whole code so I can't make sure it works
you also need to global the list data in the push_fun function
I hope it helps :)

How to move from one screen to another, with any input from the user, in pygame? [duplicate]

I'm making a little game and I want to make another window separately from my main one.
I have the the main game in a main window, and I want to open a new window and do a little animation when the user does something.
In my example code below, when the user presses "a" I want it to open a new window and blit to there.
Here I set up the two windows: (I know this doesnt work, its what I'm asking how to do)
SCREEN_X = 400
SCREEN_Y = 400
BSCREEN_X = 240
BSCREEN_Y = 160
BATTLE_SCENE = pygame.display.set_mode((BSCREEN_X, BSCREEN_Y))
SCREEN = pygame.display.set_mode((SCREEN_X, SCREEN_Y))
and then the program:
def run_ani ():
#Do animation, blitting to BATTLE_SCENE
return
def main_game():
ending=False
while ending==False:
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT: ending=True
if event.type == KEYDOWN: # key down or up?
if event.key == K_ESCAPE:
ending=True # Time to leave
print("Stopped Early by user")
elif event.key == K_a:
run_ani()
#Normal screen motion, blitting to SCREEN
if ending: pygame.quit()
return
So far what this does is draws the main screen, then when A is pressed, it stops drawing the main screen animations, but still draws the other animations on the main screen and draws in the top left corner.
I'm pretty sure it does this because I am setting BATTLE_SCENE to be smaller than the main screen, thus when blitting to BATTLE_SCENE it blits to the area I created (240x160) in the top corner of the main screen.
However I want BATTLE_SCENE to be a seperate window, so that when I press 'a' it will pop up, do its thing, then close or at least go behind the main screen.
How to do this? Is it even possible?
Do you really need multiple windows? I mean, do you really need them?
If yes, then you should probably use pyglet/cocos2d instead.
To have multiple windows in pygame, you need multiple processes (one for each window). While this is doable, it's not worth the efford. You'll need IPC to exchange data between the windows, and I guess your code will become error-prone and ugly.
Go with pyglet when you need more than one window.
The better solution is probably to divide your game into scenes. Create multiple scenes so that each one represent one stage of the game, something like MenuScene, MainScene, BattleScene, GameOverScene, OptionScene etc.
Then let each of those scenes handle input/drawing of that very part of the game.
MenuScene handles drawing and input etc. of the game's menu
MainScene handles drawing and input etc. of the running game
BattleScene handles drawing and input etc. of whatever you do in run_ani
In your mainloop, just pass control over to the current scene by implementing the methods draw(), handle_event(), and update().
Some example code to get the idea:
scenes = {'Main': MainScene(),
'Battle': BattleScene()} #etc
scene = scenes['Main']
class MainScene():
...
def handle_event(self, event):
if event.type == KEYUP:
if event.key == K_a:
scene = scenes['Battle']
...
class BattleScene():
...
def draw(self):
# draw your animation
def update(self):
# if animation is over:
scene = scenes['Main']
...
def main_game():
ending=False
While Not ending:
clock.tick(30)
for event in pygame.event.get():
scene.handle_event(event)
scene.update()
scene.draw()
This is an easy way to cleanly seperate the game logic and allow context switching.
======================================Edit=========================================
Actually it won't work. Apperantly pygame only supports one display screen, and when you initialize another, it will close the first. You will stay with two varibles, which in fact are the same surface. You can have instead the game increasing the window size and playing the battle scene on the side of it, to do this, you can call the pygame.display.set_mode() again with different values. The varible which references the display screen will still be usable, as it change its reference to the new one. After the scene is over you can decrease the window back the same way.
==================================================================================
What basically happens is you run a loop, and each iteration of it is rendering and displaying a new frame.
When you call a function inside a loop, it doesn't continue to run until you finish running the function.
One way to solve this problen is just keep calling the function that updates the battle scene in the main loop.
Another way is by using threading. Threading is basically running multiple scripts ("Threads") in the same time.
Luckily, python already implemented this for us with the threading module.
It's too long for me to explain the module here, but you can learn it here. It might be a little complex if you haven't use threads before, but after some time it will be easier.
And If you want to learn more about threading you can go here.
Specificly here, you can have two threads, one for each loop/window, and run them in the same time.
Hope I helped you!
Yes, that is possible. SDL2 is able to open multiple windows. In the example folder you can take a look at "video.py".
https://github.com/pygame/pygame/blob/main/examples/video.py
"This example requires pygame 2 and SDL2. _sdl2 is experimental and will change."

How do I exit a while loop in python with an event?

I'm making this project for school that involves displaying data to a raspberry pi. The code I'm using refreshes (and needs to refresh) incredibly quickly, but I need a way for the user to stop the output, which I believe requires some kind of key event. The thing is, I'm new to Python and I can't figure out how to exit a while loop with turtle.onkey(). I found this code:
import turtle
def quit():
global more
more = False
turtle.onkey(quit, "Up")
turtle.listen()
more = True
while more:
print("something")
Which doesn't work. I've tested it. How do I make this work, or is there another way to get user input without interrupting the flow of the program?
while loop run on thread
check this code
import threading
def something():
while more:
print("something")
th = threading.Thread(something)
th.start()
Avoid infinite loops in a Python turtle graphics program:
more = True
while more:
print("something")
You can effectively block events from firing, including the one intended to stop the loop. Instead, use timer events to run your code and allow other events to fire:
from turtle import Screen
more = True
counter = 0
def stop():
global more
more = False
def start():
global more
more = True
screen.ontimer(do_something, 100)
def do_something():
global counter
print("something", counter)
counter += 1
if more:
screen.ontimer(do_something, 100)
screen = Screen()
screen.onkey(stop, "Up")
screen.onkey(start, "Down")
screen.listen()
start()
screen.mainloop()
I've added a counter to your program just so you can more easily see when the 'something' statements stop and I've added a restart on the down key so you can start them up again. Control should always reach mainloop() (or done() or exitonclick()) to give all the event handlers a chance to execute. Some infinite loops allow events to fire but they typically have calls into the turtle methods that allow it to have control some of the time but are still the wrong approach.
Chances are that you are trying to run your code in an interactive IPython shell. That does not work. The bare Python repl shell works, though.
Here I found a project that tries to bring turtle to IPython: https://github.com/Andrewkind/Turtle-Ipython. I did not test it, and I'm not exactly sure that this is a better solution than simply using the unsugared shell.
You could have you loop check a file like this:
def check_for_value_in_file():
with open('file.txt') as f:
value = f.read()
return value
while check_for_value_in_file() == 'the right value':
do_stuff()

Categories