Why is My Python Turtle Screen not responding - python

I am trying to create a collision detection system between a turtle and another turtle's line. The problem is that whenever i run the program, the turtle screen does not respond.I am using Pycharm and Python. Please Help!
import turtle
Screen = turtle.Screen()
P1 = turtle.Turtle()
P2 = turtle.Turtle()
Screen.screensize(100, 100)
x1 = []
x2 = []
y1 = []
y2 = []
P1.penup()
P1.setheading(180)
P2.setheading(90)
P1.goto(100, 50)
P2.penup()
P2.goto(50, 0)
P2.pendown()
P1.pendown()
n = 0
Num = 0
XC = P2.position()[0]
YC = P2.position()[1]
x1.append(XC)
y1.append(YC)
while Num == 0:
XC = P2.position()[0]
YC = P2.position()[1]
x1[n] = XC
y1[n] = YC
if P1.heading() is 180:
XC = P2.position()[0]
YC = P2.position()[1]
x2[n] = XC
y2[n] = YC
P1.position()
XC1 = P1.position()[0]
YC1 = P1.position()[1]
for x in range(0, n):
for z in range(x1.index(x), x2.index(x)):
if abs(z-YC1)<10:
print("Found")
P2.forward(1)
P1.forward(0.5)

There are a few problems with that code. A major one is this line:
if P1.heading() is 180:
The is operator tests identity, i.e., it's True if two expressions evaluate to the same object. You should not use it to test if two expressions have the same value. In this case, P1.heading() returns the float object with a value of 180.0, so there's no way it could be the same object as the integer object with value 180. Thus your if block is never entered. And since the main commands that move the turtles are at the end of the if block, the turtles don't move once you enter the while loop.
Also, you are using n to index into your lists, but you never update n from zero, so all of the coordinates that you want to save are getting written into the first items of the lists.
However, if you did update n you'd run into another problem: you'd be attempting to index list items that don't exist, since x1 and y1 are of length one, and x2 and y2 are of length zero.
Anyway, here's a simplified version of your code that does update the lists correctly, and does simple collision detection. It only detects exact collision, not approximate collision, but it should get you going in the right direction.
import turtle
Screen = turtle.Screen()
Screen.screensize(100, 100)
P1 = turtle.Turtle()
P2 = turtle.Turtle()
x1 = []
x2 = []
y1 = []
y2 = []
P1.penup()
P1.setheading(180)
P2.setheading(90)
P1.goto(100, 50)
P2.penup()
P2.goto(50, 0)
P2.pendown()
P1.pendown()
while True:
print(P1.heading() is 180)
XC = P2.position()[0]
YC = P2.position()[1]
x2.append(XC)
y2.append(YC)
XC = P1.position()[0]
YC = P1.position()[1]
x1.append(XC)
y1.append(YC)
if XC in x2 and YC in y2:
print("Found")
P2.forward(1)
P1.forward(0.5)
turtle.done()

There are many errors in your code.
First,
if P1.heading() is 180:
should be
if P1.heading() == 180:
In your loop you don't change your Num var.
You are trying to access an index that was not defined
x2[n] = XC
y2[n] = YC
Your logic doesn't make much sense in that for loop either.
Here, I made some corrections and I included a timer so you can see how your turtles are moving. This should help you to visualize what you are doing
import time
import turtle
Screen = turtle.Screen()
P1 = turtle.Turtle()
P2 = turtle.Turtle()
Screen.screensize(100, 100)
P1.penup()
P1.setheading(180)
P2.setheading(90)
P1.goto(100, 50)
P2.penup()
P2.goto(50, 0)
P2.pendown()
P1.pendown()
time.sleep(1)
n = 100
if P1.heading() == 180:
for x in range(0, n):
print(P1.position())
print(P2.position())
if abs(P1.position()[1] - P2.position()[1]) > 10:
time.sleep(.3)
P2.forward(1)
P1.forward(.5)
else:
print('Found')
break

Related

Is it possible to know the coordinates of a line at a specific point in time in python Tkinter

