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 ...
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'm creating a simple game with Python using turtle package.
My aim is to have some balloons on the screen running from right to left and then when one of them is clicked to make it disappear.
What I have going wrong is that when I click one balloon, all of them are disappeared!
Here is my code
1- Main
from turtle import Screen
from balloon import Balloon
import time
screen = Screen()
screen.title('Balloons Nightmare')
screen.setup(width=600, height=600)
screen.bgpic(picname='sky-clouds.gif')
screen.tracer(0)
balloons_manager = Balloon()
current_x = 0
current_y = 0
screen.listen()
screen.onclick(fun=balloons_manager.explode_balloon, btn=1)
game_is_on = True
while game_is_on:
time.sleep(0.1)
screen.update()
balloons_manager.create_balloon()
balloons_manager.move_balloon()
screen.exitonclick()
2- balloon module
import random
from turtle import Turtle
COLORS = ["red", "yellow", "green", "blue", "black"]
MOVEMENT_SPEED = 2
class Balloon:
def __init__(self):
self.all_balloons = []
self.balloon_speed = MOVEMENT_SPEED
self.x = 0
self.y = 0
self.hidden = None
def create_balloon(self):
random_choice = random.randint(1, 9)
if random_choice == 1:
new_balloon = Turtle("circle")
new_balloon.penup()
new_balloon.turtlesize(stretch_wid=2, stretch_len=0.75)
new_balloon.color(random.choice(COLORS))
random_y_cor = random.randint(-50, 280)
new_balloon.goto(320, random_y_cor)
self.hidden = new_balloon.isvisible()
self.all_balloons.append(new_balloon)
def move_balloon(self):
for balloon in self.all_balloons:
balloon.backward(self.balloon_speed)
def explode_balloon(self, x, y):
for balloon in range(len(self.all_balloons)):
print(self.all_balloons[balloon].position())
self.all_balloons[balloon].hideturtle()
# for balloon in self.all_balloons:
# balloon.hideturtle()
I tried so many changes but nothing helped me, example of what I tried so far
getting the current x,y coordinates of the balloon so that on click to hide only the one with these coordinates but didn't work for me or I did something wrong with it
Any hints will be appreciated.
Thank you
I fixed the issue by adding an inner function in create_balloon function
def create_balloon(self):
random_choice = random.randint(1, 9)
if random_choice == 1:
new_balloon = Turtle("circle")
new_balloon.penup()
new_balloon.turtlesize(stretch_wid=2, stretch_len=0.75)
new_balloon.color(random.choice(COLORS))
random_y_cor = random.randint(-50, 280)
new_balloon.goto(320, random_y_cor)
def hide_the_balloon(x, y):
return new_balloon.hideturtle()
new_balloon.onclick(hide_the_balloon)
self.all_balloons.append(new_balloon)
Here's your code triggered by the click handler:
def explode_balloon(self, x, y):
for balloon in range(len(self.all_balloons)):
print(self.all_balloons[balloon].position())
self.all_balloons[balloon].hideturtle()
This loops over all balloons and hides them unconditionally.
You probably want to use an if in there to only conditionally trigger the hiding behavior. Compare the x and y coordinates of the click against the current balloon in the loop. Only hide the balloon if the distance is less than a certain amount (say, the radius of the balloon).
Another approach is to use turtle.onclick to add a handler function that will be trigged when the turtle is clicked.
Related:
How to see if a mouse click is on a turtle in python
Python find closest turtle via mouse click
Why is my Python Turtle program slowing down drastically the longer it runs? (since you're never removing turtles and constantly adding new ones, this is a good thread to take a look at)
I have to import a turtle and make it draw a square. I have that step done but the next step is to make that square move around the screen using the arrow keys. I already added the code which should allow that to happen but the turtle is still not moving. It just appears on the screen and I'm pressing the arrow keys but nothing moves. I'm not sure what the error in my code is.
import turtle
t = turtle.Turtle
screen = turtle.Screen()
screen.setup(300,300)
screen.tracer(0)
def square():
for i in range(4):
turtle.forward(100)
turtle.left(90)
def move_up():
turtle.setheading(90) #pass an argument to set the heading of our turtle arrow
turtle.forward(15)
def move_right():
turtle.setheading(0) #the direction is east
turtle.forward(15)
def move_down():
turtle.setheading(270) #the direction is south
turtle.forward(15)
def move_left():
turtle.setheading(180) #the direction is west
turtle.forward(15)
while True :
turtle.clear()
square() #call function
screen.update() # only now show the screen, as one of the frames
screen.onkey(move_up, "Up")
screen.onkey(move_right, "Right")
screen.onkey(move_down, "Down")
screen.onkey(move_left, "Left")
screen.listen()
Your main problem is that you tried to write the entire program at once: you didn't bother to test the pieces, and now you're in a situation where you have to fix several errors in order to get any useful output. Back up, program one part at a time, and test each part before proceeding.
Your immediate problem is that you have not bound keys to actions when you need them:
while True :
turtle.clear()
square() #call function
screen.update() # only now show the screen, as one of the frames
screen.onkey(move_up, "Up")
screen.onkey(move_right, "Right")
screen.onkey(move_down, "Down")
screen.onkey(move_left, "Left")
screen.listen()
You have an infinite loop in front of your bindings: you never get to this code, so there's no attention to the arrow keys, and your screen isn't listening. You have to do these things before your loop.
You also seem to be confused about which methods apply to an object, and which you call as class invocations. You have not instantiated a Turtle object to take movement commands.
I recommend that you return to your class materials and work more slowly through each technique. Add each to your program as you learn it ... and test before moving on.
I've been having trouble with getting my bullet (named asteroid) and my zombie. more specifically, I'm having trouble getting my game to register collision between these two turtles, it gets even weirder when you reverse the lesser than symbol into a greater than symbol. I do not know what is up with my code, any help is appreciated.(I have included the entirety of my code, since I am unsure of the source of the problem, I just know which part isn't working, I would recommend starting there.)
#the bullet that doesn't hit it's target
#Turtle Graphics game
import turtle
import random
import time
#set up screen
wn = turtle.Screen()
wn.bgcolor("grey")
finish= False
def randor1():
rand=random.randint(-280,280)
def randor2():
rand=random.randint(50,280)
def check_target_pos():
#side boundary checking
if zombie.xcor() > 280 or zombie.xcor() <- 280:
zombie.right(180)
#top/bottom boundary checking
if zombie.ycor() > 280 or zombie.ycor() <- 280:
zombie.right(180)
def check_turtle_pos():
#side boundary checking
if asteroid.xcor() > 280 or asteroid.xcor() <- 280:
asteroid.right(180)
#top/bottom boundary checking
if asteroid.ycor() > 280 or asteroid.ycor() <- 280:
asteroid.right(180)
def new_asteroid():# the turtle bullet,will change the name later on
for i in range(50):
asteroid.forward(10)
asteroid.goto(0,0)
def k2():#turn turtle left
asteroid.left(45)
def k3():#turn turtle right
asteroid.right(45)
#Draw border for arena
mypen = turtle.Turtle()
mypen.penup()
mypen.setposition(-300,-300)
mypen.pendown()
mypen.pensize(3)
for side in range(4):
mypen.forward(600)
mypen.left(90)
mypen.hideturtle()
#create turtle turtle, again will change name later
asteroid = turtle.Turtle()
asteroid.color("green")
asteroid.shape("turtle")
asteroid.penup()
asteroid.speed(0)
#create turtle zombie
def zombies():
global zombie
zombie= turtle.Turtle()
zombie.hideturtle()
zombie.color("green")
zombie.shape("circle")
zombie.penup()
zombie.speed(0)
x= random.randint(-280,280)
y= random.randint(50,280)
zombie.goto(x,y)
zombie.showturtle()
zombies()
while (finish!= True):
check_target_pos()
check_turtle_pos()
zombie.forward(1)
def end():
finish==True
wn.bye()
if asteroid.distance(zombie)<40: #problem area
end()
wn.onkey(new_asteroid, "space")#shoot button.
wn.onkey(k2, "Left")#turn left button
wn.onkey(k3, "Right") #turn right button
wn.onkey(end, "e")#exit
wn.listen()#so all the on key functions above work
Your code is somewhat a mess and doesn't facilitate the event-driven nature of turtle. There is no place for while True: loops nor time.sleep() or such in this environment. You also seem to be confusing your gun with your bullet (you're flinging your gun into space to kill the zombie, not its bullet!)
I've rewritten your code below using an event-based model with ontimer(). This is a single bullet implementation (you can't fire again until your bullet hits something or disappears into the distance):
from turtle import Screen, Turtle
from random import randint
def check_bullet_position():
if bullet.distance(turtle) > 240:
bullet.hideturtle()
if bullet.distance(zombie) < 20:
bullet.hideturtle()
reset_zombie()
def shoot_bullet():
if bullet.isvisible():
return
bullet.setheading(turtle.heading())
bullet.setposition(turtle.position())
bullet.forward(15)
bullet.showturtle()
def turn_left():
turtle.left(20)
def turn_right():
turtle.right(20)
def reset_zombie():
while zombie.distance(turtle) < 240:
x = randint(-280, 280)
y = randint(-280, 280)
zombie.goto(x, y)
zombie.setheading(zombie.towards(turtle))
def move():
zombie.forward(1)
if bullet.isvisible():
bullet.forward(2)
check_bullet_position()
screen.update()
if turtle.distance(zombie) > 20:
screen.ontimer(move)
# Set up screen
screen = Screen()
screen.tracer(False)
screen.bgcolor('grey')
# Draw border for arena
pen = Turtle()
pen.hideturtle()
pen.pensize(3)
pen.penup()
pen.setposition(-300, -300)
pen.pendown()
for _ in range(4):
pen.forward(600)
pen.left(90)
# Create turtle turtle
turtle = Turtle()
turtle.shape('turtle')
turtle.color('green')
turtle.penup()
# Create turtle bullet
bullet = Turtle()
bullet.hideturtle()
bullet.shape('circle')
bullet.shapesize(0.5)
bullet.color('yellow')
bullet.penup()
# Create turtle zombie
zombie = Turtle()
zombie.shape('circle')
zombie.color('red')
zombie.penup()
reset_zombie()
screen.onkey(shoot_bullet, 'space')
screen.onkey(turn_left, 'Left')
screen.onkey(turn_right, 'Right')
screen.onkey(screen.bye, 'e') # exit
screen.listen() # enable onkey() functions above
move()
screen.mainloop()
This should give you a starting point for building the game you envision. It is possible to write a multiple bullet implementation, it just take a little more thought and design work.
Your searching for a collision outside of the loop where you shot the projectile.
The easiest way to make your code work is to add the collision detection to the new_asteroid() function. But I don't think that's the right way.
Your technique is what is referred to as blocking. while you're projectile is moving, no other code is running. hence your collision detection is not working. change Line 38 to this.
def new_asteroid():# the turtle bullet,will change the name later on
for i in range(50):
asteroid.forward(10)
if asteroid.distance(zombie) < 40: # problem area
end()
asteroid.goto(0,0)
To create a non-blocking version of this function you would have to divide the for loop up to increments called by your main loop. so that your projectile moves once per iteration, instead of all at once.
Your program also crashes on exit. May I recommend re organizing your code into functions, then execution. it makes it much more readable. Happy gaming.
My Python turtle game obstacle freezes when the player receives input and continues afterward:
# Written in Python 3
# By
# February 4th, 2019
# Purpose: Mimic the no-wifi Google Chrome dinosaur game
# Bonus 6
import turtle # Used for graphics
from turtle import Screen # Used for inputs
import random# Used to generate a random number
import time
# Creates a turtle with the proper size and color
player = turtle.Turtle()
player.shape("square")
player.color("#cc0000")
player.turtlesize(1.5, 1.5)
player.penup()
player.goto(-50, 0)
# Creates the ground
ground = turtle.Turtle()
ground.shape("square")
ground.turtlesize(0.25, 300)
ground.penup()
ground.goto(0, -18)
# This function makes the square jump, unbinds 'Up', return to the ground, then rebinds 'Up'
def jump():
Screen().onkey(null, 'Up')
player.speed(2)
if player.ycor() == 0:
player.goto((player.xcor()), (player.ycor()+100))
print("G")
player.speed(1.5)
player.goto(-50, 0)
Screen().onkey(jump, 'Up')
# Blank function
def null():
n =1
Screen().onkey(jump, 'Up')
Screen().listen()
# Ignore this
x = 3 * random.sample(range(4), 4)
print (x)
print (x[1])
# Creating obstacles (not finished, just moves)
obst1 = turtle.Turtle()
obst1.shape("square")
obst1.turtlesize(3, 2)
obst1.penup()
obst1.goto(300,0)
obst1.speed(1)
obst1.setx(-300)
I want the obstacle to continue moving while I jump. I only have Python 3 and its standard modules. I cannot download PIP or anything else like it, for some reason. I'm trying to mimic the dinosaur game from Google Chrome. I'm new to this sort of thing so please explain any suggestions in as much detail as possible. Thanks a lot!
The way you wrote your code, you can only move one turtle at a time. Below is a rework of your code where the user controls the player, but the obstacle is controlled by a timer so they can move at the same time:
from turtle import Screen, Turtle
# This function unbinds 'Up', makes the square jump, return to the ground, then rebinds 'Up'
def jump():
screen.onkey(None, 'Up')
if player.ycor() == 0:
player.forward(150)
player.backward(150)
screen.onkey(jump, 'Up')
def move():
obstacle.forward(6)
if obstacle.xcor() > -400:
screen.ontimer(move, 100)
# Creates the ground
ground = Turtle('square')
ground.turtlesize(0.25, 45)
ground.penup()
ground.sety(-15)
# Creates a turtle with the proper size and color
player = Turtle('square')
player.color('red')
player.turtlesize(1.5)
player.speed('slowest')
player.penup()
player.setx(-100)
player.setheading(90)
screen = Screen()
screen.onkey(jump, 'Up')
screen.listen()
# Creating obstacles (not finished, just moves)
obstacle = Turtle('square')
obstacle.turtlesize(3, 1.5)
obstacle.speed('fastest')
obstacle.penup()
obstacle.setposition(400, 15)
obstacle.setheading(180)
move()
screen.mainloop()
This should more closely simulate the type of motion you're trying to achieve.