Conceptual bug in a turtle program using resetscreen() - python

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.

Related

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

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.

Use onkey() to do multiple functions with Python turtle

I'm trying to write a basic turtle drawing game/program and I've been using onkey(function, "key") to have the user input keystrokes. Well I wanted the user to be able to change the width of the pen by either hitting the up key to increase the width by one, or the down key to decrease the width by one. I know I need some kind of loop, but I don't really know where to implement it.
Here's a simple example that will make the turtle walk in a continuous circle while you press up and down arrows to change the pen width:
from turtle import Turtle, Screen
def larger():
size = turtle.pensize()
if size < 10:
turtle.pensize(size + 1)
def smaller():
size = turtle.pensize()
if size > 1:
turtle.pensize(size - 1)
def move():
turtle.circle(150, extent=3)
screen.ontimer(move, 100)
turtle = Turtle()
screen = Screen()
screen.onkey(larger, "Up")
screen.onkey(smaller, "Down")
screen.listen()
move()
screen.mainloop()
Make sure you click on the window first to make it the key listener.
I think you can't, but you can call the function insde the function you bind to the key:
from turtle import *
def function1():
do_that = "do that"
print(do_that)
def function2():
do_this = "do this"
print(do_this)
function1()
onkey(function2, "space")
do this
do that
It worked for me ;)

making an endpoint in turtle graphics

I'm making a maze with turtle graphics for a class project and I have one more main thing to complete before I'm finished...
I've created a second "turtle" to make a box at the endpoint. So the objective is to finish the maze and get the turtle in the box. But I am unsure how to make the box an actual endpoint and have a message pop up.
Here is my code:
from turtle import Turtle, Screen
screen = Screen()
screen.setup(650, 850)
screen.title("Turtle Keys")
screen.bgpic('scooby_doo_maze.gif')
move = Turtle(shape="triangle")
move.penup()
move.setx(-150)
move.sety(200)
move.pendown()
move.pensize(5)
box = Turtle(shape="square")
box.hideturtle()
box.speed(0)
box.penup()
box.setx(150)
box.sety(-190)
box.pendown()
box.right(90)
box.forward(100)
box.right(90)
box.forward(100)
box.right(90)
box.forward(100)
box.right(90)
box.forward(100)
def keyUp():
move.forward(12)
def keyLeft():
move.left(90)
def keyRight():
move.right(90)
def keyDown():
move.backward(12)
def keyReset():
move.reset()
move.penup()
move.setx(-150)
move.sety(200)
move.pendown()
move.pensize(5)
screen.onkey(keyUp, "Up")
screen.onkey(keyLeft, "Left")
screen.onkey(keyRight, "Right")
screen.onkey(keyDown, "Down")
screen.onkey(keyReset, "r")
screen.listen()
screen.exitonclick()
We just need to add few features. First, instead of drawing the end point with turtle box, we make turtle box the endpoint by expanding the turtle itself via box.shapesize(). This way, we can use move.distance(box) to determine if move is near the center of box.
Second, we need a function called by all the movement functions to test if above distance is close enough and then invoke the following:
Third, we introduce screen.textinput() to let the play know they've succeeded and offer then the option to play again, or quit. I've reworked your code below to introduce these additions and tweak it a bit for style:
from turtle import Turtle, Screen
screen = Screen()
screen.setup(650, 850)
screen.title("Turtle Keys")
screen.bgpic('scooby_doo_maze.gif')
def insideBox():
if move.distance(box) < 60:
play_again = screen.textinput("Success!", "Play again?")
if play_again and play_again.lower().startswith('y'):
keyReset()
else:
screen.bye()
def keyUp():
move.forward(12)
insideBox()
def keyLeft():
move.left(90)
def keyRight():
move.right(90)
def keyDown():
move.backward(12)
insideBox()
def keyReset():
move.reset()
move.penup()
move.goto(-150, 200)
move.pendown()
move.pensize(5)
screen.listen() # it's here because screen.textinput() unsets it
screen.onkey(keyUp, "Up")
screen.onkey(keyLeft, "Left")
screen.onkey(keyRight, "Right")
screen.onkey(keyDown, "Down")
screen.onkey(keyReset, "r")
move = Turtle(shape="triangle")
keyReset()
box = Turtle(shape="square")
box.color("black", "white")
box.shapesize(5, 5, 5)
box.penup()
box.goto(150, -190)
screen.mainloop()
This is a situation where I would avoid screen.exitonclick() as you need to click the window to get it to listen and easily end up closing it! Using screen.mainloop() should be sufficient and let the user close the window by not choosing to play again or using the window controls.

How to find a turtle that exceeds a certain point or a line?

