turtle graphics: How do I implement a pause function? - python

I'm trying to use python 3 turtle graphics to do something like presentation software: draw something, pause for a keystroke so the presenter can explain, then draw the next thing.
Here is one solution I've tried (that doesn't work):
import turtle
import time
paused = False
def unpause():
print("unpause() called")
global paused
paused = False
def pause():
global paused
paused = True
while paused:
time.sleep(0.1)
t = turtle.Turtle()
# set up listener
t.screen.listen()
t.screen.onkeypress(unpause)
# draw something
t.hideturtle()
t.pensize(5)
t.speed(1)
t.pu()
t.goto(-300,-300)
t.pd()
t.goto(-300, 300)
# pause until key is pressed
pause()
# draw some more
t.pu()
t.goto(300,-300)
t.pd()
t.goto(300, 300)
t.screen.mainloop()
The problem is that the sleep call loop totally blocks the keypress from being detected, even when I use a while loop of very short (100ms) sleeps.
If I hit a key while the first line is drawing, I see "unpause() called" in my console, so I know that the key binding is active.
Why doesn't the keypress get detected? I don't know about the internals, but I thought that the keystroke would be recorded in a buffer somewhere, and during the break between sleep calls, the listener would read the buffer and unset the paused global variable. This is not happening.
Is there some other way I could implement this?
This is on a Debian Linux system.

Turtle graphics is based on tkinter, which is an event-driven GUI framework, so you can't do things like you would in a regular procedurally-driven program — see #Bryan Oakley's answer to the question Tkinter — executing functions over time for a more detailed explanation (although the turtle module hides most of these details). Anyway, this fact means you shouldn't call time.sleep() in an tight loop like that because everything has to happen without interfering with running of the mainloop().
The "trick" to avoid calling time.sleep() is to schedule periodic checks of the global variable using the turtle.ontimer() function — so your program will work if the first part of it is changed as shown below:
import turtle
paused = False
def unpause():
print("unpause() called")
global paused
paused = False
def pause():
global paused
paused = True
pausing() # Start watching for global to be changed.
def pausing():
if paused:
turtle.ontimer(pausing, 250) # Check again after delay.
# else quit checking.
t = turtle.Turtle()
# set up listener
t.screen.onkeypress(unpause) # Reversed order of
t.screen.listen() # these two statements.
# draw something
t.hideturtle()
t.pensize(5)
t.speed(1)
t.pu()
t.goto(-300,-300)
t.pd()
t.goto(-300, 300)
# pause until key is pressed
pause()
# draw some more
t.pu()
t.goto(300,-300)
t.pd()
t.goto(300, 300)
t.screen.mainloop()

Taking the ideas your suggestions have given me (thanks martineau and kederrac!) I was able to come up with a solution. It involves wrapping each of my drawing tasks in a function, then using a dispatch function that either waits for a keypress with an ontimer loop, or calls the next drawing function.
This proof-of-concept code uses entirely too many globals, but it shows the technique:
import turtle
t = turtle.Turtle()
paused = False
current_task = 0
def unpause():
global paused
paused = False
def turtle_setup():
global t
t.screen.onkeypress(unpause)
t.screen.listen()
t.hideturtle()
t.pensize(5)
t.speed(1)
def draw_task_finished():
global paused, current_task, drawing_tasks
current_task += 1
paused = True
if current_task < len(drawing_tasks):
draw_task_after_keypress()
def draw_task_after_keypress():
global paused, current_task
if paused:
turtle.ontimer(draw_task_after_keypress, 100)
else:
drawing_tasks[current_task]()
def draw_thing_one():
global t
t.pu()
t.goto(-300,-300)
t.pd()
t.goto(-300, 300)
draw_task_finished()
def draw_thing_two():
global t
t.pu()
t.goto(300,-300)
t.pd()
t.goto(300, 300)
draw_task_finished()
drawing_tasks = [draw_thing_one, draw_thing_two]
turtle_setup()
drawing_tasks[0]()
t.screen.mainloop()

You Can Use turtle.done()function.
Just make a input() function, and if input is entered, the program will run.
I tried this with basic approach.

