Python, user input generates turtles - python

I am trying to setup a program in which the user decides how many turtles to generate and then they have a race after. My current solution is to just get an int input from the user and execute the code below (the code keeps repeating with larger numbers). I have tried putting in a loop but I am running into troubles since they all need to preform random movements in the end. Any help?
if turtNum >= 1:
turt1 = Turtle()
turt1.color(turtColour[0])
turt1.shape('turtle')
turt1.penup()
turt1.goto(0, -10)
turt1.pendown()
if turtNum >= 2:
turt2Name = input('Enter a name for the second turtle: ')
turt2 = Turtle()
turt2.color(turtColour[1])
turt2.shape('turtle')
turt2.penup()
turt2.goto(0, -25)
turt2.pendown()
This is the code I tried but got this error "list indices must be integers or slices, not str"
turtName = []
maxLengthList = turtNum
while len(turtName) < maxLengthList:
name = input('Enter the names for the turtles: ')
turtName.append(name)
for i in turtName:
turtName[i] = Turtle()
turtName[i].color(turtColour[0])
turtName[i].shape('turtle')
turtName[i].penup()
turtName[i].goto(0, -10)
turtName[i].pendown()

You can't dangle the concept of turtle racing without expecting us to get excited about seeing it happen. Below is a rough implementation that addresses the issues you had about entering the number of turtles, entering individual turtle information, storing it all and random motion:
from turtle import Turtle, Screen
from itertools import cycle
from random import randrange
MAX_TURTLES = 20
LANE_WIDTH = 25
FONT_SIZE = 18
FONT = ("Arial", FONT_SIZE, "normal")
COLORS = cycle(['red', 'green', 'blue', 'cyan', 'black', 'yellow'])
FINISH_LINE = 350
START_LINE = -200
NAME_LINE = START_LINE - 150
DELAY = 100 # milliseconds
MAX_STEP = 10
turtles = dict()
def race():
for name, turtle in turtles.items(): # should shuffle turtles
turtle.forward(randrange(MAX_STEP + 1))
if turtle.xcor() > FINISH_LINE:
return # the race is over
screen.ontimer(race, DELAY)
magic_marker = Turtle(visible=False)
magic_marker.penup()
turtNum = 0
while not 1 <= turtNum <= MAX_TURTLES:
turtNum = int(input('Enter the number of turtles: '))
for i in range(turtNum):
name = input('Enter a name for the turtle #{}: '.format(i + 1))
turtle = Turtle(shape="turtle")
turtle.color(next(COLORS))
y_offset = LANE_WIDTH * i - LANE_WIDTH * turtNum // 2
magic_marker.color(turtle.pencolor())
magic_marker.goto(NAME_LINE, y_offset - FONT_SIZE / 2)
magic_marker.write(name, font=FONT)
turtle.penup()
turtle.goto(START_LINE, y_offset)
turtle.pendown()
turtles[name] = turtle
magic_marker.color('red')
magic_marker.goto(FINISH_LINE, -FINISH_LINE)
magic_marker.pendown()
magic_marker.goto(FINISH_LINE, FINISH_LINE)
magic_marker.penup()
screen = Screen()
screen.ontimer(race, DELAY)
screen.exitonclick()

Related

How to stop randomised drawings in python from overlapping

