Python turtle wn.ontimer in an if statement - python

I have Turtle commands in my code as you see below. I would like to put a timer on these commands inside an if statement. My current code just works like so:
'# WHEN USER PRESSES SPACE, LIGHT TURNS GREEN
'# WHEN LIGHT IS GREEN ARROW MOVES 50 PIXELS
player1.pendown()
player1.forward(50)
player2.pendown()
player2.forward(50)
The arrow really only moves once every time I press the space key.
I would like to turn that into a timer so the arrow would move every 60 milliseconds until the user presses the space key again.
I tried using an wn.ontimer but I keep on messing up something. Below is how the code looks now:
def advance_state_machine():
global state_num
if state_num == 0:
tess.forward(70)
tess.fillcolor("red")
state_num = 1
else:
tess.back(70)
tess.fillcolor("green")
state_num = 0
player1.pendown()
player1.forward(50)
player2.pendown()
player2.forward(50)
wn.onkey(advance_state_machine, "space")
wn.listen()
wn.mainloop()

Your description of your problem is inconsistent and your code is out of sync with your description. Here's what I believe you stated you wanted your code to do:
The turtle moves forward 1 step every 60 milliseconds and alternates between red/forward and green/backward whenever the space bar is pressed.
I believe this code implements that, a key event is used to detect the space bar and a timer event is used to keep the turtle in motion:
from turtle import Turtle, Screen, mainloop
def advance_state_machine():
global state_num
wn.onkey(None, 'space') # avoid overlapping events
if state_num > 0:
tess.fillcolor('green')
else:
tess.fillcolor('red')
state_num = -state_num
wn.onkey(advance_state_machine, 'space')
def advance_tess():
tess.forward(state_num)
wn.ontimer(advance_tess, 60)
wn = Screen()
tess = Turtle()
tess.fillcolor('red')
state_num = 1
wn.onkey(advance_state_machine, 'space')
wn.ontimer(advance_tess, 60)
wn.listen()
mainloop()
Make sure to click on the window to make it active before trying the space bar. Left unattended, the turtle will eventually go off screen.

Related

How to fix a RecursionError in Turtle?

I'm trying to write a program with Python to emulate an 'old' online game in which you drive a worm through the screen with some inputs from the keyboard.
import turtle
# Set screen and background
wn = turtle.Screen()
wn.title("Turn with Left and Right buttons your keyboard. Click on screen to EXIT.")
wn.bgcolor("black")
# Snake settings
snake = turtle.Turtle()
snake.color("purple")
snake.shape("circle")
snake.shapesize(0.25,0.25)
snake.pensize(5)
snake.speed(10)
t = 0
# Define Go loop, turn Left and Right
def go():
t = 0
while t < 1000:
snake.forward(1)
t += 1
def left():
snake.circle(1,8)
go()
def right():
snake.circle(1,-8)
go()
# Inputs and Exit on click
wn.onkey(right, "Right")
wn.onkeypress(right, "Right")
wn.onkey(left, "Left")
wn.onkeypress(left, "Left")
wn.listen()
wn.exitonclick()
turtle.done()
The problem here is that, after some moves, the program crashes returning:
RecursionError: maximum recursion depth exceeded while calling a Python object.
I'm still a beginner so i don't get what I'm doing wrong. How can I fix the error?
What you're experiencing is a faux recursion due to events stacking. However, your code design:
while t < 1000:
snake.forward(1)
t += 1
actually relies on event stacking! That is, you expect left and right commands to come in during the go() portion of the event handler which keeps the turtle moving. This is a problematic design. Let's rework your code using an event timer instead:
from turtle import Screen, Turtle
def go():
snake.forward(1)
screen.ontimer(go, 50)
def left():
screen.onkeypress(None, 'Left') # disable handler inside handler
snake.circle(1, 8)
screen.onkeypress(left, 'Left') # reenable handler
def right():
screen.onkeypress(None, 'Right')
snake.circle(1, -8)
screen.onkeypress(right, 'Right')
screen = Screen()
screen.title("Turn with Left and Right buttons your keyboard. Click on screen to EXIT.")
screen.bgcolor('black')
snake = Turtle()
snake.color('purple')
snake.shape('circle')
snake.shapesize(0.25)
snake.pensize(5)
snake.speed('fastest')
screen.onkeypress(left, 'Left')
screen.onkeypress(right, 'Right')
screen.listen()
go()
screen.exitonclick()
From testing, what is apparent is that if your go function hasn't completed, and you are still holding down a key, it's called again, meaning that now there's two go functions on the call stack (plus a bunch of other functions like eventfun and update). Because holding the key calls the go function many times a second, and the fact that the go function takes 8+ seconds to complete, you're filling up the call stack with calls to go, hence the crash. The call stack is just something like this repeated over and over:
update, __init__.py:1314
_update, turtle.py:561
_update, turtle.py:2663
_goto, turtle.py:3197
_go, turtle.py:1606
forward, turtle.py:1638
go, script.py:21
left, script.py:26
eventfun, turtle.py:700
__call__, __init__.py:1892
Even holding down left for just 3 seconds, my call stack grows to over 600, which is really high.
To fix this you could make it so that left or right cannot be called again while that key is still held down. That's one possible way.