I want to write a turtle graphics program where, two turtles (I have named them 'tess' and 'alex') go forward on pressing specific keys ('t' for tess and 'a' for alex) and one turtle wins who passes a line that is set to (0, 350). That line itself is another turtle that forwards 400 towards east.
I have done pretty much of this, and wondering how to win a turtle that exceeds the line.
My segment of code is given below.
import turtle
t = turtle.Turtle()
wn = turtle.Screen()
wn.setup(400, 500)
wn.bgcolor("lightgreen")
wn.screensize(400, 500)
wn.setworldcoordinates(0, 0, 400, 500)
t.penup()
t.goto(0, 350)
t.pendown()
t.forward(400)
# tess
tess = turtle.Turtle()
tess.color("purple")
tess.penup()
tess.forward(100)
tess.pendown()
tess.left(90)
tess.forward(10)
# alex
alex = turtle.Turtle()
alex.color("blue")
alex.penup()
alex.forward(200)
alex.pendown()
alex.left(90)
alex.forward(10)
# making key handlers
def h1():
tess.forward(50)
def h2():
alex.forward(50)
#----------------------------
# wiring up keypresses to the handlers
wn.onkey(h1, 't')
wn.onkey(h2, 'a')
def handler_for_tess(x, y):
wn.title("Tess clicked at {0}, {1}".format(x, y))
#tess.left(90)
tess.forward(50)
def handler_for_alex(x, y):
wn.title("Alex clicked at {0}, {1}".format(x, y))
#alex.right(84)
alex.forward(50)
# listening mouse clicks
tess.onclick(handler_for_tess)
alex.onclick(handler_for_alex)
# listeing key press
wn.listen()
You just need to test the Y position of the turtles against the Y position of the finish line turtle. A starting example to the "winner" code:
# making key handlers
def h1():
tess.forward(50)
if tess.pos()[1] >= t.pos()[1]:
wn.bgcolor("pink")
def h2():
alex.forward(50)
if alex.pos()[1] >= t.pos()[1]:
wn.bgcolor("lightblue")
When a given turtle crosses the finish line the background will change to a lighter shade of the turtle itself (if you make "tess" red instead of purple...)
To finish this, you'd need to lock out the key handlers once one turtle has one and then bump the scores, etc. and restart the game.

Pygame, Adding quit function to a rectangle

I have a problem I can't seem to solve. I have tried to find a way to add a function like quitting a program to a rectangle in Pygame. Here is the code I have so far. I would like to add an on click quit feature to the quit box in the corner.
def addRect(self):
self.rect = pygame.draw.rect(self.screen, (white), (300, 200, 300, 200), 2)
pygame.display.update()
def addText(self):
self.screen.blit(self.font.render('Quit', True, (84,84,84)), (550, 375))
pygame.display.update()
I have it working with the bits above and below and it does make a "Quit" Image at the bottom corner where I need it. However, I'm again stuck on the function!
I did something very similar to this, and the way that I handled it was I made a list in the main program that had all of the "inner windows" or whatever you want to call them. Whenever the main program received a signal from a window to close it, it deleted it from the list.
To make the signal, you will want to create a rect in the location where you want the button to be. Make a function for the "inner window" and have it test for that rect being clicked. If it is clicked, have the function return something like 'closed' or whatever you want. In the main program, say something like
for window in windows:
if window.update()=='closed':
windows.remove(window)
to remove any window which is closed.
EDIT:
After looking at your code a bit more in depth, it looks like how you're doing it won't work. To add a rect, you will need to have something in your main code to store whether or not the rect is there. To close the window, you will have to change that variable.
To check if the rect should be closed, make another rect that is where the text which should be closing the window is. When this text is clicked, have the function return something which should be interpreted by the main code to close the window.
A basic example is shown below.
The class:
def update(self):
#set up the test rect
text=self.font.render('Quit', True, (84,84,84))
textrect=text.get_rect()
textrect.topleft=(550, 375)
#see if the button is pressed
if textrect.collidepoint(pygame.mouse.get_pos()) and pygame.mouse.get_pressed()[0]:
return 'closed'
#render stuff
self.rect = pygame.draw.rect(self.screen, (white), (300, 200, 300, 200), 2)
self.screen.blit(text, (550, 375))
Note that I combined your two original classes into one, as I don't see a reason why you would ever want the rect but not the text or vise versa. This is a pretty simple change if you don't like it.
Also note that this will close the window if the mouse is pressed off the button, then dragged onto it. To avoid this, you will have to pass the list gotten from pygame.event.get() as an argument for the update function, and search through it for a MOUSEBUTTONDOWN event, but this would cause unnecessary complications that I tried to avoid.
The main code:
rectOn=False
while True:
if rectOn:
if rect.update()=='closed':
rectOn=False
To make the rect appear again after it has been closed, simply set rectOn to True.
A made a small example that you can work on. Instead of buttons returning something on click, they have a function assigned to the click.
import pygame,sys
from pygame.locals import *
screen_color = (0,0,0)
class Button:
def __init__(self,pos,action):
self.rect = pygame.Rect(pos)
self.action = action
def draw(self,screen):
pygame.draw.rect(screen, (255,255,255), self.rect)
def checkCollide(self,x,y):
return self.rect.collidepoint(x,y)
def do(self):
self.action()
def action():
global screen_color
screen_color = (255,255,0)
pygame.init()
screen = pygame.display.set_mode((640,360),0,32)
buttons = []
buttons.append(Button((10,10,50,50),action))
while True:
screen.fill(screen_color)
for button in buttons:
button.draw(screen)
pygame.display.flip()
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == MOUSEBUTTONDOWN:
x,y = pygame.mouse.get_pos()
for button in buttons:
if (button.checkCollide(x,y)):
button.do()

Categories