Related

why does pycharm fail to execute screen.listen()

I am trying to create a turtle crossing game but every time I run the program neither the screen.listen() gets executed nor the screen.exitonclick()
After running the program on clicking on the turtle window it does not close neither the turtle moves forward
import turtle
from turtle import Screen
from player import Player
import time
screen = Screen()
screen.setup(width=600, height=600)
screen.tracer(0)
player = Player()
screen.listen()
screen.onkey(player.go_up(), "Up")
turtle.TurtleScreen._RUNNING = True
game_is_on = True
while game_is_on:
time.sleep(0.1)
screen.update()
screen.exitonclick()
Although I tried adding the ._RUNNING method, yet it does not make any difference
There are a few issues here:
while game_is_on: is an infinite loop since game_is_on is never changed from True to False. Anything after the loop will never run. Avoid using this pattern; the typical way to make a real-time rendering loop in turtle is ontimer.
turtle.TurtleScreen._RUNNING = True messes with an undocumented internal property. Unless you have an absolute need to, you shouldn't touch internal properties in libraries because you may be corrupting the instance's state and the property can disappear after an update. I'm not sure what you're trying to do here, but either figure out a way using the public API or drop this code entirely if it's not really needed (I don't think it is--I've never used it in a turtle program).
Although the code for Player wasn't posted, screen.onkey(player.go_up(), "Up") is likely incorrect. It invokes the go_up() method immediately and sets its return value, probably None, as the onkey handler. You probably meant screen.onkey(player.go_up, "Up") which doesn't call the method immediately, but instead passes it to the handler so it can be called later on by the turtle library, when the key is pressed.
With a little stub for Player, I'd suggest a setup like:
import turtle
class Player:
def __init__(self):
self.turtle = turtle.Turtle()
def go_up(self):
self.turtle.setheading(90)
self.turtle.forward(10)
def tick():
#### the main loop; move/update entities here ####
screen.update()
screen.ontimer(tick, 1000 // 30)
screen = turtle.Screen()
screen.setup(width=600, height=600)
screen.tracer(0)
player = Player()
screen.onkey(player.go_up, "Up")
screen.listen()
tick()
screen.exitonclick()
Now, you don't have any code in tick yet. This is the main update/rendering loop. The player's movement will be jerky because it's directly wired to the keyboard's retrigger mechanism. If this behavior isn't what you want, I suggest reacting to key presses and changing the player's position only inside of tick. See How to bind several key presses together in turtle graphics? for an example of this.

Why does Python Turtle need WIN.update() in #Game Loop?

I've written the following #Game Loop trying to teach my students a lesson. The turtle would not move or respond to any of the functions until I added the line WIN.update(). Why would that be necessary? Other turtle #Game Loops I've created have not needed it. When does it become a requirement to help the turtle respond to both key commands and user created functions?
enter image description here
In a turtle program, the update() is only necessary if you've previously done tracer(0), and doesn't directly affect keyboard events.
However, your program isn't assembled properly as while True:, or equivalent, defeats an event-driven environment like turtle. The addition of update() gave your program a chance to clear the event queue. What we really should use is a timed event. This is what I would have expected your program fragment to look like:
def game_loop():
if RUNNING:
Move() # Move the Turtle
Barriers() # Barrier Check
WIN.update() # Only if Win.tracer(0) is in effect
WIN.ontimer(game_loop, 100) # Delay in milliseconds
WIN.onkey(Up, 'Up')
WIN.onky(Down, 'Down')
WIN.onkey(Left, 'Left')
WIN.onkey(Right, 'Right')
WIN.listen()
game_loop()
WIN.mainloop()
Note that onkey() and listen() do not belong in the game loop, they only need to be applied once.

Stop python turtle with keypress

I am trying to get the turtle to stop moving forward when I press the escape key. When I press escape, nothing happens! Can anyone tell me why? A solution would be greatly appreciated.
import turtle
screen = turtle.Screen()
running = True
def stop():
running = False
print(running)
while running:
turtle.forward(1)
screen.onkey(stop, "Esc")
screen.listen()
I see several problems with your code. The primary one is a missing global statement in stop(). Secondary ones include: mixing the turtle function and object APIs; using key name 'Esc' instead of 'Escape'; putting onkey() and listen() in a loop; and potentially blocking events with your while loop.
I believe this code should do what you want:
from turtle import Screen, Turtle
running = True
def stop():
global running
running = False
def run():
if running:
turtle.forward(1)
screen.ontimer(run)
screen = Screen()
screen.onkey(stop, 'Escape')
screen.listen()
turtle = Turtle()
run()
screen.mainloop()

Is there a way to wait for a condition to be true?

I'm attempting to log coordinates 3 separate times when a user clicks on the turtle screen, then continue running other commands once that is completed. Clicking 3 times does nothing, and the shell keeps printing that it's waiting, while one additional click causes the whole thing to not work and I get a "not Responding" message from the turtle graphics window.
import turtle as t
import time
canvas=t.getcanvas()
xlist=[]
ylist=[]
listcomplete=False
def getPos(x,y):
xlist.append(canvas.winfo_pointerx()) ##Logs the x and y coords when mouse is clicked
ylist.append(canvas.winfo_pointery())
print('appended the lists.')
if len(xlist)==3:
listcomplete=True
t.onscreenclick(getPos)
def main():
while listcomplete==False:
time.sleep(1)
print('waiting...') ##Prints periodically just to let me know it's still running
main()
print('list complete.') ##Prints to alert the list has been finished
print(xlist)
(Insert rest of code to follow)
listcomplete=True within getPos() will not change the global variable, instead it will create a new varable of the same name within the local scope.
To change the global variable, you have to tell python to use it from the global scope:
def getPos(x,y):
global listcomplete # tell python to use the variable from the global scope
xlist.append(canvas.winfo_pointerx()) ##Logs the x and y coords when mouse is clicked
ylist.append(canvas.winfo_pointery())
print('appended the lists.')
if len(xlist)==3:
listcomplete=True
That's due to the default behavior of the assignment operator (=).
Other operators, such as the comparision operator (==) will lookup the variable from the enclosing scope(s) if it's not found within the local scope, thus you may use while listcomplete==False: within main() w/o telling pyton to use the variable from the global scope.
But ideally, you do not even have to use that global variable. Instead run the turtle main loop and exit the turtle window when your condition is met:
import turtle as t
canvas=t.getcanvas()
xlist=[]
ylist=[]
def getPos(x,y):
xlist.append(canvas.winfo_pointerx()) ##Logs the x and y coords when mouse is clicked
ylist.append(canvas.winfo_pointery())
print('appended the lists.')
if len(xlist)==3:
t.bye() # exit turtle window
t.onscreenclick(getPos)
t.Screen().mainloop() # will wait until turtle window is closed
print('list complete.') ##Prints to alert the list has been finished
print(xlist)
Is it possible to continue running the turtle window after the lists
have been created?
Things get difficult in turtle when you fight it's event-based model as you're trying to do. Work with the model, and things get easier. The code below presents a blank window, after you click on it in three places, it will connect your points to make a triangle:
from turtle import Screen, Turtle, mainloop
def getPosition(x, y):
screen.onscreenclick(None) # disable the handler inside the handler
positions.append((x, y))
if len(positions) == 3:
screen.ontimer(listComplete) # sometime after this function completes
else:
screen.onscreenclick(getPosition) # restore the handler
def listComplete():
for position in positions:
turtle.goto(position)
turtle.pendown()
turtle.goto(positions[0]) # close our triangle
# (Insert rest of code to follow)
positions = []
turtle = Turtle()
turtle.hideturtle()
turtle.penup()
screen = Screen()
screen.onscreenclick(getPosition)
mainloop() # invoke as function to make Python 2 friendly as well
The key is that "rest of code to follow" will be in functions, not top level code.
def handle_click(mouse_x,mouse_y): # <== I think thats what ya need right dere, I dont think it knows you're clicking
newpin = [[mouse_x,mouse_y], [0,0], 0,0,20, 1000000]
I tried putting a print statement after the click I couldn't get it to even print a test click. That may be me not trying hard enough tho ;) I just remember using something like above to handle a mouse click. (in my situation it created a pin for a pinball game) if you look up the turtle api for circle you can see the [0,0],0,0,20, 100000] means.
But ultimately that last number the 10000 whatever is "mass" so the more of it the less it moves. again my situation. turtle.onscreenclick(handle_click). Thats at least an idea :) also yes u can do a wait after an if. Throw in print statements.

