Why doesn't this circle disappear upon collision? - python

These seem to be the relevant portions of a slightly larger program. These are essentially the collision detection functions. bubbles is the original list of circles. I want the circle collided with to disappear on impact. I'm fairly sure my problem lies down there in "if collide ==" conditional.
def getDistance(point1,point2):
a= point1.getX()
b= point2.getX()
c= point1.getY()
d= point2.getY()
distance= math.sqrt((b-a)**2 + ((d-c)**2))
return distance
def balloonBubbleCollide(balloon,bubble):
point1 = balloon.getCenter()
point2= bubble.getCenter()
distance= getDistance(point1, point2)
if distance <= 30:
return True
def check(balloon, bubbles, window):
for bubble in bubbles:
collide = balloonBubbleCollide(balloon, bubble)
if collide == True:
bubbles.remove(bubble)
Assume main is running them in proper order. Just don't want to bog the post down with code.

You should not modify a list using remove while iterating over it.
To filter out the colliding bubbles use instead something like:
def check(balloon, bubbles, window):
bubbles[:] = [bubble for bubble in bubbles
if not baloonBubbleCollide(balloon, bubble)]
the code shown will create a first new list of bubble objects to keep (using a list comprehension) and then replace the current content of the bubbles list with it at once.

Related

Visualize Pathfinding Algorithm

