6

I am having trouble getting my turtle to be able to follow the arrow keys, any help on how to do so would be greatly appreciated. I'm sure this question has been asked before, though I can't seem to find it, and the ones I do find are for older versions.

import turtle
#screen
wn=turtle.Screen()
wn.bgcolor("lightblue")

I plan on this being a spaceship game
#Turtle Player
spaceship= turtle.Turtle()
spaceship.color("red")
spaceship.penup()
speed=1

This is where I am stuck, I don't know how to make the turtle follow the arrow keys

#keyboard bindings

while True:
    spaceship.forward(speed)
ggorlen
  • 44,755
  • 7
  • 76
  • 106
Elyad K
  • 77
  • 1
  • 2
  • 4
  • Check out this one: http://www.dreamincode.net/forums/topic/274724-drawing-with-arrow-keys-turtle-module/page__p__1599693entry1599693 answer of user called `atraub` explains how to do it. – jedruniu Apr 17 '17 at 11:24
  • call me an idiot, though I can't seem to implement that into my program – Elyad K Apr 17 '17 at 14:51
  • If you're looking for handling multiple keypresses at once, [How to bind several key presses together in turtle graphics?](https://stackoverflow.com/questions/47879608/how-to-bind-several-key-presses-together-in-turtle-graphics/70979967#70979967) might help. – ggorlen Jul 29 '22 at 20:14

3 Answers3

8

Avoid using an infinite loop like while True: inside a turtle graphics program, it can keep some of your events from firing.

Below is the minimal code I could come up with to make your spaceship navigatible. You should be able to build on this:

from turtle import Turtle, Screen

wn = Screen()
wn.bgcolor('lightblue')

spaceship = Turtle()
spaceship.color('red')
spaceship.penup()

speed = 1

def travel():
    spaceship.forward(speed)
    wn.ontimer(travel, 10)

wn.onkey(lambda: spaceship.setheading(90), 'Up')
wn.onkey(lambda: spaceship.setheading(180), 'Left')
wn.onkey(lambda: spaceship.setheading(0), 'Right')
wn.onkey(lambda: spaceship.setheading(270), 'Down')

wn.listen()

travel()

wn.mainloop()

Click on the turtle graphics window before issuing keyboard commands to make sure it is listening. Also, there are other approaches to how the keys should work, I've used absolute motion here but you might want relative where each press incrementally modifies your direction.

cdlane
  • 40,441
  • 5
  • 32
  • 81
4

First, we got to understand the basics. In order for the user to be able to interact with the turtle through key presses, we need to let the window listen for key presses. Since your screen is named wn, that can be done simply by calling wn.listen().

Now that the turtle graphics are listening for key presses, how will you tell the program to do something through key presses? Functions! Let's say you want a new turtle to be created every time a key is pressed; you will need to define a function like so (you can use lambda for the function, but for now, let's stick to def):

def create_new_turtle():
    new_turtle = turtle.Turtle()

Keep in mind that you shall not pass any positional arguments into the brackets when defining the function, as you will not be able to pass your arguments in when you use the function, resulting in an TypeError.

Now, let's get into how we can actually call these function during run-time when a key is pressed. With wn.listen() initiated, now all you'll need is wn.onkey, or wn.onkeypress. Taking the function above, if you want a new turtle to be created every time the user presses the SPACE key:

wn.onkey(create_new_turtle, 'space')

Do you see why we can't pass positional arguments into the function? As you can see, when using the function inside wn.onkey, we do not call it (as in, we did not add brackets on the right side of the function); wn.onkey does it for us.

Taking what we've learned, let's see them in action:

import turtle

#Screen
wn = turtle.Screen()
wn.bgcolor("lightblue")

#Turtle Player
spaceship = turtle.Turtle()
spaceship.color("red")
spaceship.penup()

#Constant
speed = 1

def up():
    spaceship.setheading(90)

def down():
    spaceship.setheading(270)
    
def left():
    spaceship.setheading(180)

def right():
    spaceship.setheading(0)

wn.listen()
wn.onkey(up, 'Up')
wn.onkey(down, 'Down')
wn.onkey(left, 'Left')
wn.onkey(right, 'Right')

while True:
    spaceship.forward(speed)

Can you guess what this does? It's pretty obvious; when the user hits the 'Up' arrow, the up function defined above will be called, when the user hits the 'Down' arrow, the down function defined above will be called, an so on.

Defining a whole function for a single command doesn't seem right, and we can't just do

wn.onkey(spaceship.setheading(90), 'Up')
wn.onkey(spaceship.setheading(270), 'Down')
wn.onkey(spaceship.setheading(180), 'Left')
wn.onkey(spaceship.setheading(0), 'Right')

