I can't move two turtle objects independently using the onkeypress function - python

I would like to move two different turtle objects(slider_1 and slider_2) upwards and downwards using the onkeypress function from Python's turtle module.
My issue is that when I move slider_1 then I can't move slider_2 without releasing the key for slider_1, and vice-versa. How can I fix my code, so that I can control slider_1 and slider_2 independently, without any pauses in movement.
main.py:
from turtle import Screen, Turtle
from slider import Slider_1, Slider_2
HEIGHT = 800
WIDTH = 600
screen = Screen()
screen2 = Screen()
screen.bgcolor("black")
screen.title("PONG")
screen.setup(height=HEIGHT, width=WIDTH)
screen.listen()
slider_1 = Slider_1()
screen.onkeypress(key="Up", fun=slider_1.up)
screen.onkeypress(key="Down", fun=slider_1.down)
slider_2 = Slider_2()
screen.onkeypress(key="w", fun=slider_2.up)
screen.onkeypress(key="s", fun=slider_2.down)
screen.exitonclick()
Slider.py:
from turtle import Turtle
SIZE = 20
SPEED = 10
class Slider(Turtle):
def __init__(self):
super().__init__()
self.penup()
self.speed("fastest")
self.shape("square")
self.shapesize(SIZE)
self.shapesize(stretch_wid=1, stretch_len=3)
# self.move = True
def up(self):
self.setheading(90)
if self.ycor() >= 360:
return
else:
self.forward(SPEED)
def down(self):
self.setheading(270)
if self.ycor() <= -360:
return
else:
self.forward(SPEED)
class Slider_1(Slider):
def __init__(self):
super().__init__()
self.color("white")
self.goto(x=-600 / 2 + SIZE, y=0)
self.setheading(90)
class Slider_2(Slider):
def __init__(self):
super().__init__()
self.color("blue")
self.goto(x=600 / 2 - SIZE, y=0)
self.setheading(90)

The issue here is the hardware key handling. You are only checking for keypress. The keyboard "key repeat" handler only triggers key repeating for the last key that was pressed.
To do what you want, your code is going to get more complicated. You will need to have key press and key release handlers for all 4 of those buttons. The key press handlers will need to set a flag, and the key release handlers will clear the flag. Then, you will need a periodic function (using screen.ontimer) running in a timer that checks the flags: "Is left moving up? Is left moving down? Is right moving up? Is right moving down?" Handle all four possibilities, then exit and wait for the next call.

Related

Python Turtle `While True` in Event Driven Environment

I have read in several posts here on Stack Overflow that "An event-driven environment like turtle should never have while True: as it potentially blocks out events (e.g. keyboard)."
Here is a Python Turtle program that seems to work fine, but which uses the while True: construct.
Could someone please explain why this approach is wrong, what problems is creates and what the correct way is to achieve the same result?
import turtle
import time
def move_snake():
"""
This function updates the position of the snake's head according to its direction.
"""
if head.direction == "up":
head.sety(head.ycor() + 20)
def go_up():
"""
callback for up key.
"""
if head.direction != "down":
head.direction = "up"
# Set up screen
screen = turtle.Screen()
screen.tracer(0) # Disable animation so we can update screen manually.
# Event handlers
screen.listen()
screen.onkey(go_up, "Up")
# Snake head
head = turtle.Turtle()
head.shape("square")
head.penup()
head.direction = "stopped" # Cheeky use of instance property to avoid global variable.
while True:
move_snake()
screen.update()
time.sleep(0.2)
turtle.done()
I can provide a crude example. Run your code above as-is. Start the snake moving. Click on the window's close button. Count the number of lines of error messages you get in the console. It could easily exceed two dozen.
Now try this same experiment with the following code which eliminates the while True::
from turtle import Screen, Turtle
class Head(Turtle):
def __init__(self):
super().__init__(shape="square")
self.penup()
self.direction = "stopped"
def move_snake():
if head.direction == "up":
head.sety(head.ycor() + 20)
screen.update()
screen.ontimer(move_snake, 200)
def go_up():
if head.direction != "down":
head.direction = "up"
# Snake head
head = Head()
# Set up screen
screen = Screen()
screen.tracer(0) # Disable animation so we can update screen manually.
# Event handlers
screen.onkey(go_up, "Up")
screen.listen()
move_snake()
screen.mainloop()
Your count of error messages should drop to zero. This is because the window close event happens in the same event loop as turtle motion.
There are other effects that you'll end up chasing later. This is just a simple, easily visible one.

