TurtleGraphics Python: Bouncing turtle off the walls? - python

So, I am trying to make a realistic bouncing function, where the turtle hits a wall and bounces off at the corresponding angle. My code looks like this:
def bounce(num_steps, step_size, initial_heading):
turtle.reset()
top = turtle.window_height()/2
bottom = -top
right = turtle.window_width()/2
left = -right
turtle.left(initial_heading)
for step in range(num_steps):
turtle.forward(step_size)
x, y = turtle.position()
if left <= x <= right and bottom <= y <= top:
pass
else:
turtle.left(180-2 * (turtle.heading()))
So, this works for the side walls, but I don't get how to make it bounce correctly off the top/bottom. Any suggestions?

Try something like this:
if not (left <= x <= right):
turtle.left(180 - 2 * turtle.heading())
elif not (bottom <= y <= top):
turtle.left(-2 * turtle.heading())
else:
pass
My python syntax is a little rusty, sorry :P. But the math is a little different for a horizontal vs. a vertical flip.
EDIT:
I suspect that what is happening is your turtle is getting into a situation where it is pointing upwards and stuck above the top wall. That would lead it to just flip indefinitely. You could try adding the following conditions:
if (x <= left and 90 <= turtle.heading() <= 270) or (right <= x and not 90 <= turtle.heading() <= 270):
turtle.left(180 - 2 * turtle.heading())
elif (y <= bottom and turtle.heading() >= 180) or (top <= y and turtle.heading <= 180):
turtle.left(-2 * turtle.heading())
else:
pass
If that works, there is probably a bug elsewhere in your code. Edge handling is tricky to get right. I assume that turtle.heading() will always return something between 0 and 360 - if not then it will be even more tricky to get right.

Gday,
Your problem seems to be that you are using the same trigonometry to calculate the right and left walls, as you are the top and bottom. A piece of paper and a pencil should suffice to calculate the required deflections.
def inbounds(limit, value):
'returns boolean answer to question "is turtle position within my axis limits"'
return -limit < value * 2 < limit
def bounce(num_steps, step_size, initial_heading):
'''given the number of steps, the size of the steps
and an initial heading in degrees, plot the resultant course
on a turtle window, taking into account elastic collisions
with window borders.
'''
turtle.reset()
height = turtle.window_height()
width = turtle.window_width()
turtle.left(initial_heading)
for step in xrange(num_steps):
turtle.forward(step_size)
x, y = turtle.position()
if not inbounds(height, y):
turtle.setheading(-turtle.heading())
if not inbounds(width, x):
turtle.setheading(180 - turtle.heading())
I've used the setheading function and a helper function (inbounds) to further declare the intent of the code here. Providing some kind of doc-string is also good practice in any code that you write (provided the message it states is accurate!!)
Your mileage may vary on the use of xrange, Python 3.0+ renames it to simply range.

Related

Drawing regular polygons inside each other with specific specing using turtle module