So, I have written a code that creates snowflakes using turtle. Essentially it asks the user how many snowflakes to generate. It then opens a turtle window and draws the snowflakes in a random place, size and colour. The random place is important for this question. Essentially, when it draws the snowflakes, is there a way to stop the snowflakes from being drawn in the (approx.) same area so that they don't overlap?
Normally yes, this would be simple but due to its random nature, I have no clue how to do this.
Here is the code:
import time
import sys
import turtle
import random
restart = True
print("This program creates snowflakes. Enjoy!")
while restart == True:
n = int(input("How many snowflakes do you want?: "))
screen = turtle.Screen()
screen.bgcolor("black")
speedy = turtle.Turtle()
speedy.speed(0)
sfcolor = ["yellow","gold","orange","red","violet","magenta","purple","navy","blue","skyblue","cyan","turquoise","lightgreen","green","darkgreen","white","BlueViolet","DeepSkyBlue","snow2","ForestGreen", "gainsboro", "GhostWhite", "goldenrod"]
def snowflake(size):
speedy.penup()
speedy.forward(10 * size)
speedy.left(45)
speedy.pendown()
speedy.color(random.choice(sfcolor))
for i in range(8):
branch(size)
speedy.left(45)
def branch(size):
for i in range(3):
for i in range(3):
speedy.forward(10.0 * size / 3)
speedy.back(10.0 * size / 3)
speedy.right(45)
speedy.left(90)
speedy.back(10.0 * size / 3)
speedy.left(45)
speedy.right(90)
speedy.forward(10.0 * size)
for i in range(n):
x = random.randint(-375, 375)
y = random.randint(-375, 375)
sfsize = random.randint(1, 4)
speedy.penup()
speedy.goto(x, y)
speedy.pendown()
snowflake(sfsize)
print(i+1," Snowflake(s)")
restart = False
print("Thanks for using the program! You will have the option to resart it shortly.")
time.sleep(3)
restart = input("Do you want to run the program again? Yes or No: ")
restart = restart.upper()
if restart == "YES":
turtle.Screen().bye()
restart = True
print("Restarting...")
elif restart == "NO":
restart = False
print("Thank you for using the program. Goodbye!")
time.sleep(3)
turtle.Screen().bye()
sys.exit()
else:
print("\nError. Program Resetting...")
turtle.Screen().bye()
print("This program creates snowflakes. Enjoy!")
restart = True
Similar to #mx0's suggestion (+1), rather than a square, we define a circle that encompasses the snowflake and for each successful placement, keep a list of existing positions and radii. We also use the radius to avoid drawing partial snowflakes near the edge of our window:
from turtle import Screen, Turtle
from random import randint, choice
WIDTH, HEIGHT = 480, 320 # small for testing
SF_COLORS = [
'yellow', 'gold', 'orange', 'red', 'violet',
'magenta', 'purple', 'navy', 'blue', 'skyblue',
'cyan', 'turquoise', 'lightgreen', 'green', 'darkgreen',
'white', 'BlueViolet', 'DeepSkyBlue', 'snow2', 'ForestGreen',
'gainsboro', 'GhostWhite', 'goldenrod',
]
def snowflake(size):
radius = 15 * size # circle roughly encompassing snowflake
position = randint(radius - WIDTH/2, WIDTH/2 - radius), randint(radius - HEIGHT/2, HEIGHT/2 - radius)
speedy.goto(position)
trys = 0
while any(speedy.distance(other_position) < (radius + other_radius) for other_position, other_radius in snowflakes):
position = randint(radius - WIDTH/2, WIDTH/2 - radius), randint(radius - HEIGHT/2, HEIGHT/2 - radius)
speedy.goto(position)
trys += 1
if trys > 100:
return False # can't fit this snowflake, signal caller to try a different `size`
snowflakes.append((position, radius))
speedy.color(choice(SF_COLORS))
speedy.penup()
speedy.forward(10 * size)
speedy.left(45)
speedy.pendown()
for _ in range(8):
branch(size)
speedy.left(45)
speedy.penup()
return True
def branch(size):
length = 10.0 * size / 3
for _ in range(3):
for _ in range(3):
speedy.forward(length)
speedy.backward(length)
speedy.right(45)
speedy.left(90)
speedy.backward(length)
speedy.left(45)
speedy.right(90)
speedy.forward(length * 3)
print("This program creates snowflakes. Enjoy!")
n = int(input("How many snowflakes do you want?: "))
screen = Screen()
screen.setup(WIDTH, HEIGHT)
screen.bgcolor('black')
speedy = Turtle()
speedy.speed('fastest')
snowflakes = []
flakes = 0
while flakes < n:
sfsize = randint(1, 4)
if snowflake(sfsize):
flakes += 1
speedy.hideturtle()
screen.exitonclick()
However, fitting snowflakes like this creates an issue. The user might request more snowflakes than can fit in a given size window. The code above partially addresses this by returning failure and letting the caller figure out what to do. Here, we simply try another snowflake size. Smarter code would reduce the random size range based on failure, and quit trying altogether when a size 1 snowflake fails!
I've removed the restart logic to simplify my example and because I'm not convinced it works.

Is there a way to check a radius around a random point in order for random points to be separate from each other