Like in the most upvoted answer, the solution is to use lambda, where the error causing code right above can be corrected to

wn.onkey(lambda: spaceship.setheading(90), 'Up')
wn.onkey(lambda: spaceship.setheading(270), 'Down')
wn.onkey(lambda: spaceship.setheading(180), 'Left')
wn.onkey(lambda: spaceship.setheading(0), 'Right')

Lastly, if you want your turtle to turn 90 degrees maximum on each turn, you can avoid 180 degree turn with if statements in the functions (which, as the functions get more advanced, it is better to use def to define the functions instead of using lambda):

import turtle

#Screen
wn = turtle.Screen()
wn.bgcolor("lightblue")

#Turtle Player
spaceship = turtle.Turtle()
spaceship.color("red")
spaceship.penup()

#Constant
speed = 1

def up():
    if spaceship.heading() != 270:
        spaceship.setheading(90)

def down():
    if spaceship.heading() != 90:
        spaceship.setheading(270)
    
def left():
    if spaceship.heading() != 0:
        spaceship.setheading(180)

def right():
    if spaceship.heading() != 180:
        spaceship.setheading(0)

wn.listen()
wn.onkey(up, 'Up')
wn.onkey(down, 'Down')
wn.onkey(left, 'Left')
wn.onkey(right, 'Right')

while True:
    spaceship.forward(speed)

Test run:

enter image description here

Red
  • 26,798
  • 7
  • 36
  • 58
0

I have solution for you. The code is not ideal but it works and you can work on it. You have to be aware, that the turtle has deafult position, and you have to tune it. That is why I pointed in the setup method my turtle to look up.

Now, You have to remember, that right(deg) and left(deg) methods are saying "please turn around by such amount of degree in given direction".

So keep in mind, what was your last direction.

Key for understanding here is that You don't have access to any absolute here. You only can change something in relation to your current posisition. So, you can't turn left, but if you know previous direction, you know how many degree you should turn your turtle to actually turn left.

My working code for your task is:

import turtle
wn = turtle.Screen()

last_pressed = 'up'

def setup(col, x, y, w, s, shape):
  turtle.up()
  turtle.goto(x,y)
  turtle.width(w)
  turtle.turtlesize(s)
  turtle.color(col)
  turtle.shape(shape)
  turtle.lt(90)
  turtle.down()
  wn.onkey(up, "Up")
  wn.onkey(left, "Left")
  wn.onkey(right, "Right")
  wn.onkey(back, "Down")
  wn.onkey(quitTurtles, "Escape")
  wn.listen()
  wn.mainloop()




#Event handlers
def up():
  global last_pressed
  if last_pressed == 'left':
    turtle.rt(90)
    turtle.fd(10)
  elif last_pressed == 'right':
    turtle.lt(90)
    turtle.fd(10)
  elif last_pressed == 'up':
    turtle.fd(10)
  else:
    turtle.rt(180)
    turtle.fd(10)

  last_pressed = 'up'

def left():
  global last_pressed
  if last_pressed == 'left':
    turtle.fd(10)
  elif last_pressed == 'right':
    turtle.lt(180)
    turtle.fd(10)
  elif last_pressed == 'up':
    turtle.lt(90)
    turtle.fd(10)
  else:
    turtle.rt(90)
    turtle.fd(10)

  last_pressed = 'left'


def right():
  global last_pressed
  if last_pressed == 'left':
    turtle.rt(180)
    turtle.fd(10)
  elif last_pressed == 'right':
    turtle.fd(10)
  elif last_pressed == 'up':
    turtle.rt(90)
    turtle.fd(10)
  else:
    turtle.lt(90)
    turtle.fd(10)

  last_pressed = 'right'

def back():
  global last_pressed
  if last_pressed == 'left':
    turtle.lt(90)
    turtle.fd(10)
  elif last_pressed == 'right':
    turtle.rt(90)
    turtle.fd(10)
  elif last_pressed == 'up':
    turtle.rt(180)
    turtle.fd(10)
  else:
    turtle.fd(10)

  last_pressed = 'down'

def quitTurtles():
  wn.bye()

setup("blue",-200,200,2,2,"turtle")

Please bear in mind that it takes some time for the turtle to actually turn, so don't press keys, click them.

I think that You can go further with this.

jedruniu
  • 520
  • 4
  • 13
  • 3
    This should work, but I downvoted because of the implementation (last_pressed + hardcoded turning). There are much better ways to do this. Either `setheading` like the other answer suggests, or getting a difference between the current heading and the desired one and turning the right amount could be much cleaner. This example violates the Don't Repeat Yourself idea a lot. – viraptor Apr 18 '17 at 02:13