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.
Related
I have been developing a fairly simple turtle program which essentially just draws snowflakes of a random size, shape, and colour. The amount is based on user input. What I would like to do with the program is add an input section at the end that asks the user whether they would like to end the program or restart it. If they choose restart, the program should start over again from the beginning. Otherwise it should follow the exit procedure I made for it. I have seen methods including WHILE Loops and other similar concepts, I just can't figure out how to apply it to my code. Is there a simple way to do this?
Here is the code:
import time
import sys
import turtle
import random
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"]
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(-200, 200)
y = random.randint(-200, 200)
sfsize = random.randint(1, 4)
speedy.penup()
speedy.goto(x, y)
speedy.pendown()
snowflake(sfsize)
print("The turtle window will close in 10 seconds. Thanks for using the program!")
print("Goodbye!")
time.sleep(10)
turtle.Screen().bye()
sys.exit()
Solved! Thanks to the advice from the user https://stackoverflow.com/users/6243352/ggorlen
This is my complete code now that it works.
import time
import sys
import turtle
import random
restart = True
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"]
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(-200, 200)
y = random.randint(-200, 200)
sfsize = random.randint(1, 4)
speedy.penup()
speedy.goto(x, y)
speedy.pendown()
snowflake(sfsize)
print("The turtle window will close in 10 seconds. Thanks for using the program!")
time.sleep(10)
turtle.Screen().bye()
restart = False
restart = input("Do you want to restart the program? Yes or No: ")
restart = restart.upper()
if restart == "YES":
restart = True
print("Restarting...")
elif restart == "NO":
restart = False
print("Thank you for using the program. Goodbye!")
time.sleep(10)
turtle.Screen().bye()
sys.exit()
else:
print("Error.")
I am trying to draw a checker board using the Turtle library and am running into an error where the board window does not open. It was working at the beginning of my session about 30 minutes ago but, I changed some stuff and want to know why it changed.
Here is my code:
##This program draws a checkboard using the turtle library
import turtle
#below initiates the turtle pen and screen
penMain = turtle.Turtle()
turtleMain = turtle.Screen()
def turtleBoard():
for x in range(4):
penMain.forward(30)
penMain.left(90)
penMain.forward(30)
turtleMain.setup(600, 600)
penMain.speed(50)
for a in range(8):
penMain.up()
penMain.setpos(0, 30 * a)
penMain.down()
for x in range(8):
if (a + x)% 2 == 0:
squareColor = 'black'
else:
squareColor = 'white'
penMain.fillcolor(squareColor)
penMain.begin_fill()
turtleBoard()
penMain.end_fill()
I believe this code works besides my one error! Thank you all for your help in advance!
I can't say what changes you made to get your current code, but this code seems to be working:
##This program draws a checkboard using the turtle library
import turtle
#below initiates the turtle pen and screen
penMain = turtle.Turtle()
turtleMain = turtle.Screen()
def turtleBoard():
penMain.forward(30)
turtleMain.setup(600, 600)
penMain.speed(50)
for a in range(8):
for x in range(8):
penMain.up()
penMain.setpos(30 * x, 30 * a)
penMain.down()
penMain.begin_fill()
for xx in range(4):
penMain.forward(30)
penMain.left(90)
if a%2 == x%2:
squareColor = 'black'
else:
squareColor = 'white'
penMain.fillcolor(squareColor)
penMain.end_fill()
turtleBoard()
turtle.done()
Now that we've seen that your code can be made to work, let's consider stamping instead of drawing to make it work more simply and more quickly:
from turtle import Screen, Turtle
SQUARES_PER_EDGE = 8
SQUARE_SIZE = 30 # in pixels
OFFSET = SQUARE_SIZE * (SQUARES_PER_EDGE / 2) - SQUARE_SIZE/2 # center the board
CURSOR_SIZE = 20
def turtleBoard():
turtle.shape('square')
turtle.shapesize(SQUARE_SIZE / CURSOR_SIZE)
turtle.penup()
for y in range(SQUARES_PER_EDGE):
for x in range(SQUARES_PER_EDGE):
turtle.goto(x * SQUARE_SIZE - OFFSET, y * SQUARE_SIZE - OFFSET)
turtle.fillcolor('black' if y % 2 == x % 2 else 'white')
turtle.stamp()
screen = Screen()
screen.setup(600, 600)
turtle = Turtle()
turtle.speed('fastest') # because I have no patience
turtleBoard()
screen.exitonclick()
I lined up the indent of the bottom 4 lines with the last 'else' statement and it worked. Thank you guys!
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()
When clicking within the random squares, it says "You clicked!" only when it draws the next square, not when you initially click.
import turtle
import random
import time
t1 = turtle.Turtle()
t2 = turtle.Turtle()
s = turtle.Screen()
def turtle_set_up():
t1.hideturtle()
t2.hideturtle()
t2.penup()
s.tracer(1, 0)
def draw():
for i in range(2):
t1.fd(400)
t1.lt(90)
t1.fd(400)
t1.lt(90)
def drawbox():
t2.pendown()
for i in range(2):
t2.fd(50)
t2.lt(90)
t2.fd(50)
t2.lt(90)
t2.penup()
def box1():
X = random.randint(0, 350)
Y = random.randint(0, 350)
Xleft = X
Xright = X + 50
Ytop = Y + 50
Ybottom = Y
t2.goto(X, Y)
drawbox()
Here is where it checks if your mouse click is within the box drawn. If it is, it erases the old box for a new one to be drawn every 3 seconds.
I don't understand why it waits to say "You clicked!" until the 3 seconds are up for the loop instead of saying it when you initially click.
I cant place a break command after the print statement that prints "You clicked!" because it's outside the loop.
between here:
t_end = time.time() + 60 * 0.05
while time.time() < t_end:
def get_mouse_click_coor(x, y):
X_click = x
Y_click = y
if Xright > X_click > Xleft and Ytop > Y_click > Ybottom:
print('You clicked!')
s.onclick(get_mouse_click_coor)
t2.clear()
and here:
def starter():
turtle_set_up()
draw()
while 1:
box1()
starter()
Applications with interactive GUI (Graphical User Interface) are event-based, which means that they perform their actions when some events happen. For such applications, if you create a waiting loop for a given amount of time (as does your code) the whole application will be blocked for this amount of time. That's why the print is only executed after the 3s delay.
All GUI libraries include a scheme to activate some timer events. For the turtle API, there is a on_timer(func, delay) method that calls a function func after some delay (expressed in milliseconds). The idea is to repeatedly call your drawbox function every 3000ms. So, your code will be based on two main callback functions: get_mouse_click called on click events, and drawbox called on timer events. Here is the modified code, I propose:
import turtle
import random
t1 = turtle.Turtle()
t2 = turtle.Turtle()
s = turtle.Screen()
def turtle_set_up():
t1.hideturtle()
t2.hideturtle()
s.tracer(1, 0)
s.onclick(get_mouse_click) # define the 'click' event callback
def draw():
for i in range(2):
t1.fd(400)
t1.lt(90)
t1.fd(400)
t1.lt(90)
def drawbox():
global box # store box coordinates as global variable
X = random.randint(0, 350) # so that they are available for testing
Y = random.randint(0, 350) # in the mouse click callback
box = (X, Y, X+50, Y+50)
t2.clear() # clear previous box before drawing new one
t2.penup()
t2.goto(X, Y)
t2.pendown()
for i in range(2):
t2.fd(50)
t2.lt(90)
t2.fd(50)
t2.lt(90)
s.ontimer(drawbox, 3000) # define timer event callback
def get_mouse_click(x, y):
if box[0] <= x <= box[2] and box[1] <= y <= box[3]:
print('You clicked!')
def starter():
turtle_set_up()
draw()
drawbox()
starter()
Computers have to run in sequence so it can only process one line at a time, so, unless i'm mistaken, your program gets 'caught' on the timer then runs through the program and back to the start.
you could develop a while loop with a nested if statement that gets datetime from the device, if turtles have datetime
I believe you can simplify this problem. Primarily by making a turtle be the inner box rather than drawing the inner box. This simplifies drawing, erasing and event handling. Avoid invoking the tracer() method until you have a working program as it only complicates debugging. We can also stamp rather than draw the border.
If we simply want to be able to click on the inner box and have it randomly move to a new location:
from turtle import Turtle, Screen
from random import randint
BORDER_SIZE = 400
BOX_SIZE = 50
CURSOR_SIZE = 20
def move_box():
x = randint(BOX_SIZE/2 - BORDER_SIZE/2, BORDER_SIZE/2 - BOX_SIZE/2)
y = randint(BOX_SIZE/2 - BORDER_SIZE/2, BORDER_SIZE/2 - BOX_SIZE/2)
turtle.goto(x, y)
def on_mouse_click(x, y):
print("You clicked!")
move_box()
screen = Screen()
turtle = Turtle('square', visible=False)
turtle.shapesize(BORDER_SIZE / CURSOR_SIZE)
turtle.color('black', 'white')
turtle.stamp()
turtle.shapesize(BOX_SIZE / CURSOR_SIZE)
turtle.onclick(on_mouse_click)
turtle.penup()
turtle.showturtle()
move_box()
screen.mainloop()
If we want to make the program more game-like and require the user to click on the inner box within 3 seconds of each move, or lose the game, then we can introduce the ontimer() event as #sciroccorics suggests:
from turtle import Turtle, Screen
from random import randint
BORDER_SIZE = 400
BOX_SIZE = 50
CURSOR_SIZE = 20
CLICK_TIMEOUT = 3000 # milliseconds
def move_box():
x = randint(BOX_SIZE/2 - BORDER_SIZE/2, BORDER_SIZE/2 - BOX_SIZE/2)
y = randint(BOX_SIZE/2 - BORDER_SIZE/2, BORDER_SIZE/2 - BOX_SIZE/2)
turtle.goto(x, y)
screen.ontimer(non_mouse_click, CLICK_TIMEOUT)
def on_mouse_click(x, y):
global semaphore
print("You clicked!")
semaphore += 1
move_box()
def non_mouse_click():
global semaphore
semaphore -= 1
if semaphore < 1:
turtle.onclick(None)
turtle.color('black')
print("Game Over!")
screen = Screen()
semaphore = 1
turtle = Turtle('square', visible=False)
turtle.shapesize(BORDER_SIZE / CURSOR_SIZE)
turtle.color('black', 'white')
turtle.stamp()
turtle.shapesize(BOX_SIZE / CURSOR_SIZE)
turtle.onclick(on_mouse_click)
turtle.penup()
turtle.showturtle()
move_box()
screen.mainloop()
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()