Cannot implement onkeyrelease() from Python's turtle module. Please advise.
Error message: 'module' object has no attribute 'onkeyrelease'.
Tried replacing turtle.onkeyrelease(stay, 'd') with wn.onkeyrelease(stay, 'd') to no avail.
import turtle
speed = 0
wn = turtle.Screen()
wn.tracer(0)
box = turtle.Turtle()
box.shape('square')
box.penup()
def move_right():
global speed
speed = 2
def stay():
global speed
speed = 0
turtle.listen()
turtle.onkey(move_right, 'd')
turtle.onkey(stay, 's')
turtle.onkeyrelease(stay, 'd')
while True:
wn.update()
box.setx(box.xcor() + speed)
My guess, based on the error message, is that you are running Python 2 and onkeyrelease() is a Python 3 method. Even so:
An artifact of the transition from Python 2 to Python 3, onkey() and onkeyrelease() are synonyms. What you probably want is onkeypress() and onkeyrelease(). Even so:
That said, it's iffy whether trying to do different things on the key press and release is going to work. On my system, both press and release are triggered by a key press. Your results, due to OS perhaps, might vary.
You may be better off using two keys, 'd' to start the motion, 's' to stop it:
from turtle import Screen, Turtle, mainloop
speed = 0
def move_faster():
global speed
speed = 2
def stay():
global speed
speed = 0
def move():
box.forward(speed)
screen.update()
screen.ontimer(move)
screen = Screen()
screen.tracer(False)
box = Turtle()
box.shape('square')
box.penup()
screen.onkey(stay, 's')
screen.onkey(move_faster, 'd')
screen.listen()
move()
mainloop()
This code should work under Python 2 and Python 3.
Related
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.
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 ...
My goal is to be able to make a snake(three turtles next to each other) move at the same time and also make the snake turn using arrow keys. I have tried the ontimer method/function but it does not work as expected. Here is my code:
import make_snake
from turtle import Turtle, Screen
game_is_on = True
screen = Screen()
screen.setup(600, 600)
screen.bgcolor("black")
screen.title("Snake Game")
snake_seg1 = make_snake.snake_segments[0]
snake_seg2 = make_snake.snake_segments[1]
snake_seg2.setheading(snake_seg1.heading())
snake_seg3 = make_snake.snake_segments[2]
snake_seg3.setheading(snake_seg2.heading())
def move_forward():
snake_seg1.forward(20)
def move_backward():
snake_seg1.backward(20)
def turn_left():
snake_seg1.left(90)
def turn_right():
snake_seg1.right(90)
screen.onkey(move_forward, "Up")
screen.onkey(move_backward, "Down")
screen.onkey(turn_left, "Left")
screen.onkey(turn_right, "Right")
while game_is_on:
for seg in make_snake.snake_segments:
seg.forward(20)
# def follow_head():
# snake_seg1.forward(20)
# snake_seg2.setheading(snake_seg1.heading())
# snake_seg2.forward(20)
# snake_seg3.setheading(snake_seg2.heading())
# snake_seg3.forward(20)
# screen.ontimer(follow_head, 0)
screen.exitonclick()
File make_snake:
from turtle import Turtle
start_positions = [0, 20, 40]
snake_segments = []
for position in start_positions:
snake_part = Turtle(shape="square")
snake_part.color("white")
snake_part.penup()
snake_part.backward(position)
snake_segments.append(snake_part)
What can I fix in my code to make it stop moving one turtle at a time?
Your program has at least one problem: you use onkey() but forgot to call listen() to allow key events.
Here's a minimalist rework of your code to get basic snake movement working, it just supports right and left turns:
from turtle import Turtle, Screen
SEGMENT_SIZE = 20
START_COORDINATES = [-SEGMENT_SIZE * count for count in range(5)]
def turn_left():
snake_segments[0].left(90)
screen.update()
def turn_right():
snake_segments[0].right(90)
screen.update()
def move_forward():
for index in range(len(snake_segments) - 1, 0, -1):
snake_segments[index].goto(snake_segments[index - 1].position())
snake_segments[0].forward(SEGMENT_SIZE)
screen.update()
screen.ontimer(move_forward, 250)
screen = Screen()
screen.setup(600, 600)
screen.title("Snake Game")
screen.tracer(False)
snake_segments = []
for position in START_COORDINATES:
snake_segment = Turtle(shape='circle', visible=False)
snake_segment.penup()
snake_segment.setx(position)
snake_segment.showturtle()
snake_segments.append(snake_segment)
snake_segments[0].color('red')
screen.onkey(turn_left, 'Left')
screen.onkey(turn_right, 'Right')
screen.listen()
screen.update()
move_forward()
screen.exitonclick()
Be careful, it's easy to lose the snake off the edge of the window and not be able to get it back again!
The thing you might be looking for here is called multiprocessing. There is an eponymous library to execute lines of code simultaneously in Python. For reference look at the following thread:
Python: Executing multiple functions simultaneously
I am attempting to make a game in python with the turtle module, I have the square moving towards the player (the circle) and the aim is for the circle to jump over the square and not get hit.
The player can jump by pressing the spacebar,
but every time you hit the space bar to jump the player jumps, but the square stops moving and you are unable to jump over.
here is my code:
import turtle
import time
wn = turtle.Screen()
wn.bgcolor("white")
wn.title("dinosaur run")
wn.tracer(1,20)
floor = turtle.Turtle()
floor.fd(370)
floor.bk(370*2)
floor.ht()
player = turtle.Turtle()
player.shape("circle")
player.penup()
player.setpos(-370,14)
def jump():
player.lt(90)
player.fd(40)
time.sleep(0.5)
player.bk(40)
player.rt(90)
turtle.listen()
turtle.onkey(jump, "space")
class cactus(turtle.Turtle):
turtle.shape("square")
turtle.penup()
turtle.speed(0)
turtle.setpos(370,14)
cactusspeed = 2
while True:
x = turtle.xcor()
x -= cactusspeed
turtle.setx(x)
Thanks a lot,
all ideas welcome,
I've tried wn.update() at the end
As provided above, your code doesn't run at all as cactusspeed never gets defined. And your class cactus doesn't have a hope of working as currently laid out (reread about Python classes.) Finally, your while True: has no business in an event driven world like turtle.
Below is my rework of your code to use an ontimer() event to control the cactus independent of the player. I also eliminated the sleep() and simply made the player move slower and jump higher. I believe this should give you the dynamic you're looking for:
from turtle import Turtle, Screen
def jump():
player.forward(100)
player.backward(100)
def move():
if cactus.xcor() < -screen.window_width()/2:
cactus.hideturtle()
cactus.setx(370)
cactus.showturtle()
else:
cactus.forward(cactusspeed)
screen.ontimer(move, 40)
screen = Screen()
floor = Turtle(visible=False)
floor.speed('fastest')
floor.fd(370)
floor.bk(370 * 2)
player = Turtle("circle", visible=False)
player.penup()
player.setpos(-370, 14)
player.setheading(90)
player.speed('slowest')
player.showturtle()
cactusspeed = 4
cactus = Turtle("square", visible=False)
cactus.speed('fastest')
cactus.penup()
cactus.setpos(370, 14)
cactus.setheading(180)
cactus.showturtle()
screen.onkey(jump, "space")
screen.listen()
move()
screen.mainloop()
I'm very new to Python and have made a couple small games during a Python Learning course but never at home. So recently I began making a game, but after just 10 minutes I stumbled upon a problem:
Nothing happened when I pressed "W" although I had writen onkeypress in the code.
See for your self:
(It's designed for full screen)
import turtle
s = turtle.Screen()
g = turtle.Turtle()
t = turtle.Turtle()
#Ground
t.speed(0)
t.up()
t.goto(-1000,-200)
t.down()
t.goto(1000,-200)
#Player
g.speed(0)
PlayerX = -600
def moveX():
g.clear()
global PlayerX
g.up()
g.goto(PlayerX,-99)
g.down()
g.color("Slate Gray")
g.begin_fill()
g.circle(-50)
g.end_fill()
PlayerX = PlayerX - 1
turtle.onkeypress(moveX, "w")
moveX()
I'm fully aware I haven't made a go backwards button.
Along with #doctorlove's spot on correction (+1) of adding listen() to allow the window to receive keyboard events, a couple of comments:
First, click on the window with your mouse to make it active otherwise it won't respond to the keyboard. Second, it can be helpful to deactivate the event handler while in the event hander, and reactivate it on the way out, to avoid problems if someone repeatedly presses the key very fast.
Here's the second comment along with some other code suggestions:
from turtle import Turtle, Screen
screen = Screen()
screen.setup(1200, 500)
# Ground
ground = Turtle()
ground.speed('fastest')
ground.penup()
ground.goto(-1000, -200)
ground.pendown()
ground.forward(2000)
# Player
player = Turtle()
player.speed('fastest')
PlayerX = -600
def moveX():
global PlayerX
screen.onkeypress(None, "w") # disable handler in handler
player.clear()
player.penup()
player.goto(PlayerX, -99)
player.pendown()
player.color("Slate Gray")
player.begin_fill()
player.circle(-50)
player.end_fill()
PlayerX -= 1
screen.onkeypress(moveX, "w") # reenable handler
screen.listen()
moveX()
screen.mainloop() # change import & use turtle.mainloop() if Python 2
mainloop() isn't required to run but the program will exit after your initial moveX() call without it. mainloop() turns control over to the Tk event handler so some events may not fire without it.
You'll need to change onkeypress() to onkey() if this is Python 2 as well as change the way that mainloop() is invoked.
I think it's called onkey not onkeypress.
Also I think you need to listen (and add a mainloop if you want it to run):
turtle.onkey(moveX, "w")
turtle.listen()
moveX() # draw things first
turtle.mainloop()
You may need to revisit the numbers you are using to make sure the shape is on the window.
with my version of python none of the others are actually correct, here is the modified code that works for me:
from turtle import Turtle, Screen, setpos, hideturtle
screen = Screen()
screen.setup(500, 500)
#Ground
t = Turtle()
t.speed(0)
t.up()
t.goto(-1000,-200)
t.down()
t.goto(1000,-200)
#Player
player = Turtle()
hideturtle()
player.speed(0)
setpos(0,0)
PlayerX = 0
def moveX():
player.clear()
global PlayerX
player.up()
player.goto(PlayerX,0)
player.down()
player.color("Slate Gray")
player.begin_fill()
player.circle(-50)
player.end_fill()
PlayerX = PlayerX - 1
screen.onkey(moveX, "w")
screen.listen()
(this can definitely be improved on)
Not sure if the change is with Python3. But onkey function seems to be dragged under Screen().
turtle.Screen().onkey(movex, "w")
#This is a short code I made using space as down and w as up, feel free to
#extract from it what you can.
import turtle
player = turtle.Turtle()
y = 0
wn = turtle.Screen()
def p_up():
global y,up
up = True
while(up==True):
y += 10
player.sety(y)
def p_down():
global y,down
down = True
while(down==True):
y -= 10
player.sety(y)
def up_stop():
global up
up = False
def down_stop():
global down
down = False
wn.listen()
wn.onkeypress(p_up,"w")
wn.onkeypress(p_down,"space")
wn.onkeyrelease(up_stop,"w")
wn.onkeyrelease(down_stop,"space")
wn.mainloop()