Couldn't get rid of (_tkinter.TclError: bad event type or keysym "UP") problem

I am running a Linux mint for the first time . I tried coding a python problem but for two days I am continiously facing problems due to Linux interface please
This is my code:-
import turtle
import time
boxsize=200
caught=False
score=0
#function that are called in keypress
def up():
mouse.forward(10)
checkbound()
def left():
move.left(45)
def right():
move.right(45)
def back():
mouse.backward(10)
checkbound()
def quitTurtles():
window.bye()
#stop the mouse from leaving the square set size
def checkbound():
global boxsize
if mouse.xcor()>boxsize:
mouse.goto(boxsize, mouse.ycor())
if mouse.xcor()<-boxsize:
mouse.goto(-boxsize, mouse.ycor())
if mouse.ycor()>boxsize:
mouse.goto(mouse.xcor(),boxsize)
if mouse.ycor()<-boxsize:
mouse.goto(mouse.xcor(),-boxsize)
#set up screen
window=turtle.Screen()
mouse=turtle.Turtle()
cat=turtle.Turtle()
mouse.penup()
mouse.penup()
mouse.goto(100,100)
#add key listeners
window.onkeypress(up ,'UP')
window.onkeypress(right ,'left')
window.onkeypress(left ,'Right')
window.onkeypress(back ,'DOWN')
window.onkeypress(quitTurtles, "Escape")
difficulty=window.numinput("difficulty","Enter a difficulty from 1 to 5",minval=1,maxval=5)
window.listen()
#main loop
#note how it changes with difficulty
while not caught:
cat.setheading(cat.towards(mouse))
cat.forward(8+diffficulty)
score=score+1
if cat.distance(mouse)<5:
caught=true
time.sleep(0.2-(0.1*difficulty))
window.textinput("GAME OVER","WELL DONE YOU SCORED:"+str(score*difficulty))
window.bye()
This code has several problems, many of which will keep it from running correctly:
Substituted move for mouse:
def up():
mouse.forward(10)
checkbound()
def left():
move.left(45)
Unnecessary global declaration as boxsize is not assigned:
def checkbound():
global boxsize
In code copy-and-paste, didn't change mouse to cat:
mouse=turtle.Turtle()
cat=turtle.Turtle()
mouse.penup()
mouse.penup()
The difficulty variable not spelled consistently:
cat.forward(8+diffficulty)
time.sleep(0.2-(0.1*difficulty))
Incorrect case for boolean:
caught=true
As noted in comments, total inconsistency in key naming case:
window.onkeypress(right ,'left')
window.onkeypress(left ,'Right')
window.onkeypress(back ,'DOWN')
Bigger picture issues are use of sleep() in an event-driven environment and lack of drawn boundaries so player knows the limits. Rather than address these issues one by one in SO questions, let's rework this code to work within the turtle event environment and be playable as a game:
from turtle import Screen, Turtle
BOX_SIZE = 600
# functions that are called in keypress
def up():
mouse.forward(15)
checkbound()
def left():
mouse.left(45)
def right():
mouse.right(45)
def back():
mouse.backward(15)
checkbound()
def checkbound():
''' stop the mouse from leaving the square set size '''
if mouse.xcor() > BOX_SIZE/2:
mouse.goto(BOX_SIZE/2, mouse.ycor())
elif mouse.xcor() < -BOX_SIZE/2:
mouse.goto(-BOX_SIZE/2, mouse.ycor())
if mouse.ycor() > BOX_SIZE/2:
mouse.goto(mouse.xcor(), BOX_SIZE/2)
elif mouse.ycor() < -BOX_SIZE/2:
mouse.goto(mouse.xcor(), -BOX_SIZE/2)
def move():
global score
cat.setheading(cat.towards(mouse))
cat.forward(2 * difficulty)
score += 1
if cat.distance(mouse) < 5:
screen.textinput("GAME OVER", "WELL DONE YOU SCORED: {}".format(score * difficulty))
screen.bye()
else:
screen.ontimer(move, 200 - 100 * difficulty)
score = 0
# set up screen
screen = Screen()
marker = Turtle()
marker.hideturtle()
marker.penup()
marker.goto(-BOX_SIZE/2, -BOX_SIZE/2)
marker.pendown()
for _ in range(4):
marker.forward(BOX_SIZE)
marker.left(90)
difficulty = int(screen.numinput("difficulty", "Enter a difficulty from 1 to 5", minval=1, maxval=5))
cat = Turtle()
cat.shapesize(2)
cat.penup()
mouse = Turtle()
mouse.penup()
mouse.goto(200, 200)
# add key listeners
screen.onkeypress(up, 'Up')
screen.onkeypress(right, 'Left')
screen.onkeypress(left, 'Right')
screen.onkeypress(back, 'Down')
screen.onkeypress(screen.bye, 'Escape')
screen.listen()
screen.ontimer(move, 1000) # give player a chance to move hand from keyboard to mouse
screen.mainloop()