I am trying to create a function that draws regular polygons inside each other as in the attached picture. The size of the polygons' side is defined by the following formula:
initial_size = initial_radius*(2*math.sin(360/(2*number_of_angles)))
I have two questions: 1. Why when I assign "initial_size" by the above formula my drawing starts in a different direction rather than when I simply assign initial_size = 100 (disregarding the formula)? 2. Can you please provide me with a hint (or any direction) on a way I can draw regular polygons as on the picture (i.e., starting from different points (moving along x-axis) and drawing each polygon inside the other?
import turtle
import math
turtle.speed(1)
def reg_polygon(number_of_angles, initial_radius):
Q = 180-(180/number_of_angles)/2
turtle.left(Q)
initial_size = initial_radius*(2*math.sin(360/(2*number_of_angles)))
if number_of_angles>=3:
sum_angle = 180*(number_of_angles-2)
angle = sum_angle/number_of_angles
for i in range(number_of_angles):
turtle.forward(initial_size)
turtle.left(180-angle)
elif number_of_angles<3:
print("Minimum number of angels should be >=3")
for i in range(3,6):
reg_polygon(i,100)
turtle.done()
Here is what the following code snippet draws; it is not an absolutely perfect reproduction of the gif you posted (some tweaks for the size progression for 3, 4, and 5-gons must be done to avoid the smaller neighbors to touch at some points - i/e the gif maker somewhat cheated!), but it follows mathematical symmetry and perfection.
The following code has some magic numbers; I may come back to it later, but not at this time. The resources I used to calculate a regular polygon can be found here, and there.
import turtle
import math
def reg_polygon(start_pos, number_of_angles, side):
interior_angle = (180 * (number_of_angles - 2)) / number_of_angles
turtle.setheading(180 - interior_angle//2)
for i in range(number_of_angles):
turtle.forward(side)
turtle.left(180 - interior_angle)
def reset_start_point():
global start_pos, startx, starty, initial_size, number_of_angles, side
startx += 8
starty -= 0
initial_size += 8
number_of_angles += 1
side = 2 * initial_size * math.sin(math.radians(180/number_of_angles))
start_pos = startx, starty
turtle.penup()
turtle.goto((startx, starty))
turtle.pendown()
start_pos = startx, starty = 0, 0
number_of_angles = 2
initial_size = 15 # radius
side = 0
while number_of_angles < 21:
reset_start_point()
reg_polygon(start_pos, number_of_angles, side)
turtle.done()

Using recursion to draw nested Triangles

I'd like to draw a series of nested triangles using recursion.
My faulty code below:
def recursiveTri(x, y, shrink):
tt.penup()
tt.setx(x)
tt.sety(y)
if x > -10:
return
for element in range(3):
tt.pendown()
tt.forward(x)
tt.right(120)
recursiveTri(x + shrink, y - shrink, shrink)
def main():
recursiveTri(-300,300,30)
main()
The current code produces the following:
Here is what I mean by nested shapes, except that I would like to draw triangles instead of squares:
And now, just for fun, the "better living through stamping" solution:
import turtle
CURSOR_SIZE = 20
def recursiveTri(side, shrink):
if side > 10:
turtle.shapesize(side / CURSOR_SIZE)
turtle.stamp()
recursiveTri(side - shrink, shrink)
turtle.hideturtle()
turtle.shape('triangle')
turtle.fillcolor('white')
recursiveTri(300, 30)
turtle.dot()
turtle.exitonclick()
This is the default orientation, you can turn it anyway you want before calling recursiveTri(). Stamping is an alternative to drawing that works best with simple geometric patterms like this.
The problem is with
tt.forward(x)
Remember that your x is always negative, and the length of a side is not x, but rather -2 * x, if you want to be symmetrical about zero. Since your triangles are nested, you can also compute the initial y from x, given that it is 1/3 of the way up the main bisector. -sqrt(3) / 3 * x therefore puts the center of the circles circumscribed around and inscribed in your triangle at 0, 0.
In fact, it is probably easier to just fix the length of a side, and compute x and y from that:
import turtle as tt
from math import sqrt
def recursiveTri(side, shrink):
if side < 10: return
tt.penup()
tt.goto(-side / 2, sqrt(3) / 6 * side)
tt.pendown()
for _ in range(3):
tt.forward(side)
tt.right(120)
recursiveTri(side - shrink, shrink)
tt.penup()
tt.home()
tt.dot()
recursiveTri(300, 30)
In this case, shrink is the total amount removed from each side. If you want it to be the amount you step forward, change the recursive call to recursiveTri(side - 2 * shrink, shrink).
The result (without multiplying shrink by 2) is

Click-Circle game - clicking outside and inside the circle

I am trying to code a game that has a red circle in which the user is supposed to click up to 7 times in the window. If the user clicks outside the circle, the circle will change its position to where the user clicked. And the game should end when the user has clicked 3 times inside the circle (does not have to be in a row) or when the user has clicked 7 times in total.
I have coded and done quite most of it I think, its just I cant seem to make it work as I want to.
from graphics import *
def draw_circle(win, c=None):
x = random.randint(0,500)
y = random.randint(0,500)
if var is None:
centa = Point(x,y)
var = Circle(centa,50)
var.setFill(color_rgb(200,0,0))
var.draw(win)
else:
p1 = c.p1
x_dif = (p1.x - x) * -1
y_dif = (p1.y - y) * -1
var.move(x_dif, y_dif)
return (var, x, y)
def main():
win= GraphWin("game",800,800)
score = 0
var,x,y = draw_circle(win)
while score <= 7:
mouseClick2=win.getMouse()
if mouseClick2.y >= y-50 and mouseClick2.y <= y +50 and
mouseClick2.x >= x-50 and mouseClick2.x <= x+50:
score=score + random.randint(0,5)
var,x,y = draw_circle(win, c)
print ("Success!")
print (("the score is, {0}").format(score))
thanks for the help in advance!
I see a couple problems.
your if mouseClick2.y >= y-50... conditional is spread out on two lines, but you don't have a line continuation character.
You never call main().
You don't import random.
You call draw_circle in the while loop with an argument of c, but there is no variable by that name in the global scope. You probably meant to pass in var.
c in draw_circle ostensibly refers to the circle object you want to manipulate, but half the time you manipulate var instead of c.
you assign a value to cvar in the loop, but never use it.
Your else block in draw_circle calculates the movement delta by subtracting the cursor coordinates from c.p1. But c.p1 is the upper-left corner of the circle, not the center of the circle. So your hit detection is off by fifty pixels.
import random
from graphics import *
def draw_circle(win, c=None):
x = random.randint(0,500)
y = random.randint(0,500)
if c is None:
centa = Point(x,y)
c = Circle(centa,50)
c.setFill(color_rgb(200,0,0))
c.draw(win)
else:
center_x = c.p1.x + 50
center_y = c.p1.y + 50
x_dif = (center_x - x) * -1
y_dif = (center_y - y) * -1
c.move(x_dif, y_dif)
return (c, x, y)
def main():
win= GraphWin("game",800,800)
score = 0
var,x,y = draw_circle(win)
while score <= 7:
mouseClick2=win.getMouse()
if mouseClick2.y >= y-50 and mouseClick2.y <= y +50 and \
mouseClick2.x >= x-50 and mouseClick2.x <= x+50:
score=score + random.randint(0,5)
var,x,y = draw_circle(win, var)
print ("Success!")
print (("the score is, {0}").format(score))
main()
Additional possible improvements:
Your hit detection checks whether the cursor is in a 50x50 rectangle centered on the circle. You could instead check whether the cursor is inside the circle if you measured the distance between the cursor and the center, and checked whether it was less than the radius.
var and c could stand to have more descriptive names.
mouseClick2 doesn't make much sense as a name, considering there's no mouseClick1.
The movement delta arithmetic could be simplified: (a-b) * -1 is the same as (b-a).
If you only use a variable's value once, you can sometimes avoid creating the variable at all if you nest expressions.
it might be nice to define constants, such as for the circle's radius, instead of having magic numbers in your code.
You can save five characters by using += to increment the score.
import math
import random
from graphics import *
RADIUS = 50
def draw_circle(win, circle=None):
x = random.randint(0,500)
y = random.randint(0,500)
if circle is None:
circle = Circle(Point(x,y),RADIUS)
circle.setFill(color_rgb(200,0,0))
circle.draw(win)
else:
circle.move(
x - circle.p1.x - RADIUS,
y - circle.p1.y - RADIUS
)
return (circle, x, y)
def main():
win= GraphWin("game",800,800)
score = 0
circle,x,y = draw_circle(win)
while score <= 7:
cursor = win.getMouse()
if math.hypot(cursor.x - x, cursor.y - y) <= RADIUS:
score += random.randint(0,5)
circle,x,y = draw_circle(win, circle)
print ("Success!")
print (("the score is, {0}").format(score))
main()
I'm not really a python guy, but I see that your hitbox is wrong. If there are any other issues then comment it/them to me.
Solving hitbox to be circle:
What you have already written is good to have thing but you should check if click was in circle not square. Pythagoras triangle is solution for this.
Check:
if (math.sqrt(delta_x **2 + delta_y **2) <= circle_radius)
where delta_x and delta_y is center coordinate minus mouse position

How can I make bullets go towards the cursor properly in Pygame

So hey, I'm new into making games and programming at all, I tried to make a small game thingy in Pygame that would include a dude shooting bullets and stuff. However, I can't really get the shooting mechanics right. I have code like this
class Projectile():
def __init__(self, surface, color, start_pos, end_pos, move_speed):
super().__init__()
# Shooter and target positions
self.start_pos = start_pos
self.end_pos = end_pos
# Trigonometry stuffs and like
self.dx = self.end_pos[0] - self.start_pos[0]
self.dy = self.end_pos[1] - self.start_pos[1]
self.rads = atan2(-self.dy,self.dx)
self.rads %= 2*pi
self.degs = degrees(self.rads)
# More stuff I dont understand but it works nearly well.
self.quarter = self.get_quarter(self.degs)
# Change the way rel_x and rel_y are calculated so stuff works fair enough
if self.quarter == 1:
self.rel_x = -self.end_pos[0] + self.start_pos[0]
self.rel_y = -self.end_pos[1] + self.start_pos[1]
elif self.quarter == 2:
self.rel_x = self.end_pos[0] - self.start_pos[0]
self.rel_y = -self.end_pos[1] + self.start_pos[1]
elif self.quarter == 3:
self.rel_x = -self.end_pos[0] + self.start_pos[0]
self.rel_y = self.end_pos[1] - self.start_pos[1]
elif self.quarter == 4:
self.rel_x = self.end_pos[0] - self.start_pos[0]
self.rel_y = self.end_pos[1] - self.start_pos[1]
self.d = (self.rel_x**2 + self.rel_y**2)**0.5
self.angle_x = ((self.rel_x * cos(self.rads)) / self.d) * move_speed
self.angle_y = ((self.rel_y * -sin(self.rads)) / self.d) * move_speed
def move(self):
self.leftover_x += self.angle_x
self.leftover_y += self.angle_y
self.rect.x += int(self.leftover_x)
self.rect.y += int(self.leftover_y)
self.leftover_x = (self.leftover_x % 1)
self.leftover_y = (self.leftover_y % 1)
Move is written that way because Pygame doesn't let you move something 1.337 of a pixel, so I move something by 1, then save .337, and when the saved endings of the number go over 1 they get added as well.
In result, I get bullets going something like that
With the numbers I marked screen quarters that get used in the if statement within init.
You can see, kinda doesn't work. I want the bullets to make a circle. What should I do.
I will admit, I don't know trigonometry. I didn't learn it in school yet, will have it in a year. Thus, I would like someone to explain it to me as simply as possible. And tell me of a better way to get this going instead of using this quarters thing.
Congratulations! You've discovered sin() and cos() for yourself.
Pull out a piece of paper and mark two points: one for the cursor position (=target), and one for the bullet's current (starting?) position.
Now draw a line between them. The length of that line is your code's .d (for distance, I presume).
Then draw a horizontal line from one of the points, far enough so it ends exactly over or under the other point. The length of that is your rel_x (in Calculus you'll call it "dx").
Then draw the vertical line to finish a triangle -- that length is your rel_y (or "dy").
I'm not sure why you need the variables you've called angle_x and angle_y. Once fired, the angle will always be the same, like a bullet (unless the target is moving and you're flying a pursuit course, like a homing missile; to do a "pure pursuit" you just re-calculate the course each turn, so it always points to where the target is at that time).
What you need next is how far to move. That's going to be some part of d. If your speed is greater than d, you cover all of d and arrive; otherwise you only cover a fraction of d. So you want something like:
if (speed > d): fraction_of_d = 1
else: fraction_of_d = speed/d
Now all you need to do is notice that if you're going 1/n of the way to the target, you'll also be covering 1/n of the x and of the y distances (stare at the paper longer if you don't see that right off).
But you already have the total x and y distances (rel_x and rel_y). So you just multiply each one by fraction_of_d, and that tells you how far to move along that direction. Then you update the bullet's location by those amounts.
You'll learn in trig that sin() is just the length of the "opposite" (the vertical side of the triangle), divided by the length of the hypotenuse (d). cos() is just the length of the "adjacent" (the horizontal side) divided by the length of the hypotenuse. So you've effectively calculated the sine and cosine already.
Hope that makes it clearer.
-steve
Okay. I found the solution myself. Bullets still kinda go not exactly to the cursor in quarters 2 and 3 but it makes a circle.
As it turned out, the fact that I had the sin() and cos() functions there was the problem. I just removed them to make the code be
self.angle_x = (self.rel_x / self.d) * move_speed
self.angle_y = (self.rel_y / self.d) * move_speed
and it works now. I also could remove this quarter detection thing, it works now without it. Final code looks like this:
class Projectile():
def __init__(self, surface, color, start_pos, end_pos, move_speed):
super().__init__()
# Shooter and target positions
self.start_pos = start_pos
self.end_pos = end_pos
# Trigonometry stuffs and like
# NOW REDUNDANT
# self.dx = self.end_pos[0] - self.start_pos[0]
# self.dy = self.end_pos[1] - self.start_pos[1]
# self.rads = atan2(-self.dy,self.dx)
# self.rads %= 2*pi
# self.degs = degrees(self.rads)
# More stuff I dont understand but it works nearly well.
# self.quarter = self.get_quarter(self.degs)
self.rel_x = self.end_pos[0] - self.start_pos[0]
self.rel_y = self.end_pos[1] - self.start_pos[1]
self.d = (self.rel_x**2 + self.rel_y**2)**0.5
self.angle_x = (self.rel_x / self.d) * move_speed
self.angle_y = (self.rel_y) / self.d) * move_speed
and the result looks like this:
Still, someone should post a better answer that also resolves the issue of bullets not going exactly to the mouse click position.

Python Drawing a Circle with X Radius Using Forward()

I'm using Python Turtles to draw a circle using forward() and right().
I have a for loop counting from 0 to 359, and each time it triggers, it moves the turtle forward 1 and right 1.
But the problem is I need specific diameters. I am nearly 100% sure I'll need to use trig, but I've tried to no avail.
I can't figure out the math how to do it. We're supposed to use forward() and right(), NOT circle().
Thanks!
Here is a working example:
import turtle
import math
def circle(radius):
turtle.up()
# go to (0, radius)
turtle.goto(0,radius)
turtle.down()
turtle.color("black")
# number of times the y axis has been crossed
times_crossed_y = 0
x_sign = 1.0
while times_crossed_y <= 1:
# move by 1/360 circumference
turtle.forward(2*math.pi*radius/360.0)
# rotate by one degree (there will be
# approx. 360 such rotations)
turtle.right(1.0)
# we use the copysign function to get the sign
# of turtle's x coordinate
x_sign_new = math.copysign(1, turtle.xcor())
if(x_sign_new != x_sign):
times_crossed_y += 1
x_sign = x_sign_new
return
circle(100)
print('finished')
turtle.done()
Well, a complete circle is 360°, and you are planning on turning 360 times, so each turn should be:
right( 360 ° / 360 ), or
right(1)
The distance traveled will be one circumference, or π * diameter, so your forward might be:
forward( diameter * π / 360 )
I haven't tested this yet -- give it a try and see how it works.
This is one of the exercises in "Think Python," in chapter 4. It really is a horrible exercise to have this early in the book, especially with the "hint" given. I'm using forward and left here, but you can switch left with right.
You should have the polygon function:
def polygon(t, length, n):
for i in range(n):
bob.fd(length)
bob.lt(360 / n)
Then you create a circle function:
def circle(t):
polygon(t, 1, 360)
That will draw a circle, no radius needed. The turtle goes forward 1, then left 1 (360 / 360), 360 times.
Then, if you want to make the circle bigger, you calculate the circumference of the circle. The hint says:
Hint: figure out the circumference of the circle and make sure that
length * n = circumference.
Ok, so the formula for circumference = 2 * pi * radius. And the hint says length * n = circumference. n = 360 (number of sides/degrees). We have circumference, so we need to solve for length.
So:
def circle(t, r):
circumference = 2 * 3.14 * r
length = circumference / 360
polygon(t, length, 360)
Now, call the function with whatever radius you want:
circle(bob, 200)

Categories