0

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:

double line curved corners enter image description here.

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.

still curvy ...

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 ).

Claudio
  • 7,474
  • 3
  • 18
  • 48

2 Answers2

2

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.

cdlane
  • 40,441
  • 5
  • 32
  • 81
  • The proposed solution works perfect for me and it comes along with meaningful and making sense corrections and comments. For the sake of completeness I will update my question with the code I will be actually using as it differs a bit from the code provided in this answer. – Claudio Jun 23 '22 at 05:48
  • @ cdlane : you propose `blue_player.status = 'turning'` and `blue_player.status = 'moving'` where I tend to use in time critical loops `blue_player.turning = True ` and `blue_player.moving = True ` in order to avoid computational more costly string comparisons in `if` statements. Please give some feedback on the reason for your described preference. – Claudio Jun 23 '22 at 06:52
  • 2
    @Claudio, first it was just an example. Second, I consider a working program to be *more* efficient than a broken one. Third, consider that these are all static strings in the text of the code (not user input) so they're compared by pointer (i.e. `is`), just like True and False, not character by character. Fourth, the environment has built-in delays as it operates (updates) at human speed, not maximally efficient CPU speed. – cdlane Jun 23 '22 at 17:49
  • The interactions between the keyboard and the timing events are not as simple as I believed. The proposed solution helps, but doesn't solve the problem. – Claudio Jun 23 '22 at 23:06
0

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 ...

Claudio
  • 7,474
  • 3
  • 18
  • 48