Conceptual bug in a turtle program using resetscreen()

Criteria I'm trying to meet:
The screen resets when the user presses the SPACEBAR, meaning the drawn lines go away and the unnamed turtle returns to the center but it doesn’t return to the default turtle color and shape!
""" A simple drawing program
Click and drag the center turtle to draw
Click the colorful buttons to change the
turtle's color,
and then draw different shapes.
Press the SPACEBAR key to reset the
drawing.
"""
from turtle import *
turtle_1 = Turtle()
turtle_2 = Turtle()
turtle_3 = Turtle()
def callback_1(x,y):
color("red")
shape("circle")
circle(100)
def callback_2(x,y):
color("blue")
shape("square")
circle(100,steps=4)
def callback_3(x,y):
color("green")
shape("triangle")
circle(100,steps=3)
def place_turtles():
turtle_1.color("red")
turtle_1.shape("circle")
turtle_1.penup()
turtle_1.goto(-200,-200)
turtle_2.color("blue")
turtle_2.shape("square")
turtle_2.penup()
turtle_2.goto(0,-200)
turtle_3.color("green")
turtle_3.shape("triangle")
turtle_3.penup()
turtle_3.goto(200,-200)
def start_over():
resetscreen()
place_turtles()
listen()
onkey(start_over, "space")
ondrag(goto)
place_turtles()
This code allows the user to drag the turtle, press buttons, and reset the screen when they press SPACEBAR. For some reason, though, resetting the screen also resets the color of the turtle. How can I prevent this from happening?
Basically what I want to happen is if, say, the user clicks on the blue square button, then resets the screen to hide the shape drawn by the button, all of the turtles return to their original positions, but the unnamed turtle does not change its previous color and shape. Let me know if I need to elaborate further.
With a "better late than never" attitude, I believe I've reworked your code to get the behavior you desired. I also reworked your drag logic to give you better drawing capability:
from turtle import Turtle, Screen, getturtle
def callback_1(x, y):
anonymous.color(*turtle_1.color())
anonymous.shape(turtle_1.shape())
anonymous.circle(100)
def callback_2(x, y):
anonymous.color(*turtle_2.color())
anonymous.shape(turtle_2.shape())
anonymous.circle(100, steps=4)
def callback_3(x, y):
anonymous.color(*turtle_3.color())
anonymous.shape(turtle_3.shape())
anonymous.circle(100, steps=3)
def setup_turtles():
turtle_1.onclick(callback_1)
turtle_1.color("red")
turtle_1.penup()
turtle_1.goto(-200, -200)
turtle_2.onclick(callback_2)
turtle_2.color("blue")
turtle_2.penup()
turtle_2.goto(0, -200)
turtle_3.onclick(callback_3)
turtle_3.color("green")
turtle_3.penup()
turtle_3.goto(200, -200)
def start_over():
colors = anonymous.color()
anonymous.reset()
anonymous.color(*colors)
def drag_handler(x, y):
anonymous.ondrag(None) # disable event inside event handler
anonymous.goto(x, y)
anonymous.ondrag(drag_handler) # reenable event on event handler exit
anonymous = getturtle()
anonymous.ondrag(drag_handler)
turtle_1 = Turtle(shape="circle")
turtle_2 = Turtle(shape="square")
turtle_3 = Turtle(shape="triangle")
setup_turtles()
screen = Screen()
screen.onkey(start_over, "space")
screen.listen()
screen.mainloop()
Many of the changes are just for personal style reasons. The two key changes are: just reset the anonymous turtle itself, not the screen; rather than use goto as an event handler, wrap it in a function that turns off drag events during the goto call.

Python - Keyboard Multiple Turtle Objects