I was doing a pathfinding visualizer in pygame and I pretty much finished but there's still one thing that I do not like about the algorithm part of it and it's the fact that when you press the visualize algorithm button it shows you the shortest path in yellow and all of the nodes the algorithm has visited ever in light blue but it shows you instantaneously and I want it to color the nodes accordingly step by step to actually reach the effect of visualizing (like in here https://clementmihailescu.github.io/Pathfinding-Visualizer/#), I tried to write some code in the function that seemed like it would have worked as intended but it didn't, here is the code:
# Breadth First Search Algorithm
def bfs(graph, start, goal):
explored = []
# Queue for traversing the
# graph in the BFS
queue = [[start]]
# If the desired node is
# reached
if start == goal:
return
# Loop to traverse the graph
# with the help of the queue
while queue:
path = queue.pop(0)
node = path[-1]
y, x = node
# Codition to check if the
# current node is not visited
if node not in explored and nodes_rows[x][y].color is not BLACK:
nodes_rows[x][y].color = LIGHT_BLUE
neighbours = graph[node]
# Loop to iterate over the
# neighbours of the node
for neighbour in neighbours:
new_path = list(path)
new_path.append(neighbour)
queue.append(new_path)
# Condition to check if the
# neighbour node is the goal
if neighbour == goal:
new_path.remove(start)
new_path.remove(goal)
return new_path
explored.append(node)
return None
The nodes_rows[x][y].color == color_name is the code that is responsible for coloring nodes on the grid which is represented by a dictionary(I provided it so it's gonna be easier for you to understand how coloring works in general in my program). The problem with that implementation is when I do add the coloring part at if statement to color all the neighbors it does it instantly on the grid without showing a kind of an animation that shows the coloring process node by node, my question is can I do it so it colors them each iteration rather than all at once by adding something to this code and not writing a new one and if I do need to write a new one that what is the instructions how can I do so?
Here is what I mean by coloring all at once like it does for now:
https://cdn.discordapp.com/attachments/772816508015083552/832303260911272046/PowerPoint_-_1_2021-04-15_20-13-35_Trim.mp4
Edit:
try:
while True:
if not ticks or pygame.time.get_ticks() - ticks >= 500:
ticks = pygame.time.get_ticks()
nodes = next(algorithm)
if nodes_rows[nodes[-1][1]][nodes[-1][0]].color != BLUE:
nodes_rows[nodes[-1][1]][nodes[-1][0]].color = LIGHT_BLUE
pygame.display.update()
except StopIteration:
pass
Tried doing it with yield and if I print it it does yield a list every half a second with a new explored node at the end like intended but it updates it all at once after waiting total amount of ticks I tried playing with the indent of display.update() but didn't work either I don't even know what to do at this point
Thanks to everyone contributing to help <3
Per the comments above, here is a simple example of a generator to help you grasp the idea.
def counter(x):
for i in range(x):
yield i
c = counter(3)
In: next(c)
Out: 0
In: next(c)
Out: 1
In: next(c)
Out: 2
What's happening is that every time you call next, the function will continue to run until it reaches the next yield, at which point it will return the yielded value.
It will also remember where it left off, so the next time you call next, the function will continue where it left off.
In your application, this could be used to yield the list of explored locations, then draw those locations in whatever color you like, call next(bfs) to step forward and yield the next list of explored locations, and so on until of course you find the solution and run out of items to yield.
One more example, perhaps a little more closely related to what you are trying to do:
def make_path():
path = []
i = 0
while True:
path.append(i)
i += 1
yield path
c = make_path()
for _ in range(6):
print(next(c))
Out: [0]
[0, 1]
[0, 1, 2]
[0, 1, 2, 3]
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 5]
i think the problem is that you do not update the canvas in your while queue loop.
the program will execute your bfs algorithm and then it will update the canvas.
i honestly don't use pygame very regularly, but i think to force the canvas to repaint you need to stick a pygame.display.update() inside your while loop.
Pygame is not my forte but, I think there are some missing things in your project:
1.- You don't really control when colors are shown on your screen. This can be achieved with this:
pygame.display.update()
Documentation: https://devdocs.io/pygame/ref/display#pygame.display.update
Usually when working with graphics, you add a color, and when then next screen painting update comes you see it, what you must do is to force the painting so you can animate safely.
2.- Coloring animations are not being done by default.
I couldn't help noticing this in your initial question: "it does it instantly on the grid without showing a kind of an animation that shows the coloring process node by node". If you want an animation like the example linked above (Pathfinding visualizer), I'm afraid you have to do it manually. Try creating a test with a white square and start painting growing circles inside interpolating colors, until you reach the walls and paint the full square. This should be similar to what are you trying to accomplish.
3.- Perhaps you should add some stops when you are animating your canvas.
The refresh rate of your hardware is way too faster than your human eye.
To properly see a coloring animation you should try adding execution stops, when interpolating colors.
nodes_rows[x][y].color = Color(r, g, b) # Start with a color and interpolate incrementing or decrementing r, g and b until your reach a final color.
pygame.display.update() # Update your screen
pygame.time.wait(1000) # In milliseconds, add some time wait so screen can be painted, if time is long enough perhaps screen updates meanwhile and you do not need to update the screen.
4.- (Optional) Parallel painting: Using threads.
If this does not work right for you, add threads.
As I stated before, pygame is not my forte, but consider adding threads to animate each square color. I've found a post here explaining this.
using threading in pygame
I hope this helps you and have a happy coding experience! :D

The bottom rectangle gets deleted instead of the top-most

I'm showing 25 rectangles (objects of My_rect) randomly on the screen.
rects = []
for i in range(25):
rects.insert(0, My_rect(canvas))
My goal is to remove them as I click on them.
This is how I'm handling clicks:
if ev.type == pygame.MOUSEBUTTONUP and ev.button == 1:
click_x, click_y = ev.pos
for cur_rect in rects:
if cur_rect.contains_point(click_x, click_y):
rects.remove(cur_rect)
break
The contains_point function is defined like this:
def contains_point(self, point_x, point_y):
rect = Rect(self.x, self.y, self.width, self.width)
return rect.collidepoint(point_x, point_y)
When I click on a separately-positioned rectangle, it gets deleted, as it should be. However, when I click on a rectangle that overlaps another one beneath it, the bottom-most rectangle disappears, which is contrary to my goal.
To me, the strangest thing is that I specifically added rectangles to rects using insert(0, ...) and not append() in order to prevent that effect.
What haven't I taken into consideration?
As you explain in your comment, you draw the rectangles in the order they appear in the list, so the first ones are drawn first, and end up at the bottom.
Then, you also test for the click by iterating on the list in the same order, so you test the rectangles at the bottom first.
The way you inserted the rectangles in the list is irrelevant. Just do it using append - furthermore, using insert(0) is very inefficient for large lists, as all items have to be moved each time you insert one.
Just iterate the list in reverse order when testing for the click:
for cur_rect in reversed(rects):
if cur_rect.contains_point(click_x, click_y):
rects.remove(cur_rect)
break

Pygame: trying to get the whole sprite to cross a boundary instead of just the top left pixel

I am trying to make my sprite move according to the astar pathfinding algorithm. However, after I implemented it, I realised that the movement of the sprite is only in accordance to the top left pixel. That means that if the algorithm tells it to move up after crossing a boundary, it will do so once the top left pixel crosses that boundary. However, this means that the entire sprite has not actually crossed, resulting in a collision if there is an obstacle just above it. Is there any way to tell it to move left more before moving up]1
def astar_ghost(pac,ghost):
maze=astar.create_maze(screen_width,screen_height,obstacles) #creates a maze of 0s and 1s. The 1s represent the obstacles
start=(ghost.gridloc[0],ghost.gridloc[1])
end=(ghost.goal_node[0],ghost.goal_node[1])
goal_node=astar.astar(maze,start,end)
if goal_node==None:
pass
else:
ghost.goal_node=goal_node
game=True
if ghost.goal_node[0]<ghost.gridloc[0]:#left
print('move left')
game=collision(pac,ghost) #collision is another function that checks for collision and returns True or False. If False, the game will be over
ghost.left=True
ghost.right=False
ghost.up=False
ghost.down=False
elif ghost.goal_node[0]>ghost.gridloc[0]:#right
print('move right')
game=collision(pac,ghost)
ghost.left=False
ghost.right=True
ghost.up=False
ghost.down=False
elif ghost.goal_node[1]<ghost.gridloc[1]:#up
print('move up')
game=collision(pac,ghost)
ghost.left=False
ghost.right=False
ghost.up=True
ghost.down=False
elif ghost.goal_node[1]>ghost.gridloc[1]:#down
print('move down')
game=collision(pac,ghost)
ghost.left=False
ghost.right=False
ghost.up=False
ghost.down=True
You are asking a few different questions here. I'll answer here to what I think you're trying to ask: Is there a way to check if an entire sprite has crossed a boundary, instead of just the top-left corner?. So, my answer (note this will only work if your boundary line is linear): You need to check each of the corners individually, then, if all of them have returned True, then you move on. Example:
def collision(sprite1, boundary):
def internal_collision(point, boundary):
... # The actual math happens here, returns True/False
corners = []
for h in [0, 1]:
for j in [0, 1]:
corners.append([sprite1.rect.x+(h*sprite1.rect.width),
sprite1.rect.y+(j*sprite1.rect.height)])
corner_check = []
for corner in corners:
corner_check.append(internal_collision(corner, boundary))
return all(corner_check)
I don't know how your code works, so I've tried to keep this as malleable and understandable as possible, so you can re-implement it in your own code.

