This is my attempt to program the Mandelbrot set in Python 3.5 using the Pygame module.
import math, pygame
pygame.init()
def mapMandelbrot(c,r,dim,xRange,yRange):
x = (dim-c)/dim
y = (dim-r)/dim
#print([x,y])
x = x*(xRange[1]-xRange[0])
y = y*(yRange[1]-yRange[0])
x = xRange[0] + x
y = yRange[0] + y
return [x,y]
def checkDrawBox(surface):
for i in pygame.event.get():
if i.type == pygame.QUIT:
pygame.quit()
elif i.type == pygame.MOUSEBUTTONDOWN:
startXY = pygame.mouse.get_pos()
boxExit = False
while boxExit == False:
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONUP:
boxExit = True
if boxExit == True:
return [startXY,pygame.mouse.get_pos()]
pygame.draw.rect(surface,[255,0,0],[startXY,[pygame.mouse.get_pos()[0]-startXY[0],pygame.mouse.get_pos()[1]-startXY[1]]],1)
pygame.display.update()
def setup():
dimensions = 500
white = [255,255,255]
black = [0,0,0]
checkIterations = 100
canvas = pygame.display.set_mode([dimensions,dimensions])
canvas.fill(black)
xRange = [-2,2]
yRange = [-2,2]
xRangePrev = [0,0]
yRangePrev = [0,0]
newxRange = [0,0]
newyRange = [0,0]
while True:
if not ([xRange,yRange] == [xRangePrev,yRangePrev]):
draw(dimensions, canvas, xRange, yRange, checkIterations)
pygame.display.update()
xRangePrev = xRange
yRangePrev = yRange
box = checkDrawBox(canvas)
if box != None:
maxX = max([box[0][0],box[1][0]])
maxY = max([box[0][1],box[1][1]])
newxRange[0] = mapMandelbrot(box[0][0],0,dimensions,xRange,yRange)[0]
newxRange[1] = mapMandelbrot(box[1][0],0,dimensions,xRange,yRange)[0]
newyRange[0] = mapMandelbrot(0,box[0][1],dimensions,xRange,yRange)[1]
newyRange[1] = mapMandelbrot(0,box[1][1],dimensions,xRange,yRange)[1]
xRange = newxRange
yRange = newyRange
def draw(dim, surface, xRange, yRange, checkIterations):
for column in range(dim):
for row in range(dim):
greyVal = iteration(0,0,mapMandelbrot(column,row,dim,xRange,yRange),checkIterations,checkIterations)
surface.set_at([dim-column,row],greyVal)
def iteration(a, b, c, iterCount, maxIter):
a = (a*a) - (b*b) + c[0]
b = (2*a*b) + c[1]
iterCount = iterCount - 1
if iterCount == 0:
return [0,0,0]
elif abs(a+b) > 17:
b = (iterCount/maxIter)*255
return [b,b,b]
else:
return iteration(a,b,c,iterCount,maxIter)
setup()
I believe that the iteration algorithm is correct, but the output doesn't look right:
Wondering what might be the problem? Sorry for the code dump, just not sure which part may cause it to look like that.
Fascinating bug -- it literally looks like a squashed bug :)
The problem lies in the two lines:
a = (a*a) - (b*b) + c[0]
b = (2*a*b) + c[1]
You are changing the meaning of a in the first line, hence using the wrong a in the second.
The fix is as simple as:
a, b = (a*a) - (b*b) + c[0], (2*a*b) + c[1]
which will cause the same value of a to be used in calculating the right hand side.
It would be interesting to work out just what your bug has produced. Even though it isn't the Mandelbrot set, it seems to be an interesting fractal in its own right. In that sense, you had a very lucky bug. 99% percent of the times, bugs lead to garbage, but every now and then they produce something quite interesting, but simply unintended.
On Edit:
The Mandelbrot set is based on iterating the complex polynomial:
f(z) = z^2 + c
The pseudo-Mandelbrot set which this bug has produced is based on iterating the function
f(z) = Re(z^2 + c) + i*[2*Re(z^2 + c)*Im(z) + Im(c)]
where Re() and Im() are the operators which extract the real and imaginary parts of a complex number. This isn't a polynomial in z, though it is easy to see that it is a polynomial in z,z* (where z* is the complex conjugate of z). Since it is a fairly natural bug, it is almost certain that this has appeared somewhere in the literature on the Mandelbrot set, though I don't remember ever seeing it.
I decided to learn about the mandelbrot set and wrote my own version! I used python's complex data type, which should make the mandelbrot calculation for each pixel a bit clearer. Here is a screenshot of the result:
And here is the source code/code dump:
import pygame
import sys
def calc_complex_coord(x, y):
real = min_corner.real + x * (max_corner.real - min_corner.real) / (width - 1.0)
imag = min_corner.imag + y * (max_corner.imag - min_corner.imag) / (height - 1.0)
return complex(real, imag)
def calc_mandelbrot(c):
z = c
for i in range(1, max_iterations+1):
if abs(z) > 2:
return i
z = z*z + c
return i
def calc_color_score(i):
if i == max_iterations:
return black
frac = 255.0 - (255.0 * i / max_iterations)
return (frac, frac, frac)
def update_mandelbrot():
for y in range(height):
for x in range(width):
c = calc_complex_coord(x, y)
mandel_score = calc_mandelbrot(c)
color = calc_color_score(mandel_score)
mandel_surface.set_at((x, y), color)
if __name__ == "__main__":
pygame.init()
(width, height) = (500, 500)
display = pygame.display.set_mode((width, height))
pygame.display.set_caption("Mandelbrot Magic")
clock = pygame.time.Clock()
mandel_surface = pygame.Surface((width, height))
black = (0, 0, 0)
red = (255, 0, 0)
max_iterations = 50
min_corner = complex(-2, -2)
max_corner = complex(2, 2)
box = pygame.Rect(0, 0, width, height)
update_mandel = True
draw_box = False
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
x, y = event.pos
box = pygame.Rect(x, y, 0, 0)
draw_box = True
elif event.type == pygame.MOUSEMOTION:
x, y = event.pos
if draw_box:
box = pygame.Rect(box.left, box.top, x - box.left, y - box.top)
elif event.type == pygame.MOUSEBUTTONUP:
x, y = event.pos
update_mandel = True
display.blit(mandel_surface, (0, 0))
if draw_box:
pygame.draw.rect(display, red, box, 1)
if update_mandel:
box.normalize()
new_min_corner = calc_complex_coord(box.left, box.top)
new_max_corner = calc_complex_coord(box.right, box.bottom)
min_corner, max_corner = new_min_corner, new_max_corner
update_mandelbrot()
update_mandel = False
draw_box = False
pygame.display.update()
clock.tick(60)
The two issues with this code are that one, it is quite slow in updating the mandelbrot set, and two, the aspect ratios get distorted if you work with non-square windows or box-selections. Let me know if any of the code is unclear!
Related
I'm new to python and I'm trying to make a chess game with pygame, I have the chessboard and the various pieces placed as objects on it. This is the pieces class:
class piece(object):
def __init__(self, x, y, which):
self.x = x
self.y = y
self.which = which
self.square = getsquare(x + PIECE/2, y + PIECE/2)
self.dragging = False
def drag(self, x, y):
limit = 720
if x >= limit:
x = limit
self.x = x - PIECE/2
self.y = y - PIECE/2
def draw(self, win):
win.blit(self.which, (self.x, self.y))
self.square = getsquare(self.x + PIECE/2, self.y + PIECE/2)
where PIECE is the dimension of the spritesheets with the pieces images. I tried to make a drag system for pieces (stored in a 64 element long list) and by using only 1 piece it worked, but when I used the full list it stopped working without rasing any error. This is the drag system:
"""event listeners"""
for event in pygame.event.get():
if event.type == pygame.QUIT: #quit event
run = False
"""mouse release"""
if event.type == pygame.MOUSEBUTTONUP:
clickpos = pygame.mouse.get_pos()
x = clickpos[0]
y = clickpos[1]
sqr = getsquare(x, y)
for i in pieceslist:
if not i == "none":
if i.dragging:
i.dragging = False
try:
i.x = squarepos(i.square[0], i.square[1])[1]
i.y = squarepos(i.square[0], i.square[1])[0]
except:
i.x = squarepos(originalsquare[0], originalsquare[1])[1]
i.y = squarepos(originalsquare[0], originalsquare[1])[0]
"""mouse click"""
if event.type == pygame.MOUSEBUTTONDOWN:
clickpos = pygame.mouse.get_pos()
x = clickpos[0]
y = clickpos[1]
#print("X: " + str(x) + ", Y: " + str(y))
sqr = getsquare(x, y)
for i in pieceslist:
if not i == "none":
if sqr == i.square:
originalsquare = sqr
i.dragging = True
"""mouse drag"""
if event.type == pygame.MOUSEMOTION:
clickpos = pygame.mouse.get_pos()
x = clickpos[0]
y = clickpos[1]
#print("X: " + str(x) + ", Y: " + str(y))
sqr = getsquare(x, y)
for i in pieceslist:
if not i == "none":
if i.dragging:
i.drag(x, y)
since pieceslist is filled with piece objects and "none" strings I made the if checks (I know there surely are better ways to do this but I'm new to python)
So, the problem is that the click event works and it modifies dragging, but when it comes to the drag event the object no longer has dragging == True
EDIT:
squarepos() returns the coordinates where to put the spritesheet, getsquare() returns the coordinates by row-column:
def getsquare(x, y):
if x <= BORDER or y <= BORDER or x >= squarepos(1, 9)[0] or y >= squarepos(9, 1)[1]:
pass #not on the board
else:
x -= BORDER
y -= BORDER
x /= SQUARE
y /= SQUARE
return [int(x) + 1, int(y) + 1]
EDIT:
Full program here for testing and debugging
The dragging algorithm actually works. However, defBoardPieces() is called in every frame. Therefore, the game is reset every frame. And the dragging has no effect.
Remove the defBoardPieces() call from the drawBoardPieces function, but call it once before the application loop:
#renders board pieces
def drawBoardPieces(win):
# defBoardPieces() <--- DELETE
for i in pieceslist:
if not i == "none":
i.draw(win)
pieceslist = []
startsquare = []
defBoardPieces() # <--- INSERT
run = True
while run:
# [...]
Call defBoardPieces() also in reset:
def reset():
global position
position = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"
defBoardPieces()
Use mouse.get_pressed()[0] to drag. 0 is left button.
if event.type == pygame.MOUSEBUTTONDOWN:
if pygame.mouse.get_pressed()[0]:
... drag code here
Objects all go off in the same line (45 degrees to the left)...
When I extract the random_direction function to test it by itself, it gives the same vectors just flipped 180 or the x is the same and y is the same but negative... stuff like that.
import pygame
import os
import math
import random
from pygame.math import Vector2
def random_direction():
vector = Vector2(random.uniform(-max_speed, max_speed), random.uniform(-max_speed, max_speed))
if vector.length() == 0:
return vector
else:
return Vector2.normalize(vector)
def scaled(vector, scale):
if vector.length() == 0:
return vector
else:
return Vector2.normalize(vector) * scale
def clamped(vector, limit):
if vector.length() <= limit or vector.length() == 0:
return vector
else:
return Vector2.normalize(vector) * limit
def shoot():
for i in range(len(boids)):
boids[i]['velocity'] = boids[i]['desired_direction'] * max_speed
boids[i]['boid'].x += boids[i]['velocity'].x
boids[i]['boid'].y += boids[i]['velocity'].x
# if boids[i]['boid'].x >= WIDTH:
# boids[i]['boid'].x = 0
# elif boids[i]['boid'].x <= 0:
# boids[i]['boid'].x = WIDTH
# elif boids[i]['boid'].y >= HEIGHT:
# boids[i]['boid'].y = 0
# elif boids[i]['boid'].y <= 0:
# boids[i]['boid'].y = HEIGHT
def draw_window():
WIN.fill((0, 0, 0))
# for i in range(n):
# rot_image = pygame.transform.rotate(image_scaled, math.degrees(math.atan2(boids[i]['velocity'].x, boids[i]['velocity'].y)) +180)
# WIN.blit(rot_image, (boids[i]['boid'].x - int(rot_image.get_width()/2), boids[i]['boid'].y - int(rot_image.get_height()/2))) #########
for i in range(len(boids)):
WIN.blit(image_scaled, (boids[i]['boid'].x, boids[i]['boid'].y))
pygame.display.update()
WIDTH, HEIGHT = 1440, 720 #1680, 990
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('Sim')
FPS = 60
image = pygame.image.load(os.path.join('Assets', 'long_fish.png'))
image_width, image_height = 40, 40
image_scaled = pygame.transform.scale(image, (image_width, image_height))
#boid = pygame.Rect(WIDTH/2, HEIGHT/2, image_width, image_height)
max_speed = 10 #2
steer_strength = 0.04 #2
wander_strength = 0.4 #0.2
# desired_direction = Vector2(0, 0)
# velocity = Vector2(0, 0)
# shoot_direction = random_direction()
n = 30
boids = []
for i in range(n):
boids.append({'boid': pygame.Rect(WIDTH/2, HEIGHT/2, image_width, image_height),
'desired_direction': random_direction(),
'velocity': Vector2(0,0)})
def main():
clock = pygame.time.Clock()
run = True
while run:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
shoot()
draw_window()
pygame.quit()
if __name__ == '__main__':
main()
I've made things like this before on pygame and they work but I'm not sure why they do and this doesn't.
Your vector generator generates with bounds of a square:
because of the way you set it up. You then normalize the vector, putting whatever it raised into the bounding circle. Because of this method, values nearer to the corners are more likely to be chosen. From the origin to the edge, there is a distance of one. From the origin to the corner, there is a distance of 1.41 . Thus, values that normalize to a corner are more likely to be chosen
This can give you the impression of not having truly random values, as some values pop up more frequently than others.
The way around this is to generate an already normalized vector, py choosing a point from a circle.
The best way to do this is to generate an angle, in radians. Ex:
>>> angle = math.radians(random.randint(0, 360))
Then, use some basic trigonometry to turn that angle into a point
>>> x = math.cos(angle)
>>> y = math.sin(angle)
The tuple (x, y) should be an unbiased value, that is as random as your pseudorandom (how computers do random, it's actually a big complex equation that generates a value really close to random, but it actually isn't) generator will get.
Implementing this into your code:
def random_direction():
a = math.radians(random.randint(0, 360))
return pygame.Vector2(math.cos(a), math.sin(a))
Like the title says I wanted to create a visualized bubble sort with python and pygame. The sort works perfectly but when it comes to visualize it it never gets the correct output.
The sort works perfectly,but the screen bars donot get swapped.
I also added some colours to better understand the code...
The Code:-
import pygame
import random
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
L = []
rect = []
n = 10 # n belongs till [50,220]
WidthOfEachBar = 800 // (n + 1)
class Rect:
def __init__(self, x, y, width, height):
self.X = x
self.Y = y
self.width = width
self.height = height
self.colour = BLACK
def show(self):
pygame.draw.rect(screen, self.colour, (self.X, self.Y, self.width, self.height))
def changeCol(self, colour):
self.colour = colour
def array(n_):
global L
arr = [(3 * i) for i in range(1, n_ + 1)]
for a in range(n_):
random_no = random.choice(arr)
L.append(random_no + 10)
arr.remove(random_no)
array(n)
for i in range(n):
x = 50 + (i + 1) * (1 + WidthOfEachBar)
y = 680 - L[i]
rect.append(Rect(x, y, WidthOfEachBar, L[i]))
def swap(a, b):
global rect
rect[a], rect[b] = rect[b], rect[a]
def bubble_sort():
global n, rect
for ii1 in range(n):
for j in range(n - 1):
rect[j].colour = GREEN
rect[j + 1].colour = GREEN
if rect[j].height > rect[j + 1].height:
# print(r[j].X, r[j + 1].X)
swap(j, j + 1)
# print(r[j].X, r[j + 1].X)
for amb in range(n):
print(rect[amb].height, end=" ")
print()
screen.fill(WHITE)
for no1 in range(n):
rect[no1].show()
pygame.time.delay(0)
pygame.display.update()
rect[j].colour = BLACK
rect[j + 1].colour = BLACK
pygame.init()
screen = pygame.display.set_mode((1000, 700))
pygame.display.set_caption("SORTING VISUALS")
is_sorted = False
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(WHITE)
if not is_sorted:
bubble_sort()
pygame.display.update()
Here the screen is updated everytime, but I couldn't understand the problem.
The code is much fishy,I know but any help would be great
The problem in your code is that you're swapping the elements in the list, but the corresponding rectangles are not changing their position. You should change your swap function:
def swap(a, b):
global rect
rect[a], rect[b] = rect[b], rect[a]
rect[a].X, rect[b].X = rect[b].X, rect[a].X
This will now swap the elements in the list as well as swap the positions of their corresponding rectangles.
I copied a code of YouTube, about displaying 3d cubes on a screen in Python, without the use of external modules (like PyOpenGL). It works fine, but the moment you go between two cubes, the display gets messed up. Here is my code:
import pygame, sys, math, random
def rotate2d(pos, rad): x,y=pos; s,c = math.sin(rad),math.cos(rad); return x*c-y*s,y*c+x*s
class Cam:
def __init__(self, pos=(0,0,0),rot=(0,0)):
self.pos = list(pos)
self.rot = list(rot)
def events(self, event):
if event.type == pygame.MOUSEMOTION:
x, y = event.rel; x/=200; y/=200
self.rot[0]+=y; self.rot[1]+=x
def update(self, dt, key):
s = dt*10
if key[pygame.K_q]: self.pos[1]+=s
if key[pygame.K_e]: self.pos[1]-=s
x,y = s*math.sin(self.rot[1]),s*math.cos(self.rot[1])
if key[pygame.K_w]: self.pos[0]+=x; self.pos[2]+=y
if key[pygame.K_s]: self.pos[0]-=x; self.pos[2]-=y
if key[pygame.K_a]: self.pos[0]-=y; self.pos[2]+=x
if key[pygame.K_d]: self.pos[0]+=y; self.pos[2]-=x
if key[pygame.K_r]: self.pos[0]=0; self.pos[1]=0;\
self.pos[2]=-5; self.rot[0]=0; self.rot[1]=0
class Cube:
faces = (0,1,2,3),(4,5,6,7),(0,1,5,4),(2,3,7,6),(0,3,7,4),(1,2,6,5)
colors = (255,0,0),(255,128,0),(255,255,0),(255,255,255),(0,0,255),(0,255,0)
def __init__(self,pos=(0,0,0),color=None,v0=(-1,-1,-1),v1=(1,-1,-1),v2=(1,1,-1),v3=(-1,1,-1),v4=(-1,-1,1),v5=(1,-1,1),v6=(1,1,1),v7=(-1,1,1)):
if color != None:
if len(color) == 3 or len(color) == 4:
self.colors = tuple((color for i in range(6)))
if len(color) == 6:
self.colors = color
self.vertices = (v0,v1,v2,v3,v4,v5,v6,v7)
x,y,z = pos
self.verts = [(x+X/2,y+Y/2,z+Z/2) for X,Y,Z in self.vertices]
pygame.init()
w,h = 400,400; cx,cy = w//2, h//2
screen = pygame.display.set_mode((w,h))
clock = pygame.time.Clock()
cam = Cam((0,0,-5))
pygame.event.get(); pygame.mouse.get_rel()
pygame.mouse.set_visible(0); pygame.event.set_grab(1)
occupied = []
cube1 = Cube((0,0,0))
cube2 = Cube((0,0,2))
objects = [cube1, cube2]
while True:
dt = clock.tick()/1000
for event in pygame.event.get():
if event.type == pygame.QUIT: pygame.quit(); sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE: pygame.quit(); sys.exit()
cam.events(event)
screen.fill((0,0,0))
face_list = []; face_color = []; depth = []
for obj in objects:
vert_list = []; screen_coords = []
for x,y,z in obj.verts:
x-= cam.pos[0]; y-=cam.pos[1];z-=cam.pos[2]
x,z = rotate2d((x,z),cam.rot[1])
y,z = rotate2d((y,z),cam.rot[0])
vert_list += [(x,y,z)]
f = 200/z
x,y = x*f,y*f
screen_coords+=[(cx+int(x),cy+int(y))]
for f in range(len(obj.faces)):
face = obj.faces[f]
on_screen = False
for i in face:
x,y = screen_coords[i]
if vert_list[i][2]>0 and x>0 and x<w and y>0 and y<h: on_screen = True; break
if on_screen:
coords = [screen_coords[i] for i in face]
face_list += [coords]
face_color += [obj.colors[f]]
depth += [sum(sum(vert_list[j][i]**2 for i in range(3)) for j in face) / len(face)]
order = sorted(range(len(face_list)),key=lambda i:depth[i],reverse=1)
for i in order:
try: pygame.draw.polygon(screen,face_color[i],face_list[i])
except: pass
key = pygame.key.get_pressed()
pygame.display.flip()
cam.update(dt,key)
And here is what happens when you try and navigate between the two cubes:
Can someone please suggest an edit to the code that would stop the cubes from becoming distorted when the cam.pos is close to the cubes?
The application does not correctly draw the geometry, when apart of a faces (primitive, side of a cube) is behind and the other part in front of the eye position. That happens if the transformed z coordinate (vert_list += [(x,y,z)]) is positive for the some vertices and negative for negative for some other vertices that form primitive (face).
You can test that behavior with ease, if you skip all the faces, where at least one z coordinate is negative (behind the eye):
while True:
# [...]
for obj in objects:
# [...]
for f in range(len(obj.faces)):
face = obj.faces[f]
#on_screen = False
#for i in face:
# x,y = screen_coords[i]
# if vert_list[i][2]>0 and x>0 and x<w and y>0 and y<h: on_screen = True; break
# draw a face if any projected coordinate (x, y) is in the viewing volume
on_screen = False
for i in face:
x,y = screen_coords[i]
if x>0 and x<w and y>0 and y<h: on_screen = True; break
# skip a face if NOT ALL z coordinates are positive
if on_screen:
on_screen = all([vert_list[i][2]>0 for i in face])
if on_screen:
# [...]
The issue can be solved by clipping the geometry at hypothetical near plane. See Viewing frustum:
while True:
# [...]
for obj in objects:
# [...]
for f in range(len(obj.faces)):
face = obj.faces[f]
on_screen = False
for i in face:
x,y = screen_coords[i]
if vert_list[i][2]>0 and x>0 and x<w and y>0 and y<h: on_screen = True; break
# clip geometry at near plane
if on_screen:
near = 0.01
for i in face:
if vert_list[i][2]<0:
x, y, z = vert_list[i]
nearscale = 200/near
x,y = x*nearscale,y*nearscale
screen_coords[i] = (cx+int(x),cy+int(y))
if on_screen:
coords = [screen_coords[i] for i in face]
face_list += [coords]
face_color += [obj.colors[f]]
depth += [sum(sum(vert_list[j][i]**2 for i in range(3)) for j in face) / len(face)]
So my question is simple: How do I make a sprite that my mouse can't pass through? I've been experimenting, and I found an unreliable way to do it that is also super glitchy. If anyone knows how I might go about this, please help.
Here is the code that I am currently using:
import pygame
import pyautogui
import sys
import time
pygame.init()
game_display = pygame.display.set_mode((800,600))
pygame.mouse.set_visible(True)
pygame.event.set_grab(True)
exit = False
class Wall(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((30, 100))
self.image.fill((255, 255, 255))
self.rect = self.image.get_rect()
self.rect.center = (200, 200)
def collision(self):
loc = pygame.mouse.get_pos()
yy = loc[1]
xx = loc[0]
if yy >= self.rect.top and yy <= self.rect.bottom and xx >= self.rect.left and xx <= self.rect.right:
if xx >= 200:
pyautogui.move(216 - xx, 0)
if xx <= 200:
pyautogui.move(-xx + 184, 0)
w = Wall()
all_sprites = pygame.sprite.Group()
all_sprites.add(w)
print(w.rect.top)
print(w.rect.bottom)
while (not exit):
mouse_move = (0,0)
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
exit = True
w.collision()
clock = pygame.time.Clock()
game_display.fill((0, 0, 0))
clock.tick(30)
all_sprites.update()
all_sprites.draw(game_display)
pygame.display.flip()
pygame.quit()
note: please ignore my extra import statements, I am going to use them for later.
To do what you want you have to check if the line form the previous mouse position to the new mouse position intersects the rectangle. Write a function IntersectLineRec which checks for the intersection and use it and returns a list of intersection points, sorted by the distance.
The function returns a list of tules whith points and distances:
e.g.
[((215.0, 177.0), 12.0), ((185.0, 177.0), 42.0)]
prev_loc = pygame.mouse.get_pos()
class Wall(pygame.sprite.Sprite):
# [...]
def collision(self):
global prev_loc
loc = pygame.mouse.get_pos()
intersect = IntersectLineRec(prev_loc, loc, self.rect)
prev_loc = loc
if intersect:
ip = [*intersect[0][0]]
for i in range(2):
tp = self.rect.center[i] if ip[i] == loc[i] else loc[i]
ip[i] += -3 if ip[i] < tp else 3
pyautogui.move(ip[0]-loc[0], ip[1]-loc[1])
prev_loc = loc = ip
The function IntersectLineRec has to check if one of the 4 outer lines between the 4 corners of the rectangle inter sects the line between the mouse positions:
def IntersectLineRec(p1, p2, rect):
iL = [
IntersectLineLine(p1, p2, rect.bottomleft, rect.bottomright),
IntersectLineLine(p1, p2, rect.bottomright, rect.topright),
IntersectLineLine(p1, p2, rect.topright, rect.topleft),
IntersectLineLine(p1, p2, rect.topleft, rect.bottomleft) ]
iDist = [(i[1], pygame.math.Vector2(i[1][0] - p1[0], i[1][1] - p1[1]).length()) for i in iL if i[0]]
iDist.sort(key=lambda t: t[1])
return iDist
IntersectLineRec checks if to endless lines, which are defined by to points are intersecting. Then it checks if the intersection point is in the rectangles which are defined by the each of the lines (the line is the diagonal of the rectangle):
def IntersectLineLine(l1_p1, l1_p2, l2_p1, l2_p2):
isect, xPt = IntersectEndlessLineLine(l1_p1, l1_p2, l2_p1, l2_p2)
isect = isect and PtInRect(xPt, l1_p1, l1_p2) and PtInRect(xPt, l2_p1, l2_p2)
return isect, xPt
To check if a point is in an axis aligned rectangle has to check if both coordinates of the point are in the range of the coordinates of the rectangle:
def InRange(coord, range_s, range_e):
if range_s < range_e:
return coord >= range_s and coord <= range_e
return coord >= range_e and coord <= range_s
def PtInRect(pt, lp1, lp2):
return InRange(pt[0], lp1[0], lp2[0]) and InRange(pt[1], lp1[1], lp2[1])
The intersection of to endless lines can be calculated like this:
def IntersectEndlessLineLine(l1_p1, l1_p2, l2_p1, l2_p2):
# calculate the line vectors and test if both lengths are > 0
P = pygame.math.Vector2(*l1_p1)
Q = pygame.math.Vector2(*l2_p1)
line1 = pygame.math.Vector2(*l1_p2) - P
line2 = pygame.math.Vector2(*l2_p2) - Q
if line1.length() == 0 or line2.length() == 0:
return (False, (0, 0))
# check if the lines are not parallel
R, S = (line1.normalize(), line2.normalize())
dot_R_nvS = R.dot(pygame.math.Vector2(S[1], -S[0]))
if abs(dot_R_nvS) < 0.001:
return (False, (0, 0))
# calculate the intersection point of the lines
# t = dot(Q-P, (S.y, -S.x)) / dot(R, (S.y, -S.x))
# X = P + R * t
ptVec = Q-P
t = ptVec.dot(pygame.math.Vector2(S[1], -S[0])) / dot_R_nvS
xPt = P + R * t
return (True, (xPt[0], xPt[1]))
See the animation: