Been using turtle library to make a billiard and cue ball trajectory. I have implemented the ball trajectory physics, so when it hits the walls (or limited distance specified) it reflects to the appropriate direction. But I faced a problem where the ball goes to a direction I didn't specify then according to the heading given it rotates a bit to that direction. I debugged the code and found no issues, and tried to find where the coordinates the cue ball is trying to initially go to, knowing that if the loop of ball movement changes, the initial value always is to go to that direction and breaks the point of having a heading.
import turtle
from math import *
t = turtle.Turtle()
width=200
# White ball - Moving ball
t.color("black")
t.shape("circle")
t.penup()
t.goto(90,-80)
# White ball - Permanent
ball=turtle.Turtle()
ball.speed(0)
ball.penup()
ball.goto(90,-80)
ball.color("black")
ball.shape("circle")
angleString="Enter the hit force's angle (0-360) in degrees: "
angle=int(input(angleString))
# Moving Ball
t.color("yellow")
# Ball movement mechanic
step=20
t.penup()
LIMIT_X=width-8
LIMIT_Y=(width/2)-8
t.setheading(angle)
t.pendown()
x=-LIMIT_X
y=LIMIT_Y
t.dx=step*cos(angle)
t.dy=step*sin(angle)
for x in range(50):
t.speed(1)
print(x,t.pos(), t.heading())
x+=t.dx
y+=t.dy
t.goto(x,y)
# Check vertical borders
if abs(x)>LIMIT_X:
t.dx=-t.dx
# Check horizontal borders
if abs(y)>LIMIT_Y:
t.dy=-t.dy
turtle.done()
showcasing an example of angle 120
I see two problems with your code. First, you have two different x variables in play at the same time:
x=-LIMIT_X
y=LIMIT_Y
t.dx=step*cos(angle)
t.dy=step*sin(angle)
for x in range(50):
t.speed(1)
print(x,t.pos(), t.heading())
x+=t.dx
y+=t.dy
t.goto(x,y)
I'm guessing the iteration x variable is only meant for the debugging print() statement, not to position the ball, and should be renamed.
Second, angle is in degrees but the trigonometric funtions from the math module assume radians, so these calculations make no sense:
t.dx=step*cos(angle)
t.dy=step*sin(angle)
Use the radians() function from math to convert the angle. (Turtle can work with either, but you need to tell it if you want to use radians instead of degrees.)
Additionally, this statement does nothing:
t.setheading(angle)
as you move your ball with goto(). The heading would only matter if you were moving your ball with forward().
My rework of your code fixing the above along with other changes:
from turtle import Screen, Turtle
from math import sin, cos, radians
WIDTH = 200
LIMIT_X = WIDTH - 8
LIMIT_Y = WIDTH/2 - 8
STEP = 20
ANGLE_STRING = "Enter the hit force's angle (0-360) in degrees: "
CURSOR_SIZE = 20
angle = int(input(ANGLE_STRING))
screen = Screen()
# Billard table (roughly)
table = Turtle()
table.hideturtle()
table.shape('square')
table.shapesize(WIDTH / CURSOR_SIZE + 1, WIDTH*2 / CURSOR_SIZE + 1)
table.color('green')
table.stamp()
# Black ball - Permanent
permanent = Turtle()
permanent.shape('circle')
permanent.color('black')
permanent.penup()
permanent.goto(90, -80)
# Yellow ball - Moving ball
moving = permanent.clone()
moving.color('yellow')
moving.pendown()
x = -LIMIT_X
y = LIMIT_Y
moving.dx = STEP * cos(radians(angle))
moving.dy = STEP * sin(radians(angle))
for z in range(50):
moving.speed('slowest')
print(z, moving.pos(), moving.heading())
x += moving.dx
y += moving.dy
moving.goto(x, y)
# Check vertical borders
if abs(x) > LIMIT_X:
moving.dx = -moving.dx
# Check horizontal borders
if abs(y) > LIMIT_Y:
moving.dy = -moving.dy
screen.mainloop()
I'm not saying it does what you want, it's simply less buggy. Perhaps basing your motion on setheading() and forward() instead of goto() might get you the result you desire. You'll have to rethink your wall reflection logic, of course.
Related
So my assignement is to:
make a blue rectangle
write a function that makes the turle move in a random direction within an interval of 90 degrees and move forward in a random interval of 0-25
create a blue square
Move the turle to a random point in the square
Code so the turtle moves back inside the square if it leaves it
Create an additonal turle (both should have different colors)
Use the same statement to move both turtles (with the move_random function) 500 times
if the turtles are closer than 50 units - print a string that counts the number of times they are 50 units close.
This is what it should look like:
enter image description here
I've added some comments to explain my thought process
Any and all help is appreciated
The code:
EDIT: fixed the indentations, now i get the error message on the last line that the name "meet" is not defined. Also if i run the code without the last line which is supposed to print the amount of close encounters, nothing happens, no errors, but no turtles either.
import turtle
import random
#makes the jump function
def jump(t, x, y):
t.penup()
t.goto(x, y)
t.pendown()
#creares a turtle at a defined place
def make_turtle(x, y):
t = turtle.Turtle()
jump(t, x, y) # Use of the function defined above
return t
#function to create a rectangle and fill it with a color
def rectangle(x, y, width, height, color):
t = make_turtle(x, y)
t.speed(0)
t.hideturtle()
t.fillcolor(color)
t.begin_fill()
for dist in [width, height, width, height]:
t.forward(dist)
t.left(90)
t.end_fill()
#function to move turtle in a random heading (90 degree interval) between 0--25 units forward
#While also making it turn around if it is outside of the square
def move_random(t):
if abs(t.pos()[0]) >= 250 or abs(t.pos()[1]) >= 250:
target = (0, 0)
d = (0,0)
t.setheading(d)
else:
ini = t.heading()
new = rd.randint(ini - 45, ini + 45)
t.setheading(new)
t.forward(rd.randint(0, 25))
#creates the square and both turtles
t = make_turtle(0 , 0)
t.color("green")
t2 = make_turtle(0 , 0)
t2.color("black")
rectangle(-250, -250, 500, 500, "lightblue")
jump(t, rd.randint(-250, 250), rd.randint(-250, 250))
jump(t2, rd.randint(-250, 250), rd.randint(-250, 250)) #jumps the turles randomly in the square
meet = 0
for i in range(1, 501): #makes the turtles move randomly as specified above
move_random(t)
move_random(t2)
if t.distance(t2) < 50:
t.write("close")
meet += 1
print(str(meet), "close encounter") #prints the amount of times they are close to each other
if abs(t.pos()[0]) >= 250 or abs(t.pos()[1]) >= 250:
target = (0, 0)
d = (0,0)
t.setheading(d)
else:
See that function before the "else:"? You missed a tab there.
I have to make a change to this specific code, which produces a square grid of circles, I have to change the code to make a triangle grid of circles.
import turtle
window = turtle.Screen()
my_boi = turtle.Turtle()
my_boi.speed(0)
for y in range(-200,200,50):
for x in range(-200,200,50):
my_boi.penup()
my_boi.setposition(x,y)
my_boi.pendown()
my_boi.circle(20)
window.exitonclick()
I'm sure there is a smarter approach, but this is one way to do it:
import turtle
window = turtle.Screen()
my_boi = turtle.Turtle()
my_boi.speed(0)
for (i,y) in enumerate(range(-200,200,50)):
for x in range(-200+(25*i),200-(25*i),50):
my_boi.penup()
my_boi.setposition(x,y)
my_boi.pendown()
my_boi.circle(20)
window.exitonclick()
turtle.done()
In the second for-loop the range is iteratively decreased by 1/2 of the circle diameter in each side.
I'd simplify things somewhat:
from turtle import Screen, Turtle
window = Screen()
my_boi = Turtle()
my_boi.speed('fastest')
my_boi.penup()
for y in range(1, 9):
my_boi.setposition(-25 * y + 25, 50 * y - 250)
for x in range(y):
my_boi.pendown()
my_boi.circle(20)
my_boi.penup()
my_boi.forward(50)
my_boi.hideturtle()
window.exitonclick()
Only the starting position of each row has to be calculated and placed via setposition(). The column positions can be a simple forward() statement.
I know this is an old post, but I was looking for the answer and managed to figure it out, so to help everyone else out, All you need to do is change the stop range in the nested loop to be y. I switched the x and y variables because I wanted the triangle to be flat, but if you need it the other way that also works.
import turtle
window = turtle.Screen()
my_boi = turtle.Turtle()
my_boi.speed(0)
for x in range(-200,200,50):
for y in range(-200,x,50):
my_boi.penup()
my_boi.setposition(x,y)
my_boi.pendown()
my_boi.circle(20)
window.exitonclick()
I am a beginner at Python and I'm new to Stack Exchange. I'm trying to write a program that has 5 turtles moving within a square. I've got code that does what I want, but it's tedious and I'd like to initialize all my turtles using classes instead of doing it one by one. I just want them to start out at random coordinates and with a random heading.
The problems with my code:
Only one turtle is shown on screen. Two are defined in the code below.
The turtle's heading and coordinates aren't being initialized.
Here's the code that I've tried:
import numpy as np
from turtle import *
# setting up screen
reset()
screensize(550)
Screen().bgcolor('black')
tracer(0)
# drawing box
t0 = Turtle()
t0.penup()
t0.goto(-256,-256)
t0.color('cyan')
t0.pendown()
for i in range(4):
t0.forward(512)
t0.left(90)
t0.ht()
# parameters
velocity = 5
iterations = 200
boxsize = 512
ranheader = np.random.random()*360
ranx = np.random.random()*boxsize
rany = np.random.random()*boxsize
class turtle_agents(Turtle):
def _init_(self):
self.up()
self.seth(ranheader)
self.setpos(ranx,rany)
self.velocity = velocity
self.down()
# turtle
t1 = turtle_agents()
t1.color('green')
t2 = turtle_agents()
t2.color('blue')
# turtle movement
for turtle in turtles():
for i in range(iterations):
turtle.forward(velocity)
if turtle.xcor() >= 256:
turtle.goto(-256,t0.ycor())
elif turtle.xcor() <= -256:
turtle.goto(256,t0.ycor())
elif turtle.ycor() >= 256:
turtle.goto(t0.xcor(),-256)
elif turtle.ycor() <= -256:
turtle.goto(t0.xcor(),256)
update()
exitonclick()
only one turtle shown on screen. Two are defined in the code below.
the turtle's heading and coordinates aren't being initialized.
I believe the problem is that you defined the random position and heading once, outside the turtle creation loop so they all start in the same place, move in the same direction at the same speed. I.e. they're right on top of each other.
We don't need #BlivetWidget's explicit List to fix the problem since, as you discovered, turtles are already maintained in a list which we can get via the screen's turtles() method. Below is my rework of your code to fix various issues:
from turtle import Screen, Turtle
from random import randrange, randint
# parameters
COLORS = ['green', 'blue', 'red', 'orange', 'white']
ITERATIONS = 500
VELOCITY = 5
BOX_SIZE = 512
# setting up screen
screen = Screen()
screen.setup(BOX_SIZE + 50, BOX_SIZE + 50)
screen.bgcolor('black')
screen.tracer(False)
# drawing box
turtle = Turtle()
turtle.hideturtle()
turtle.color('cyan')
turtle.penup()
turtle.goto(-BOX_SIZE/2, -BOX_SIZE/2)
turtle.pendown()
for _ in range(4):
turtle.forward(BOX_SIZE)
turtle.left(90)
# turtle
for color in COLORS:
angle = randrange(360)
x = randint(-BOX_SIZE/2, BOX_SIZE/2)
y = randint(-BOX_SIZE/2, BOX_SIZE/2)
turtle = Turtle()
turtle.color(color)
turtle.setheading(angle)
turtle.penup()
turtle.setposition(x, y)
turtle.pendown()
# turtle movement
for _ in range(ITERATIONS):
for turtle in screen.turtles():
turtle.forward(VELOCITY)
x, y = turtle.position()
if x >= BOX_SIZE/2:
turtle.penup()
turtle.setx(-BOX_SIZE/2)
turtle.pendown()
elif x <= -BOX_SIZE/2:
turtle.penup()
turtle.setx(BOX_SIZE/2)
turtle.pendown()
elif y >= BOX_SIZE/2:
turtle.penup()
turtle.sety(-BOX_SIZE/2)
turtle.pendown()
elif y <= -BOX_SIZE/2:
turtle.penup()
turtle.sety(BOX_SIZE/2)
turtle.pendown()
screen.update()
screen.exitonclick()
I agree with #BlivetWidget that "you don't need to create a class just to move them to your starting positions". I use a simple loop above.
You should consider storing your turtles in a list, as the turtles are already objects and you don't need to create a class just to move them to your starting positions. Lists in Python are incredibly powerful because they can store arbitrary data types. Here, I will create 5 turtles and move them so you can tell them apart:
import turtle
num_turtles = 5
my_turtles = [turtle.Turtle() for i in range(num_turtles)]
for i, turt in enumerate(my_turtles):
turt.forward(50 * i)
You want to do the same thing, just replace my turt.forward() line with whatever you want the turtles to do. In your case, go to a random position within your square.
Been at this for the past few hours, trying to make a small program where an image chases the cursor around. So far I've managed to make it so that the image is directly on top of the cursor and follows it around that way. However what I need is for the image to actually "chase" the cursor, so it would need to initially be away from it then run after it until it's then on top of the mouse.
Basically hit a wall with whats going wrong and what to fix up, here's what I've gotten so far:
from __future__ import division
import pygame
import sys
import math
from pygame.locals import *
class Cat(object):
def __init__(self):
self.image = pygame.image.load('ball.png')
self.x = 1
self.y = 1
def draw(self, surface):
mosx = 0
mosy = 0
x,y = pygame.mouse.get_pos()
mosx = (x - self.x)
mosy = (y - self.y)
self.x = 0.9*self.x + mosx
self.y = 0.9*self.y + mosy
surface.blit(self.image, (self.x, self.y))
pygame.display.update()
pygame.init()
screen = pygame.display.set_mode((800,600))
cat = Cat()
Clock = pygame.time.Clock()
running = True
while running:
screen.fill((255,255,255))
cat.draw(screen)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
pygame.display.update()
Clock.tick(40)
Probably not in the best shape of coding, been messing with this for just over 5 hours now. Any help is much appreciated! Thanks :)
Assuming you want the cat to move at a fixed speed, like X pixels per tick, you need to pick a new position X pixels toward the mouse cursor. (If you instead want the cat to move slower the closer it gets, you'd instead pick a position a certain % of the way between the current position and the mouse cursor. If you want it to move faster the closer it gets, you need to divide instead of multiply. And so on. But let's stick with the simple one first.)
Now, how do you move X pixels toward the mouse cursor? The usual way of describing this is: You find the unit vector in the direction from the current position to the cursor, then multiply it by X, and that gives you the steps to add. And you can reduce that to nothing fancier than a square root:
# Vector from me to cursor
dx = cursor_x - me_x
dy = cursor_y - me_y
# Unit vector in the same direction
distance = math.sqrt(dx*dx + dy*dy)
dx /= distance
dy /= distance
# speed-pixel vector in the same direction
dx *= speed
dy *= speed
# And now we move:
me_x += dx
me_y += dy
Note that me_x and me_y are going to be floating-point numbers, not integers. That's a good thing; when you move 2 pixels northeast per step, that's 1.414 pixels north and 1.414 pixels east. If you round that down to 1 pixel each step, you're going to end up moving 41% slower when going diagonally than when going vertically, which would look kind of silly.
I am trying to fill the color in these squares:
Right now the turtle only fills the corners of theses squares, not the entire square.
Here is my code:
import turtle
import time
import random
print ("This program draws shapes based on the number you enter in a uniform pattern.")
num_str = input("Enter the side number of the shape you want to draw: ")
if num_str.isdigit():
squares = int(num_str)
angle = 180 - 180*(squares-2)/squares
turtle.up
x = 0
y = 0
turtle.setpos(x,y)
numshapes = 8
for x in range(numshapes):
turtle.color(random.random(),random.random(), random.random())
x += 5
y += 5
turtle.forward(x)
turtle.left(y)
for i in range(squares):
turtle.begin_fill()
turtle.down()
turtle.forward(40)
turtle.left(angle)
turtle.forward(40)
print (turtle.pos())
turtle.up()
turtle.end_fill()
time.sleep(11)
turtle.bye()
I've tried moving around turtle.begin_fill() and end_fill() in numerous locations with no luckā¦ Using Python 3.2.3, thanks.
I haven't really used turtle, but it looks like this may be what you want to do. Correct me if I've assumed the wrong functionality for these calls:
turtle.begin_fill() # Begin the fill process.
turtle.down() # "Pen" down?
for i in range(squares): # For each edge of the shape
turtle.forward(40) # Move forward 40 units
turtle.left(angle) # Turn ready for the next edge
turtle.up() # Pen up
turtle.end_fill() # End fill.
You're drawing a series of triangles, using begin_fill() and end_fill() for each one. What you can probably do is move your calls to begin_fill() and end_fill() outside the inner loop, so you draw a full square and then ask for it to be filled.
Use fill
t.begin_fill()
t.color("red")
for x in range(4):
t.fd(100)
t.rt(90)
t.end_fill()
Along with moving begin_fill() and end_fill() outside the loop, as several folks have mentioned, you've other issues with your code. For example, this is a no-op:
turtle.up
I.e. it doesn't do anything. (Missing parentheses.) This test:
if num_str.isdigit():
Doesn't do much for you as there is no else clause to handle the error. (I.e. when it isn't a number, the next statement simply uses the string as a number and fails.) This calculation seems a bit too complicated:
angle = 180 - 180*(squares-2)/squares
And finally there should be a cleaner way to exit the program. Let's address all these issues:
from turtle import Screen, Turtle
from random import random
NUMBER_SHAPES = 8
print("This program draws shapes based on the number you enter in a uniform pattern.")
num_str = ""
while not num_str.isdigit():
num_str = input("Enter the side number of the shape you want to draw: ")
sides = int(num_str)
angle = 360 / sides
delta_distance = 0
delta_angle = 0
screen = Screen()
turtle = Turtle()
for x in range(NUMBER_SHAPES):
turtle.color(random(), random(), random())
turtle.penup()
delta_distance += 5
turtle.forward(delta_distance)
delta_angle += 5
turtle.left(delta_angle)
turtle.pendown()
turtle.begin_fill()
for _ in range(sides):
turtle.forward(40)
turtle.left(angle)
turtle.forward(40)
turtle.end_fill()
screen.exitonclick()