This is the game that I have made I want the circle that has been generated to move across the line without actually touching it, the circle moves to the right and downwards when no keypresses are made and is supposed to jump when the spacebar is pressed, when it collides with the line (logic I made for hitreg) it will restart with a brand new line and the circle at the starting position. When it goes from one end to the other it'll restart with a new line that has more divisions so that the game is difficult as you progress through the levels. The only issue I am having right now is that I am not able to start up the game correctly without it exiting the code immediately and I think the reason might be that the coordinates I have calculated are not accurate enough and sometimes the circle crosses the line and still doesn't register a collision.
Code for the game
from tkinter import *
from random import randint as rand
import keyboard, math
window = Tk()
window.geometry("1440x720")
canvas = Canvas(window, width=1440, height=720,
bg="white")
l1, l2, l3 = [], [], []
x = 1
b = canvas.create_oval(0, 300, 10, 300 + 60)
c = b
def genLine():
global x
f = 0
z = 360
y = 1440 / x
while x != 0:
ran = rand(300, 420)
u = canvas.create_line(f, z, y, ran)
r = canvas.coords(u)
l1.append(r)
f = y
x -= 1
y += y
z = ran
def hitReg():
global l2, l3
for i in l1:
grad = ((i[3] - i[1]) / (i[2] - i[0]))
l2.append(grad)
for i in l2:
for f in l1:
x = 0
length = f[2] - f[0]
while x != length:
point = x * i + f[1]
l3.append(math.ceil( point))
x += 0.25
def move_b():
global l3, x
canvas.move(c, 1, 1)
y = canvas.coords(c)
if keyboard.is_pressed('space'):
canvas.move(b, 1, -5)
elif y[1] in l3 or y[3] in l3:
exit()
elif y[2] >= 1440:
x += 1
genLine()
hitReg()
move_b()
else:
pass
window.after(10, move_b)
genLine()
hitReg()
move_b()
canvas.pack()
window.mainloop()
Main problem is
elif y[1] in l3 or y[3] in l3:
because l3 has all y values which are on line and it compares y[1] and y[3] with all y values but it should compare only with the nearest - with point which has the same x as center of oval.
I was thinking to use canvas.find_overlapping() or canvas.find_closest() but it would need to create small rectange or small oval for every hit point on line.
Finally I created hit points every 1px instead of 0.25px so I could put them on list and easily get
point_y = points[ int(oval_center_x) ]
and later check oval_y1 < point_y < oval_y2.
I also used window.bind("<space>", function) to move oval so I don't need extra module keyboard. Besides, I use Linux and keyboard on Linux needs admin privilege.
I also changed names of variables and functions to make them more readable.
As for exit() I created function reset() which sets level, removes all lines canvas.delete(*lines), moves oval to beginning canvas.moveto(oval, 0, 330), and generates new line (using value level) and generates hit points. Using reset(level+1) I can generate next level, and using reset(1) I can go back to first level.
import tkinter as tk # PEP8: `import *` is not preferred
from random import randint # PEP8: every module in separted line
# --- functions --- # PEP8: all functions directly after imports
# PEP8: `lower_case_names` for functions and variables
def generate_lines(level):
regions = []
lines = []
x1 = 0
y1 = 360
step = 1440 / level
x2 = step
for i in range(level):
y2 = randint(300, 420) # 360-60, 360+60
region = (x1, y1, x2, y2)
regions.append(region)
line = canvas.create_line(x1, y1, x2, y2)
lines.append(line)
x1 = x2
y1 = y2
x2 += step
return lines, regions
def generate_hit_points(regions):
points = []
for x1,y1,x2,y2 in regions:
steps = int(x2 - x1)
dy = (y2 - y1) / steps
y = y1
for _ in range(steps): # cant use `range(y1, y2, dy)` because `dy` is `float`, not `int`
points.append(y) # I don't need `x`
y += dy
return points
def move_oval():
canvas.move(oval, 1, 1)
x1, y1, x2, y2 = canvas.coords(oval)
oval_center_x = int(x1+10)
point_y = points[oval_center_x]
#print(y1, '<', int(point_y), '<', y2, '?')
#if y1 >= point_y or point_y >= y2: # less readable
if not (y1 < point_y < y2): # more readable
print('end')
reset(1)
elif x2 >= 1440:
print('next level')
reset(level+1)
window.after(25, move_oval)
def on_press_space(event):
canvas.move(oval, 0, -15)
def reset(new_level):
# all globals moved to one function
global lines
global points
global level
level = new_level
canvas.delete(*lines) # remove all lines
canvas.moveto(oval, 0, 330) # move oval to the beginning
lines, regions = generate_lines(level)
points = generate_hit_points(regions)
# --- main ---
lines = [] # PEP8: every variable in separted line
points = [] # PEP8: every variable in separted line
level = 1
window = tk.Tk()
window.geometry("1440x720")
canvas = tk.Canvas(window, width=1440, height=720, bg="white")
canvas.pack()
oval = canvas.create_oval(0, 360-30, 10, 360+30)
window.bind('<space>', on_press_space)
window.bind('q', lambda event:window.destroy())
reset(1)
# start moving
move_oval()
window.mainloop()
PEP 8 -- Style Guide for Python Code