Python: How do I detect visible shape detection in Tkinter

I am new to Python and I have a program that has 2 classes, one is essentially a rectangle and the other is essentially a circle. I am drawing them using the Canvas in Tkinter in the way below:
def draw(self):
self.canvas.delete("all")
self.rect.draw(self.canvas)
self.ball.draw(self.canvas)
The Ball class has its location variables and diameter variables and the Rect class has its location and dimension variables.
I am wondering how I detect the collision between these two "shapes". I know that one was is to treat the Ball as a square and do basic rectangle collision but I would like to know how to be precise.
I was also wondering if there was anything similar in Python to the way shape collision can be done in Java. In my Java game I use the following code to detect collision between any 2 Shapes:
public boolean collisionCheck(Shape a, Shape b) {
Area aA = new Area(a);
Area aB = new Area(b);
aA.intersect(aB);
return !aA.isEmpty();
}
Is there anything similar to this simple solution in Python?
And if not how would I go about circle-rectangle collision in Python?
Thank you for any help
I managed to figure this out using a method that Tkinter's Canvas object has. Every time something is drawn using Canvas it is given an ID. So as long as you record that ID somewhere you can use the find_overlapping method that the Canvas object has.
Say you have an object, in my case a custom Platform object, that keeps the ID stored in a variable. I did so like this:
def draw_platform(self, canvas): #Located in the Platform Class
self.ID = canvas.create_rectangle(self.x, self.y, self.x+self.w, self.y+self.h)
Now I can use this ID to determine if there are any object overlapping it.
def check_collision(self, plat, other_plat):
result = self.canvas.find_overlapping(plat.x, plat.y, plat.x + plat.w, plat.y + plat.h)
for i in result:
if i == other_plat.ID:
return True
return False
result returns a tuple of the ID's that are located within the rectangle bounds entered into find_overlapping. You can than loop through the tuple to see if any of the ID's match your other shape.
This works great for Rectangle-Rectangle collision and Circle-Rectangle collision.

