Keybind within a while loop in pythons turtle - python

I am attempting to create a study game where a question will fall downward and you type in the answer, however I don't understand how to record key inputs without stopping the motion of the lowering question.
to put simply, I want to be able to lower the question and use my keyboard to input at the same time without stopping the motion.
text_letter = 0
def text_insert(answer):
global text_letter
print("hello")
text_letter += 1
def text_lower(question,answer):
global text_letter
text.penup()
text.goto(random.randint(-250,250),355)
text.pendown()
text.color("white")
text.write("Start", font=("Arial", 20, "normal"))
x,y = text.pos()
delay = .01
wn.textinput("Answer", "Answer:")
turtle.listen()
turtle.onkey(text_insert(answer),answer[text_letter])
while y > -355:
time.sleep(delay)
y -= 1
text.goto(x,y)
text.write(question, font=("Arial", 20, "normal"))
text.clear()

This may be a more complicated answer than you anticipated: If you leave off the second, key, argument to the turtle's onkeypress() function, it will call your key press handler code when any key is pressed. It simply won't tell you which key!
We can work around this poor design by rewriting the underlying code to pass tkinter's event.char to the turtle's event handler in the case where no key has been set.
Once that's done, we can use a turtle timed event to lower the question from the top of the window while the user's typed input shows up at the bottom of the window.
Here's my one question simulation of such to help get you started:
from turtle import Screen, Turtle
from functools import partial
FONT_SIZE = 20
FONT = ("Arial", FONT_SIZE, "normal")
def text_lower(question):
question_turtle.forward(1)
question_turtle.clear()
question_turtle.write(question, align="center", font=FONT)
screen.update()
if question_turtle.ycor() - answer_turtle.ycor() > FONT_SIZE:
screen.ontimer(lambda: text_lower(question), 15)
else:
question_turtle.clear()
def _onkeypress(self, fun, key=None):
if fun is None:
if key is None:
self.cv.unbind("<KeyPress>", None)
else:
self.cv.unbind("<KeyPress-%s>" % key, None)
else:
if key is None:
def eventfun(event):
fun(event.char)
self.cv.bind("<KeyPress>", eventfun)
else:
def eventfun(event):
fun()
self.cv.bind("<KeyPress-%s>" % key, eventfun)
def display_character(character):
global answer
if not character:
return
if ord(character) == 13:
answer_turtle.clear()
answer_turtle.setx(0)
# do something with answer and then:
answer = ""
else:
answer += character
answer_turtle.write(character, move=True, font=FONT)
screen.update()
screen = Screen()
screen.tracer(False)
screen._onkeypress = partial(_onkeypress, screen)
question_turtle = Turtle(visible=False)
question_turtle.penup()
question_turtle.setheading(270)
question_turtle.sety(screen.window_height()/2 - FONT_SIZE)
answer_turtle = Turtle(visible=False)
answer_turtle.penup()
answer_turtle.sety(FONT_SIZE - screen.window_height()/2)
answer = ""
screen.onkeypress(display_character)
screen.listen()
text_lower("What is the air-speed velocity of an unladen swallow?") # A: An African or European swallow?
screen.mainloop()

Related

Python Turtle race game. Ending game function does not seem to work