I would like to create a program in which a Turtle object responds to key presses. I can do this, but I can't seem to understand how to move a second Turtle object, which is controlled by the computer, while the first one is moving. Any help would be appreciated.
Here is my code:
from turtle import *
from Tkinter import Tk
root = Tk()
root.withdraw()
turtle = Turtle()
def h1():turtle.forward(10)
def h2():turtle.left(45)
def h3():turtle.right(45)
def h4():turtle.back(10)
def h5(root=root):root.quit()
onkey(h1,"Up")
onkey(h2,"Left")
onkey(h3,"Right")
onkey(h4,"Down")
onkey(h5,"q")
listen()
root.mainloop()
Insert a second turtle before listen() that moves with keys w,a,s,d:
turtle2 = Turtle()
def h11():turtle2.forward(10)
def h21():turtle2.left(45)
def h31():turtle2.right(45)
def h41():turtle2.back(10)
onkey(h11,"w")
onkey(h21,"a")
onkey(h31,"d")
onkey(h41,"s")
I can't seem to understand how to move a second Turtle object, which
is controlled by the computer, while the first one is moving.
Below is some minimal code that does as you describe. Green turtle Pokey is computer controlled while red turtle Hokey is user controlled (click on the window first so your keystrokes are heard):
from turtle import Turtle, Screen
def move_pokey():
pokey.forward(10)
x, y = pokey.position()
if not (-width/2 < x < width/2 and -height/2 < y < height/2):
pokey.undo()
pokey.left(90)
screen.ontimer(move_pokey, 100)
hokey = Turtle(shape="turtle")
hokey.color("red")
hokey.penup()
pokey = Turtle(shape="turtle")
pokey.setheading(30)
pokey.color("green")
pokey.penup()
screen = Screen()
width = screen.window_width()
height = screen.window_height()
screen.onkey(lambda: hokey.forward(10), "Up")
screen.onkey(lambda: hokey.left(45), "Left")
screen.onkey(lambda: hokey.right(45), "Right")
screen.onkey(lambda: hokey.back(10), "Down")
screen.onkey(screen.bye, "q")
screen.listen()
screen.ontimer(move_pokey, 100)
screen.mainloop()
This is not finished code (shutdown of the timer event should be cleaner, Hokey's handlers should lock out additional events while running, etc.) but should give you a basic idea of how to go about it.

Pyglet: fireball shooter, fireballs keep accelerating whenever I press a specified key instead of keeping constant speed

How do you make this code work? Just have pyglet installed and change "fireball.png" with the name of an image stored in the directory where you saved this code to a file.
import pyglet
class Fireball(pyglet.sprite.Sprite):
def __init__(self, batch):
pyglet.sprite.Sprite.__init__(self, pyglet.resource.image("fireball.png"))
# replace "fireball.png" with your own image stored in dir of fireball.py
self.x = 10 # Initial x coordinate of the fireball
self.y = 10 # Initial y coordinate of the fireball
class Game(pyglet.window.Window):
def __init__(self):
pyglet.window.Window.__init__(self, width = 315, height = 220)
self.batch_draw = pyglet.graphics.Batch()
self.fps_display = pyglet.clock.ClockDisplay()
self.fireball = []
def on_draw(self):
self.clear()
self.fps_display.draw()
self.batch_draw.draw()
if len(self.fireball) != 0: # Allow drawing of multiple
for i in range(len(self.fireball)): # fireballs on screen
self.fireball[i].draw() # at the same time
def on_key_press(self, symbol, modifiers):
if symbol == pyglet.window.key.A:
self.fireball.append(Fireball(batch = self.batch_draw))
pyglet.clock.schedule_interval(func = self.update, interval = 1/60.)
print "The 'A' key was pressed"
def update(self, interval):
for i in range(len(self.fireball)):
self.fireball[i].x += 1 # why do fireballs get faster and faster?
if __name__ == "__main__":
window = Game()
pyglet.app.run()
This code creates a black background screen, where the fps are displayed and a fireball is shot along the x direction from the position (10, 10) whenever you press the A key.
You will notice that the more fireballs you shoot, the faster all fireballs will start to go.
Questions:
Why do the fireballs go faster and faster each time I press A ?
How should I stop the fireballs from accelerating each time I press A ?
The fireball goes faster and faster because every time you press the A you add another call of self.update to the scheduler. So self.update is called more and more times each time resulting in more updates of the position. To fix that move the line below to the __init__().
pyglet.clock.schedule_interval(func = self.update, interval = 1/60.)

Categories