Unexpected Python turtle behavior when turning was invoked from keyboard

I have observed an unexpected behavior of the turtle graphics when turning the turtle by pressing keys on the keyboard. For example turning to the opposite direction resulted in a double line instead of following the already drawn line in the opposite direction or I was getting sometimes sharp and sometimes curved corners when turning or experienced both kinds of unexpected results:
.
I was not able to reproduce this behavior on a very simple example as it occured only if the change of turtle heading was invoked by pressing a key on a keyboard.
The question is, what is the reason for this behavior and how can it be avoided?
By the way: turning the turtle forth and back let it circle on these two lines it has drawn and changing the size of pen does not change the amount of the 'jump' to the side.
Below the code demonstrating this behavior:
import turtle
screen = turtle.Screen()
screen.bgcolor('green')
# Create the Blue Player Turtle and its controls
blue_player = turtle.Turtle()
blue_player.pencolor("blue")
blue_player.pensize(1)
blue_player.is_moving = False
def blue_up() : blue_player.setheading( 90);blue_player.is_moving=True
def blue_down() : blue_player.setheading(270);blue_player.is_moving=True
def blue_left() : blue_player.setheading(180);blue_player.is_moving=True
def blue_right(): blue_player.setheading( 0);blue_player.is_moving=True
screen.onkey(blue_up, "Up")
screen.onkey(blue_down, "Down")
screen.onkey(blue_left, "Left")
screen.onkey(blue_right, "Right")
# Move the Blue Player horizontally forth and back
blue_player.setheading(0)
blue_player.forward(100)
blue_player.setheading(180)
blue_player.forward(50)
# Allow pausing the game by pressing space
game_paused = False
def pause_game():
global game_paused
if game_paused: game_paused = False
else: game_paused = True
screen.onkey(pause_game, "space")
# Establishing a screen.ontimer() loop driving the turtles movement
def gameloop():
if not game_paused:
if blue_player.is_moving: blue_player.forward(1)
# check_collisions()
screen.ontimer(gameloop, 50) # 10ms (0.01s) (1000ms/10ms = 100 FPS)
gameloop()
screen.listen()
screen.mainloop()
Update 2022-06-22 : It seems that this and some other effects have something to do with the delay value in the `.ontimer() function and the distance for a move of the pen.
Update 2022-06-23: below the code I will be using where the described unexpected effect doesn't occur anymore as it differs a bit from the code provided in the answer:
from turtle import Screen, Turtle
screen = Screen()
# Create the Blue Player Turtle and its controls
blue_player = Turtle()
blue_player.pensize(5)
blue_player.moving = False # user-defined property
blue_player.turning = False # user-defined property
def turning(player, angle):
player.turning=True; player.setheading(angle); player.turning=False
if not player.moving: player.moving=True
def blue_up() : turning(blue_player, 90)
def blue_down() : turning(blue_player, 270)
def blue_left() : turning(blue_player, 180)
def blue_right(): turning(blue_player, 0)
screen.onkey(blue_up, "Up")
screen.onkey(blue_down, "Down")
screen.onkey(blue_left, "Left")
screen.onkey(blue_right, "Right")
# Move the Blue Player horizontally forth and back
blue_player.forward(100); blue_player.backward(50)
# Allow pausing the game by pressing space
screen.game_paused = False # user-defined property/attribute
def pause_game():
screen.game_paused = not screen.game_paused
screen.onkey(pause_game, "space")
# Establishing a screen.ontimer() loop driving the turtles movement
def gameloop():
if not screen.game_paused:
if not blue_player.turning:
if blue_player.moving:
blue_player.forward(3)
screen.ontimer(gameloop, 1) # 1 means 1 ms (0.001s)
gameloop()
screen.listen()
screen.mainloop()
In between I have found a way how to provoke the from time to time anyway occurring unexpected behavior also with the new version of the code. If you press the keys very fast in a sequence you get curved turns instead of sharp 90 degree edges.
In other words: the by cdlane proposed approach helps, but doesn't completely solve the problem of getting curved corners. so the question remains open to a final solution or at least to an explanation why a 100% solution is not possible.
Update 2022-06-24:
Finally it turns out that a 100% solution is possible and so simple that I still wonder how it comes that I haven't found it by myself before ( see the hopefully right answer provided by myself ).
The turtle heading animation turns in discrete steps, during which your ontimer() event is kicking in and advancing the turtle. We might be able to use tracer() and update() to fix this but let's try a simpler fix by defining a new state for the turtle, 'turning':
from turtle import Screen, Turtle
def blue_up():
blue_player.status = 'turning'
blue_player.setheading(90)
blue_player.status = 'moving'
def blue_down():
blue_player.status = 'turning'
blue_player.setheading(270)
blue_player.status = 'moving'
def blue_left():
blue_player.status = 'turning'
blue_player.setheading(180)
blue_player.status = 'moving'
def blue_right():
blue_player.status = 'turning'
blue_player.setheading(0)
blue_player.status = 'moving'
# Allow pausing the game by pressing space
game_paused = False
def pause_game():
global game_paused
game_paused = not game_paused
# Establishing a screen.ontimer() loop driving the turtles movement
def gameloop():
if not game_paused:
if blue_player.status == 'moving':
blue_player.forward(1)
# check_collisions()
screen.ontimer(gameloop, 50) # 10ms (0.01s) (1000ms/10ms = 100 FPS)
screen = Screen()
screen.bgcolor('green')
# Create the Blue Player Turtle and its controls
blue_player = Turtle()
blue_player.pencolor('blue')
blue_player.pensize(1)
blue_player.status = 'stationary' # user-defined property
# Move the Blue Player horizontally forth and back
blue_player.forward(100)
blue_player.backward(50)
screen.onkey(blue_up, 'Up')
screen.onkey(blue_down, 'Down')
screen.onkey(blue_left, 'Left')
screen.onkey(blue_right, 'Right')
screen.onkey(pause_game, 'space')
screen.listen()
gameloop()
screen.mainloop()
Check if this solves the problem sufficiently for your purposes.
The by cdlane provided answer explains the basics behind the mechanism of turtle turning. So the reason for the unexpected behavior is clear:
The turtle heading animation turns in discrete steps, during which an .ontimer() event is kicking in and advancing the turtle.
The fact that the by cdlane provided solution proposal doesn't really work in all cases put me on the path to a solution that seem to be resistent against any attempts to provoke the unexpected behavior. The drawn lines are now all perpendicular to each other and the painting pen moves only in the horizontal and vertical direction.
The very simple 'trick' that solves the problem is to check the current value of turtle heading in the loop moving the pen forward, so the only change needed to the code posted in the question is:
if blue_player.heading() in [0.0, 90.0, 180.0, 270.0]:
blue_player.forward(1)
It's so simple that I wonder how it comes I didn't think about it before ...

Jump and Move sideways

I can't figure out how to move sideways and jump at the same time, only one or the other.
I have tried asyncio and multithreading/multiprocessing and couldn't get it to work. I am all out of ideas and can't find anymore online. I also have another issue where I can jump and if I reach the apex of the jump and hold a or d I can move side to side floating.
class Player():
def __init__(self,health,boosts,height,width):
self.health = health
self.booosts = boosts
self.height = height
self.width = width
def jump():
global gravtest, press
press.remove("'w'")
gravtest = False
y = player[0].ycor()
for i in range (1, 10):
player[0].sety(y+(i*5))
time.sleep(0.05)
#while player[0]
gravtest = True
# def powers(self, boosts):
import turtle as t
import time, random
from pynput.keyboard import Key, Listener
t.ht()
press = []
gravtest = True
wn = t.Screen()
wn.title("Jump Man")
wn.bgcolor("white")
wn.screensize(250, 250)
wn.setup(width=1.0, height=1.0)
player = [t.Turtle(), Player(100, [], 25, 25)]
player[0].speed(0)
player[0].shapesize(0.5)
player[0].shape("square")
player[0].color("black")
player[0].up()
player[0].goto(0, 0)
floor = t.Turtle()
floor.speed(0)
floor.shape("square")
floor.shapesize(100)
floor.color("black")
floor.up()
floor.goto(0, -1150)
def gravity():
global gravtest
if gravtest == True:
grav = 0
while player[0].distance(floor) > 1007:
y = player[0].ycor()
player[0].sety(y + grav)
if grav > -5:
grav -= 1
player[0].sety(y + grav)
gravtest = False
if player[0].distance(floor) < 1045:
player[0].sety(-145)
def show(key):
global press
if not(format(key) in press):
press.append(format(key))
print(key)
def rem(key):
global press
if format(key) in press:
press.remove(format(key))
def move():
global press
while "'a'" in press:
player[0].setx(player[0].xcor()-2)
while "'d'" in press:
player[0].setx(player[0].xcor()+2)
if press == '\'s\'':
print()
gravity()
if "'w'" in press:
jump()
with Listener(on_press = show, on_release = rem) as listener:
while 1:
move()
Your problem with moving and jumping is that you have separate loops for each that try to handle one kind of movement of the movement in one place. That won't work properly if other stuff (like jumping while moving, or moving while falling under gravity) are supposed to be possible.
Instead, you need to have just one main loop. On each run of that loop, you should do one frame worth of movement of whatever kinds is appropriate (e.g. moving horizontally, falling or jumping). This may require some bookkeeping to keep track of how long the vertical acceleration from a jump lasts.
I'd make the main loop something like this:
on_the_ground = True
jumping = False
while True:
horizontal_movement() # this is always possible if the buttons are pressed
if on_the_ground or jumping:
jump() # can start or continue a jump if a button is pressed
else:
gravity() # fall, if you're not grounded or accelerating in a jump
handle_collisions_etc()
time.sleep(1/FRAMERATE)
Note, I've made some assumptions about the game logic you want. If you don't want to be able to move horizontally while in the air (as you can in many platformer games), you'll need to change this a little bit.
The jump function will need to keep track of how long you've been jumping for, since you probably want the player to be limited in how high they can go. I'm not exactly sure I understand the logic of your current code, so I'll leave it up to you to figure out exactly what to track.
A final suggestion is to move some of the global variables into the Player class. Even the turtle object you're using to draw things could be stored as an attribute on the instance you create.

How to make multiple clones run at the same time in python turtle

I am trying to make a code where you can press the spacebar and an object will move forwards constantly. I am hoping to be able to have multiple of these objects moving at once without having to code hundreds of them separately.
This is my current code:
Bullet:
bullet = turtle.Turtle()
bullet.speed(0)
bullet.shape("circle")
bullet.color("red")
bullet.shapesize(stretch_wid=0.5, stretch_len=0.5)
bullet.penup()
bullet.goto(-200, -200)
bullet.hideturtle()
Movement:
def shoot_bullet():
stop = False
bullet2 = bullet.clone()
bullet2.showturtle()
while stop == False:
y = bullet2.ycor()
bullet2.sety(y + 20)
wn.update()
time.sleep(0.5)
...
onkeypress(shoot_bullet, "space")
This works until I press space again and the bullet just stops as 'bullet2' has been redefined as the new bullet I create when I press space. Is there a way to create multiple clones which can run on top of each other?
Your while stop == False: loop and time.sleep(0.5) have no place in an event-driven environment like turtle. Instead, as we fire each bullet, the below code attaches a timer event that moves it along until it disappears. At which point the bullet is recycled.
This simplified example just shoots bullets in random directions from the center of the screen. You can keep hitting the space bar to generate simultaneous bullets that all move in their own direction until they get far enough away:
from turtle import Screen, Turtle
from random import randrange
def move_bullet(bullet):
bullet.forward(15)
if bullet.distance((0, 0)) > 400:
bullet.hideturtle()
bullets.append(bullet)
else:
screen.ontimer(lambda b=bullet: move_bullet(b), 50)
screen.update()
def shoot_bullet():
screen.onkey(None, 'space') # disable handler inside hander
bullet = bullets.pop() if bullets else bullet_prototype.clone()
bullet.home()
bullet.setheading(randrange(0, 360))
bullet.showturtle()
move_bullet(bullet)
screen.onkey(shoot_bullet, 'space') # reenable handler on exit
bullet_prototype = Turtle('circle')
bullet_prototype.hideturtle()
bullet_prototype.dot(10) # just for this example, not for actual code
bullet_prototype.shapesize(0.5)
bullet_prototype.color('red')
bullet_prototype.penup()
bullets = []
screen = Screen()
screen.tracer(False)
screen.onkey(shoot_bullet, 'space')
screen.listen()
screen.mainloop()

How to start moving a turtle via space bar with python

I'm trying to set up a simple turtle program in python where I can start moving the turtle with a press of the space bar, and he keeps moving until I hit the space bar again. I can get him to move a fixed distance with the space press but can't get it to continue.
Here is what I'm working with:
from turtle import *
# PUT YOUR CODE HERE
setup(800,600)
home()
pen_size = 2
color("blue")
title("Turtle")
speed("fastest")
drawdist= 25
current_state = penup
next_state = pendown
#Button Instructions
def move_up():
seth(90)
forward(drawdist)
def move_down():
seth(270)
forward(drawdist)
def move_left():
seth(180)
forward(drawdist)
def move_right():
seth(0)
forward(drawdist)
def space_bar():
seth(90)
forward(drawdist)
global current_state, next_state
next_state()
current_state, next_state = next_state, current_state
#Change Pen Color
def red():
color("red")
def green():
color("green")
def blue():
color("blue")
#Button Triggers
s= getscreen()
s.onkey(move_up,"Up")
s.onkey(move_down,"Down")
s.onkey(move_left,"Left")
s.onkey(move_right,"Right")
s.onkey(space_bar,"space")
s.onkey(red,"r")
s.onkey(green,"g")
s.onkey(blue,"b")
listen()
done()
I don't see that you ever got an answer to your query:
start moving the turtle with a press of the space bar, and he keeps
moving until I hit the space bar again
The suggested onkeypress() fix doesn't do this. Here's a simplified example that does what you want, starts the turtle when you hit the space bar and stops it when you hit the space bar again:
from turtle import Turtle, Screen
screen = Screen()
turtle = Turtle(shape="turtle")
turtle.speed("fastest")
def current_state():
global moving
moving = False
turtle.penup()
def next_state():
global moving
turtle.pendown()
moving = True
move()
def space_bar():
global current_state, next_state
next_state()
current_state, next_state = next_state, current_state
def move():
if moving:
turtle.circle(100, 3)
screen.ontimer(move, 50)
current_state()
screen.onkey(space_bar, "space")
screen.listen()
screen.mainloop()
I've used circular motion in this example so you can start and stop the turtle as much as you want.
Replace the function 'onkey' with the function 'onkeypress'.
The 'onkey' function fires once regardless of holding down the key while 'onkeypress' fires as you would expect when holding down the key.
The correct and easiest way is this(NOT FOR EVENT LISTENER SPACE BAR, THIS IS JUST EVENT LISTENERS):
import turtle
import random
t = turtle.Turtle()
screen = turtle.Screen(
def goForward():
t.forward(input_value)
screen.onkey(goForward, "Forward")
The word "right" in hashtags only means to press the right key.

Categories

Resources