Reputation:
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 ).
Upvotes: 0
Views: 139
Reputation:
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 ...
Upvotes: 0
Reputation: 41905
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.
Upvotes: 2