drawing a jagged mountain curve using turtle-graphics and recursion

I am trying to create a function for a homework assignment which draws a jagged mountain curve using turtles and recursion. The function is called jaggedMountain(x,y,c,t) where x x,y are end coordinates, c is a complexity constant, and t is the turtle object. I am trying to create an image like this:
def jaggedCurve(x,y,c,t):
t.pendown()
x1 = t.xcor() + x / 2
y1 = t.ycor() + y / 2
y1 = y + (random.uniform(0,c)-0.5) * (t.xcor() - x)
if (x1,y1) == (x,y):
return None
else:
jaggedCurve(x1,y1,c,t)
This crashes quickly as the base case never executes, the function is called 993 times, and the recursion depth is exceeded. I have been scratching my head with this for quite some time, are there any suggestions?
Initially, I see two issues with your code. The first is:
if (x1,y1) == (x,y):
Turtles wander a floating point plane, the odds of these being exactly equal is small. You're likely better off doing something like:
def distance(x1, y1, x2, y2):
return ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
...
if distance(x1, y1, x, y) < 1.0:
The second issue is that jaggedCurve() draws nothing nor returns anything that can be used for drawing. Somewhere you need to actually move the turtle to cause something to be drawn.
Finally, though it's hard to be certain without a value for c, my guess is even with the above changes you won't get you what you want. Good luck.
Very interesting problem!
My solution is to make a recursive function that draws a mountain curve given two end points. Randomly pick a x coordinate value that lies in between two end points and compute the range of possible y coordinate given the maximum possible slope and randomly pick a y value in between this range and do this recursively. When to end points are close enough, just draw the line between them. Here is the code:
MAX_SLOPE = 45
MIN_SLOPE = -45
MIN_HEIGHT = 0
def dist_squared(P1,P2):
return (P1[0]-P2[0])**2 + (P1[1]-P2[1])**2
def mountain(P1,P2):
if dist_squared(P1,P2) < 1:
turtle.goto(P2)
return
x1,y1 = P1
x2,y2 = P2
x3 = random.uniform(x1,x2)
y3_max = min((x3-x1)*math.tan(math.radians(MAX_SLOPE)) + y1, (x2-x3)*math.tan(-math.radians(MIN_SLOPE)) + y2)
y3_min = max((x3-x1)*math.tan(math.radians(MIN_SLOPE)) + y1, (x2-x3)*math.tan(-math.radians(MAX_SLOPE)) + y2)
y3_min = max(y3_min, MIN_HEIGHT)
y3 = random.uniform(y3_min,y3_max)
P3 = (x3, y3)
mountain(P1,P3)
mountain(P3,P2)
return
turtle.up()
turtle.goto(-400,0)
turtle.down()
mountain((-400,0),(400,0))
I know this was posted like 3 months ago, but hopefully this is helpful to someone that was also assigned this terrible problem 5 days before finals! Ha!
The struggle I had with this problem was not realizing that you only need to pass in one point. To get the point the turtle is starting at, you just use .xcor() and .ycor() that are included in the turtle library.
import turtle
import random
def mountain (x, y, complexity, turtleName):
if complexity == 0:
turtleName.setposition(x, y)
else:
x1 = (turtleName.xcor() + x)/2
y1 = (turtleName.ycor() + y)/2
y1 = y1 + (random.uniform(0, complexity) - 0.5) * (turtleName.xcor() - x)
complexity = complexity - 1
mountain(x1, y1, complexity, turtleName)
mountain(x, y, complexity, turtleName)
def main ():
#Gets input for first coordinate pair, splits, and assigns to variables
coordinate = str(input("Enter the coordinate pair, separated by a comma: "))
x, y = coordinate.split(',')
x = int(x)
y = int(y)
complexity = int(input("Enter the complexity: "))
while complexity < 0:
complexity = int(input("Input must be positive. Enter the complexity: "))
Bob = turtle.Turtle()
mountain(x, y, complexity, Bob)
main ()