I'm currently creating a drawing project, where I use random to create random star points in the sky. I currently have the ability for two points to not be the same, but I would like to find a way to make it so they wouldn't land in a x radius circle. Is there any way in python to complete this
import turtle as t, random as r
screen=t.Screen()
screen.bgcolor("#3A3B3C")
t.speed(1)
def randomStars(y, num):
t.penup()
t.pensize(2)
t.color("white")
locations = []
for x in range(num):
repeat = True
t.penup()
t.seth(90)
y_pos = starLocationY(y)
x = starLocationX()
while repeat == True:
if [x,y_pos] in locations:
y_pos = starLocationY(y)
x = starLocationX()
else:
locations.append([x,y_pos])
repeat = False
t.goto(x,y_pos)
t.pendown()
t.seth(60)
t.fd(2)
t.fd(-2)
t.seth(-60)
t.fd(2)
t.fd(-2)
t.seth(240)
t.fd(2)
t.fd(-2)
t.seth(120)
t.fd(2)
t.fd(-2)
randomStars(85,30)
p.s: I'm using trinket for the project, as required by the class, so the modules are limited
link to trinket:https://trinket.io/python/9776ba1b8a
We can use any on a generator invoking turtle's distance() method on the elements of your locations list. Easier than it sounds:
from turtle import Screen, Turtle
from random import randint
EXCLUSION_RADIUS = 35 # in pixels
def starLocationY(y):
return randint(y, 190)
def starLocationX():
return randint(-190, 190)
def randomStars(y, number):
turtle.penup()
turtle.pensize(2)
turtle.color('white')
locations = []
for _ in range(number):
y_pos = starLocationY(y)
x = starLocationX()
while True:
turtle.goto(x, y_pos)
if any(turtle.distance(location) < EXCLUSION_RADIUS for location in locations):
y_pos = starLocationY(y)
x = starLocationX()
else:
locations.append((x, y_pos))
break
turtle.pendown()
for heading in range(0, 360, 120):
turtle.setheading(heading)
turtle.forward(2)
turtle.backward(4)
turtle.forward(2)
turtle.penup()
screen = Screen()
screen.bgcolor('#3A3B3C')
turtle = Turtle()
turtle.hideturtle()
turtle.speed('fastest') # because I have no patience
randomStars(85, 20)
screen.exitonclick()

Need help detecting collision in python

I’ve tried multiple things to try to detect collision between the player turtle and the apple turtle and nothing happens. What I wanted it to do is that when the turtles get too close, the player turtle disappears. If the apple gets to low below the player, it resets back up top.
Here is the code I’ve done. I’m new to code so I’m not very good.
import turtle as trtl
import random as rand
ground_height = -200
# lists for the turtle to use
colors = ["blue", "darkorange", "cyan", "green", "black"]
shapes = [ "triangle", "turtle", "classic"]
sizes = [0.5,1,1.25,1.5,2]
#creates the turtles
player = trtl.Turtle(shape = rand.choice(shapes)) #gives the circle its shape
player.turtlesize(1.5)
counter = trtl.Turtle()
apple = trtl.Turtle(shape = "circle")
apple.color("red")
apple.turtlesize(rand.choice(sizes))
#gives turtles their colors
player.color(rand.choice(colors))
player.penup()
apple.penup()
apple.setx(rand.randint(0,200))
apple.sety(rand.randint(0,200))
#sets up timer
font_setup = ("Arial", 20, "normal")
timer = 0
counter_interval = 1000
timer_up = False
counter.hideturtle()
counter.penup()
counter.goto(-200,160)
#gives player movement
def move_player():
player.forward(10)
def player_left():
player.left(180)
def player_right():
player.right(180)
apple.right(90)
#lets the timer start and end
def countdown():
global timer, timer_up
counter.clear()
if timer <= -1:
counter.write("Time's Up", font=font_setup)
timer_up = True
else:
counter.write("Timer: " + str(timer), font=font_setup)
timer += 1
apple.forward(10)
counter.getscreen().ontimer(countdown, counter_interval)
countdown()
# lets the player move on key press
wn = trtl.Screen()
wn.listen()
wn.onkeypress(move_player, "w")
wn.onkeypress(player_left,"a")
wn.onkeypress(player_right,"d")
wn.mainloop()
Testing for collision can be as simple as adding a clause to the if statement in the countdown() function:
elif apple.distance(player) < 15:
counter.write("Collision!", font=FONT)
Below is my rework of your code that incorporates this change as well addresses various style and coding issues I noticed:
from turtle import Screen, Turtle
from random import choice, randint
# lists for the turtle to use
COLORS = ['blue', 'dark orange', 'cyan', 'green', 'black']
SHAPES = ['triangle', 'turtle', 'classic']
SIZES = [0.5, 1, 1.25, 1.5, 2]
FONT = ('Arial', 20, 'normal')
COUNTER_INTERVAL = 1000
# give player movement
def move_player():
player.forward(10)
def player_left():
player.left(180)
def player_right():
player.right(180)
# set up timer
timer = 0
def countdown():
global timer
counter.clear()
if timer <= -1:
counter.write("Time's Up", font=FONT)
elif apple.distance(player) < 15:
counter.write("Collision!", font=FONT)
else:
counter.write("Timer: " + str(timer), font=FONT)
timer += 1
apple.forward(10)
screen.ontimer(countdown, COUNTER_INTERVAL)
# create turtles
player = Turtle(shape=choice(SHAPES))
player.turtlesize(1.5)
player.color(choice(COLORS))
player.penup()
counter = Turtle()
counter.hideturtle()
counter.penup()
counter.goto(-200, 160)
apple = Turtle(shape='circle')
apple.color('red')
apple.turtlesize(choice(SIZES))
apple.penup()
apple.setposition(randint(0, 200), randint(0, 200))
apple.setheading(270)
screen = Screen()
# let player move on key press
screen.onkeypress(move_player, 'w')
screen.onkeypress(player_left, 'a')
screen.onkeypress(player_right, 'd')
screen.listen()
countdown()
screen.mainloop()
What I wanted it to do is that when the turtles get too close, the
player turtle disappears. If the apple gets to low below the player,
it resets back up top
My fix above just detects and announces the collision, you'll need to augment it to include these additional actions.