Playing music with Pyglet and Tkinter in Python

I wanted to create a simple gui with a play and stop button to play an mp3 file in python. I created a very simple gui using Tkinter that consists of 2 buttons (stop and play).
I created a function that does the following:
def playsound () :
sound = pyglet.media.load('music.mp3')
sound.play()
pyglet.app.run()
I added that function as a command to the button play. I also made a different function to stop music:
def stopsound ():
pyglet.app.exit
I added this function as a command to the second button. But the problem is that when I hit play, python and the gui freeze. I can try to close the window but it does not close, and the stop button is not responsive. I understand that this is because the pyglet.app.run() is executing till the song is over but how exactly do I prevent this? I want the gui to stop the music when I click on the button. Any ideas on where I can find a solution to this?
You are mixing two UI libraries together - that is not intrinsically bad, but there are some problems. Notably, both of them need a main loop of their own to process their events. TKinter uses it to communicate with the desktop and user-generated events, and in this case, pyglet uses it to play your music.
Each of these loops prevents a normal "top down" program flow, as we are used to when we learn non-GUI programming, and the program should proceed basically with callbacks from the main loops. In this case, in the middle of a Tkinter callback, you put the pyglet mainloop (calling pyglet.app.run) in motion, and the control never returns to the Tkinter library.
Sometimes loops of different libraries can coexist on the same process, with no conflicts -- but of course you will be either running one of them or the other. If so, it may be possible to run each library's mainloop in a different Python thread.
If they can not exist together, you will have to deal with each library in a different process.
So, one way to make the music player to start in another thread could be:
from threading import Thread
def real_playsound () :
sound = pyglet.media.load('music.mp3')
sound.play()
pyglet.app.run()
def playsound():
global player_thread
player_thread = Thread(target=real_playsound)
player_thread.start()
If Tkinter and pyglet can coexist, that should be enough to get your music to start.
To be able to control it, however, you will need to implement a couple more things. My suggestion is to have a callback on the pyglet thread that is called by pyglet every second or so -- this callback checks the state of some global variables, and based on them chooses to stop the music, change the file being played, and so on.
I would do something like:
import pyglet
from pyglet.gl import *
class main (pyglet.window.Window):
def __init__ (self):
super(main, self).__init__(800, 600, fullscreen = False)
self.button_texture = pyglet.image.load('button.png')
self.button = pyglet.sprite.Sprite(self.button_texture)
self.sound = pyglet.media.load('music.mp3')
self.sound.play()
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_mouse_press(self, x, y, button, modifiers):
if x > self.button.x and x < (self.button.x + self.button_texture.width):
if y > self.button.y and y < (self.button.y + self.button_texture.height):
self.alive = 0
def on_key_press(self, symbol, modifiers):
if symbol == 65307: # [ESC]
self.alive = 0
def render(self):
self.clear()
self.button.draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
x = main()
x.run()
This solution is the easiest one:
import pyglet
foo=pyglet.media.load("/data/Me/Music/Goo Goo Dolls/[1998] Dizzy Up The Girl/11 - Iris.mp3")
foo.play()
def exiter(dt):
pyglet.app.exit()
print "Song length is: %f" % foo.duration
# foo.duration is the song length
pyglet.clock.schedule_once(exiter, foo.duration)
pyglet.app.run()
source: http://ubuntuforums.org/showthread.php?t=1651906
There is a media player implementation in the pyglet documentation:
http://www.pyglet.org/doc/programming_guide/playing_sounds_and_music.html
The script you should look at is media_player.py
Hopefully this will get you started

Categories

Resources