I have got a problem with how to end my turtle python game. The code seems to function if I place the turtles at the starting/ending point from the beginning of the code, but it does not register when the turtle reaches the endpoint in gameplay. From what I know I think my maths for the end function is right. I am new and appreciate the help. I am currently offline though.
CODE:
import time
import turtle
from turtle import *
wn = turtle.Screen()
name=textinput("Question", "what is your name")
#display
pencolor("white")
penup()
goto(0,170)
write("hello " +name,align='center',font=('Comic Sans', 20))
#wn = turtle.screen() if the code doesn't work
#diffrent turtles here
t1 = turtle.Turtle()
t2 = turtle.Turtle()
t3 = turtle.Turtle()
#starting psoition
turtle.penup()
t1.penup()
turtle.goto(-1, -230)
t1.goto(-1, -170)
#starting line postion
def f():
fd(10)
def b():
bk(10)
def l():
left(10)
def r():
right(10)
#testing
def fo():
t1.fd(10)
def ba():
t1.bk(10)
def le():
t1.left(10)
def ri():
t1.right(10)
#turtle coordinates
first=turtle.ycor()
second=turtle.xcor()
third=t1.ycor()
fourth=t1.xcor()
#when to end the game
if (turtle.ycor()>= (-160)) and (turtle.ycor()<= (-240)):
if (turtle.xcor()>= (0)) and (turtle.xcor()<= (11)):
print("Finally working")
#replaced with write who the winner is later
if (t1.ycor()>= (-160)) and (t1.ycor()<= (-240)):
if (t1.xcor()>= (0)) and (t1.xcor()<= (11)):
print("Finally")
# onkey creates the key board = turtle.onkey("function, key") You have to keep pressing keys for it to move
turtle.onkey(f, "w")
turtle.onkey(b, "s")
turtle.onkey(l, "a")
turtle.onkey(r, "d")
wn.onkey(fo, "Up")
wn.onkey(ba, "Down")
wn.onkey(le, "Left")
wn.onkey(ri, "Right")
listen()
#WINDOW SETUP
window = Screen()
window.setup(800, 800)
window.title("Turtle game")
turtle.bgcolor("forestgreen")
t3.color("black")
t3.speed(0)
t3.penup()
t3.setpos(-140, 250)
t3.write("THE TURTLE RACE", font=("Comic Sans", 30, "bold"))
t3.penup()
#turtle ask name
#add images here
#turtle controls
# def creates a function. : means opperation f means move turtle foward. fd push turtle forward
# onkey creates the key board = turtle.onkey("function, key") You have to keep pressing keys for it to move
t2.speed(0)
t2.color("grey")
t2.pensize(100)
t2.penup()
t2.goto(-200, -200)
t2.left(90)
t2.pendown()
t2.forward(300)
t2.right(90)
t2.forward(500)
t2.right(90)
t2.forward(300)
t2.right(90)
t2.forward(500)
turtle.penup()
Firstly, your maths is not quite right - a coordinate can never be both <= -240 and >= -160. It should be t.ycor() >= -240 and t.ycor() <= -160, or more briefly, -240 <= t.ycor() <= -160.
Secondly, the condition as it stands is only checked once, when the code is first run. Instead, you need to get the program to check it regularly. You can do this by adding a general onkeypress event handler which is checked every time any key is pressed.
def check_status():
for player, t in enumerate([turtle, t1]):
if 0 <= t.xcor() <= 11 and -240 <= t.ycor() <= -160:
print(f"Player {player} is at the endpoint")
...
wn.onkeypress(check_status)
listen()

Lives counter in Python turtle graphics

I could do with some help on how to make a lives counter in Python turtle graphics.
My code:
def draw_lives():
global lives
lives = turtle.Turtle()
lives.penup
lives.hideturtle
lives.goto(-200, 400)
while True:
lives.write("Lives: " + str(lives), font=("Arial", 50, "normal"))
if lives > 0:
lives.write("You have lost all lives. Try again.", font=("Arial", 50, "normal"))
break
I thought on making my lives counter a Turtle and not just a random counter somewhere (which would actually sound better).
Furthermore did I get the error for my if lives > 0: that the > is not supported between instances of Turtle and int.
Can someone help?
Your code is badly constructed -- let's look at specifics. The primary issue is that you use the same variable name, lives, for both the counter and the turtle that displays the counter. Name them differently. If you do so, you don't need:
global lives
The next problem is basic Python:
lives.penup
lives.hideturtle
These are method calls so they should be:
lives.penup()
lives.hideturtle()
Finally your while True: has no business here, or anywere in a turtle event-driven program. And you're missing a line of code or two in your if statement.
Let's rework your code so that it updates the value of the lives counter on the screen:
from turtle import Screen, Turtle
FONT = ("Arial", 24, "normal")
def draw_lives():
lives_pen.clear()
if lives > 0:
lives_pen.write("Lives: " + str(lives), font=FONT)
else:
lives_pen.write("You have lost all lives. Try again.", font=FONT)
lives = 5
lives_pen = Turtle() # this turtle is only used to update lives counter display
lives_pen.hideturtle()
lives_pen.penup()
lives_pen.goto(-200, 300)
lives_pen.write("Lives: " + str(lives), font=FONT)
if __name__ == '__main__':
from time import sleep
# test code
screen = Screen()
for _ in range(lives + 1):
draw_lives()
sleep(1)
lives -= 1
screen.exitonclick()
The __main__ section is just test code to confirm that draw_lives() works the way we want -- so toss it.
Utility turtles like lives_pen should only be created once, not every time you need to update the counter as they are global entities and are not garbage collected when the function exits.
It's a bad practice to use global in your code. Instead, you can create a custom attribute for your turtles.
It's super easy, barely an inconvenience:
from turtle import Turtle
pen = Turtle()
pen.lives = 5 # Here, the lives attribute is created
You may even do the same for the font, though it may be unnecessary:
pen.font = ("Arial", 30, "normal")
If loosing lives is the only situation where the life count will be updated, do not keep rewriting them in the loop
(unless, of course, the something gets in the way of the text, and you want the text to be displayed on top),
only rewrite it when a life is lost.
We can redraw and update the lives in a function like this:
def update_lives():
pen.clear()
if pen.lives:
pen.write(f"Lives: {pen.lives}", font=pen.font)
else:
pen.write("You have lost all lives. Try again.", font=pen.font)
pen.lives -= 1 # Remove this line and this becomes your typical text updater
To see this in action, I implemented a demo where a life is lost whenever the user presses the SPACE bar:
from turtle import Turtle, Screen
wn = Screen()
def update_lives():
pen.clear()
if pen.lives:
pen.write(f"Lives: {pen.lives}", font=pen.font)
else:
pen.write("You have lost all lives. Try again.", font=pen.font)
pen.lives -= 1
pen = Turtle(visible=False)
pen.penup()
pen.goto(-300, 200)
pen.lives = 5
pen.font = ("Arial", 30, "normal")
update_lives()
wn.listen()
wn.onkey(update_lives, 'space')
With the above code, when the user reaches 0, pressing SPACE again will make the function proceed to display negative values.
To fix that, for your main game loop, use while pen.lives to tell python to only keep looping until the number of lives remaining is greater than 0:
while pen.lives:
# Your game code here
wn.update()
Output:

