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
Related
I am new to Python and trying to build a simple game. In the game is just one ant and this ant follows simple rules: if ant is on white field, he paints the tile black (or other color), turns right, moves forward. If ant is on colored tile, remove color, turn left, move forward.
Currently I am trying to do this with turtle and stamps. Stamp ID and coordinates are stored in a dict, a loop is checking if ant position is in dict keys and there is the problem: when the ant moves to the same position, where the first stamp is, this check return False and the ant never escape the loop.
from turtle import Turtle, Screen
screen = Screen()
screen.setup(height=900, width=1000)
screen.title("The Ant")
ant = Turtle()
ant.color("brown")
ant.pencolor("black")
ant.penup()
kleks_liste = {}
game_running = True
ant.shape("square")
ant.setpos(5, 5)
kleks_liste[ant.pos()] = ant.stamp()
ant.shape("classic")
ant.forward(20)
while game_running:
pos_key = ant.pos()
if pos_key in kleks_liste.keys():
ant.clearstamp(kleks_liste[pos_key])
kleks_liste.pop(pos_key)
ant.left(90.00)
ant.forward(20.00)
else:
ant.right(90.00)
ant.shape("square")
kleks_liste[pos_key] = ant.stamp()
ant.shape("classic")
ant.forward(20.00)
screen.exitonclick()
What am I doing wrong? If I comment out ant.forward in the else section, the check works and returns True. Can someone explain the behavior?
The Vec2D returned by pos() contains floats which need to be compared with an epsilon. One workaround is to cast these values to integers, either with int or round. This shouldn't lose accuracy since you're working on a grid.
Instead of pos_key = ant.pos(), try pos_key = tuple(map(int, ant.pos())). Don't forget to change the first stamp key outside the loop too.
As an aside, you can use if pos_key in kleks_liste: without .keys().
I'm new to curses but I have some experience. I'm trying to make a top-down style game in python with ncurses, but I don't even know where to start. I'm wanting the character to be centered on the screen and move around the map, but the environment to not all be visible on the screen at once. Is this possible? I would like to know before I get started.
For the background, if it's text-based, you could draw the map using 2-layer list or tuple that gets printed using loops and slices with window.addstr(x,y,string)
example, with stdscr as the name of the window:
map_tiles = [['a','1','+'],['b','2','-'],['c','3','=']]
inx = 0
iny = 0
range = 1
def scroll(bx,by):
stdscr.clear()
for y, x_line in enumerate(map_tiles[by : by+range]):
for x, tile in enumerate(x_line[bx : bx+range]):
stdscr.addstr(y, x, f'{tile}')
then, by calling scroll(inx.iny) the tiles within range of the slices map_tiles[by : by+range] and x_line[bx : bx+range] will get printed in the terminal.
You can then bind keys to add increments to inx and iny to change the visible tiles.
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.
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.
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.