Create a lot of objects and delete them

so i'm kinda new to programming, but for now with python and pygame i'm trying to create a small game. It is quite simple, the player will move around dodging small projectiles. However i'm having trouble creating a lot of objects, for example i need to randomly generate a position and a speed for a new projectile, and how do i create many objects like that, plus when they actually go 'out of the screen' they should disapear.
So I also need to delete them. My first idea was to create a list of objects, I and a loop that would move them one by one before updating the screen, but how do i create new objects with different names while I don't know how many projectiles will be on the screen, it should be random.
class Projectiles:
ProjectilesCount = 0
def __init__(self, x, y, speed):
self.pos = (x,y)
self.speed = speed
Projectiles.ProjectilesCount += 1
def moveProj(self):
x, y = self.pos
x -= self.speed
self.pos = (x,y)
pygame.draw.line(DISPLAY, WHITE, self.pos, (x,y+self.SIZE), self.SIZE)
bullet = Projectiles(500,200,5)
bullet.SIZE = SIZE
while true:
# some stuff
bullet.moveProj()
pygame.display.update()
fpsClock.tick(FPS)
This is the class i use for now (it just goes left) and it works for just one projectile.
You want a list:
list_of_bullets = []
for i in range(100):
list_of_bullets.append(Projectiles(...))
Use dictionaries. A list will work, but when using objects where the order is not important use dictionaries. Dictionaries also perform better when iterating through lots of data within them. Use some code similar to below...
bulletdict = {}
#This is a list because we can change the value within a function without returning it
#You could also use a global variable instead of a list
bulletcount = [0]
def SpawnBullet(bulletdict,bulletcount):
bulletdict[bulletcount] = Projectiles
bulletcount[0] += 1
The key will be a number and the value will be the bullet itself. To iterate through the dictionary to update stuff do...
for item in bulletdict:
bulletdict[item].moveproj(...)
If you need to remove stuff use code similar to this. I usually have 'checkDelete' functions inside my objects that I can call to check to see if we should remove them. This is used for things like deleting objects if they get off the screen or collide with something.
def deleteObjects(objectdict):
deletelist = []
for item in objectdict:
if objectdict[item].checkdelete() == True:
deletelist.append(item)
for item in deletelist:
del objectdict[item]
This allows for easy use and iteration of your objects. You can always tell the last spawned object because it will have the highest number as the key. This key number will also tell you the total number of spawned objects if you so need it.
I hope this helps you and good luck making your game.

Categories

Resources