Making turtle objects draw next to each other instead of on top of each other

I am having an issue where instead of my turtle objects drawing a car length next to each other, they are drawing on top of each other. I've tried just about every variation of a loop I could think of, but just can not figure it out despite it probably being something so simple. In addition to this, I haven't been able to figure out how to get the name of the car a user enters to appear above their respective car.
Here is what it is supposed to look like:
https://i.stack.imgur.com/rubG3.png
import turtle
import random
turtle.setup(575,575)
pen = turtle.Turtle()
turtle.bgcolor("white")
pen.speed(0)
pen.pensize(2)
pen.hideturtle()
colorCar = []
carName = []
# ask user how many cars
carNum = int(turtle.numinput("cars", "Enter how many cars you have: ", 1, 1, 10))
# respective values (add more variables if needed for sizing)
widthPadding = turtle.window_width()/carNum
width= widthPadding * .8
padding = widthPadding * .1
length = width * 2
# controlls how long horizontally
carWidth = width * 0.5
wheelWidth = carWidth * .1
# controls how long vertically
carLength = width * 0.2
for row in range(carNum):
x = - turtle.window_width()/2 + carWidth
y = - turtle.window_height()/2 + padding + row * widthPadding
# ask user for color of car
for i in range(carNum):
color = turtle.textinput("car color", f"Enter Color of Car {i +1}").lower().strip()
colorCar.append(color)
# ask for car name
name = turtle.textinput("car name", f"Enter Name of Car {i +1}").lower().strip()
carName.append(name)
for row in range(carNum):
pen.up()
pen.setpos(x + (width - length)/2 + padding, y)
pen.down()
#draw cars
pen.color("black")
pen.pensize(4)
pen.fillcolor(color)
pen.begin_fill()
for i in range(4):
if i%2== 0:
pen.forward(carWidth)
else:
pen.forward(carLength)
pen.left(90)
pen.end_fill()
# write name above car
turtle.write(name)
turtle.done()
This code is a mess. You need to use better variable names. It's not clear what various "width", "length" and "padding" variables do. In fact, this is where comments are really needed, not comments like # ask for car name just before you obviously ask for the car name.
Below I've reworked your code to address your two stated issues: the cars line up horizontally rather than atop each other; the names of the cars appear above the cars:
from turtle import Screen, Turtle
screen = Screen()
screen.setup(575, 575)
# ask user how many cars
carNum = int(screen.numinput("cars", "Enter how many cars you have: ", 1, 1, 10))
# respective values (add more variables if needed for sizing)
widthPadding = screen.window_width() / carNum
width = widthPadding * 0.8
padding = widthPadding * 0.1
length = width * 2
# controls how long horizontally
bodyWidth = width / 2
# controls how long vertically
bodyHeight = width * 0.2
carColors = []
carNames = []
for i in range(1, carNum + 1):
# ask user for color of car
color = screen.textinput("car color", f"Enter Color of Car {i}").lower().strip()
carColors.append(color)
# ask for car name
name = screen.textinput("car name", f"Enter Name of Car {i}").lower().strip()
carNames.append(name)
# draw cars
turtle = Turtle()
turtle.hideturtle()
turtle.speed('fastest')
turtle.pensize(4)
for car in range(carNum):
x = bodyWidth - screen.window_width()/2 + car * widthPadding
y = padding - screen.window_height()/2
turtle.penup()
turtle.setpos(x + (width - length)/2 + padding, y)
turtle.pendown()
turtle.fillcolor(carColors[car])
turtle.begin_fill()
for side in range(4):
if side % 2 == 0:
turtle.forward(bodyWidth)
else:
turtle.forward(bodyHeight)
turtle.left(90)
turtle.end_fill()
# write name above car
turtle.penup()
turtle.sety(turtle.ycor() + 3*bodyHeight/2)
turtle.write(carNames[car])
screen.mainloop()