Optimisation of Shadow Casting Python

I have been working on a Shadow Caster for a small RPG I'm doing.
The trouble I have is that when I use it in my game it is just way way way to slow and induces a horrible lag.
Please do not be too frighten by the lenght of the post. It is fairly straightforward but so that you can run the code I included all the Bresenham's algorithms as well.
The principle is as follow:
- make a black surface
- define a light source with a position and a radius.
- get all the points on the circumference of the circle defined by this position and radius using Bresenham's Circle Algorithm.
- for each point along the circumference draw a ligne from the position of the light source using Bresenham's Line Algorithm.
- then iterate over the points of the line and check if they collide with every obstacle displayed on the screen.
- If there is no collision draw a WHITE circle centered on that point with a radius of 10 pixels or so.
- If there is a collision move on to the next point along the circle circumference.
- finally blit the surface with all the white circles on a surface which has a transparency value of 100 for the black color and a full transparency for the WHITE color.
So far I have attempted the following:
Which did reduce the lag:
- restrict the obstacle list to the ones displayed on the screen
- consider the screen edges as obstacles to reduce the iteration of area not visible.
- iterate only over every 3 points around the circle and 12 points along the lines.
Which didn't change anything:
- using ellipses going from the light source to the edge of the range or the obstacle instead of lots of circles along the line. The problem was that I had to redraw surface for each ellipse and then rotate the whole lot.
If you have any suggestions on how to make this more efficient I would be happy to here then.
Bresenham's Line Algo:
def get_line(start, end):
"""Bresenham's Line Algorithm
Produces a list of tuples from start and end
>>> points1 = get_line((0, 0), (3, 4))
>>> points2 = get_line((3, 4), (0, 0))
>>> assert(set(points1) == set(points2))
>>> print points1
[(0, 0), (1, 1), (1, 2), (2, 3), (3, 4)]
>>> print points2
[(3, 4), (2, 3), (1, 2), (1, 1), (0, 0)]
"""
# Setup initial conditions
x1, y1 = start
x2, y2 = end
dx = x2 - x1
dy = y2 - y1
# Determine how steep the line is
is_steep = abs(dy) > abs(dx)
# Rotate line
if is_steep:
x1, y1 = y1, x1
x2, y2 = y2, x2
# Swap start and end points if necessary and store swap state
swapped = False
if x1 > x2:
x1, x2 = x2, x1
y1, y2 = y2, y1
swapped = True
# Recalculate differentials
dx = x2 - x1
dy = y2 - y1
# Calculate error
error = int(dx / 2.0)
ystep = 1 if y1 < y2 else -1
# Iterate over bounding box generating points between start and end
y = y1
points = []
for x in range(x1, x2 + 1):
coord = (y, x) if is_steep else (x, y)
points.append(coord)
error -= abs(dy)
if error < 0:
y += ystep
error += dx
# Reverse the list if the coordinates were swapped
if swapped:
points.reverse()
return points
Bresenham's Circle Algo:
def get_circle((dx,dy),radius):
"Bresenham complete circle algorithm in Python"
# init vars
switch = 3 - (2 * radius)
points = set()
x = 0
y = radius
# first quarter/octant starts clockwise at 12 o'clock
while x <= y:
# first quarter first octant
points.add((x,-y))
# first quarter 2nd octant
points.add((y,-x))
# second quarter 3rd octant
points.add((y,x))
# second quarter 4.octant
points.add((x,y))
# third quarter 5.octant
points.add((-x,y))
# third quarter 6.octant
points.add((-y,x))
# fourth quarter 7.octant
points.add((-y,-x))
# fourth quarter 8.octant
points.add((-x,-y))
if switch < 0:
switch = switch + (4 * x) + 6
else:
switch = switch + (4 * (x - y)) + 10
y = y - 1
x = x + 1
offset_points = set()
for pt in points:
offset_points.add((pt[0]+dx,pt[1]+dy))
return offset_points
def shadow_gen(shadow_surf,source,cir_pt,obstacles):
line_points = get_line(source.pos,cir_pt)
for line_pt in line_points[0::12]:
for obs in obstacles:
pygame.draw.circle(shadow_surf, WHITE, line_pt, 20, 0) #radius to 5px and 0 to fill the circle
if obs.rect.collidepoint(line_pt) or pygame.Rect(0,0,500,500).collidepoint(line_pt) == False:
return
My Classes for the light sources, obstacles and shadow mask:
class Obstacle(object):
def __init__(self,x,y):
self.surf = pygame.Surface((150,150))
self.rect = pygame.Rect((x,y),(150,150))
self.surf.fill(pygame.color.Color('blue'))
class Light_Source(object):
def __init__(self,x,y,range_):
self.range = range_
self.pos = (x,y)
class Night_Mask(object):
def __init__(self):
self.surf = pygame.Surface((500,500)) #Screenwidth and height
self.alpha = 100
self.light_sources = []
'''setting initial alpha and colorkey'''
self.surf.set_colorkey(WHITE)
self.surf.set_alpha(self.alpha)
def apply_shadows(self, obstacles):
shadow_surf = pygame.Surface((500,500))
for source in self.light_sources:
circle_pts = list(get_circle(source.pos,source.range))
for cir_pt in circle_pts[0::3]:
shadow_gen(shadow_surf,source,cir_pt,obstacles)
self.surf.blit(shadow_surf, (0, 0))
The shadow generation functions which allows me to break out of both line and obstacle loop without using an exception in my apply_shadows method of the Night_Mask class:
def shadow_gen(shadow_surf,source,cir_pt,obstacles):
line_points = get_line(source.pos,cir_pt)
for line_pt in line_points[0::12]:
for obs in obstacles:
pygame.draw.circle(shadow_surf, WHITE, line_pt, 20, 0) #radius to 5px and 0 to fill the circle
if obs.rect.collidepoint(line_pt) or pygame.Rect(0,0,500,500).collidepoint(line_pt) == False:
return
And finally the main pygame example loop to run all of the above:
pygame.init()
screen = pygame.display.set_mode((500, 500))
bg = pygame.Surface((500,500))
bg.fill(pygame.color.Color('yellow'))
ob_a = Obstacle(75,80)
ls = Light_Source(75,75,300)
night_m = Night_Mask()
night_m.light_sources.extend([ls])
while True:
screen.fill(pygame.color.Color('black'))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
ls.pos = pygame.mouse.get_pos()
night_m.apply_shadows([ob_a])
screen.blit(bg, (0, 0))
screen.blit(ob_a.surf,ob_a.rect)
screen.blit(night_m.surf, (0, 0))
pygame.display.flip()
Here is the entire code from start to finish for an easy copy paste:
import pygame
import sys
WHITE = (255,255,255)
'''FUNCTIONS'''
def get_line(start, end):
"""Bresenham's Line Algorithm
Produces a list of tuples from start and end
>>> points1 = get_line((0, 0), (3, 4))
>>> points2 = get_line((3, 4), (0, 0))
>>> assert(set(points1) == set(points2))
>>> print points1
[(0, 0), (1, 1), (1, 2), (2, 3), (3, 4)]
>>> print points2
[(3, 4), (2, 3), (1, 2), (1, 1), (0, 0)]
"""
# Setup initial conditions
x1, y1 = start
x2, y2 = end
dx = x2 - x1
dy = y2 - y1
# Determine how steep the line is
is_steep = abs(dy) > abs(dx)
# Rotate line
if is_steep:
x1, y1 = y1, x1
x2, y2 = y2, x2
# Swap start and end points if necessary and store swap state
swapped = False
if x1 > x2:
x1, x2 = x2, x1
y1, y2 = y2, y1
swapped = True
# Recalculate differentials
dx = x2 - x1
dy = y2 - y1
# Calculate error
error = int(dx / 2.0)
ystep = 1 if y1 < y2 else -1
# Iterate over bounding box generating points between start and end
y = y1
points = []
for x in range(x1, x2 + 1):
coord = (y, x) if is_steep else (x, y)
points.append(coord)
error -= abs(dy)
if error < 0:
y += ystep
error += dx
# Reverse the list if the coordinates were swapped
if swapped:
points.reverse()
return points
def get_circle((dx,dy),radius):
"Bresenham complete circle algorithm in Python"
# init vars
switch = 3 - (2 * radius)
points = set()
x = 0
y = radius
# first quarter/octant starts clockwise at 12 o'clock
while x <= y:
# first quarter first octant
points.add((x,-y))
# first quarter 2nd octant
points.add((y,-x))
# second quarter 3rd octant
points.add((y,x))
# second quarter 4.octant
points.add((x,y))
# third quarter 5.octant
points.add((-x,y))
# third quarter 6.octant
points.add((-y,x))
# fourth quarter 7.octant
points.add((-y,-x))
# fourth quarter 8.octant
points.add((-x,-y))
if switch < 0:
switch = switch + (4 * x) + 6
else:
switch = switch + (4 * (x - y)) + 10
y = y - 1
x = x + 1
offset_points = set()
for pt in points:
offset_points.add((pt[0]+dx,pt[1]+dy))
return offset_points
def shadow_gen(shadow_surf,source,cir_pt,obstacles):
line_points = get_line(source.pos,cir_pt)
for line_pt in line_points[0::12]:
for obs in obstacles:
pygame.draw.circle(shadow_surf, WHITE, line_pt, 20, 0) #radius to 5px and 0 to fill the circle
if obs.rect.collidepoint(line_pt) or pygame.Rect(0,0,500,500).collidepoint(line_pt) == False:
return
'''CLASSES'''
class Obstacle(object):
def __init__(self,x,y):
self.surf = pygame.Surface((150,150))
self.rect = pygame.Rect((x,y),(150,150))
self.surf.fill(pygame.color.Color('blue'))
class Light_Source(object):
def __init__(self,x,y,range_):
self.range = range_
self.pos = (x,y)
class Night_Mask(object):
def __init__(self):
self.surf = pygame.Surface((500,500)) #Screenwidth and height
self.alpha = 100
self.light_sources = []
'''setting initial alpha and colorkey'''
self.surf.set_colorkey(WHITE)
self.surf.set_alpha(self.alpha)
def apply_shadows(self, obstacles):
shadow_surf = pygame.Surface((500,500))
for source in self.light_sources:
circle_pts = list(get_circle(source.pos,source.range))
for cir_pt in circle_pts[0::3]:
shadow_gen(shadow_surf,source,cir_pt,obstacles)
self.surf.blit(shadow_surf, (0, 0))
'''MAIN GAME'''
pygame.init()
screen = pygame.display.set_mode((500, 500))
bg = pygame.Surface((500,500))
bg.fill(pygame.color.Color('yellow'))
ob_a = Obstacle(75,80)
ls = Light_Source(75,75,300)
night_m = Night_Mask()
night_m.light_sources.extend([ls])
while True:
screen.fill(pygame.color.Color('black'))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
ls.pos = pygame.mouse.get_pos()
night_m.apply_shadows([ob_a])
screen.blit(bg, (0, 0))
screen.blit(ob_a.surf,ob_a.rect)
screen.blit(night_m.surf, (0, 0))
pygame.display.flip()
Your lag issue appears to be coming from the method Night_Mask.apply_shadows(self, obstacles). This appears to be due to the pure amount of iterations the nested for loop needs to go through.
Reducing the value of range_ in the constructor of Light_Source(x, y, range_) reduces the lag by reducing the aforementioned methods iterations, but the visual effect is worse. I found that the fps started to really drop for me after setting the variable past ~65-70.
There is a Pygame graphics library, that handles shadows very well.
Link to the page:http://pygame.org/project-Pygame+Advanced+Graphics+Library-660-4586.html
Direct download for version 8.1.1 from site:link
This is the description of the library from the site:
This is an all purpose graphics library for easily creating complicated effects quickly, and with a minimum of code. Run the very well commented examples, each less than a page long (not counting comments), and learn how to make complicated effects like shadows and antialiasing.
Here is an image from the page showing an example of shadows.
I downloaded and tested the library, and it works very well. I tested on Pygame1.9.2a0 for python 3.4
I believe this to be the easiest fix for your problem, and should help you with future projects as well. I hope this helps.