Code beyond the Onclick and Listen doesn't get executed

this is my first time posting here.
I'm trying to make a Title Screen before getting the user in my game. When I hit enter, I want to break out of the While loop into a the main game loop, or go into the next section of the While loop. However, when I hit enter and the onkey use my Enter function which sets the In_title to False, it doesn't seem to respond with anything. (this is in python with turtle, also I'm working using replit.)
I've created a variable named in_title = True.
I've tried breaking out of the while loop using if in_title == false then break.
import turtle
import random
import math
import time
#set up for the screen title.
screen_size = [500,500]
screen_color = "#1a2645"
t = turtle.Turtle()
screen = t.getscreen()
screen.setup(screen_size[0],screen_size[1])
screen.bgcolor(screen_color)
screen.tracer(0)
t.ht()
game_state = 1 #to see if the game is still runninng. #0, 1 are no, yes.
in_title = True #to see if the game is in title or not.
select = turtle.Turtle()
select.color("yellow")
select.speed(0)
select.up()
select.setx(-80) #putting it in the middle of the text.
select.sety(30)
#all of the entity here:
player = {"p": turtle.Turtle(), "lives": 3, "color": "white", "rad": 15}
player["p"].ht()
harm = {"color": "red", "rad": 10}
num_harm = 10
good = {"g": turtle.Turtle(), "color": "white", "rad": 8}
good["g"].ht()
#universal movement function.
def moving_harm(h,rad): #function to move the harm objects.
if h.ycor() == (250 + rad/2):
h.up()
h.setx(random.randrange(-250,250))
h.sety(h.ycor() - 5)
screen.update()
if (h.ycor() != 250 + rad/2) and (h.ycor() != -250 - rad/2):
h.sety(h.ycor() - 5)
if (h.ycor() == (-250 - rad/2)):
h.sety(250 + rad/2)
screen.update()
def up():
if game_state == 1:
select.sety(30) #hard coded the number so it's in the middle.
print(select.ycor())
screen.update()
# def down():
# def left():
# def right():
def enter():
global in_title
if (game_state == 1):
if(select.ycor() == 30):
select.down()
select.forward(150)
select.setheading(180)
select.color("white")
select.forward(150)
screen.update()
time.sleep(0.2)
screen.clear()
screen.update()
time.sleep(0.1)
screen.setup(screen_size[0],screen_size[1])
screen.bgcolor(screen_color)
in_title = False
#main menu operation:
def main_game():
global game_state
#writing out the tittle screen.
menu = turtle.Turtle()
menu.up()
menu.sety(150)
menu.color("white")
menu.write("Sorta a Side Scroller Game", align="center", font=("Arial", 22, "normal"))
menu.sety(100)
menu.write("By Silver", align="center", font=("Arial", 15, "normal"))
menu.ht()
menu.sety(25)
menu.write("Start game", align="center", font=("Arial", 15, "normal"))
#create all the harm turtles:
for i in range(num_harm):
h = "h"+str(i+1)
harm[str(h)] = turtle.Turtle()
harm[str(h)].up()
harm[str(h)].ht()
#handle control at the title screen.
screen.onkey(enter, "Enter")
if in_title == False:
print("got passed that")
main_game()
screen.listen()
screen.update()
screen.mainloop()
no error message where given.
The way you have it, main_game() only is called once, right before screen's main loop begins. That means that this fragment
if in_title == False:
print("got passed that")
happens once and then control flow leaves main_game() forever, and the screen main loop begins.
Since the fragment only exists in main_game(), and main_game() isn't set to be called on any button press or called from anywhere else in the code, that fragment will never be called ever again. So, the if in_title == False: will never be encountered and print("got passed that") will never happen, even when in_title is False.
In this case, there is a place you already know is going to have control flow when in_title == False, and that is at the end of enter. So, you could put the code you have in mind there. This is the solution I suggested in the comments:
def enter():
global in_title
if (game_state == 1):
if(select.ycor() == 30):
# ...
in_title = False
print("got passed that")
#main menu operation:
def main_game():
# ...
screen.onkey(enter, "Enter")

Does turtle have an onkeyhold method

In a program I am writing, turtle's onkey() method doesn't do exactly what I need. I know there are also onkeypress and onkeyrelease but neither do what I need. Is there a method for continuously running a function while a key is held? Such as
import turtle
num = 0
def add():
global num
num += 1
print(num)
turtle.onkey(add, "Up")
turtle.listen()
turtle.mainloop()
If you did something like this, for taking the keyboard input, onkey only responds once, and it only responds on the release of the key. Is there a method that would continuously run the function while it is being held down?
Below's a rough example that does what you describe using onkeypress() and onkeyrelease() in combination. Once you press 'Up' it will start counting whole seconds until you release that key. Then it will write to the screen the number of whole seconds the key was held:
from turtle import Turtle, Screen
FONT = ('Arial', 36, 'normal')
def add_start():
global seconds
print('pressed')
if seconds < 0:
turtle.undo() # remove previous time
seconds = 0
screen.ontimer(add, 1000)
def add():
global seconds
if seconds >= 0:
seconds += 1
screen.ontimer(add, 1000)
def add_stop():
global seconds
print('released')
turtle.write(seconds, align='center', font=FONT)
seconds = -1
screen = Screen()
turtle = Turtle(visible=False)
turtle.write(0, align='center', font=FONT)
seconds = -1
screen.onkeypress(add_start, 'Up')
screen.onkeyrelease(add_stop, 'Up')
screen.listen()
screen.mainloop()
Here's the catch: you need to turn off key repeat at the operating system level on your system. For example, On my Mac, in System Preferences / Keyboard, I'd do:
Otherwise the system will generate press and release events as part of standard key repeat.

code executing in odd order with turtle graphics

i am currently trying to make a game like snake in python using turtle graphics and have encountered a game breaking bug when you turn the turtle using a and d keys if you are in line with any previous turn. it appears to be executing code out of order but i have no real idea whats happening.
the whole code is below
import turtle
import random
import math
x = 400
y = 400
global speed
global points
points = 0
speed = 2
posList = ['']
# turns your character left
def left():
global speed
global posList
key = True
char.fd(speed)
char.left(90)
char.fd(speed)
# turns your character left
def right():
global speed
global posList
key = True
char.fd(speed)
char.right(90)
char.fd(speed)
# adds one box to the point counter
def point():
global points
points += 1
wall.speed(0)
wall.pendown()
wall.forward(50)
wall.seth(90)
wall.forward(10)
wall.seth(180)
wall.forward(50)
wall.seth(270)
wall.forward(10)
wall.penup()
wall.seth(90)
wall.forward(12)
wall.seth(0)
dot.setx(random.randint(-200,200))
dot.sety(random.randint(-200,200))
print(points)
# checks if curren posisition is anywhere you have ever been
def checkBracktrack(pos, poslist):
found = False
for thing in posList:
if thing == pos:
found=True
return found
# creates the box that the game occurs in
turtle.colormode(255)
screen = turtle.Screen()
dot = turtle.Turtle()
dot.penup()
dot.speed(0)
dot.shape('turtle')
dot.setx(random.randint(-200,200))
dot.sety(random.randint(-200,200))
wall = turtle.Turtle()
wall.speed(0)
wall.penup()
wall.goto(x/2,y/2)
wall.pendown()
wall.seth(180)
wall.forward(400)
wall.seth(270)
wall.forward(400)
wall.seth(0)
wall.forward(400)
wall.seth(90)
wall.forward(400)
wall.seth(270)
wall.forward(400)
wall.seth(0)
wall.penup()
wall.forward(100)
char = turtle.Turtle()
x = 0
y = 0
# updates the position of the player turtle
while True:
screen.onkey(left,"a")
screen.onkey(right,"d")
char.hideturtle()
char.forward(speed)
char.speed(0)
turtle.listen(xdummy=None, ydummy=None)
print(char.pos())
print(posList[(len(posList)-1)])
# checks if current position is the same as any position it has ever been in !this is the bit that is having problems!
if checkBracktrack(char.pos(),posList):
speed = 0
break
# checks if it is close enough to a point marker to
if char.ycor() in range(dot.ycor()-10,dot.ycor()+10) and char.xcor() in range(dot.xcor()-10,dot.xcor()+10):
point()
# checks if in the box
if char.ycor() not in range(-200,200) or char.xcor() not in range(-200,200):
speed = 0
# adds current location to the list
posList.append(char.pos())
char.fd(speed)
print('you travelled',len(posList),'pixels')
print('collided with yourself')
print(char.pos())
print(posList)
name = input('quit')
screen.mainloop()
There are many little issues with your code: you need to reread about when to use global; your checkBracktrack() function takes poslist as an argument but operates on the global posList instead (case typo); your pixels travelled distance calculation is incorrect because of extra fd() calls and a speed greater than 1; your proximity test can be greatly simplified using turtle's .distance() method; your code to display points on the game board doesn't work at all; you call onkey() over and over again in a loop when you only need to call it once for each key; your checkBracktrack() function has an unnecessary loop.
The biggest issue I have with the code is the while True: which shouldn't happen in event-based code. I've rewritten, and simplified your code below, addressing the above issues as well as others:
from turtle import Turtle, Screen
from random import randint
FONT = ('Arial', 24, 'normal')
WIDTH, HEIGHT = 400, 400
SPEED = 1
def left():
""" turns your character left """
char.left(90)
def right():
""" turns your character right """
char.right(90)
def point():
""" adds one box to the point counter """
global points
points += 1
wall.undo()
wall.write(points, font=FONT)
dot.setpos(randint(-WIDTH/2, WIDTH/2), randint(-HEIGHT/2, HEIGHT/2))
def checkBracktrack(pos, poslist):
""" checks if current posiition is anywhere you have ever been """
return pos in poslist
def move_char():
""" updates the position of the player turtle """
over = False
char.forward(SPEED)
# checks if current position is the same as any position it has ever been at
if checkBracktrack(char.pos(), posList):
over = True
# checks if in the box
elif not (-200 <= char.ycor() <= 200 and -200 <= char.xcor() <= 200):
over = True
if over:
print('you travelled', len(posList), 'pixels')
return
# adds current location to the list
posList.append(char.pos())
# checks if it is close enough to a point marker
if char.distance(dot) < 20:
point()
screen.ontimer(move_char, 10)
points = 0
posList = []
# creates the box in which the game occurs
screen = Screen()
screen.onkey(left, "a")
screen.onkey(right, "d")
screen.listen()
dot = Turtle('turtle')
dot.speed('fastest')
dot.penup()
dot.setpos(randint(-WIDTH/2, WIDTH/2), randint(-HEIGHT/2, HEIGHT/2))
wall = Turtle(visible=False)
wall.speed('fastest')
wall.penup()
wall.goto(WIDTH/2, HEIGHT/2)
wall.pendown()
for _ in range(4):
wall.right(90)
wall.forward(400)
wall.penup()
wall.forward(100)
wall.write("0", font=FONT)
char = Turtle(visible=False)
char.speed('fastest')
move_char()
screen.mainloop()
My belief is that the problem that prompted your original question got fixed in the process of reworking the code.

Categories

Resources