How do I get Python turtle to recognize when I click the background?

I'm trying to make a game where you click a bunch of random circles and get a score, however I also want it to deduct from your score when you miss the circle. I've been trying to use screen.onclick() but instead of deducting the score on a misclick it seems to deduct the score every second for no reason. What am I doing wrong?
import turtle
from random import random, randint
import time
CURSOR_SIZE = 20
score=0
def addscore():
global score
score += 1
def deletescore():
global score
score -= 1
def my_circle(color):
radius = (15)
circle = turtle.Turtle('circle', visible=False)
circle.shapesize(radius / CURSOR_SIZE)
circle.color(color)
circle.penup()
while True:
nx = randint(2 * radius - width // 2, width // 2 - radius * 2)
ny = randint(2 * radius - height // 2, height // 2 - radius * 2)
circle.goto(nx, ny)
for other_radius, other_circle in circles:
if circle.distance(other_circle) < 2 * max(radius, other_radius):
break
else:
break
circle.showturtle()
circle.onclick(lambda x,y,t=circle: (circle.hideturtle(), addscore()))
screen.onclick(deletescore())
return radius, circle
username=str(input("Set your username: "))
screen = turtle.Screen()
screen.bgcolor("lightgreen")
screen.title("Speed Clicker")
width, height = screen.window_width(), screen.window_height()
circles = []
gameLength = 30
difficulty = 20
startTime = time.time()
while True:
time.sleep(1/difficulty)
rgb = (random(), random(), random())
timeTaken = time.time() - startTime
circles.append(my_circle(rgb))
screen.title('SCORE: {}, TIME LEFT: {}'.format(score,int(round(gameLength - timeTaken,0))))
if time.time() - startTime > gameLength:
break
screen.title('GG! FINAL SCORE: {}'.format(score))
screen.mainloop()
The problem is this line:
screen.onclick(deletescore())
It's in the wrong place (only needs to be called once, not in a loop) and the argument is incorrect, it should be passing the function not calling it:
screen.onclick(deletescore)
The fix is multifold: first, move the modified statement to just before your while True: statement. Then fix the definition of deletescore() to take x and y arguments that we won't use but are necessary to be a click handler. (Or wrap it in a lambda like the call to addscore())
However, a click on a turtle can also be passed through as a click on the screen. To counter this, we can add 2 in addscore() instead of 1 since deletescore() will get called as well. That should be enough to get things working.
However, we really should eliminate the while True: loop and sleep() call which have no place in an event-driven program. Instead we use ontimer() to keep things moving and not potentially block other events. The reformulated code would be more like:
from turtle import Turtle, Screen
from random import random, randint
from time import time
CURSOR_SIZE = 20
def addscore():
global score
score += 2 # add 2 as this will also count as a -1 screen click!
def deletescore():
global score
score -= 1
def my_circle(color):
circle = Turtle('circle', visible=False)
circle.shapesize(radius / CURSOR_SIZE)
circle.color(color)
circle.penup()
while True:
nx = randint(2 * radius - width // 2, width // 2 - radius * 2)
ny = randint(2 * radius - height // 2, height // 2 - radius * 2)
circle.goto(nx, ny)
for other_radius, other_circle in circles:
if circle.distance(other_circle) < 2 * max(radius, other_radius):
break
else:
break
circle.onclick(lambda x, y: (circle.hideturtle(), addscore()))
circle.showturtle()
return radius, circle
def play():
rgb = (random(), random(), random())
timeTaken = time() - startTime
circles.append(my_circle(rgb))
screen.title('SCORE: {}, TIME LEFT: {}'.format(score, int(round(gameLength - timeTaken, 0))))
if time() - startTime > gameLength:
screen.title('FINAL SCORE: {}'.format(score))
screen.onclick(None)
screen.clear()
else:
screen.ontimer(play, 1000 // difficulty)
screen = Screen()
screen.bgcolor("lightgreen")
screen.title("Speed Clicker")
width, height = screen.window_width(), screen.window_height()
score = 0
circles = []
radius = 15
difficulty = 20
gameLength = 30
screen.onclick(lambda x, y: deletescore())
startTime = time()
play()
screen.mainloop()

Categories

Resources