python distance formula coordinate plane error

My goal is to make a circle shape out of lines in pygame, using random endpoints around the edge of a circle and a constant starting point (the middle of the circle). So I decided that I would give the pygame.draw.line function: screen, aRandomColor, startingPosition, and endingPosition as arguments. Ending position is a tuple containing a randomly generated x value, and a helper function will calculate the y value based on the radius of the circle. My first function calculates the y value like this:
import math
import random
def findY(pos1, pos2, distance, bothValues=False):
p1 =pos1
p2 = pos2
x1 = float(p1[0])
y1 = float(p1[1])
x2 = float(p2[0])
d = float(distance)
y2 = y1 - math.sqrt(d**2 - (x1-x2)**2)
_y2 = y1 + math.sqrt(d**2 - (x1-x2)**2)
if bothValues==True:
return y2, _y2
else:
return y2
and the line drawer:
width = 500
height = 500
def randLine(surface, color=rand, start=rand, end=rand,length=rand):
if start==rand:
start = randPos()
if end==rand:
end = randPos()
if color==rand:
color = randColor()
if length != rand:
end_x = float(random.randint(0,width))
end_pos = (end_x, "y")
y2=findMissing(start_pos, end_pos,l,bothValues=True)
a = random.randint(0,1)
if a==0:
y2 = float(y2[0])
else:
y2 = float(y2[1])
lst = list(end_pos)
lst[1] = y2
end_pos = tuple(lst)
pygame.draw.line(surface, color, start_pos, end_pos)
Then:
drawRandLine(screen,start=(200,200),lenght=100)
(the other functions that those ones called like randPos aren't the problem). This for some reason generated an error that I diagnosed as the value inside the math.sqrt() was a negative number. But that can't happen, since every value in there is raised to power of 2, and thats what I'm confused about. So I changed the value inside math.sqrt() to its absolute value. This made the function not raise any errors, but the circle drawn looked like this:
I know that pygame's coordinate plane's y values upside down, but should that make a difference?
One way of getting a a uniform distribution of angles would be to generate a random angle theta between 0 and 2 * math.pi, and use trigonometry to find the co-ordinates of the end point of the line:
def drawRandLineTrig(start_pos, length):
theta = random.rand() * 2 * math.pi
end_pos = (start_pos[0] + length*math.cos(theta), start_pos[1] + length*math.sin(theta))
# ...
I could Do:
def randPos(origin=(0,0), xdis=width, ydis=height):
randx = random.randint(float(origin[0])-xdis,float(origin[0])+xdis)
randy = random.randint(float(origin[1])-ydis,float(origin[1])+ydis)
return (randx, randy)
def drawRandLine(surface, color=random, start_pos=random, end_pos=random,l=random):
if start_pos==random:
start_pos = randPos()
if end_pos==random:
end_pos = randPos()
if color==random:
color = randColor()
if l != random:
b = random.randint(0,1)
l=float(l)
origin = start_pos
dis = l
if b==0:
end_x = float(random.randint((origin[0]-dis),(origin[0]+dis)))
end_pos = (end_x, "y")
y2=findMissing(start_pos, end_pos,l,bothValues=True)
a = random.randint(0,1)
if a==0:
y2 = float(y2[0])
else:
y2 = float(y2[1])
lst = list(end_pos)
lst[1] = y2
end_pos = tuple(lst)
else:
end_y = float(random.randint((origin[1]-dis),(origin[1]+dis)))
end_pos = ("x", end_y)
x2=findMissing(start_pos, end_pos,l,bothValues=True)
a = random.randint(0,1)
if a==0:
x2 = float(x2[0])
else:
x2 = float(x2[1])
lst = list(end_pos)
lst[0] = x2
end_pos = tuple(lst)
pygame.draw.line(surface, color, start_pos, end_pos)
Which fixes the sqrt of a negative problem, and places an even distribution of lines by either calculating the x OR the y, and setting their limits to the specified length, to avoid the strange length of some of the lines.

Python Beginner - How to equate a regression line from clicks and display graphically?

I am reading Python Programming by John Zelle and I am stuck on one the exercises shown in the picture below.
You can view my code below. I know the code is very ugly. (Any tips are appreciated)
Here's my code so far:
from graphics import *
def regression():
# creating the window for the regression line
win = GraphWin("Regression Line - Start Clicking!", 500, 500)
win.setCoords(0.0, 0.0, 10.0, 10.0)
rect = Rectangle(Point(0.5, 0.1), Point(2.5, 2.1))
rect.setFill("red")
rect.draw(win)
Text(rect.getCenter(), "Done").draw(win)
message = Text(Point(5, 0.5), "Click in this screen")
message.draw(win)
points = [] # list of points
n = 0 # count variable
sumX = 0
sumY = 0
while True:
p = win.getMouse()
p.draw(win)
# if user clicks in a red square it exits the loop and calculates the regression line
if (p.getX() >= 0.5 and p.getX() <= 2.5) and (p.getY() >= 0.1 and p.getY() <= 2.1):
break
n += 1 # count of the points
# get the sum of the X and Y points
sumX = sumX + p.getX()
sumY = sumY + p.getY()
# tuple of the X and Y points
dot = (p.getX(), p.getY())
points.append(dot)
avgX = sumX / n
avgY = sumY / n
top = 0
bottom = 0
# my ugly attempt at the regression equation shown in the book
for i in points:
gp = 0
numer = points[gp][0] * points[gp][1]
top = top + numer
denom = points[gp][0] ** 2
bottom = bottom + denom
gp += 1
m = (top - sumX * sumY) / (bottom - sumX ** 2)
y1 = avgY + m * (0.0 - avgX)
y2 = avgY + m * (10.0 - avgX)
regressionline = Line(Point(0, y1), Point(10.0, y2))
regressionline.draw(win)
raw_input("Press <Enter> to quit.")
win.close()
regression()
When I run the program the regression line never appears to be the real line of best fit. I believe I am interpreting the regression equation incorrectly in my code. What needs to be changed to get the correct regression line?
Issues:
from my_library import * should be avoided; better to specify exactly what you want from it. This helps keep your namespace uncluttered.
you've got one massive block of code; better to split it into separate functions. This makes it much easier to think about and debug, and may help you reuse code later. Sure, it's a toy problem, you're not going to reuse it - but the whole point of doing exercises is to develop good habits, and factoring your code this way is definitely a good habit! A general rule of thumb - if a function contains more than about a dozen lines of code, you should consider splitting it further.
the exercise asks you to keep track of x, y, xx, and xy running sums while getting input points. I think this is kind of a bad idea - or at least more C-ish than Python-ish - as it forces you to do two different tasks at once (get points and do math on them). My advice would be: if you are getting points, get points; if you are doing math, do math; don't try doing both at once.
similarly, I don't like the way you've got the regression calculation worrying about where the sides of the window are. Why should it know or care about windows? I hope you like my solution to this ;-)
Here's my refactored version of your code:
from graphics import GraphWin, Point, Line, Rectangle, Text
def draw_window()
# create canvas
win = GraphWin("Regression Line - Start Clicking!", 500, 500)
win.setCoords(0., 0., 10., 10.)
# exit button
rect = Rectangle(Point(0.5, 0.1), Point(2.5, 2.1))
rect.setFill("red")
rect.draw(win)
Text(rect.getCenter(), "Done").draw(win)
# instructions
Text(Point(5., 0.5), "Click in this screen").draw(win)
return win
def get_points(win):
points = []
while True:
p = win.getMouse()
p.draw(win)
# clicked the exit button?
px, py = p.getX(), p.getY()
if 0.5 <= px <= 2.5 and 0.1 <= py <= 2.1:
break
else:
points.append((px,py))
return points
def do_regression(points):
num = len(points)
x_sum, y_sum, xx_sum, xy_sum = 0., 0., 0., 0.
for x,y in points:
x_sum += x
y_sum += y
xx_sum += x*x
xy_sum += x*y
x_mean, y_mean = x_sum/num, y_sum/num
m = (xy_sum - num*x_mean*y_mean) / (xx_sum - num*x_mean*x_mean)
def lineFn(xval):
return y_mean + m*(xval - x_mean)
return lineFn
def main():
# set up
win = draw_window()
points = get_points(win)
# show regression line
lineFn = do_regression(points)
Line(
Point(0., lineFn(0. )),
Point(10., lineFn(10.))
).draw(win)
# wait to close
Text(Point(5., 5.), "Click to exit").draw(win)
win.getMouse()
win.close()
if __name__=="__main__":
main()
the for loop is all messed up! you have an i that changes in the loop, but then use gp which is always 0.
you want something more like:
for (X, Y) in points:
numer += X * Y
denom += X * X
...or move gp = 0 to before the for loop.
...or drop that part completely and add a sumXY and a sumXX to the sumX and sumY.
either way, once you fix that it should be ok (well, or maybe some other bug....).

Categories

Resources